问卷系统的 AI 化改造之路

28 阅读4分钟

最近有幸参与了公司内部的一个 AI 项目的开发,想分享一下过程中遇到的问题以及收获的经验

项目背景

问卷 场景接入 AI ,支持

  • AI 创建问卷。
  • AI 生成统计。

当时调研发现 腾讯问卷 已经接入了 AI 相关的能力,想直接接入节省开发成本。但奈何费用太贵了,所以决定参考它的交互来自研实现。

核心功能实现思路

AI 生成问卷

我的实现思路是让 AI 生成定义好的 json 来渲染表单。

具体流程如下:

---
config:
  layout: dagre
---
flowchart LR
    Start(["开始"]) --> Define["定义 JSON 字段要求说明"]
    Define --> Prompt["作为提示词发送给 AI"]
    Prompt --> Gen["AI 根据要求返回指定格式 JSON"]
    Gen --> Validate{"使用 Zod 校验"}
    Validate -- 校验失败 --> Retry["修复提示词/重试"]
    Retry --> Prompt
    Validate -- 校验通过 --> Loop["遍历 JSON 数据"]
    Loop --> Render["生成并渲染相应的表单组件"]
    Render --> End(["完成"])

    style Define fill:#f9f,stroke:#333,stroke-width:2px
    style Gen fill:#bbf,stroke:#333,stroke-width:2px
    style Validate fill:#fb1,stroke:#333,stroke-width:2px
    style Render fill:#bfb,stroke:#333,stroke-width:2px

AI 生成统计结果

生成统计结果有以下要求:

  1. markdown 流式渲染
  2. 支持各类图表(饼图、折线图等)

关于 markdown 流式渲染,我找到了以下优秀的开源解决方案:

项目名称开发团队支持框架
streamdownVercelReact
@ant-design/x-markdownsAnt DesignReact
markstream-vue社区开发者 (Simon-He95)React, Vue

我在项目中使用的是 streamdown,但是它对流式渲染类似 <custom-component></custom-component> 的自定义组件支持的并不好,只能自定义内置的一些 markdown 组件。

x-markdown 在设计时便考虑到了自定义组件的处理,会在解析 markdown 时为自定义组件添加流式处理相关的 props ,你可以方便的处理加载中的状态。如果你本来就使用了 ant design 作为组件库,并且有在 markdown 中渲染自定义组件的需求,首选 x-markdown

图表渲染我通过 mermaid 来实现,让 AI 来选择合适的图表展示统计结果。也可以通过自定义图表组件来实现,但是要将组件的 api 整理成文档发给 AI,阿里巴巴也开源了相关的图表渲染实现,可以作为参考。

问题和经验总结

AI 接入

我们在项目中接入了多个 AI 大模型:

  • deepseek v3
  • deepseek r1
  • qwen3-max
  • qwen3-plus

当模型多了以后,问题也随之而来:相同的提示词,有的模型可以完美输出结果,但是有的模型连提示词都不能正确理解。

为了解决这个问题,我们将提示词单独抽离出来在团队内部进行展示,征集团队的意见来改进优化提示词,甚至要针对不同的模型适配专有的提示词

这让我更加深刻的体会到为什么需要提示词工程

提示词设计

下面涉及到的代码和提示词只是辅助说明思路,实际项目中还需要根据业务场景进行调整

问卷系统中集成了很多表单项,如果将所有表单项的 api 一次性发给 AI 的话,上下文会很庞大并且有很多 AI 完全不需要的表单 api

为了解决这个问题,我先用一个 map 来存储所有的表单项以及它的 api:

const apiMap = {
  input: {
    description: '组件介绍',
    api: 'markdown api 描述'
  },
  radio: {
    description: '组件介绍',
    api: 'markdown api 描述'
  },
  checkbox: {
    description: '组件介绍',
    api: 'markdown api 描述'
  },
  // ... 省略其它组件
}

然后我把和 AI 的交互分为2步:

  1. 设计表单
  2. 生成表单 json

在设计表单阶段,我只需要将所有支持表单项的名称和描述发给 AI

const prompt = `你是一个表单设计专家,请根据以下表单组件列表设计一个问卷表单
支持的表单组件列表:
${Object.entries(apiMap).map(([key, value]) => `- ${key}: ${value.description}`).join('\n')}

请输出一个包含表单内容和表单项的设计, 格式如下:
{
  "formItems": [ "input", "radio", ... ] // 只包含表单项名称
  "content": "问卷具体内容..." // 使用 markdown 格式描述问卷的具体内容
}

在生成表单 json 阶段,我会根据上一步 AI 返回的表单项名称,从 apiMap 中取出对应的 api 发给 AI

const apis = formItems.map((item: string) => {
  return `### ${item}\n\n${apiMap[item].api}`
}).join('\n\n')

const prompt = `你是一个表单生成专家,表单内容如下:
${content}

表单组件 API 如下:
${apis}

请根据表单内容和组件 API 生成一个符合要求的 JSON
`

这样就可以在上下文中去掉无关表单项的 api,极大的减少上下文占用问题。

日志

在真正投入使用的时候,渐渐出现了以下问题:

  • 总听到有人说:"AI生成的内容为什么和我的需求完全没有关联?怎么生成时间这么长?"
  • 同事和领导可能会质疑这是 AI 的问题,还是你自己的代码逻辑有问题?
  • 提示词是通过各种复杂业务逻辑拼接出来的,比较难定位是提示词写的有问题,还是代码写的有问题。

为了应对以上问题,我们可以为 AI 接口请求添加日志,记录请求的提示词耗时响应内容等关键指标,帮助我们更好的排查问题。

结语

以上便是我在整个过程中的思考和总结。

AI 爆火的这段时间,我一直在积累相关的知识,但是却一直没有一个很好的实践机会。这个项目是一个很好的契机,帮我在真实项目中验证了自己的学习成果,希望也能对你有所帮助。