diff --git a/README.md b/README.md index 6a0f6a8..f916479 100644 --- a/README.md +++ b/README.md @@ -74,12 +74,17 @@ docs/langgraph-backend-migration-plan.md # 十阶段迁移作业计划 evals/ # 调用真实服务的评测用例与 runner ``` -## 已知限制 +## 当前状态与已知限制 -- LangGraph 脚手架与后端适配层骨架已就位(作业 1—2):`src/agent/`、 - `src/backends/langgraph.py`、`AGENT_BACKEND` 开关;节点逻辑与适配器实现 - 为待完成的 TODO,对应测试 `test/agent/`、`test/backends/test_langgraph_backend.py` - 在完成前会失败,属预期状态。 -- `set_info` / `get_info` / `delete_session` 仍直接依赖 FastGPT 状态与历史,迁移到共享 - 业务状态属于作业 6。 -- 服务启动需要有效的 FastGPT 配置;仅运行单元测试不需要。 +作业 1—5 已完成(LLM 提议 + 代码校验架构,提示词与 `workflow/20261020` +逐字对齐,全量测试 143 通过)。逐作业进度、真实模型评测结果与开放问题 +见 [docs/langgraph-backend-migration-plan.md](docs/langgraph-backend-migration-plan.md) +文末“当前进度与已知差异”。 + +- 真实模型黄金评测:`collection_skip_known_info` 12/12 通过; + `photo_skip_plate_confirm` 0/4、`no_false` 2/8,统一卡在短促回答“对” + 的分流语义上,待与 FastGPT 应用层配置对账(差异已留档,未叠代码守卫)。 +- `set_info` / `get_info` 仍直接依赖 FastGPT 状态与历史,迁移到共享 + 业务状态属于作业 6;`delete_session` 已按后端分流(langgraph 清 checkpoint)。 +- 服务启动(langgraph 模式)不需要 FastGPT 配置;需要 LLM provider 配置。 +- 流式为“完整答案切片”,真实增量输出属作业 8;`formUpdate` 差量属作业 6。 diff --git a/docs/langgraph-backend-migration-plan.md b/docs/langgraph-backend-migration-plan.md index 068b8c4..6b3ad06 100644 --- a/docs/langgraph-backend-migration-plan.md +++ b/docs/langgraph-backend-migration-plan.md @@ -1,6 +1,6 @@ # LangGraph 迁移执行作业:从脚手架到等价 API 服务 -> 版本:重写版,依据本地 `main` 提交 `22853f1` 及本分支现有后端边界编写。 +> 版本:以 `workflow/20261020/事故信息采集2025118.json` 为当前迁移基准;20261016 保留作历史对照。 > > 读者:掌握 Python、异步函数、HTTP 和基本测试,尚未独立交付过 LangGraph 服务的本科生。 > @@ -33,7 +33,7 @@ |---|---|---:|---:| | 1. LangGraph 脚手架 | 不依赖模型运行最小图,并恢复同一会话 | 4—6 小时 | 8 | | 2. 接入现有 API | fake 图经现有 `/chat` 返回 JSON 和 SSE | 6—8 小时 | 10 | -| 3. 领域状态与事件 | 用代码推进拍照、无回复和失败事件 | 8—12 小时 | 12 | +| 3. 领域状态与事件 | 校验模型预测的下一阶段,记录事件上下文 | 8—12 小时 | 12 | | 4. 事故信息采集 | 多轮采集、跳过已知信息并正确分流 | 12—18 小时 | 15 | | 5. 拍照与信息核实 | 跑完单车、双车及更正信息流程 | 10—16 小时 | 12 | | 6. 同一份业务数据 | 四个业务接口共享内存态并正确删除 | 8—12 小时 | 10 | @@ -55,7 +55,7 @@ | `src/core/fastgpt_client.py` | 客户端何时初始化,依赖如何注入,何时关闭? | | `src/api/endpoints.py` | 哪些逻辑是协议适配,哪些仍直接调用 FastGPT? | | `src/schemas/models.py` | 请求默认值、字段长度、返回类型分别是什么? | -| `workflow/20261016/事故信息采集2025118.json` | 当前各模式实际走哪些节点和条件边? | +| `workflow/20261020/事故信息采集2025118.json` | 当前各模式实际走哪些节点和条件边? | | `evals/cases/`、`evals/README.md` | 当前已经覆盖哪些对话?runner 实际断言了什么? | | `test/api/`、`test/backends/`、`test/test_client_mode.py` | 已有后端边界测试和公共契约测试是否与当前代码一致? | @@ -105,7 +105,7 @@ **验收命令(本作业新增):** ```bash -python examples/langgraph_smoke.py +python -m examples.langgraph_smoke python -m pytest test/agent/test_scaffold.py -q ``` @@ -147,9 +147,9 @@ python -m pytest test/agent/test_scaffold.py -q 6. 连续两次无回复产生 `0004`,中间有效回复如何重置计数必须有测试。 7. 把“本轮输出字段”在每轮开始时清空;历史事故字段继续保留,避免旧 `formUpdate` 在下一轮重复返回。 -**交付物:** `events.py`、`transitions.py`(节点内确定性规则)、按工作流拆分的多节点图(`graph.py`)、状态模型、规则来源表、参数化转换测试。 +**交付物:** `events.py`、`transitions.py`(模型提议的转换校验)、按工作流拆分的多节点图(`graph.py`)、状态模型、规则来源表、参数化转换测试。 -**验收:** 无模型调用即可走完拍照事件链;在拍照中输入“继续”保持当前步骤;非法跨阶段转换不写状态;明确的转人工事件可以终止当前流程。 +**验收:** 使用可注入的 fake LLM 回放阶段提议,验证合法提议、非法跳步、无回复和失败输入的上下文传递;普通“拍好了”不解析为客户端标记。真实阶段码由模型预测,代码不凭标记强制推进。 **讲解题:** 为什么外部看到 `1002`,内部仍必须区分采集、单车核实和双车核实? @@ -174,6 +174,8 @@ python -m pytest test/agent/test_scaffold.py -q **验收:** 离线用例覆盖正常单/双车、一次提供全部信息、一次补齐剩余字段、数量未知、无人伤否定、模糊人伤、不在现场、未来时间、主动转人工、澄清重置。真实模型至少运行 `collection_skip_known_info` 和 `no_false` 全部现有案例。 +**当前状态(2026-10-21):** 已实现。真实模型下 `collection_skip_known_info` 12/12 通过;`no_false` 2/8,其余 6 例与 `photo_skip_plate_confirm` 0/4 全部卡在同一模式——开放问题上的短促回答(“对”)被模型直接分流,而评测期望先澄清。移植侧已与工作流逐字对齐(提示词、history=50、welcome 话术、变量渲染),差异仅存于模型行为/应用层配置,详见文末开放问题。 + 现有评测 runner 只检查 SSE 状态码,无法证明“没有重复询问”。本作业必须增加当前问题 ID、已确认字段及提问行为的可检查结果,或提供带人工判定的脱敏对话报告。新增字段不进入公共 API。 ## 作业 5:完成拍照和人员信息核实 @@ -182,7 +184,7 @@ python -m pytest test/agent/test_scaffold.py -q **任务:** -1. 单车按 `2000 → 2001 → 2002 → 2003 → 2005` 推进,保留历史 `2004` 映射但正常新流程不再询问车牌。 +1. 单车按 `2000 → 2001 → 2002 → 2003 → 2005` 推进。`2004` 是确认单车车牌步骤(来源:当事人确认,20261020 提示词仍写 2003→2005):车牌已明确(当事人已提供或拍摄识别成功)时由 2003 直达 2005,未明确时进入 2004 询问、确认后 2004→2005。两种走向都合法,由模型按上下文判断,代码按白名单校验(2003→{2004,2005}、2004→{2005} 已登记)。 2. 双车按 `2010 → 2011 → 2012 → 2013 → 2014 → 2015` 推进。`direct` 的最后一次拍摄完成直接进入 `3002`,在同一回复中完整询问第一辆车驾驶员身份。 3. 核实节点消费上一节点已经提出的问题。用户回答“是”后直接问姓名,不能因进入新节点再问一次驾驶员身份。 4. 根据工作流实际条件边分别复现两种 clientMode 的后续路径。当前 `browser_addon` 拍照分支可以结束于 `0000`,不能强行套用 direct 的人员核实流程。 @@ -193,7 +195,7 @@ python -m pytest test/agent/test_scaffold.py -q **交付物:** 单车/双车阶段节点、模式路由表、人员核实状态、完整场景回放。 -**验收:** `photo_skip_plate_confirm` 四个现有案例全部通过;新增单车 direct 核实完成、双车两方核实完成、拒绝驾驶员身份、号码分段更正、阶段交接不重复提问场景。所有确定性拍照步骤为零次 LLM 调用。 +**验收:** `photo_skip_plate_confirm` 四个现有案例全部通过;新增单车 direct 核实完成、双车两方核实完成、拒绝驾驶员身份、号码分段更正、阶段交接不重复提问场景。拍照步骤、失败和无回复均由对应 chatNode 提示词驱动模型判断;正常每轮一次对话模型调用,格式或转换错误共用最多一次修复。记录调用次数,不要求零调用。 ## 作业 6:使四个业务接口共享同一份状态 @@ -336,7 +338,7 @@ docs/langgraph/ 每次作业按表中分值计分:功能与正确性占该作业 60%,可复现的验证占 30%,设计讲解与限制说明占 10%。只提交图示或设计文档、没有运行结果,不能取得该作业的功能分。 -以下为结课必过项,分数不能替代:公共契约保持兼容;四个接口使用一致的会话路由和状态;确定性事件不能非法跳步;成功返回前数据已持久化;失败可恢复;删除不留下可被复活的旧会话;完整请求链路在 LangGraph 文本模式下不调用 FastGPT。 +以下为结课必过项,分数不能替代:公共契约保持兼容;四个接口使用一致的会话路由和状态;非法阶段转换不得写状态(由白名单校验,模型提议不算数);成功返回前数据已持久化;失败可恢复;删除不留下可被复活的旧会话;完整请求链路在 LangGraph 文本模式下不调用 FastGPT。 最终演示使用一个全新会话:启动服务 → 采集双车事故 → 完成拍照 → 注入人员预填值 → 更正一段手机号 → 重启服务 → 继续第二方核实 → 查询全部信息 → 删除会话 → 验证无残留。随后展示一次明确人伤分流、一次模糊人伤澄清和一次数据库失败的 SSE error。 @@ -352,3 +354,60 @@ docs/langgraph/ | 调用方对任意字段写入、错误、SSE 顺序的依赖 | 作业 6/8 验收前 | 当前行为取样、兼容层实现 | | 服务身份、租户归属与数据保护方案 | 作业 9/生产灰度前 | 本地服务与合成数据验证 | | 延迟/成本预算、灰度观察周期与负责人 | 作业 10 灰度前 | 基线测量、压测与回退演练 | +| FastGPT 应用层配置(应用级系统提示词、思考模式开关)与线上部署差异 | 作业 4/5 验收对账前 | 移植代码、离线评测与差异记录 | +| 评测案例与工作流导出的分歧(“对”分流语义、2004 走向)的权威口径 | 作业 5 验收前 | 白名单双走向、逐字比对测试、失败证据留档 | + +## 当前进度与已知差异(2026-10-21 更新) + +### 作业进度 + +| 作业 | 状态 | 证据 | +|---|---|---| +| 1. 脚手架 | ✅ 完成 | 冒烟轮数 1/2/1;`test_scaffold.py` | +| 2. 接入 API | ✅ 完成 | `AGENT_BACKEND=langgraph` 启动验证;`test_langgraph_backend.py` | +| 3. 领域状态与事件 | ✅ 完成 | 事件解析 14 例、白名单校验 24 例;`LEGAL_TRANSITIONS` 17 条均带 `source_ref` | +| 4. 采集 + LLM | ✅ 完成 | 真实模型 `collection_skip_known_info` 12/12;单测含逐字比对 | +| 5. 拍照与核实 | ◐ 代码完成,评测有差异 | 单测全过;真实模型评测差异见下 | +| 6—10 | 未开始 | — | + +全量单元测试 143 通过;七份提示词与 20261020 工作流逐字比对由参数化测试锁定。 + +### 已落地的架构决策 + +1. **LLM 提议、代码校验(propose-validate)**:所有 stage_code 由节点内 LLM 产出(提示词即程序),代码只做前缀解析、白名单校验(含 clientMode 出口)和落库;曾尝试的代码强制推进/短促词守卫已按此分工移除。 +2. **依赖注入走 `context_schema`**:`GraphDeps(llm=...)` 经 `Runtime` 注入调用期,不进 state/checkpoint;`build_graph()` 与 LLM 解耦。 +3. **与工作流逐字对齐**:提示词原文渲染(含 `{{$变量}}` 替换:系统时间按 Asia/Shanghai、人员预填字段缺失时显式标注“未提供”);聊天历史按 user/assistant 消息传递(上限 50 条),对齐 chatNode `history=50`;welcome 话术为 Assigned Reply#3 原文并计入对话历史。 +4. **不引入 LangGraph Server / langgraph_sdk**:生产形态的 Agent Server 需要 LangSmith license key、外网 beacon 出网及 Postgres+Redis 全栈,与课程目标(作业 7 亲手实现持久化)和部署环境不符;`langgraph dev` 仅为内存态,无增量能力。自部署高并发方案 = 嵌入库模式:无状态 FastAPI 多副本 + AsyncPostgresSaver + 每会话 advisory lock 串行 + 连接池预算(社区参照:langgraph-agent-toolkit、agent-bakery)。 +5. **2004 语义**:确认单车车牌;车牌已明确时可 2003→2005 跳过,未明确时 2003→2004→2005。两种走向均在白名单登记,由模型判断。 + +### 真实模型评测现状(deepseek-v4.1-flash) + +| 黄金案例 | 结果 | 说明 | +|---|---|---| +| `collection_skip_known_info` | 12/12 | 采集主流程与“跳过已知项”完全对齐 | +| `photo_skip_plate_confirm` | 0/4 | 全部卡在同一步 | +| `no_false` | 2/8 | 2 个 ASR 案例通过 | + +**统一失败模式**:开放问题(“您现在还在事故现场吗?”)未获明确回答时,用户输入短促词(“对”),评测期望保持 1002 澄清,模型直接分流 2000/2010。移植侧已排除提示词、历史、变量、温度(0 与缺省均试过)差异;模型名与 FastGPT 应用一致(deepseek-v4.1-flash)。 + +### 开放问题(对账前按提交规则留档,不叠代码守卫) + +1. **FastGPT 应用层配置差异**:应用级系统提示词、思考模式开关不随工作流 JSON 导出,可能是分流行为差异来源。行动:请负责人在 FastGPT 应用设置中核对并导出;或在本机起 FastGPT(`ANALYSIS_SERVICE_URL` 指向 127.0.0.1:3030)直接跑评测对照。 +2. **评测案例与工作流导出的分歧**:`no_false` 单车案例期望 2003→2004,而 20261020 导出提示词写 2003→2005(`photo_skip_plate_confirm` 注释亦写“不经过 2004”)。已按“2004=车牌确认、双走向合法”实现,权威口径待确认。 +3. **“对”的判定语义**:评测期望模型区分“对=开放问题的模糊回答(澄清)”与“信息已齐无待定问题(分流)”。当前模型不区分。若应用层配置排除后仍不通过,按规则 4 记录差异及采用依据,或由评测负责人重新录制该步骤预期。 + +### 代码地图(截至作业 5) + +```text +src/agent/ + graph.py 图装配 + 条件路由 + GraphDeps(context_schema) + welcome + state.py AgentState(全部可序列化;client 等依赖不进 state) + events.py 客户端标记 → ClientEvent + transitions.py 白名单校验(LEGAL_TRANSITIONS,含 2004 双走向) + collect.py 采集节点 + validated_reply(一次调用、一次修复预算) + photo.py 拍照节点(四份提示词按 kind×mode 选择) + confirm.py 核实节点(字段提取按 person_index 隔离) + llm.py LLMService Protocol / Fake / OpenAI(超时重试温度) / workflow fake + fields.py 字段三态校验;clock.py 可控时钟 + prompts/ 七份版本化提示词(来源 nodeId + 逐字原文) +```