2026-10-16 16:15:18 +08:00
|
|
|
|
# LangGraph 迁移执行作业:从脚手架到等价 API 服务
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
> 版本:重写版,依据本地 `main` 提交 `22853f1` 及本分支现有后端边界编写。
|
|
|
|
|
|
>
|
|
|
|
|
|
> 读者:掌握 Python、异步函数、HTTP 和基本测试,尚未独立交付过 LangGraph 服务的本科生。
|
|
|
|
|
|
>
|
|
|
|
|
|
> 最终成果:调用方继续使用现有 API,事故处理流程由 LangGraph 执行,业务数据可以持久化和恢复。
|
|
|
|
|
|
>
|
|
|
|
|
|
> 本文是执行计划;下文标为“新增”的目录、命令和能力均为待实现交付物。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
## 作业说明
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
你将接手一个真实的交通事故信息采集服务。现有 FastAPI 服务把对话交给 FastGPT 工作流,本作业要求逐步用 LangGraph 实现同样的外部服务。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
先搭建一个可以运行的图,再把它接到现有接口,然后逐项补齐业务能力。每次作业结束,都必须留下一份他人可以运行的程序和验证证据。不要等到所有模块写完才第一次启动服务。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
完成作业后,你应当能够解释:一次请求如何变成图输入,节点如何读取和更新状态,条件边为何选择某个阶段,消息如何进入模型,业务字段如何落库,以及客户端收到成功后为何可以相信数据已经保存。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
本期迁移范围为 `POST /chat`、`POST /chat?stream=true`、`POST /set_info`、`POST /get_info`、`DELETE /delete_session`。保留 `GET /` 的存活响应。语音 WebSocket、Pipecat、STT/TTS 和语音协议不在本次改造范围;切换文本后端时,必须明确现有语音管线仍依赖 FastGPT 的配置与生命周期,不能因此破坏语音服务启动。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
### 提交规则
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
1. 按作业 1—10 顺序推进。每次提交对应一个可独立审查的里程碑。
|
|
|
|
|
|
2. 每次提交附运行命令、预期结果、实际结果和已知限制;测试失败必须解释原因。
|
|
|
|
|
|
3. 真实模型只用于后期联调和评测。日常图测试使用固定输出的 fake LLM,能够离线重复执行。
|
|
|
|
|
|
4. 每个业务规则都要注明来源:代码、工作流节点或评测案例。来源冲突时记录差异及采用依据。
|
|
|
|
|
|
5. 配置样例使用占位值;对话样例使用合成数据;不得提交凭证、真实身份证和手机号。
|
|
|
|
|
|
6. 本文给出的工时是单人专注开发的估算,不包含等待基础设施和业务确认的时间。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
### 里程碑总览
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
| 作业 | 结束时可以演示什么 | 预计工时 | 分值 |
|
|
|
|
|
|
|---|---|---:|---:|
|
|
|
|
|
|
| 1. LangGraph 脚手架 | 不依赖模型运行最小图,并恢复同一会话 | 4—6 小时 | 8 |
|
|
|
|
|
|
| 2. 接入现有 API | fake 图经现有 `/chat` 返回 JSON 和 SSE | 6—8 小时 | 10 |
|
|
|
|
|
|
| 3. 领域状态与事件 | 用代码推进拍照、无回复和失败事件 | 8—12 小时 | 12 |
|
|
|
|
|
|
| 4. 事故信息采集 | 多轮采集、跳过已知信息并正确分流 | 12—18 小时 | 15 |
|
|
|
|
|
|
| 5. 拍照与信息核实 | 跑完单车、双车及更正信息流程 | 10—16 小时 | 12 |
|
|
|
|
|
|
| 6. 同一份业务数据 | 四个业务接口共享内存态并正确删除 | 8—12 小时 | 10 |
|
|
|
|
|
|
| 7. PostgreSQL 与恢复 | 重启、并发和失败重试后数据仍一致 | 16—24 小时 | 13 |
|
|
|
|
|
|
| 8. 完整流式执行 | 真正增量输出,并处理错误和断连 | 10—16 小时 | 8 |
|
|
|
|
|
|
| 9. 服务运行保障 | 配置、健康检查、日志和访问边界可验证 | 8—12 小时 | 5 |
|
|
|
|
|
|
| 10. 等价性与发布 | 完整回归、演示、灰度与回退演练 | 10—16 小时 | 7 |
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
预计合计 92—140 小时,可按每周 12—18 小时安排为 7—10 周。先完成离线里程碑,再安排真实模型和 PostgreSQL 的集成环境。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
## 开工前的阅读地图
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
只做必要阅读,记录一页笔记后立即进入作业 1。不要先照搬大型生产模板。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
| 文件 | 阅读时应回答的问题 |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `src/backends/chat.py` | `ChatInput`、`ChatResult`、`TextDelta`、`FormUpdate` 怎样隔离供应商? |
|
|
|
|
|
|
| `src/backends/fastgpt.py` | FastGPT 返回的文本和表单如何转成中立结果? |
|
|
|
|
|
|
| `src/core/fastgpt_client.py` | 客户端何时初始化,依赖如何注入,何时关闭? |
|
|
|
|
|
|
| `src/api/endpoints.py` | 哪些逻辑是协议适配,哪些仍直接调用 FastGPT? |
|
|
|
|
|
|
| `src/schemas/models.py` | 请求默认值、字段长度、返回类型分别是什么? |
|
|
|
|
|
|
| `workflow/20261016/事故信息采集2025118.json` | 当前各模式实际走哪些节点和条件边? |
|
|
|
|
|
|
| `evals/cases/`、`evals/README.md` | 当前已经覆盖哪些对话?runner 实际断言了什么? |
|
|
|
|
|
|
| `test/api/`、`test/backends/`、`test/test_client_mode.py` | 已有后端边界测试和公共契约测试是否与当前代码一致? |
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
阅读 LangGraph 官方文档中的 `StateGraph`、state/reducer、conditional edges、`InMemorySaver`、`AsyncPostgresSaver`、异步执行和 streaming 章节。采用依赖版本对应的 API,首次提交记录版本和文档链接,避免混用旧教程。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
有两处特别需要辨别:`prompts/20261012/事故信息收集.txt` 与最新工作流内嵌提示词并不完全一致;`docs/chat-stream-mode.md` 未列出全部现有字段和事件。以当前代码、最新工作流和新评测案例交叉核对,不能单独以旧文档决定兼容行为。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
## 必须守住的外部契约
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
以下是执行作业时的检查表。作业 2 将其变成可运行的契约夹具。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
| 项目 | 必须保留的行为 |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `/chat` 请求 | `sessionId`、`timeStamp`、`text`;`clientMode` 默认为 `direct`,空串、空白和 null 也归一到 `direct`;允许 `browser_addon` |
|
|
|
|
|
|
| 可选开关 | `needFormUpdate=false`、`useTextChunk=false`;两者的当前行为分别取样冻结 |
|
|
|
|
|
|
| 非流式响应 | `sessionId`、`timeStamp`、`outputText`、`formUpdate`、`nextStage`、`nextStageCode`、字符串 `code`、`msg` |
|
|
|
|
|
|
| 状态映射 | 内部 `3001/3002/1002 → 1002`,`2006 → 2004`,`2017 → 2016`,`2020 → 0002`;历史映射保留,即使新流程不再经过该状态 |
|
|
|
|
|
|
| 客户端命名空间 | `browser_addon` 的外部码为 `browser_addon.<归一化状态码>`,领域状态仍保存原始内部码 |
|
|
|
|
|
|
| SSE | 事件名为 `stage_code`、`formUpdate`、`text_delta`、`done`、`error`;正文不泄漏 state 标签 |
|
|
|
|
|
|
| `/set_info` | `value` 是字符串,`includeInputInfo` 默认为 false;保留输入 schema,内部改为直接写业务状态 |
|
|
|
|
|
|
| `/get_info` | `value` 仍为 JSON 编码后的字符串;支持 `all/acdinfo/acdhuman1/acdhuman2` 和单字段 |
|
|
|
|
|
|
| 分组读值 | 分组内布尔值映射为字符串 `"1"/"0"`,缺失字段为 `""`;单字段当前直接 `json.dumps`,不得错误套用分组转换 |
|
|
|
|
|
|
| `/delete_session` | 使用 DELETE 和 JSON 请求体,保留已有响应字段和字符串业务码 |
|
|
|
|
|
|
| 错误 | 区分 HTTP 状态与响应体业务码,保存当前认证、限流、后端失败和参数错误的样例 |
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
现有 SSE 允许 `formUpdate` 在状态码前后到达,不能把旧计划中的固定事件顺序当成既有契约。最低保证为:`stage_code` 先于正文、一次成功只有一个 `done`、失败以一个 `error` 终止且不再发送 `done`。如果要进一步收紧表单事件顺序,须先用真实消费者验证并记录约定。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
模型流式和非流式的一致性,使用**同一固定模型输出或同一已记录回放**验证。对真实模型分别调用两次,不能要求自由生成文本逐字相同。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
## 作业 1:搭建并运行 LangGraph 脚手架
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
**学习目标:** 理解图、状态、节点、边与 thread 的关系,先让它们在命令行中工作。
|
|
|
|
|
|
|
|
|
|
|
|
**输入:** 当前仓库、可用 Python 环境;不需要数据库和真实模型密钥。
|
|
|
|
|
|
|
|
|
|
|
|
**任务:**
|
|
|
|
|
|
|
|
|
|
|
|
1. 在现有依赖管理方式上加入明确版本的 LangGraph 及必要依赖,记录 Python 版本和可复现安装方法。此时无需为迁移包管理器重构全仓。
|
|
|
|
|
|
2. 新增 `src/agent/state.py` 和 `src/agent/graph.py`。最小状态含 `session_id`、`turn_count`、`input_text`、`response_text`、`stage_code`。
|
|
|
|
|
|
3. 创建 `START → normalize_input → reply → END`。`reply` 使用固定模板,每次只增加一次轮数。
|
|
|
|
|
|
4. 使用 `InMemorySaver` 编译图,通过 `configurable.thread_id` 区分会话;使用 `ainvoke` 执行。
|
|
|
|
|
|
5. 新增 `examples/langgraph_smoke.py`:同一 thread 输入两次,另一个 thread 输入一次,打印轮数和文本。脚本不导入 FastGPT、不读取其密钥。
|
|
|
|
|
|
6. 添加最小图测试:同会话累计轮数、不同会话隔离、重复执行脚本得到相同结果。
|
|
|
|
|
|
|
|
|
|
|
|
**交付物:** 最小图、示例脚本、依赖安装说明和上述三个断言。
|
|
|
|
|
|
|
|
|
|
|
|
**验收命令(本作业新增):**
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
python examples/langgraph_smoke.py
|
|
|
|
|
|
python -m pytest test/agent/test_scaffold.py -q
|
2026-07-26 07:24:30 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
预期三个调用的轮数分别为 `1、2、1`。内存 saver 的数据在进程退出后消失,这是本阶段明确的限制。
|
|
|
|
|
|
|
|
|
|
|
|
**讲解题:** 如果每轮随机生成 thread ID,会丢失什么?把数据库连接放进 state 会造成什么问题?
|
|
|
|
|
|
|
|
|
|
|
|
## 作业 2:把最小图接入现有 API
|
|
|
|
|
|
|
|
|
|
|
|
**学习目标:** 建立第一条端到端路径:HTTP → 后端适配器 → 图 → HTTP。
|
|
|
|
|
|
|
|
|
|
|
|
**前置:** 作业 1 通过。
|
|
|
|
|
|
|
|
|
|
|
|
**任务:**
|
|
|
|
|
|
|
|
|
|
|
|
1. 新增 `src/backends/langgraph.py`,实现现有 `ChatBackend`。`complete()` 返回 `ChatResult`,`stream()` 返回 `TextDelta | FormUpdate`。
|
|
|
|
|
|
2. 初期继续让 API 层解析 state prefix。图返回 `<state>1002</state>固定文本`,避免同时重写供应商边界和公共协议。
|
|
|
|
|
|
3. 提供 `AGENT_BACKEND=fastgpt|langgraph` 类型化配置和依赖工厂。LangGraph fake 模式启动不得要求 FastGPT 密钥;仍启用语音 FastGPT 路径时单独校验所需配置。
|
|
|
|
|
|
4. 将资源初始化和关闭纳入 lifespan。两种后端分别执行启动冒烟验证,不仅在测试中覆盖依赖。
|
|
|
|
|
|
5. 在现有 `test/api/` 补充 HTTP 级契约测试;检查 OpenAPI schema、JSON 响应和 SSE 事件。
|
2026-10-19 13:32:31 +08:00
|
|
|
|
6. 在 Conda 的 `py3.11` 环境先运行已有测试并记录基线。当前测试已覆盖默认 `clientMode=direct`、API 向 `ChatInput` 传递该模式,以及 `nextStageCode` 的 32 字符上限。后续按当前实际契约维护预期,不能为消除红灯收窄合法协议。
|
2026-10-16 16:15:18 +08:00
|
|
|
|
7. 保存请求与响应夹具,覆盖两种 clientMode、分句开关、表单开关、state 标签跨 chunk 和错误响应。
|
|
|
|
|
|
|
|
|
|
|
|
**交付物:** 可切换的图后端、HTTP 契约夹具、修正说明、两种后端的启动方法。
|
|
|
|
|
|
|
|
|
|
|
|
**验收:** `POST /chat` 返回固定文本和 `1002`;`browser_addon` 返回 `browser_addon.1002`;流式请求得到 stage、正文和单个 done;所有已有测试通过或有明确的环境阻断记录。此时允许把固定完整答案切为几个片段,提交说明必须标注“尚未接入模型增量输出”。
|
|
|
|
|
|
|
|
|
|
|
|
## 作业 3:建立领域状态和确定性事件
|
|
|
|
|
|
|
|
|
|
|
|
**学习目标:** 让业务状态可以检查和推演,并区分“事件”与“用户说了一句话”。
|
|
|
|
|
|
|
|
|
|
|
|
**任务:**
|
|
|
|
|
|
|
|
|
|
|
|
1. 扩充 state:内部阶段、原始状态码、待回答问题、事故字段、两方人员字段、字段确认状态、澄清计数、无回复计数、本轮 patch、响应文本和业务版本。
|
|
|
|
|
|
2. 使用 Pydantic 校验边界数据;图 state 保持可序列化,不保存 HTTP client、连接、音频和图片 base64。
|
|
|
|
|
|
3. 在 API/应用边界把 `【拍摄完成】`、`【用户无回复】`、`【客户端连续3次拍摄识别失败:原因】` 转成明确事件。图内部按事件类型处理,普通“拍好了”不能当完成事件。
|
2026-10-19 16:25:04 +08:00
|
|
|
|
4. 图结构对齐工作流节点:事故信息收集、单车拍照、双车拍照、信息确认各为独立节点(对照 `workflow/20261016/事故信息采集2025118.json` 的 chatNode),节点间用条件边按 `stage_code` 路由(对照 Condition#2/#7/#9 等分支)。节点内部的小步推进(拍照步骤、分流判断)由节点内 LLM 提出目标阶段码——模型负责自然语言,代码按白名单(`transitions.LEGAL_TRANSITIONS`)校验,非法提议不写状态(受控修复重试在作业 4-10);白名单条目必须对照工作流提示词与条件边登记来源,不能只凭状态码相像填写。所有 stage_code(含拍照失败 → 0005、连续两次无回复 → 0004)都由节点内 LLM 提议,代码只负责事件解析、无回复计数等上下文簿记与合法性校验,不代替模型决定。
|
2026-10-16 16:15:18 +08:00
|
|
|
|
5. 拍照失败通知收到一次就产生 `0005`;客户端已经完成三次统计,服务端不得再累计三次。
|
|
|
|
|
|
6. 连续两次无回复产生 `0004`,中间有效回复如何重置计数必须有测试。
|
|
|
|
|
|
7. 把“本轮输出字段”在每轮开始时清空;历史事故字段继续保留,避免旧 `formUpdate` 在下一轮重复返回。
|
|
|
|
|
|
|
2026-10-19 16:25:04 +08:00
|
|
|
|
**交付物:** `events.py`、`transitions.py`(节点内确定性规则)、按工作流拆分的多节点图(`graph.py`)、状态模型、规则来源表、参数化转换测试。
|
2026-10-16 16:15:18 +08:00
|
|
|
|
|
|
|
|
|
|
**验收:** 无模型调用即可走完拍照事件链;在拍照中输入“继续”保持当前步骤;非法跨阶段转换不写状态;明确的转人工事件可以终止当前流程。
|
|
|
|
|
|
|
|
|
|
|
|
**讲解题:** 为什么外部看到 `1002`,内部仍必须区分采集、单车核实和双车核实?
|
|
|
|
|
|
|
|
|
|
|
|
## 作业 4:实现事故信息采集与 LLM 接入
|
|
|
|
|
|
|
|
|
|
|
|
**学习目标:** 用模型理解自然语言,用代码验证并提交业务状态。
|
|
|
|
|
|
|
|
|
|
|
|
**任务:**
|
|
|
|
|
|
|
|
|
|
|
|
1. 从最新工作流提取事故采集提示词和节点路由,保存为可审查文本并注明源节点 ID。明确 `direct` 与 `browser_addon` 的差异。
|
|
|
|
|
|
2. 实现可注入的异步 LLM 服务:fake 与真实 provider 使用相同接口,包含超时、取消、有限重试和模型配置。
|
|
|
|
|
|
3. 普通自然语言轮次由同一次回复生成 `<state>XXXX</state>正文`,代码验证前缀、状态枚举和转换守卫。不得仅为复判状态再调用一次模型。
|
|
|
|
|
|
4. 定义候选字段的结构化提取接口和校验。确需独立提取调用时记录原因、调用次数和延迟;不得从用户可见回复中靠正则猜全部业务字段。
|
|
|
|
|
|
5. 对每个字段区分未知、候选、已确认。最新工作流要求:已经明确提供的有效信息直接记录,跳过已知项;一次补齐剩余信息后直接分流;不能沿用旧提示词逐项重复确认。
|
|
|
|
|
|
6. 注入可控时钟和时区,校验相对时间、当前时间、未来时间与边界;测试不依赖运行机器当天时间。
|
|
|
|
|
|
7. 实现人伤明确肯定、明确否定、模糊/ASR 破碎三类输入;未知时停留并澄清,不凭关键词直接转人工。
|
|
|
|
|
|
8. 用户已不在现场仍继续采集。数量未知不能从“追尾”等词擅自推断。已明确无人伤后不重复问,新的明确矛盾信息可以触发更新。
|
|
|
|
|
|
9. 澄清阈值按阶段配置。采集的计入失败与核实的计入失败规则不同,不能全局统一成“任意无效回答两次转人工”。
|
|
|
|
|
|
10. 对 prefix 缺失、重复、未知或迁移非法最多做一次受控修复重试;失败返回可识别错误,不提交业务状态,也不泄漏原始模型内容。
|
|
|
|
|
|
|
|
|
|
|
|
**交付物:** 版本化提示词、字段候选与校验模型、采集节点、模型服务、fake 对话夹具、真实模型评测报告。
|
|
|
|
|
|
|
|
|
|
|
|
**验收:** 离线用例覆盖正常单/双车、一次提供全部信息、一次补齐剩余字段、数量未知、无人伤否定、模糊人伤、不在现场、未来时间、主动转人工、澄清重置。真实模型至少运行 `collection_skip_known_info` 和 `no_false` 全部现有案例。
|
|
|
|
|
|
|
|
|
|
|
|
现有评测 runner 只检查 SSE 状态码,无法证明“没有重复询问”。本作业必须增加当前问题 ID、已确认字段及提问行为的可检查结果,或提供带人工判定的脱敏对话报告。新增字段不进入公共 API。
|
|
|
|
|
|
|
|
|
|
|
|
## 作业 5:完成拍照和人员信息核实
|
|
|
|
|
|
|
|
|
|
|
|
**学习目标:** 实现阶段交接,避免换节点后重复提问或丢失上一轮答案。
|
|
|
|
|
|
|
|
|
|
|
|
**任务:**
|
|
|
|
|
|
|
|
|
|
|
|
1. 单车按 `2000 → 2001 → 2002 → 2003 → 2005` 推进,保留历史 `2004` 映射但正常新流程不再询问车牌。
|
|
|
|
|
|
2. 双车按 `2010 → 2011 → 2012 → 2013 → 2014 → 2015` 推进。`direct` 的最后一次拍摄完成直接进入 `3002`,在同一回复中完整询问第一辆车驾驶员身份。
|
|
|
|
|
|
3. 核实节点消费上一节点已经提出的问题。用户回答“是”后直接问姓名,不能因进入新节点再问一次驾驶员身份。
|
|
|
|
|
|
4. 根据工作流实际条件边分别复现两种 clientMode 的后续路径。当前 `browser_addon` 拍照分支可以结束于 `0000`,不能强行套用 direct 的人员核实流程。
|
|
|
|
|
|
5. 单车 direct 在有效车损位置后进入核实链,参照实际工作流连边与评测;不要仅依据单车拍照节点内部的结束话术决定最终路由。
|
|
|
|
|
|
6. 实现姓名、身份证后四位、手机号后四位核实;否认后采集正确值;分段号码先累计,完整后复述确认,再覆盖正式字段。
|
|
|
|
|
|
7. 双车保存当前人员索引,第一方完成后移交电话并核实第二方。不能把第二方数据写入第一方字段。
|
|
|
|
|
|
8. 每个阶段记录无效回答计数规则和全局中断优先级,覆盖短促词不误转人工。
|
|
|
|
|
|
|
|
|
|
|
|
**交付物:** 单车/双车阶段节点、模式路由表、人员核实状态、完整场景回放。
|
|
|
|
|
|
|
|
|
|
|
|
**验收:** `photo_skip_plate_confirm` 四个现有案例全部通过;新增单车 direct 核实完成、双车两方核实完成、拒绝驾驶员身份、号码分段更正、阶段交接不重复提问场景。所有确定性拍照步骤为零次 LLM 调用。
|
|
|
|
|
|
|
|
|
|
|
|
## 作业 6:使四个业务接口共享同一份状态
|
|
|
|
|
|
|
|
|
|
|
|
**学习目标:** 将流程状态和表单数据连接起来,形成第一个功能完整的内存版本。
|
|
|
|
|
|
|
|
|
|
|
|
**任务:**
|
|
|
|
|
|
|
|
|
|
|
|
1. 新增应用服务和 repository 接口:`chat`、`set_info`、`get_info`、`delete_session`,endpoint 仅处理校验、协议和依赖。
|
|
|
|
|
|
2. 用内存 repository 完成接口接线。图每轮读取最新业务状态,`set_info` 后的下一次聊天必须看到新值。
|
|
|
|
|
|
3. 建立字段注册表,完整覆盖当前代码中的三个分组。人员字段包括 `sfzmwh1/2`、`sjwh1/2`、`csbw1/2`,不能只迁移姓名和号码。
|
|
|
|
|
|
4. 在注册表记录外部字符串表示、内部类型、可写性、分组和敏感级别。保留区分空值、false、0 和未知的能力,不得用 `bool("0")` 转换布尔值。
|
|
|
|
|
|
5. `set_info` 和 `get_info` 不调用 LLM,也不创建/删除辅助聊天记录;继续接受 `includeInputInfo`,说明它在新后端已无需触发辅助对话。
|
|
|
|
|
|
6. `formUpdate` 从已验证并正式提交的字段差量产生,按 `needFormUpdate` 的契约返回,保留已有嵌套或平铺结构的夹具。
|
|
|
|
|
|
7. 定义未知 key 的兼容策略。当前 get 对未知 key 返回 JSON 空串;若旧调用方存在任意 key 写入,先审计其用途。新增白名单和内部字段保护的行为差异必须进入兼容清单。
|
|
|
|
|
|
8. 删除时清理业务状态、图 checkpoint、消息和会话路由;重复删除的行为固定为测试。不得仅删除 FastGPT 历史而留下 LangGraph 数据。
|
|
|
|
|
|
|
|
|
|
|
|
**交付物:** 应用服务、字段注册表、内存 repository、四接口组合测试、兼容差异清单。
|
|
|
|
|
|
|
|
|
|
|
|
**验收演示:** 创建会话 → `/set_info` 写入车牌 → `/get_info` 读回 → `/chat` 使用车牌继续核实 → 分组读值 → 删除 → 再读与再次聊天均不泄漏旧数据。用 spy 证明 set/get/delete 没有模型调用。
|
|
|
|
|
|
|
|
|
|
|
|
## 作业 7:接入 PostgreSQL,验证并发和故障恢复
|
|
|
|
|
|
|
|
|
|
|
|
**学习目标:** 将“单进程能运行”推进到“进程退出后仍可信”。
|
|
|
|
|
|
|
|
|
|
|
|
**任务:**
|
|
|
|
|
|
|
|
|
|
|
|
1. 新增本地 PostgreSQL 启动配置、`.env.example`、业务表 migration 和清理步骤。建议 `app` schema 管业务数据,单独配置 checkpointer 表;确认采用版本是否支持所选 schema/search path。
|
|
|
|
|
|
2. 使用异步数据库访问和 `AsyncPostgresSaver`,在 lifespan 管理连接池。业务 migration 与 saver 初始化分别执行,生产部署前完成,不在每个请求中建表。
|
|
|
|
|
|
3. 至少建立 session、业务字段、业务事件、幂等记录。每轮保存原始内部状态码、业务版本、规则/图版本、已提交 patch。
|
|
|
|
|
|
4. 明确业务表是 `/get_info` 的权威读源,checkpoint 用于执行恢复。两个存储使用不同连接时不能宣称天然处于同一事务。
|
|
|
|
|
|
5. 采用可恢复的提交协议:先记录稳定 turn/event ID 和 pending 状态;执行图;在一个业务事务内提交字段、版本、事件与可重放结果;确认 checkpoint 与提交版本一致后才发送成功。恢复时按业务事件修复滞后 checkpoint,或丢弃未提交的图进度。
|
|
|
|
|
|
6. 若选择其他一致性方案,提交对应的时序图,并证明下列三个故障窗口有确定结果:checkpoint 先成功、业务事务先成功、响应发送前进程退出。
|
|
|
|
|
|
7. 同 session 修改跨进程串行化,使用 PostgreSQL 锁或数据库 lease;不要只用进程内 `asyncio.Lock`。避免跨长时间模型调用占用行锁事务;使用独立连接的会话 advisory lock 时说明连接固定与释放方式。
|
|
|
|
|
|
8. 添加 `state_version` 冲突检测。每个请求使用独立异步会话,不跨并发任务共享 ORM session。
|
|
|
|
|
|
9. 可选 `Idempotency-Key` 进入 HTTP 边界;相同 key/相同请求重放结果,不同请求返回明确冲突。没有调用方稳定 key 时,只能保证内部节点恢复幂等,不能承诺客户端重新 POST 也自动去重。
|
|
|
|
|
|
10. 完成删除的持久化清理和删除/聊天并发控制。定义“删除后新聊天”的新会话边界,防止迟到任务复活旧数据。
|
|
|
|
|
|
|
|
|
|
|
|
**交付物:** PostgreSQL 配置、migration、持久化 repository、恢复协议时序图、真实数据库集成测试。
|
|
|
|
|
|
|
|
|
|
|
|
**验收:** 从空库安装成功;重启服务后继续同会话;chat 与 set 并发无静默覆盖;相同 key 重试不重复推进;三个故障窗口均可恢复;删除后重启也无残留。数据库不可用时不得偷偷降级成内存模式。
|
|
|
|
|
|
|
|
|
|
|
|
## 作业 8:完成真实增量 SSE
|
|
|
|
|
|
|
|
|
|
|
|
**学习目标:** 流式输出既低延迟,又能诚实表达尚未提交和已提交的状态。
|
|
|
|
|
|
|
|
|
|
|
|
**任务:**
|
|
|
|
|
|
|
|
|
|
|
|
1. 使用当前 LangGraph 版本支持的异步事件/消息流,从实际 LLM token 到 `TextDelta` 建立增量链路;不要用“先等完整回复,再逐字切分”冒充真实流式。
|
|
|
|
|
|
2. 校验完整 prefix 后才能释放正文。覆盖 `<state>` 和结束标签被任意切分、正文中再次出现标签、前缀长期不闭合、未知和非法状态码;设缓存上限和超时。
|
|
|
|
|
|
3. 业务状态的合法性由共享 validator 决定;API 保留既有状态映射。非流式和流式不能各维护一套转换规则。
|
|
|
|
|
|
4. 允许前缀和正文在最终持久化前发出,但把它们视为本轮暂定输出;`done` 只能在业务状态和 checkpoint 提交确认后发送。持久化失败必须发送终止 error,不能再发送 done。
|
|
|
|
|
|
5. 一旦对外发送正文,不得透明重跑并拼接另一条模型答案。prefix 前的修复重试与正文后的失败使用不同策略。
|
|
|
|
|
|
6. `formUpdate` 仅发布已提交字段;处理没有更新、更新先就绪、更新晚于正文的情况,核对消费者允许的顺序。
|
|
|
|
|
|
7. 保留 `useTextChunk` 的分句行为:改变片段边界不改变最终正文;使用同一录制输出验证流式与非流式结果相等。
|
|
|
|
|
|
8. 客户端断开后取消模型/图任务并释放资源;若提交已经完成,保存结果供稳定幂等 key 重放;若尚未完成,恢复协议决定下次从何处继续。
|
|
|
|
|
|
|
|
|
|
|
|
**交付物:** 增量流适配、断连/错误处理、事件轨迹夹具、首段正文耗时报告。
|
|
|
|
|
|
|
|
|
|
|
|
**验收:** 第一个正文片段在模型生成结束前出现;成功只有一个 done;模型/数据库失败只有一个终止 error;断连无遗留任务;同一回放的两种模式正文、状态与 patch 一致。HTTP 已开始发送 SSE 后的失败通过事件表达,不能尝试再改 HTTP 状态。
|
|
|
|
|
|
|
|
|
|
|
|
## 作业 9:配置、可观测性与访问边界
|
|
|
|
|
|
|
|
|
|
|
|
**学习目标:** 让运维和其他开发者能够定位问题并安全运行服务。
|
|
|
|
|
|
|
|
|
|
|
|
**任务:**
|
|
|
|
|
|
|
|
|
|
|
|
1. 配置区分 development/test/production,校验模型、数据库、后端开关、超时和连接池。生产持久化缺失时启动或 readiness 失败。
|
|
|
|
|
|
2. 保留 `GET /`,新增 `/health/live`、`/health/ready`。readiness 检查数据库和 saver,不每次真实调用收费模型。
|
|
|
|
|
|
3. 为请求、turn、节点和模型调用建立关联 ID。日志记录状态、版本、耗时、错误类型,不记录原始表单、完整用户输入、凭证和完整号码。
|
|
|
|
|
|
4. 记录成功率、模型调用次数、正文首段延迟、总耗时、非法转换、持久化失败和 session 冲突;指标 label 不放 session ID。
|
|
|
|
|
|
5. 复用部署环境已有服务身份和会话归属。若现有服务尚无鉴权,把新增认证列为上线前联调事项,不在兼容迁移中默默要求全部调用方更换协议。
|
|
|
|
|
|
6. 多租户环境所有查询与 thread ID 绑定可信租户身份;禁止从未经验证的请求字段直接相信 tenant。无多租户需求时不要为作业虚构账号系统。
|
|
|
|
|
|
7. 记录敏感业务表、事件、checkpoint 和备份的保护与保留方案;删除检查覆盖这些数据,不仅覆盖 API 返回值。
|
|
|
|
|
|
|
|
|
|
|
|
**交付物:** 启动配置说明、健康检查、脱敏日志样例、指标说明、部署访问边界说明。
|
|
|
|
|
|
|
|
|
|
|
|
**验收:** 注入数据库故障可看到 readiness 失败及关联日志;运行合成敏感数据用例后,日志无明文泄漏;同一请求可以定位到具体失败节点。
|
|
|
|
|
|
|
|
|
|
|
|
## 作业 10:证明等价,并交付可运行服务
|
|
|
|
|
|
|
|
|
|
|
|
**学习目标:** 用证据说明调用方可以继续使用同样的服务。
|
|
|
|
|
|
|
|
|
|
|
|
**任务:**
|
|
|
|
|
|
|
|
|
|
|
|
1. 完成下表全部验收。HTTP schema、JSON/SSE、字段、分组和业务规则分别验证,不用“状态码正确”代替全部正确。
|
|
|
|
|
|
2. 保留现有 runner 调用真实 HTTP 服务,补充读取字段、patch、终止事件和提问行为的验证。所有 fixture 明确来源与预期,禁止只按新实现生成预期答案。
|
|
|
|
|
|
3. FastGPT 与 LangGraph 使用相同合成场景、相同初始数据、独立会话执行,比较状态轨迹、字段和终止原因;自然语言允许合理改写。
|
|
|
|
|
|
4. 每个真实模型黄金案例至少执行三次,报告逐次结果和波动。固定模型与提示词版本,并给出失败对话,不能只汇报平均值。
|
|
|
|
|
|
5. 记录 FastGPT 与 LangGraph 在同一环境的成功率、正文首段与总耗时 P50/P95、模型调用数;明确样本量和并发。性能发布预算须在灰度前填写,不能到发布时仍写“待定”。
|
|
|
|
|
|
6. 新会话按 session 固定选择后端;不要每个请求随机切换。同一会话的 chat/set/get/delete 必须使用相同路由。旧 FastGPT 会话留在旧后端直至结束,首版不自动迁移历史对话。
|
|
|
|
|
|
7. Shadow 使用独立 thread、独立数据和受控模型额度,禁止写真实业务表。比较请求序列时保持对应会话上下文。
|
|
|
|
|
|
8. 演练关闭新会话的 LangGraph 分流。已经进入 LangGraph 的会话继续路由至 LangGraph 或执行明确的状态转换;单改环境开关不会自动把两套会话数据互相转换。
|
|
|
|
|
|
9. 编写从干净检出开始的安装、数据库准备、启动、验证、恢复和停止说明。启动命令应为 `python -m uvicorn src.main:app --host 127.0.0.1 --port 8000`,依赖步骤按实际交付填写。
|
|
|
|
|
|
10. 观察期结束后再决定移除 FastGPT。语音仍依赖 FastGPT 时保留该依赖,文本迁移完成不等于整个仓库都能删除 FastGPT。
|
|
|
|
|
|
|
|
|
|
|
|
### 最终验收矩阵
|
|
|
|
|
|
|
|
|
|
|
|
| 类别 | 必测内容 | 通过要求 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| API 契约 | 四个业务接口、两种 clientMode、输入默认值、长度限制和历史映射 | 所有固定契约用例通过;差异有显式记录和处理 |
|
|
|
|
|
|
| 事故采集 | 跳过已知项、补齐即分流、不推断数量、不在现场、时间校验 | 所有离线场景通过;真实模型黄金案例逐次报告 |
|
|
|
|
|
|
| 安全与澄清 | 人伤肯定/否定/模糊、主动人工、计数阈值、无回复 | 确定性规则全部通过;黄金集中明确人伤无漏转,否定/模糊无误转 |
|
|
|
|
|
|
| 拍照与核实 | 单/双车、模式差异、跨阶段回答、分段更正、两方隔离 | 完整状态与字段断言通过,无非法跳步 |
|
|
|
|
|
|
| 数据 | set/get/chat 一致、分组布尔值、单字段 JSON、差量和删除 | 无丢字段、旧 patch 重发或删除残留 |
|
|
|
|
|
|
| 持久化 | 重启、三类故障窗口、并发、幂等与冲突 | 无重复提交、静默覆盖和不可恢复分歧 |
|
|
|
|
|
|
| 流式 | prefix 切分、真实增量、分句、断连、失败与完成 | 不泄漏标签;终止事件唯一;done 前已持久化 |
|
|
|
|
|
|
| 运行 | 无 FastGPT 的文本模式启动、健康检查、脱敏、路由回退 | 干净环境可复现;语音原有启动和路由不受破坏 |
|
|
|
|
|
|
|
|
|
|
|
|
真实模型小样本通过不能证明线上错误率为零。达不到矩阵中的黄金集要求时,修正后重新执行受影响案例,并在报告中保留失败记录。
|
|
|
|
|
|
|
|
|
|
|
|
### 最终提交目录
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
src/
|
2026-10-16 16:15:18 +08:00
|
|
|
|
agent/ # state、events、transitions、graph、nodes、prompts
|
|
|
|
|
|
backends/langgraph.py # 适配已有 ChatBackend
|
|
|
|
|
|
services/ # 事故应用服务、LLM 服务
|
|
|
|
|
|
repositories/ # 内存实现与 PostgreSQL 实现
|
|
|
|
|
|
core/ # 配置、资源生命周期、数据库
|
|
|
|
|
|
api/ # 保留现有路径与公共 schema
|
|
|
|
|
|
examples/langgraph_smoke.py
|
|
|
|
|
|
test/
|
|
|
|
|
|
agent/ # 离线状态与图场景
|
|
|
|
|
|
api/ # HTTP JSON/SSE 契约与组合调用
|
|
|
|
|
|
integration/ # PostgreSQL、并发和恢复
|
|
|
|
|
|
evals/ # 复用并扩展现有真实服务评测
|
|
|
|
|
|
alembic/ # 业务表迁移(使用该工具时)
|
|
|
|
|
|
docs/langgraph/
|
|
|
|
|
|
runbook.md # 从零启动和日常操作
|
|
|
|
|
|
contract.md # 真实接口样例与兼容差异
|
|
|
|
|
|
rule-sources.md # 工作流节点、规则和测试对应关系
|
|
|
|
|
|
recovery.md # 提交协议、故障窗口与恢复
|
|
|
|
|
|
evaluation.md # 等价性、质量和性能证据
|
|
|
|
|
|
rollout.md # session 路由、灰度、回退演练
|
2026-07-26 07:24:30 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
目录可以按实际实现微调,但每个交付物必须有明确位置。保持现有 `test/`,无需为套用模板重命名整个测试树。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
## 评分与完成条件
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
每次作业按表中分值计分:功能与正确性占该作业 60%,可复现的验证占 30%,设计讲解与限制说明占 10%。只提交图示或设计文档、没有运行结果,不能取得该作业的功能分。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
以下为结课必过项,分数不能替代:公共契约保持兼容;四个接口使用一致的会话路由和状态;确定性事件不能非法跳步;成功返回前数据已持久化;失败可恢复;删除不留下可被复活的旧会话;完整请求链路在 LangGraph 文本模式下不调用 FastGPT。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
最终演示使用一个全新会话:启动服务 → 采集双车事故 → 完成拍照 → 注入人员预填值 → 更正一段手机号 → 重启服务 → 继续第二方核实 → 查询全部信息 → 删除会话 → 验证无残留。随后展示一次明确人伤分流、一次模糊人伤澄清和一次数据库失败的 SSE error。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
## 执行时需要登记的环境决策
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
作业 1—3 可直接离线推进。到相应阶段前再落实下列信息,并写入 runbook;不要把所有环境问题都变成搭脚手架的前置阻塞。
|
2026-07-26 07:24:30 +08:00
|
|
|
|
|
2026-10-16 16:15:18 +08:00
|
|
|
|
| 决策 | 最晚落实时间 | 未落实时可以继续的工作 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| 真实 LLM provider、模型、超时与预算 | 作业 4 真实模型验收前 | fake LLM、领域与接口测试 |
|
|
|
|
|
|
| 工作流冲突规则和两种模式的最终路由 | 作业 4—5 验收前 | 来源提取、离线夹具、差异记录 |
|
|
|
|
|
|
| PostgreSQL 版本、连接与部署权限 | 作业 7 前 | 内存完整功能与本地数据库开发 |
|
|
|
|
|
|
| 调用方对任意字段写入、错误、SSE 顺序的依赖 | 作业 6/8 验收前 | 当前行为取样、兼容层实现 |
|
|
|
|
|
|
| 服务身份、租户归属与数据保护方案 | 作业 9/生产灰度前 | 本地服务与合成数据验证 |
|
|
|
|
|
|
| 延迟/成本预算、灰度观察周期与负责人 | 作业 10 灰度前 | 基线测量、压测与回退演练 |
|