Files
ZNJJ-api-server/docs/agent-streaming.md
T

3.9 KiB

LangGraph 原生并行与流式输出

文本模型使用 ChatOpenAI,业务图嵌入 FastAPI。对外仍使用原来的 stage_code / text_delta / formUpdate / done / error SSE 契约。

图内并行

事故采集阶段:

flowchart LR
    P[normalize_input] --> R[collect:生成并校验回复]
    P --> X[extract_accident_fields:提取候选字段]
    R --> M[merge_collect]
    X --> M
    M --> C[应用服务事务提交]

条件边只在采集阶段选中两个分支;welcome、拍照、人员核实走自己的分支。 通过 add_edge(["collect", "extract_accident_fields"], "merge_collect") 保证两个分支都完成后,汇合节点只运行一次。

normalize_input 固定服务器参考时间并清空本轮临时通道。回复节点只更新 回复、阶段和对话簿记;提取节点只写 extracted_accident_fields;汇合节点 更新 accident_fields / turn_patch,再清空候选通道。两条分支都读取本轮开始时 的快照,不需要为并发写同一字段增加合并 reducer。

人员核实的提取提示词当前依赖本轮模型回复,并负责声明人员切换。 本次保留它的串行依赖与提示词,仍能在抽取完成前发送完整回复。

原生流与校验

后端使用 LangChain astream_events(version="v2"),其生命周期管理负责 消费期间的任务调度,以及流关闭后的取消与等待。

原生事件 条件 应用服务适配
on_chain_stream name=validated_reply_tokens TextDelta
on_chain_end name=validated_reply ReplyFinished,刷新分句器
on_chain_end 根 accident_turn 正常结束 已提交结果,可发送 FormUpdate

validated_reply_tokens 是原生 RunnableGenerator,封装现有模型文本迭代器, 等待完整合法的 state 前缀后才产出第一段文本。原始 ChatOpenAI 的 model 事件 仍可被 callbacks 追踪,但公开文本只消费受控回复 Runnable 的事件。 原生 messages 流包含校验前 token,单纯按模型或节点过滤不能替代前缀校验。

validated_reply 是原生 RunnableLambda。完整回复通过格式、阶段转换和可选 时间依据校验后,其正常 on_chain_end 才表示回复结束。模型结束事件本身 不能证明这些业务校验通过,因此不作为刷新最后一句的依据。

字段提取保留 complete / ainvoke,显式 stream=False,避免事件追踪 callback 把该调用隐式切换为流式。辅助模型输出不会映射为 TextDelta。

回复结束与事务完成

ReplyFinished 仅是后端中立适配层的内部标记,不新增公开 SSE 事件。 它立即刷新 useTextChunk 的剩余完整回复,覆盖有/无字段差量和 needFormUpdate=false 的情况。

文本可以早于提取和业务事务完成到达客户端;FormUpdate 与 done 仍在事务 成功后出现。完整回复失败、图失败、业务提交失败时,发送 error 而不发送 done。 已经输出的文字不可撤回,客户端仍以 done 判断本轮成功。

取消流时必须等图分支与模型关闭后才释放会话锁。持久化模式继续从业务提交 快照恢复;并行分支产生的未提交 checkpoint 不成为业务成功状态。

验证

test/backends/test_native_parallel_streaming.py 使用事件屏障验证:

  • 回复和抽取同时运行,汇合与提交等待两者完成。
  • 抽取阻塞时已经收到完整正文和 ReplyFinished。
  • 分句器最后一句及时刷新;公开 SSE 类型、文本顺序和终止事件保持兼容。
  • 抽取 JSON 不泄漏,原生节点和汇合在事件流中可见。
  • 断开取消剩余抽取,SQLite 未提交状态可恢复并重试。
  • 完整回复校验失败和业务提交失败均不产生虚假成功。
  • ChatOpenAI 的真实 HTTP mock 路径保留普通提取与流式回复的请求参数。

官方接口参考: