Files
ZNJJ-api-server/docs/assignment6-compat.md
T
Eric Wang 24e2cfbe10 fix(backends): make streamed replies and session state consistent
- Validate stage prefixes before forwarding model tokens. Invalid prefixes
  may use the existing repair budget; once output starts, validation failure
  aborts the turn without appending a regenerated reply. Bound the token
  queue and close inner streams when consumption stops.
- Replace delete-time lock eviction with reference-counted session locks
  covering holders and waiters. Serialize set/get with chat/stream/delete
  and release cancelled waiters without replacing a live lock.
- Store person synonyms under workflow-native driver keys and expose
  consistent alias views to single/group reads and graph prompt injection.
- Separate committed field patches from public formUpdate visibility.
  Commit callbacks keep repository fields synchronized even when updates
  are hidden or the consumer closes after graph completion.
- Disable OpenAI SDK automatic retries. Own bounded retry attempts in the
  application, use 1/2/4 second backoff, retry streams only before text is
  emitted, enforce attempt timeouts, and close upstream/client resources.
- Translate FastGPT SDK max_retries (total attempts) from the configured
  extra-retry count, and verify the real SDK loop uses four attempts.
- Document the per-LLM budget and streaming failure semantics without
  claiming the whole multi-call HTTP turn has the same timeout.

Validation: 267 offline tests passed, including actual SDK requests through
MockTransport, stream repair/abort, timeout/close, lock races, aliases and
formUpdate visibility; existing four dependency deprecation warnings.
2026-10-23 15:54:51 +08:00

8.2 KiB
Raw Blame History

作业 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 清理重复触发但缺失静默容忍) 行为固定为测试

图表单键 <-> 业务键映射(作业 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}。
  • 流式 chat 的 formUpdate 差量写回 repository (图提交时即同步业务字段;FormUpdate 是否返回不影响写回)。
  • get_info 返回值中的敏感字段(证件号/手机号)回显边界: 注册表已记录 SensitiveLevel,访问控制随作业 9 落地。

流式、并发与字段一致性约定

  • 阶段前缀先缓冲并按当前节点的转换规则校验,非法前缀不推送; 未输出时仍可使用一次修复预算。已开始输出后,全文格式或时间依据 校验失败会以错误终止,不在同一流中拼接修复答案,不提交本轮业务字段。 已经到达客户端的正文不可撤回;客户端只在收到 done 后将一轮视为成功。
  • 会话锁引用包括持有者与等待者,只有最后一个使用者退出才回收。 chat、stream、set、get、delete 全部按同一会话串行;不同会话独立。 取消流时先关闭内部生成器、取消图任务,再释放会话锁。
  • needFormUpdate 只决定对外是否返回差量。图提交时通过内部回调同步 repository,关闭差量显示或消费者在提交后断开不会丢失业务字段。
  • 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 层验收演示链路。