Delete checkpoints before removing business fields. Suppress only an explicit CheckpointNotFoundError; propagate storage failures and cancellation so the endpoint cannot report false success. Prefer async checkpoint deletion and fail clearly when cleanup is unsupported. Classify SSE error events as 401 for authentication, 429 for rate limits, and 500 for other failures, using the same fixed messages as non-stream responses. Errors terminate the stream without a success done event or provider exception text in the response. Validation: 286 offline tests passed (19 added), including failed-delete HTTP retry with preserved business/checkpoint state, explicit missing threads, cancellation, missing cleanup capability, and HTTP SSE errors before and after text output.
9.5 KiB
9.5 KiB
作业 6 兼容差异清单:LangGraph 业务状态 vs FastGPT 现状
范围:
/set_info、/get_info、/delete_session、/chat四个接口在AGENT_BACKEND=langgraph下接入应用服务 + repository 后的行为差异。 FastGPT 模式(AGENT_BACKEND=fastgpt)保留旧实现,不受影响。
接口响应契约(保持不变)
| 项 | 现状(FastGPT) | LangGraph(作业 6) |
|---|---|---|
| 请求/响应模型 | ProcessRequest/Response_* |
相同(endpoint 未改模型) |
get_info 分组名 |
all / acdinfo / acdhuman1 / acdhuman2 |
相同 |
get_info 布尔外部表示 |
True→"1",False→"0",未知→"" |
相同(to_external_string) |
get_info 未知 key |
返回 JSON 空串 '""' |
保持 '""'(现状兼容) |
includeInputInfo 字段 |
触发 FastGPT 辅助对话 | 继续接受但忽略——新后端零辅助对话 |
有意的行为差异(新后端收紧)
| 项 | FastGPT 现状 | LangGraph | 理由 |
|---|---|---|---|
set_info 未知 key |
任意 key 写入 state 变量 | 拒绝(500 + "入参不规范:未知或受保护的字段 key") | 白名单保护;任意写入会污染图输入。上线前需审计旧调用方是否依赖任意 key 写入 |
set_info 图内部键(stage_code 等) |
可写(state 是裸 dict) | 拒绝(INTERNAL_FIELD_KEYS 保护) |
内部状态只允许图自己推进 |
set_info 字段值 |
任意字符串原样存 | 按注册表类型解析(bool/int 严格),无法解析拒绝 | 保留 空/False/0/未知 区分,杜绝 bool("0") 陷阱 |
| 图表单键与业务键 | FastGPT state 为裸 dict,无映射概念 | 建立双向映射(作业 6):表单六键(has_injury 等)<-> acd 业务键(ywrysw 等);bjrjs/ywmtc 无表单来源,仅 set_info 可写 |
图的表单语义与 get_info 业务分组需要同一份事实 |
| LLM 调用 | set/get 走辅助对话 + 事后删记录 | 零 LLM 调用(测试 spy 保证) | 成本与延迟;删除 race 消失 |
重复 delete_session |
FastGPT SDK 行为未固定 | 幂等成功(checkpoint 正常返回或适配器明确报告不存在才可继续;存储故障返回业务码 500) | 不再将清理失败当成删除成功 |
图表单键 <-> 业务键映射(作业 6 建立)
| 图表单键(accident_fields) | 业务键(acdinfo 分组) |
|---|---|
has_injury |
ywrysw |
has_non_motor_vehicle_hit |
ywfjdc |
is_on_scene |
sfsgxc |
vehicle_count |
jdcsl |
accident_time |
sgfssj |
accident_cause |
sgyy |
- chat 方向:repository 业务键经映射注入图为表单键(图只认表单语义)。
- 写回方向:图产出的表单键差量经映射落 repository 业务键,get_info 分组即可读到。
bjrjs(报警人是否驾驶员)、ywmtc(是否撞摩托车)无表单来源: 不进入注入映射,仅支持 set_info 直写、get_info 直读。
与 workflow 20261022 的字段对账(2026-10-22 核对)
核对来源:workflow/20261022/事故信息采集2025118.json(已与 main@c7ca781 同步)
及历史版本 20250419—20261021。
| 注册表内容 | 对账结果 |
|---|---|
图表单六键(has_injury 等,FORM_FIELDS 白名单) |
一致。workflow form 全局变量(bNJkeXot)初始即这六键;「更新表单」code 节点白名单语义(只遍历表单已有字段、无效值/同值筛除)与本服务 _merge_form 相同 |
acd 业务八键(ywrysw 等) |
语义一致,出处不同。八键全部出自 20250419 版 workflow 的提取 schema(含 sgyy 事故原因);20251015 起 workflow 已弃用,API 代码自 a1a4bce 起沿用为对外契约。新后端注册表保留 = 兼容旧契约 |
人员分组键(xm1/sfzmhm1/sfzmwh1/sjwh1/csbw1 等每组 8 键) |
任何 workflow 版本都不存在。系 API 服务端自定义契约(get_info 分组)。sfzmwh/sjwh/csbw 的全量覆盖是契约要求,与 workflow 无关 |
作业 6 表单键<->业务键映射(has_injury->ywrysw 等) |
workflow 中无此映射——两侧键名从未在同一系统共存(workflow 只用表单键,API 只用业务键)。映射是迁移时按中文语义建立的桥(对照 20250419 提取 schema 的 desc 字段),属本服务新增语义,已双向测试固定 |
对账发现的一个真实差异
20261022 workflow 的 state 变量中人员信息键为
driver1ID / driver1Phonenumber(×2,sandbox 另把 hphm1/2
车牌改名 driver{i}License 输出给确认提示词——该名不是 state 键),
与 API get_info 分组键(sfzmhm1/sjwh1/sjhm1 等)不同名。
旧 FastGPT 实现按 API 分组键读 state,这些键在 workflow state 中
本就读空——属于既有行为。
处理(2026-10-22 已落地):注册表把 workflow 原生键
driver{i}ID / driver{i}Phonenumber(×2,同义 sfzmhm/sjhm)
登记进 human1/human2 分组——
- set/get_info 单键直读写 driver 系键与旧 API 行为一致;
- 有意差异:
acdhuman1/2分组输出现包含 driver 系键 (旧 FastGPT 分组输出没有它们)。调用方按 JSON 取键,多键无害; 这修正了旧实现"workflow 真实人员数据无法分组读到"的问题; driver{i}License非 state 键,对其 set_info 按未知键拒绝 (workflow 不消费它);- 同义键统一存储:
sfzmhm{i}与driver{i}ID共用后者,sjhm{i}与driver{i}Phonenumber共用后者。单键和分组读取 继续提供两套键名;图输入同时注入一致的别名视图,避免旧 driver 值 遮盖 API 键的新值。同一批差量包含相互冲突的别名值时拒绝提交。
待审计项(作业 6-7 要求的后续工作)
- 审计现有调用方是否存在对
set_info任意 key 写入的依赖 (如动态自定义键)。存在则在注册表补登记或提供逃生口。 workflow 人员键driver{i}ID/driver{i}Phonenumber及对应 API 别名均已登记并统一存储;driver{i}License只是车牌的 输出名称,外部写车牌继续使用hphm{i}。 流式(图提交时即同步业务字段;FormUpdate 是否返回不影响写回)。chat的formUpdate差量写回 repositoryget_info返回值中的敏感字段(证件号/手机号)回显边界: 注册表已记录SensitiveLevel,访问控制随作业 9 落地。
流式、并发与字段一致性约定
- 阶段前缀先缓冲并按当前节点的转换规则校验,非法前缀不推送; 未输出时仍可使用一次修复预算。已开始输出后,全文格式或时间依据 校验失败会以错误终止,不在同一流中拼接修复答案,不提交本轮业务字段。 已经到达客户端的正文不可撤回;客户端只在收到 done 后将一轮视为成功。
- 会话锁引用包括持有者与等待者,只有最后一个使用者退出才回收。 chat、stream、set、get、delete 全部按同一会话串行;不同会话独立。 取消流时先关闭内部生成器、取消图任务,再释放会话锁。
- needFormUpdate 只决定对外是否返回差量。图提交时通过内部回调同步 repository,关闭差量显示或消费者在提交后断开不会丢失业务字段。
- 删除在同会话锁内先清理 checkpoint,再删除业务字段。只有存储正常返回, 或适配器明确抛出 CheckpointNotFoundError 时才视为 checkpoint 清理成功; 连接、超时、一般 RuntimeError/KeyError 等故障必须上报,业务字段保留供重试。 缺少 checkpointer 或不支持删除操作也返回失败。优先使用 adelete_thread, 仅在没有异步接口时兼容同步 delete_thread。若业务存储删除本身失败, 同样返回失败;此顺序不构成跨存储事务,可能需要再次重试完成清理。
- SSE 错误按 error 事件的 code 区分:鉴权 401、限流 429、其余 500, 使用与非流式相同的固定文案,不回传底层异常原文。首包前和输出途中 都使用同一分类;错误是终止事件,随后不会发送 done。已经建立的 SSE HTTP 响应保持 200,调用方检查事件内的业务码。
- SDK 自动重试关闭,由应用层统一控制;默认首次尝试加 3 次重试, 每次尝试总超时 60 秒,退避 1、2、4 秒。单次 LLM 调用理论预算 4×60+1+2+4=247 秒(另有响应关闭的清理时间)。FastGPT SDK 的 max_retries 实际代表总尝试数,适配层传入 4 以表达额外重试 3 次; 其 timeout 是 HTTP SDK 超时,不将它等同于整个多节点请求总期限。 流式首个文本 token 前可重试,发出文本后失败则终止,不重放。 该预算并非整个多次 LLM 调用的聊天回合总超时。
验证
test/services/test_fields_registry.py:注册表完整性、类型/敏感级别、 外部表示、bool("0")防线。test/services/test_app_service.py:四接口组合(set→get→chat 注入→ 差量写回→分组读→删除→无泄漏),set/get/delete 零模型调用 spy, 重复删除幂等。test/api/test_assignment6_endpoints.py:HTTP 层验收演示、删除失败保留数据及重试成功链路。test/services/test_delete_failure_semantics.py:存储故障、明确不存在、取消、清理能力缺失与重试。test/api/test_stream_error_codes.py:实际 LangGraphBackend 过 HTTP 协议层, 验证首包前/输出途中 401、429、500 分类、文案与非流式一致、错误后无 done。