固定回答模板扩展指南

本文说明如何在 lanque-app 中修改现有固定回答模板、增加数据组件,或接入一个全新的模板类型。

1. 先判断属于哪一种改动

需求示例主要改动范围
修改现有模板视觉调整 report 的字号、留白或表格对应 src/answers/**/*Template.vue 或其子组件
给现有模板增加组件report 增加一种真实数据图表完整模板、src/answers/composed/、组件注册表
给现有模板增加数据字段report.data 新增一种 MCP 原始记录类型、Schema、提示词、模板及文本导出
新增全新模板 ID新增 inventoryproduction完整运行链路,不能只新增 Vue 文件
新增全新业务域在 AGV、机车检修之外增加库存域全新模板链路,加业务域识别和 MCP 工具过滤

最重要的区别:

  • src/answers 负责“数据最后怎么画”。
  • 意图分类、MCP 取数、数据契约和校验负责“什么时候使用、给什么数据、数据是否可信”。

2. 当前运行链路

用户问题

意图分类:选择 template ID

业务域识别:agv / rail

发现并按业务域筛选 MCP 工具

模型规划、调用工具并收集本轮真实结果

client__submit_answer_payload
  ├─ 使用该模板的 JSON Schema 约束参数
  ├─ 校验业务域
  └─ 校验每条记录能否在本轮 MCP 结果中找到来源

生成 { template, data } 固定载荷

前端再次执行 normalize + Ajv 校验

整页大卡片或数据组件拼接

因此,仅把一个 Vue 文件放进 src/answers 不会让模板自动生效。

3. 只修改现有模板

当前主要固定模板为:

  • agv.statussrc/answers/AgvStatusTemplate.vue
  • workordersrc/answers/WorkOrderTemplate.vue
  • reportsrc/answers/ReportTemplate.vue

agv.dispatch 主要用于历史数据兼容,新 AGV 请求会落到 agv.status,不建议以它作为新功能入口。

3.1 只调整样式或布局

如果 template ID 和 data 结构均不变化,只需调整对应模板或它引用的子组件,不必修改模型、MCP 和 Schema 逻辑。

3.2 给现有模板增加数据组件

当前整页模式与拼接模式共用同源子组件。建议按以下方式接入:

  1. src/answers/composed/<业务目录>/ 新增组件。
  2. 组件接收当前模板的原始 data,不要自行请求 MCP。
  3. 在完整 *Template.vue 中以 embedded 方式组合该组件。
  4. src/answers/composed/registry.ts 注册拼接模式下的出现条件、顺序、区域和宽度。

推荐组件约定:

defineProps<{
  data: SomeTemplateData;
  embedded?: boolean;
}>();
  • embedded: true:作为完整大卡片内部区块,不渲染第二层卡片边框。
  • 默认模式:作为独立拼接组件,使用克制的卡片外壳。

3.3 空数据语义

模板应区分下面两种情况:

// 没有该字段:本轮没有查询这项数据
{}

// 字段存在且为空:查询过,真实结果为 0 条
{ plans: [] }

判断“是否查询过”应使用 own-property,而不是只写 data.plans ?? []。非空数组才渲染大型内容区块;全局空态只保留一个,避免每个组件重复显示空卡。

4. 新增全新模板 ID

假设新增模板 ID 为 inventory,至少需要完成以下步骤。

4.1 定义 TypeScript 数据契约

文件:src/contracts/answers.ts

需要:

  1. inventory 加入 AnswerTemplate
  2. 定义 InventoryData,字段应贴近 MCP 的真实返回。
  3. 定义 InventoryAnswer
  4. 将其加入 AnswerPayload 联合类型。
  5. TEMPLATE_TITLE 中加入用户可见标题。

不要把图表坐标、卡片颜色、标题文案或模型计算的 KPI 放进数据契约。契约应以真实业务记录为主,统计值由客户端确定性派生。

4.2 定义 JSON Schema

文件:src/contracts/answerSchemas.ts

需要:

  1. 为新数据结构定义严格 Schema。
  2. 明确 required、enum、数组元素和 additionalProperties
  3. 将 Schema 加入 dataSchemas

同一份 Schema 会同时用于:

  • client__submit_answer_payload 的模型函数参数。
  • 前端收到载荷后的 Ajv 校验。

未登记 Schema 时,即使模型返回了新模板,前端也会判定为未知模板并拒绝渲染。

只有在确实需要兼容常见格式错误时,才调整 normalizeAnswerPayload();不得利用 normalize 生成缺失的业务事实。

4.3 注册整页模板

文件:src/answers/registry.ts

导入新的 Vue 模板,并建立:

['inventory', InventoryTemplate]

模板组件必须能够接收 data prop。

项目中虽然已有 registerTemplate()registerAnswerSchema(),但目前没有自动扫描目录或自动加载插件模板的入口。因此,现阶段仍然需要显式注册。

4.4 配置模板提示词和本地意图

文件:src/llm/systemPrompt.ts

需要补充:

  • TEMPLATE_PAYLOAD_RULES:模型应原样提交哪些 MCP 字段,以及严禁补造哪些字段。
  • DATA_COLLECTION_NEEDS:为该视图应收集哪些数据维度。
  • selectAnswerTemplate():明确关键词时的本地快速路由和分类失败兜底。

提示词只描述数据需求和真实性约束,不描述 HTML、CSS 或图表坐标。

4.5 扩展模型意图分类器

文件:src/ipc/openai/OpenAiIpcClient.ts

需要同步修改:

  • ClassifiedTemplate 的合法值判断。
  • 兼容普通文本响应时使用的模板解析。
  • 意图分类器 system prompt 中的模板说明。
  • select_answer_template 函数的 enum。
  • 客户端执行进度中使用的模板名称。

遗漏这里时,真实模型不会选择新模板,即使渲染组件和 Schema 已经存在。

4.6 定义复制与语音摘要

文件:src/utils/answerText.ts

分别为新模板实现:

  • answerPayloadToPlainText():复制、无障碍文本等场景使用。
  • answerPayloadToSpeechText():TTS 使用的简洁摘要。

不要把 JSON、全部表格或视觉坐标直接交给 TTS。

5. 新增业务域时的额外工作

如果新模板属于 AGV 和机车检修之外的业务域,还需要扩展:

5.1 业务域类型与识别

文件:src/llm/systemPrompt.ts

  • BusinessDomain
  • selectBusinessDomain()

回答形态和业务域是两个维度。例如 report 是回答形态,它可以承载 AGV 报表,也可以承载机车检修报表。

5.2 MCP 工具域过滤

文件:src/ipc/openai/OpenAiIpcClient.ts

需要检查并扩展:

  • relatedDeniedTools()
  • toolMatchesDomain()
  • filterDiscoveryForDomain()
  • submittedDomainIssue()

这些逻辑决定模型能看到哪些 MCP 工具,以及最终提交的数据能否跨业务域。

模板接入不会自动授予 MCP 权限。对应 Server、Tool、Skill 权限仍需由平台授权配置或应用配置提供。

6. 组件拼接模式

如果新模板需要支持设置页中的“数据组件拼接”,还要修改:

  • src/components/AnswerCard.vue:将新 template ID 加入拼接模式允许范围。
  • src/answers/composed/registry.ts:扩展数据联合并注册组件。

每个组件注册项应明确:

  • supports:支持哪些 template ID。
  • when:哪些真实字段到达时出现。
  • order:阅读顺序。
  • zone:概览、分析、关系或明细。
  • span:所占宽度。

如果不接入拼接模式,新模板仍可走整页模式,但设置切换到拼接模式时会回退到整页模板。产品上应明确这是预期行为还是遗漏。

7. 体验配套项

以下不是数据链的核心,但完整产品通常应该同步处理。

7.1 等待骨架与状态文案

  • src/components/ViewSkeleton.vue:增加新模板的骨架形状。
  • src/features/chat/MessageStream.vue:增加步骤标签到模板的映射,以及等待阶段名称。

7.2 Mock 模式

文件:src/ipc/mock/fixtures.ts

如果未配置 API 时也要演示新模板,需要增加:

  • 合法的固定 payload fixture。
  • Mock 意图匹配规则。
  • Mock 回答选择分支。

只运行真实模型时可以暂不添加,但会失去无 API 场景的回归能力。

7.3 设置页

当前“整页大卡片 / 数据组件拼接”是全局显示模式,不需要为每个模板增加一个设置项。

8. 数据真实性约束

新模板应继续遵守当前固定视图链路的约束:

  1. 模型只整理本轮成功 MCP 结果中的原始记录。
  2. 模型不生成 HTML、CSS、SVG、图表坐标或视觉配置。
  3. 模型不补齐 MCP 没有返回的工单号、负责人、路线、ETA 等事实。
  4. 汇总、排序、状态翻译和图表序列尽量由前端纯函数计算。
  5. 每条非空记录必须能在本轮 MCP 结果中找到来源。
  6. 空数组是有效结果;缺字段不能被解释为业务数量为零。
  7. 可选字段缺失时隐藏对应细节或显示“—”,不得制造默认业务值。

9. 推荐目录结构

src/answers/
  InventoryTemplate.vue
  registry.ts
  composed/
    inventory/
      presentation.ts
      InventoryOverviewBlock.vue
      InventoryDistributionBlock.vue
      InventoryListBlock.vue
    registry.ts

src/contracts/
  answers.ts
  answerSchemas.ts

presentation.ts 只放确定性的展示派生,例如排序、分组、计数和格式化;不要在这里请求网络或补造记录。

10. 验收清单

必测数据场景

  • 字段未查询。
  • 已查询但数组为空。
  • 单条记录。
  • 多条及大量记录。
  • 可选字段缺失。
  • 超长编号和长文本。
  • 无效 enum、错误类型和额外字段能够被 Schema 拒绝。

必测界面场景

  • 整页大卡片模式。
  • 数据组件拼接模式。
  • 桌面宽度与小窗口宽度。
  • 明暗主题。
  • 横向、纵向滚动。
  • 复制内容。
  • TTS 摘要。
  • 流式等待骨架与最终视图切换。

必测运行场景

  • 明确关键词能命中新模板。
  • 模型分类器能返回新 template ID。
  • 分类接口失败时本地规则能合理兜底。
  • 只向模型暴露当前业务域的 MCP 工具。
  • MCP 返回真实数据时可以提交并通过来源校验。
  • 无相关数据时不生成模拟记录或空壳视图。

11. 本地验证命令

在项目根目录运行:

.\node_modules\.bin\vue-tsc.cmd --noEmit
.\node_modules\.bin\vite.cmd build

随后至少在应用内分别用整页、拼接两种模式测试一次真实问题。

12. 最小接入清单

新增全新模板 ID 时,可按下面顺序执行:

  • contracts/answers.ts:模板 ID、Data、Answer、联合类型、标题。
  • contracts/answerSchemas.ts:严格数据 Schema。
  • answers/*Template.vue:完整视图。
  • answers/registry.ts:整页模板注册。
  • llm/systemPrompt.ts:字段规则、数据需求、本地意图。
  • OpenAiIpcClient.ts:分类枚举、说明、解析与进度名。
  • 新业务域时:MCP 工具域过滤和提交域校验。
  • utils/answerText.ts:复制文本和 TTS 摘要。
  • 需要拼接时:AnswerCard.vueanswers/composed/registry.ts
  • ViewSkeleton.vueMessageStream.vue:等待体验。
  • ipc/mock/fixtures.ts:Mock 回归。
  • 类型检查、生产构建及界面矩阵验证。