Files
smartmate/docs/frontend/newagent_business_card_对接说明.md
LoveLosita 0b0ed3c61a Version: 0.9.47.dev.260427
后端:
1. execute 节点继续拆职责——超大 execute.go 下沉为 node/execute 子包,按决策流、动作路由、上下文锚点、工具执行、状态快照、工具展示与参数解析拆分;顶层 execute.go 收敛为桥接导出,降低单文件编排/业务/模型/工具逻辑混写
2. 节点公共能力继续沉到 shared——抽出 LLM 纠错回灌、完整上下文调试日志、thinking 开关、统一上下文压缩、可见 assistant 文本持久化等 node_* 公共件,减少 execute 独占实现并为其他节点复用铺路
3. speak 文本整理能力独立收口——新增 speak_text 辅助文件,补齐正文归一化的独立承载,继续收缩 execute 主文件体积

前端:
4. NewAgent 时间线接入 business_card 业务卡片协议——schedule_agent.ts 新增 task_query / task_record 卡片载荷类型与 business_card kind;AssistantPanel 增加业务卡片事件存储、时间线恢复、块渲染分支与 BusinessCardRenderer 接入,同时保留 interrupt / status / tool / reasoning 多块并存
5. 新增任务查询卡片与任务记录卡片组件,并补充 DesignDemo 设计预览页与路由,前端可先行验证 business_card 的视觉与交互落点

文档:
6. 新增 newagent business card 前后端对接说明,明确 timeline kind、payload 结构、卡片分类、前后端发射/渲染约束
2026-04-27 17:35:55 +08:00

721 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# NewAgent 业务卡片前后端对接说明
## 1. 文档目标
本文用于约定 NewAgent 聊天时间线中的“业务结果卡片”协议,供前端先行实现卡片容器与渲染逻辑,后端后续按同一标准补齐事件发射。
本次只覆盖两类卡片:
1. 查询任务卡片
2. 任务记录卡片
其中“任务记录卡片”统一承载以下两个入口语义:
- 随口记
- 创建任务
这样做的原因是:两者最终落到前端展示时,表达的都是“系统中新增了一条任务/提醒”,如果硬拆成两套完全独立协议,会造成字段重复、渲染重复和样式分叉。
## 2. 适用范围
本说明只约定聊天时间线中的结构化卡片事件,不重做整套聊天 UI也不影响现有
- `assistant_text`
- `tool_call`
- `tool_result`
- `confirm_request`
- `schedule_completed`
现阶段建议复用“像 schedule_completed 一样通过 extra 事件驱动前端卡片”的模式,但不复用 `schedule_completed` 这个具体 kind。
## 3. 总体设计原则
### 3.1 业务卡片走独立事件
业务卡片不应伪装成:
- `status`
- `tool_result`
- `schedule_completed`
原因如下:
1. `status` 更适合阶段提示,不适合承载稳定业务结果。
2. `tool_result` 更适合工具过程回执,不适合承载用户真正关心的结果实体。
3. `schedule_completed` 是“排程完成信号卡”,语义过窄,不适合继续复用到任务域。
因此,本次建议新增统一事件类型:
- `business_card`
### 3.2 统一入口,卡片内再分类型
后端统一发:
- `kind = business_card`
- `display_mode = card`
再在 payload 中细分:
- `card_type = task_query`
- `card_type = task_record`
其中:
- `task_query` 表示“查到了什么”
- `task_record` 表示“刚刚记下/创建了什么”
### 3.3 尽量直接携带结果快照
本批业务卡片不建议完全照搬 schedule 的“只发信号、前端二次补拉”模式,而应优先直接携带卡片渲染所需的最小结果快照。
原因如下:
1. 查询任务结果是“本轮对话当时查到的内容”,若前端二次补拉,结果可能已变化。
2. 随口记 / 创建任务通常只需要 1 条新增结果,直接随事件下发最稳。
3. 业务卡字段本身不重,没有必要为一张小卡额外走一轮查询接口。
因此,本次推荐:
1. 查询任务卡:直接下发查询条件摘要 + 命中列表快照
2. 任务记录卡:直接下发新建任务摘要
## 4. 事件协议
## 4.1 Timeline kind 扩展
前端 `TimelineEvent.kind` 建议新增:
```ts
type TimelineEventKind =
| 'user_text'
| 'assistant_text'
| 'tool_call'
| 'tool_result'
| 'confirm_request'
| 'schedule_completed'
| 'interrupt'
| 'status'
| 'business_card'
```
## 4.2 payload 扩展建议
建议在现有 `payload` 中显式新增 `business_card` 字段,不建议长期把正式协议塞进 `meta`
推荐结构:
```ts
type BusinessCardType = 'task_query' | 'task_record'
type TaskRecordSource = 'quick_note' | 'create_task'
interface TimelineBusinessCardPayload {
card_type: BusinessCardType
title?: string
summary?: string
source?: TaskRecordSource
data: TaskQueryCardData | TaskRecordCardData
}
```
对应地,`TimelineEvent.payload` 可扩为:
```ts
interface TimelineEventPayload {
reasoning_content?: string
stage?: string
block_id?: string
display_mode?: 'card'
tool?: TimelineToolPayload
confirm?: TimelineConfirmPayload
business_card?: TimelineBusinessCardPayload
}
```
## 4.3 SSE extra 扩展建议
后端流式 extra 建议新增:
```json
{
"kind": "business_card",
"block_id": "quick_task.result",
"stage": "quick_task",
"display_mode": "card",
"business_card": {
"card_type": "task_query",
"title": "找到 4 条未完成任务",
"summary": "按截止时间升序",
"data": {}
}
}
```
建议后端在 `OpenAIChunkExtra` 中显式新增:
- `BusinessCard *StreamBusinessCardExtra`
而不是长期挂入 `Meta`
原因:
1. 业务卡已经被确认会长期存在,不再是灰度字段。
2. 前端渲染会高频依赖这些字段,显式类型更稳。
3. 时间线持久化也更容易做结构校验。
## 5. 两类卡片的数据结构
## 5.1 查询任务卡片
### 5.1.1 目标
用于展示“本轮查询任务时实际查到了哪些任务”,重点是结果快照,而不是工具过程。
### 5.1.2 推荐数据结构
```ts
interface TaskQueryCardTaskItem {
id: number
title: string
priority_group?: number
priority_label?: string
deadline_at?: string
is_completed?: boolean
}
interface TaskQueryCardData {
query_summary?: string
result_count: number
shown_count: number
has_more?: boolean
tasks: TaskQueryCardTaskItem[]
}
```
### 5.1.3 最小字段集
最少需要:
- `card_type = task_query`
- `title`
- `data.result_count`
- `data.tasks`
其中 `tasks` 每项最少建议包含:
- `id`
- `title`
### 5.1.4 增强字段
有条件时建议补充:
- `query_summary`
- `priority_label`
- `deadline_at`
- `is_completed`
- `shown_count`
- `has_more`
### 5.1.5 降级规则
1. 若只有 `result_count` 无任务列表:
- 前端仍可渲染简版统计卡,不展示列表区。
2. 若任务项缺少 `priority_label` / `deadline_at`
- 隐藏对应字段行,不展示占位符。
3.`result_count = 0`
- 渲染空结果态卡片,而不是回退纯文本。
### 5.1.6 示例
```json
{
"kind": "business_card",
"payload": {
"stage": "quick_task",
"block_id": "quick_task.result",
"display_mode": "card",
"business_card": {
"card_type": "task_query",
"title": "找到 4 条未完成任务",
"summary": "按截止时间升序",
"data": {
"query_summary": "关键词:离散数学;仅未完成;截止时间升序",
"result_count": 4,
"shown_count": 3,
"has_more": true,
"tasks": [
{
"id": 101,
"title": "离散数学作业 3",
"priority_group": 2,
"priority_label": "重要不紧急",
"deadline_at": "2026-04-29 21:00",
"is_completed": false
},
{
"id": 105,
"title": "离散数学命题证明复习",
"priority_group": 2,
"priority_label": "重要不紧急",
"deadline_at": "2026-05-01 18:00",
"is_completed": false
},
{
"id": 108,
"title": "离散数学错题整理",
"priority_group": 3,
"priority_label": "普通任务",
"deadline_at": "",
"is_completed": false
}
]
}
}
}
}
```
## 5.2 任务记录卡片
### 5.2.1 目标
用于展示“刚刚记下/创建出的那条任务结果”,统一承载:
- 随口记
- 创建任务
### 5.2.2 推荐数据结构
```ts
interface TaskRecordCardData {
id?: number
title: string
priority_group?: number
priority_label?: string
deadline_at?: string
urgency_threshold_at?: string
status?: string
created_at?: string
}
```
卡片额外语义通过外层字段区分:
```ts
interface TimelineBusinessCardPayload {
card_type: 'task_record'
title?: string
summary?: string
source?: 'quick_note' | 'create_task'
data: TaskRecordCardData
}
```
### 5.2.3 最小字段集
最少需要:
- `card_type = task_record`
- `source`
- `data.title`
### 5.2.4 增强字段
有条件时建议补充:
- `id`
- `priority_label`
- `deadline_at`
- `urgency_threshold_at`
- `status`
- `created_at`
- `summary`
### 5.2.5 source 语义约定
#### `source = quick_note`
表示这条记录来自“随口记”入口。
前端展示建议:
1. 头部弱化“正式创建”措辞。
2. 更强调“已帮你记下”。
3. 若字段较少,只展示标题和轻量标签即可成立。
#### `source = create_task`
表示这条记录来自“明确创建任务”入口。
前端展示建议:
1. 头部可用更正式的“任务已创建”表达。
2. 更适合展示 deadline / priority / status 等结构化信息。
### 5.2.6 降级规则
1. 若只有 `title`
- 仍渲染最简任务记录卡。
2. 若没有 `deadline_at`
- 不展示“无截止时间”字样,直接隐藏该字段区。
3. 若没有 `priority_label`
- 不展示优先级标签,避免为了填满 UI 硬造信息。
### 5.2.7 示例:随口记
```json
{
"kind": "business_card",
"payload": {
"stage": "quick_task",
"block_id": "quick_task.result",
"display_mode": "card",
"business_card": {
"card_type": "task_record",
"title": "已帮你记下",
"summary": "一条轻量提醒已写入任务系统",
"source": "quick_note",
"data": {
"id": 301,
"title": "周三晚上给导师发周报",
"priority_group": 2,
"priority_label": "重要不紧急",
"deadline_at": "2026-04-29 20:00",
"created_at": "2026-04-27 16:10:00"
}
}
}
}
```
### 5.2.8 示例:创建任务
```json
{
"kind": "business_card",
"payload": {
"stage": "execute",
"block_id": "execute.result",
"display_mode": "card",
"business_card": {
"card_type": "task_record",
"title": "任务已创建",
"summary": "已写入任务系统",
"source": "create_task",
"data": {
"id": 405,
"title": "完成离散数学第 1 节复习",
"priority_group": 1,
"priority_label": "重要紧急",
"deadline_at": "2026-04-28 22:00",
"status": "todo",
"created_at": "2026-04-27 16:12:00"
}
}
}
}
```
## 6. 前端对接要求
## 6.1 时间线模型扩展
前端建议扩展:
1. `TimelineEvent.kind` 增加 `business_card`
2. `payload.business_card` 增加强类型
3. `DisplayAssistantBlock.type` 增加 `business_card`
建议结构:
```ts
type DisplayAssistantBlockType =
| 'tool'
| 'status'
| 'reasoning'
| 'content'
| 'content_indicator'
| 'schedule_card'
| 'business_card'
```
## 6.2 组件拆分建议
建议前端按“两层组件”实现:
### 第一层:统一容器
- `BusinessCardRenderer.vue`
职责:
1. 根据 `card_type` 分发子组件
2. 兜底空态 / 未知类型
3. 统一外边距、动画、时间线嵌入样式
### 第二层:两张业务卡
- `TaskQueryResultCard.vue`
- `TaskRecordCard.vue`
这样可以保持:
1. 外层时间线接入统一
2. 卡片内部样式独立演进
3. 后续若新增其他业务卡,只需继续扩展 renderer
## 6.3 渲染策略
### 查询任务卡
建议展示:
1. 头部标题
2. 查询摘要
3. 命中数量
4. 任务列表(建议最多展示 3~5 条)
5. 若有更多结果,展示“还有 N 条”
### 任务记录卡
建议展示:
1. 头部标题
2. 来源标签:`随口记生成` / `已创建任务`
3. 任务标题
4. 优先级 / 截止时间等辅助信息
## 6.4 未知字段兼容
前端渲染必须允许以下情况存在:
1. 后端只返回最小字段集
2. 某些增强字段为空
3. 不同入口产生的字段丰富度不同
因此前端实现原则是:
- 只消费拿得到的字段
- 不对缺失字段报错
- 不渲染“空标签”“空时间”“--”
## 7. 后端对接要求
## 7.1 发射位置建议
考虑到当前 `node` 目录正在整理,本轮先定协议,不要求立刻改动节点实现。后端后续落地时,建议在以下业务完成点发射:
### 查询任务卡
建议在“查询任务成功且拿到最终结果快照”后发射。
候选位置:
- `quick_task` 查询成功路径
- 后续若有独立查询任务工具域,也应在最终结果汇总后发射
### 任务记录卡
建议在“写入任务系统成功并拿到任务结果”后发射。
候选位置:
- `quick_task` create 成功路径,对应 `source = quick_note`
- 正式任务创建成功路径,对应 `source = create_task`
## 7.2 发射时机约束
业务卡片必须满足以下约束:
1. 只在业务真实成功后发射
2. 不能在参数未齐、等待确认、仅计划阶段时提前发射
3. 不能把“工具调用开始”误当成“业务结果卡”
换句话说:
- `tool_call/tool_result` 负责过程
- `business_card` 负责结果
## 7.3 与纯文本回复的关系
业务卡片不是纯文本回复的替代物,而是补充物。
建议后端保持:
1. 正常 assistant speak 继续输出
2. 业务卡片作为同轮时间线中的独立 block 插入
这样用户既能看到自然语言结果,也能看到结构化回执。
### 7.3.1 默认范式:短正文 + 结果卡
本次明确约定:业务卡片默认采用“短正文 + 结果卡”的组合范式,不采用“用卡片替换 LLM 正文”的方案。
推荐理解如下:
1. LLM 正文负责自然语言衔接、解释和收口。
2. 业务卡片负责结构化结果展示。
3. 两者是互补关系,不是替代关系。
推荐表现形态:
- 查询任务:
- 正文示例:`我找到 4 条相关任务,先给你列重点。`
- 后接:查询任务卡片
- 随口记:
- 正文示例:`我帮你记下来了。`
- 后接:任务记录卡片
- 创建任务:
- 正文示例:`这条任务已经创建好了。`
- 后接:任务记录卡片
### 7.3.2 为什么不采用“卡片替换正文”
不建议让节点直接吞掉 LLM 原本准备输出的正文,只保留卡片,原因如下:
1. 自然语言回复承担上下文衔接作用,直接去掉后,聊天感会突然中断。
2. 卡片负责结果快照,正文负责语气和解释,两者职责不同。
3. “替换正文”会引入额外分支判断,容易再次出现“该显示的被吞掉 / 不该显示的被露出”的问题。
因此,本次协议层明确规定:
- `business_card` 是正文补充,不是正文替代。
- 前端收到 `business_card` 时,不应主动隐藏同轮 `assistant_text`
- 后端发出 `business_card` 时,也不应把它当作“正文已无需输出”的信号。
### 7.3.3 正文长度约束
虽然保留正文,但在存在业务卡片的场景下,正文应尽量短,不要把卡片里已经结构化展示的内容再用长段文字完整复述一遍。
建议约束:
1. 正文以一句或两句为宜。
2. 正文只表达结论、态度或过渡,不重复列出完整字段。
3. 任务标题、时间、优先级、命中列表等细节尽量交给卡片承载。
换句话说,本次推荐的最终交互形态是:
- 先给一句自然语言反馈
- 再给结构化业务卡片
而不是:
- 大段正文完整复述一遍
- 再来一张内容几乎重复的卡片
### 7.3.4 顺序建议
如果同一轮既有正文又有业务卡片,推荐前端按以下顺序展示:
1. `assistant_text`
2. `business_card`
原因:
1. 更符合自然阅读流:先“听结果”,再“看详情”。
2. 能保持聊天节奏,不会让卡片突兀抢到正文前面。
3. 与当前 `assistant_text + 结构化卡片` 的时间线模型一致。
### 7.3.5 卡片位置约束
本次进一步明确:业务卡片应当紧跟在“与之对应的那段 assistant 正文”后面,而不是拖到整轮消息流的绝对结尾再统一补发。
推荐顺序:
1. 业务成功
2. 输出一句简短 `assistant_text`
3. 立即输出对应的 `business_card`
4. 本轮结束,或仅保留极短的必要收尾
不推荐顺序:
1. 先输出结果正文
2. 中间再插入其他阶段提示、补充说明或收尾文案
3. 最后才在整轮末尾补一张业务卡片
这样做的问题是:
1. 卡片和对应正文的语义绑定会变弱。
2. 用户会误以为卡片是在回应更后面的内容,而不是前面的那句结果。
3. 卡片会更像“附录”或“补充材料”,而不是本轮结果的结构化主回执。
因此,前后端统一按以下口径理解:
- 业务卡片不是“整轮结束彩蛋”
- 业务卡片是“对应正文的紧随结果块”
也就是说,位置上应理解为:
- 紧跟对应消息后面
- 而不是放在整轮会话的绝对结尾
### 7.3.6 对后端发射时机的直接要求
后端后续补发 `business_card` 时,应尽量保证:
1. 卡片事件在对应 `assistant_text` 之后立即进入时间线。
2. 卡片事件之后不要再接大段重复解释。
3. 若确实需要补一句收尾,也应控制在极短长度内,避免把卡片重新推离它所服务的正文。
前端在渲染时,也不应为了“统一收口”而把业务卡片重新移动到该轮消息的最末尾。
## 8. 时间线持久化要求
若当前时间线已经会持久化 `tool_call``tool_result``confirm_request``schedule_completed`,则 `business_card` 也应进入同一条时间线持久化链路。
要求:
1. 刷新页面后能恢复卡片
2. 渲染顺序仍以 `seq` 为准
3. 不能只在 SSE 在线期间可见
## 9. 推荐落地顺序
考虑到当前 execute/node 还在精简,推荐顺序如下:
### 第一步:前端先落承载层
1. 扩展 timeline 类型
2. 扩展 `business_card` payload 类型
3. 新增 `BusinessCardRenderer.vue`
4. 新增 `TaskQueryResultCard.vue`
5. 新增 `TaskRecordCard.vue`
6.`AssistantPanel.vue` 接入渲染分支
### 第二步:后端补协议结构
1. 在 stream extra 中新增 `business_card`
2. 在 timeline 持久化 DTO 中补 `business_card`
3. 保证刷新后可恢复
### 第三步:后端补业务发射点
1. quick task 查询成功 -> 发 `task_query`
2. quick note 创建成功 -> 发 `task_record(source=quick_note)`
3. 正式创建任务成功 -> 发 `task_record(source=create_task)`
## 10. 本次明确不做的事
本说明暂不覆盖:
1. 修改工具调用卡片协议
2. 修改确认卡片协议
3. 把业务卡片统一改为前端二次补拉
4.`随口记``创建任务` 再拆成两套完全独立事件类型
## 11. 最终结论
本次业务卡片推荐采用以下标准:
1. 新增统一时间线事件:`business_card`
2. 卡片类型只保留两类:
- `task_query`
- `task_record`
3. `task_record``source` 区分:
- `quick_note`
- `create_task`
4. 卡片优先直接携带结果快照,不走“仅发信号、前端再查一次”的默认模式
5. 前端可先按本文把渲染层做完,后端后续按同一协议补发事件