大规模语言模型 (LLM) 拥有大量的数据来源,能针对用户提出的问题提供不同形式的回答,但其回答形式仅限于“文本”。尽管文本内容清晰,但在包含复杂逻辑或需要向外展示的场景下,文本表达存在局限性。可以想象,将“文本” 转换为“可视化” 分析模型甚至UI界面将具有更出色的效果。本文将汇总关于这种场景的探索和实现思路。
效果展示
AI 可视化分析模型是结合了 LLM 的能力,依据用户的需求生成互动式、直观且适用于交互设计师、视觉设计师和产品设计师的常用模型,如用户旅程地图、用户画像等。同时,也满足产品经理所需的商业画布、SWOT 分析等常用分析模型。效果如下:
功能分析与拆解
实现思路
站在用户的角度来看这个需求由以下几个阶段组成:
从上面的流程可以看出,整个过程需要与 LLM 产生两次对话:
- 第一次调用进行模版筛选,对于 LLM 来说是非常简单的,只需要写好简单的 Prompt 即可。
- 第二次调用为生产模版数据,也就是最关键的一步。那这个模版数据怎么生产呢?
理想的AI
理想中的 AI 当然是编写提示词通过 LLM 直接输出设计稿数据,再通过图形数据解析器在图形编辑器中创建出设计稿。按这个思路可以分以下几步来做:
- 了解设计稿数据格式
- 让 LLM 学习设计稿数据
- 针对每个模型编写 Prompt 让 LLM 生产出设计稿数据
- 通过图形数据解析器解析设计稿数据插入图形编辑器中
了解设计稿数据格式
选定 Figma 作为图形编辑器。所以要了解 Figma 设计稿的数据结构。我们可以从在这个网站 Figma Api Live中获取到 Figma 设计稿的源数据。可以看到一个设计稿的数据是非常复杂的。包含:层级关系,坐标,矩阵,填充,文字,边框等等。这样一个简单的画板设计稿数据足有 6.8k。由于 LLM 存在最大返回 Token 限制。所以这个思路从第一步就证明是行不通的。
现实中的AI
上图是用户画像模型的设计稿到最终产物的对比。可以看出,整个过程中设计稿的基础轮廓并不会改变,只有便签的创建和特定区块内文字的变化。再重新回过头来看这个需求的本质,其实就是 LLM 输出文本到设计稿中文本的替换。
最终思路
只需要利用 LLM 输出的结构化文本,再进行设计稿的文本替换。按照这个思路可以分以下几步来做:
- 单个模型的设计稿数据
- 定义该模型的 LLM 文本输出的数据结构
- 组合 LLM 文本数据结构和设计稿数据,生产出设计稿数据
- 编写解析器解析设计稿数据插入图形编辑器
提示词开发
上文说到整个需求会存在跟 LLM 的两次对话,跟 LLM 相关的对话 Prompt 调试是必不可少的一部分。在 AIGC 时代,成为 Prompt 工程师似乎是无法避免的宿命。
模型推荐
模型推荐这一块相对来说比较简单,Prompt 如下:
我想让你做一个模版推荐,我会把你精通的模版都告诉你,请你根据我输入的问题给我推荐适合的模版。你现在精通:用户旅程地图、用户画像、精益画布、用户故事、SWOT分析、干系人地图。推荐模板要求:1.如果问题内含有模板相关的词汇,请优先推荐对应模板。2.如果没有适合的模板,请回复:暂无适合的模板。3.推荐模板最多5个,最少1个,按推荐优先级排序。注意:只要推荐模板名称,不要回答问题,也不要对模板进行解释。
只需要对 LLM 输出的文本用正则提取即可。
export const getRecommends = (txt) => {
return txt.match(/(用户旅程地图|用户画像|精益画布|用户故事|SWOT分析|干系人地图)/g);
};
模型的结构化文本
以上是根据产品同学在进行需求调研时的提示语跟 LLM 产生的对话, 可以看到 LLM 可以生成结构化的数据,那如何去解析这些数据呢?
不如试试让 LLM 直接输出 JSON 数据吧
从上图可以看出,只要告诉 LLM 一个固定的 JSON 输出格式。则在对话可以生成定义好的 JSON 数据格式,只需要通过正则提取出该 JSON 即可。
export const parseGptJson = (txt) => {
const data = txt.match(/'([^']*)'/g); // 提取json字符串片段
const res = JSON.parse(txt);
}
但在开发过程中也发现了如下几点问题:
- 虽然可以生成结构化数据,但整体生成的结果内容仍然有些“泛”,数据的质量不高,对用户参考价值不大。
- LLM 输出的数据不够稳定,通过正则提取加 JSON.parse 的方式错误率很高。
- LLM 输出完整数据的时间长,需要很长的等待时间,会造成用户的等待焦虑。
提示词增强
在探索过程中,对提示词进行了多次调整,始终无法生成稳定,高质量的内容。以精益画布的提示词:
你现在是一个创业专家,能够熟练的使用精益画布,精益画布分成:1.问题&用户痛点。2.用户细分。3.独特卖点。4.解决方案。5.渠道。6.关键指标。7.竞争壁垒。8.成本结构。9.收入分析。现在有用户向你询问如何进行创业,你需要用精益画布的方式分点(不少于4个点)给出解答,解答内容务必非常详细。固定回答格式如下:xxx
为提高稳定性,做了以下措施:
- 加了各种定语如:尽可能详细、每个点不少于 4 个等限制条件。
- 增加示例数据:从 LLM 的原理上来看,它的模式是通过推理来生成回答。那我们可以直接告诉它你理想的数据例子,提高其推理效率。
最终整个提示语的由以下几部分组成:
设计稿拆解
设计稿最小母版
以用户旅程地图为例,对设计稿进行拆解,缩减内容则可以得出一个最小的设计稿母版。通过这个最小模板再组合 LLM 输出的数据可以输出最终的设计稿。整个模型由以下几大类组成:
- 列的头部(固定内容)
- 旅程模块:旅程一,旅程二,旅程三(表格)
- 名称,用户目标与期望
模型的预定义数据
结合上文,我们可以归纳出一个模型需要做以下的数据准备:
- 通过 Figma Api Live可以拿到最小母版的设计稿数据
- 模型关联的 schema.json 按每个模型的特点进行定制
- 模型提示词由不同的模型特性决定
- 示例数据用于增强提示词
设计稿数据组装
输出数据结构定义
通过归纳分析各个模型,可以总结出模型设计稿存在大量的共性,设计稿内可以分为以下三种模块:
固定输出模块
如表头,标题,分割线。用于定义用户不会修改的模块,这类模块不需要做文本替换。
表格型模块
如用户旅程地图中的每个阶段,这种类表格需要通过以 schema.json 来做内容的数据描述。
"ths": ["阶段一","阶段二"],
"data": {
"阶段一": {
"栏目一": ["内容", "内容"],
"栏目二": ["内容", "内容"]
},
"阶段二": {
"栏目一": ["内容", "内容"],
"栏目二": ["内容", "内容"]
}
}
分块型模块
如 SWOTA 分析,该模型的每一个区块都固定的,只需要进行文本内容块的处理即可。则其 schema.json 如下:
"data": {
"优势": ["xx","xx"],
"劣势": ["xx","xx"],
"机会": ["xx","xx"],
"威胁": ["xx","xx"],
"通过优势利用机会的策略": ["xx","xx"],
"利用优势防止威胁的策略": ["xx","xx"],
"通过机会最小化弱点的策略": ["xx","xx"],
"将其潜在威胁最小化的策略": ["xx","xx"]
}
设计稿数据生产
下一步就是针对各个模型进行设计稿的数据生产了,上图可以是设计稿中的画板列表,会按这个画板结构与数据建立索引关系。
以用户旅程地图为例,其 schema.json 如下
"schema": {
"user": {
"用户信息": "x",
"用户目标与期望": "x"
},
"ths": ["旅程一"],
"data": {
"旅程一": {
"用户行为": ["1"],
"服务触点": ["1"],
"用户预期": ["1"],
"情绪曲线": "中",
"用户痛点": ["1"],
"机会点": ["1"]
}
}
},
要根据 schema.json 生产出设计稿需要做以下几件事情:
* 设计稿建立图层 名称与 scheme.json 中 key 的索引关系
* 固定模块直接从设计稿母板数据中输出对应的数据即可
* 名称和用户目标与期望找到索引并替换文本
* 根据旅程创建出对应的旅程模块。以母版中的旅程一为基准,拷贝后,进行位置偏移,并计算出最外层的宽度。
* 每一列根据返回文本数量,如旅程一中的用户行为里有 4 个文本。则创建出四个便签。并处理好每一个便签的位置关系即可。
组装器需要针对每一个模版编写组装逻辑。但逻辑大部分是通用的,如在后续增加模版,此处的开发成本很低。
Figma 数据解析
上图是设计稿数据到 Figma 的解析流程图,核心流程如下:
- 输入设计稿数据
- 节点树深度优先遍历
- 节点类型判断并创建节点
- 节点属性设置:位置,尺寸,填充,边框等
Figma 提供的图形创建能力可以https://www.figma.com/plugin-docs/api/api-reference/文档中了解。
解决用户等待焦虑
跟传统的 webAPI 不同,LLM 接口完整数据的响应时长根据数据量大小决定,本应用会输出大量文本,模型需要 40-60 秒的时间完成所有数据响应,因此会造成用户等待时间焦虑。
对话式交互
最初的设计是将文本展示给用户,这种方式是当前主流的LLM对话式应用的交互形式。实现这种方式只需要将LLM流式输出的文本展示出来即可。然而,这种方式的缺点也非常明显,画布中并没有实质性的图形渲染,且用户无法通过对话进行互动。因此,以文本作为主要的用户交互方式体验是不完整和不够理想的,不是最佳的解决方案。
渐进式渲染
那能不能像打字机效果一样,在流式数据传输过程中,一边生成一遍是渲染内容呢?
难点在于在组装模版和渲染过程中,我们是拿到标准化的数据结构再一次性插入画布。而在流式数据传输过程中返回的数据,只是整个最终结构化数据的某一个片段。如下所示:
// 最终的json数据
const data = '你好,以下是头脑风暴/n {"data":{"用户获取":["1","2","3","4","5"],"用户活跃":["1","2","3","4","5"],"用户留存":["1","2","3","4","5"],"获得收益":["1","2","3","4","5"],"推荐传播":["1","2","3","4","5"]}}'
// 流式传输过程中数据示例1
const process1 = '你好,以下是头脑风暴/n {"data":{"用户获取":["1","2","3","4","5"],"用户活跃":["1","2","3","4","5"],"用户留存":["1","2","3","4","5"],"获得收益":["1","2","3","4'
// 流式传输过程中数据示例2
const process2 = '你好,以下是头脑风暴/n {"data":{"用户获取":["1","2","3","4","5"],"用户活跃":["1","2","3","4","5"],"用户留存":["1","2","3","4","5"],"获得收益":[,'
如上面的数据示例所示,在流式传输过程中,需要把 process1 和 process 的数据转为下面的标准化 JSON 数据:
// 过程中数据示例1
const process1Filling = '{"data":{"用户获取":["1","2","3","4","5"],"用户活跃":["1","2","3","4","5"],"用户留存":["1","2","3","4","5"],"获得收益":["1","2","3","4"]}}'
const process2Filling = '{"data":{"用户获取":["1","2","3","4","5"],"用户活跃":["1","2","3","4","5"],"用户留存":["1","2","3","4","5"]}}'
前文提到了通过正则提取加 JSON.parse 的方式错误率很高。要实现上面的提取和补全,我们需要把 LLM 返回的内容提取和补全成标准的 JSON 数据,实现 JSON 数据提取的可控。然后在流式输出过程中写一个定时器,每隔一段时间走设计稿组装+渲染流程即可。
如下图所示,将整个渲染过程简化为五帧:
JSON 提取与补全算法
上图展示了整个算法的运行流程,其核心是实现一个有限状态自动机,通过逐个解析字符串并进行拼装和补充从而生成标准化的 JSON 数据格式。
分析模型示例
总结
在本文中,我们详细总结了实现 AI 可视化分析模型的过程中所需进行的功能拆解和实现思路。在此基础上,还分享了在利用 LLM 生成结构化数据时所遇到的问题及其相应解决方案。我们相信这些经验总结能够为在此领域工作的同行提供有价值的帮助,帮助大家更好地理解和掌握这些技术。同时,这些经验也能为后续的研究工作提供有益参考。我们希望这些总结能够为读者提供一个清晰详尽的指导和实践思路。
作者:腾讯程序员
文章来源:腾讯技术工程
推荐阅读
更多腾讯AI相关技术干货,请关注专栏腾讯技术工程 欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。