Dify Agent History 层详解:跨 Run 持久化 pydantic-ai 会话历史、快照续跑与自动压缩

Dify Agent History 层详解:跨 Run 持久化 pydantic-ai 会话历史、快照续跑与自动压缩 Dify Agent History 层详解跨 Run 持久化 pydantic-ai 会话历史、快照续跑与自动压缩【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/difyDify Agent 通过 Agenton 分层架构组织一次运行Run其中 history 层是唯一负责跨 Run 记忆的可选层它把 pydantic-ai 的消息历史序列化进会话快照session snapshot让下一次运行能无缝接续上一次对话。本文基于 history 层官方文档 与仓库源码完整讲解该层的契约约束、接入方式、压缩compaction策略、快照回写与续跑流程并给出源码级实现证据与故障排查表。1. History 层是什么一个纯状态层从 history 层文档 的定义看history 层将 pydantic-ai 的会话历史存储在 Agenton 会话快照中适用于后续 Run 需要恢复之前对话的场景。它的设计边界非常克制只贡献状态state-only不提供任何 prompt 文本、用户输入或工具不持有活动资源层本身没有数据库连接、进程句柄等需要生命周期管理的对象因此可以安全地 suspend/resume历史只活在可序列化结构里可变历史仅存在于runtime_state.messages随快照序列化/反序列化。这一边界在 PydanticAIHistoryLayer 源码 的模块 docstring 中明确写出The layer is intentionally state-only: it contributes no system prompts, user prompts, or tools, and it owns no live resources.2. 层契约Layer contract属性值保留层名history类型 idpydantic_ai.history配置无none依赖无none约束规则最多使用一个 history 层名称必须是history不得声明依赖。这些常量在协议层的定义为DIFY_AGENT_HISTORY_LAYER_ID history见 schemas.pyPYDANTIC_AI_HISTORY_LAYER_TYPE_ID pydantic_ai.history见 history.py。3. 基本用法from agenton_collections.layers.pydantic_ai import PYDANTIC_AI_HISTORY_LAYER_TYPE_ID from dify_agent.protocol import DIFY_AGENT_HISTORY_LAYER_ID, RunLayerSpec history_layer RunLayerSpec( nameDIFY_AGENT_HISTORY_LAYER_ID, typePYDANTIC_AI_HISTORY_LAYER_TYPE_ID, )该层需要与你已有的 prompt、plugin、LLM 层放入同一个 composition运行图中随CreateRunRequest.composition.layers[]提交。类型注册方面从源码结构看默认层提供器create_default_layer_providers()已内置pydantic_ai.history类型这一点由测试 test_runtime_history.py 中的test_default_layer_providers_include_pydantic_ai_history_layer直接断言。4. 源码解析状态模型与写入 APIPydanticAIHistoryRuntimeState 是真正参与序列化的状态模型只有一个字段class PydanticAIHistoryRuntimeState(BaseModel): Serializable history state stored in Agenton session snapshots. messages: list[ModelMessage] Field(default_factorylist) model_config: ClassVar[ConfigDict] ConfigDict(extraforbid, validate_assignmentTrue)两个细节值得注意extraforbidvalidate_assignmentTrue快照反序列化时拒绝未知字段任何对messages的赋值都会触发 Pydantic 校验保证快照中的历史始终是合法消息结构helper 方法从不原地修改列表replace_messages/append_messages/clear全部通过新列表整体赋值实现见 第 49-59 行源码注释解释了动机——Helper methods always assign fresh lists instead of mutating the stored list in place so Pydantic assignment validation continues to guard the serialized state。这样赋值校验不会被绕过。层对外暴露的读取入口是message_history属性返回存储历史的浅拷贝运行器在每次模型调用前从这里取出历史传入 pydantic-ai。5. 运行时的三项校验规则validate_history_layer_composition 在 Run 进入运行图之前执行逐条实现文档契约def validate_history_layer_composition(composition: RunComposition) - None: history_layers [layer for layer in composition.layers if layer.type PYDANTIC_AI_HISTORY_LAYER_TYPE_ID] if not history_layers: return if len(history_layers) 1: raise ValueError(Only one pydantic_ai.history layer is supported, named history...) if history_layer.name ! DIFY_AGENT_HISTORY_LAYER_ID: raise ValueError(...must use reserved layer name history, got ...) if history_layer.deps: raise ValueError(...does not support dependencies...)规则与报错文案和文档的 Troubleshooting 表一一对应并各有针对性测试覆盖test_runtime_history.py违规场景抛错片段对应测试出现多个 history 层Only one pydantic_ai.history layer is supportedtest_..._rejects_multiple_history_layers层名不是historymust use reserved layer name historytest_..._rejects_misnamed_history_layer声明了depsdoes not support dependenciestest_..._rejects_history_layer_dependencies6. 压缩与持久化Compaction and persistence6.1 压缩目标窗口的计算当 LLM 层提供了context_window_tokens时Dify Agent 会构建一个 Harness 压缩能力。实现位于 build_compaction_capabilityinput_budget context_window_tokens * 4 // 5 # floor(window * 0.8) max_tokens model_settings.get(max_tokens) if model_settings is not None else None if max_tokens is not None and max_tokens 0: input_budget min(input_budget, context_window_tokens - max_tokens) if input_budget 0: raise ValueError(Model max_tokens must leave a positive input context budget.)与文档公式完全一致对正的model_settings.max_tokensHarness 目标为min(floor(window * 0.8), window - max_tokens)否则为floor(window * 0.8)。目标值非正时即max_tokens几乎吃满上下文窗口在调用模型之前就抛出ValueError拒绝该 Run——这是配置错误而非运行期退化的处理方式。另外两点边界context_window_tokens未提供时直接返回None禁用压缩历史原样发送子压缩器的max_tokens1参数仅用于满足pydantic_ai_harness构造器的至少配置一个触发器校验源码注释明确说明they are not one-token Dify policy thresholds。6.2 两级压缩策略构建出的TieredCompaction包含两级 tiercompaction.py 第 31-42 行层级压缩器参数语义第一级ClearToolResultskeep_pairs3、clear_tool_inputsFalse先清空较早的工具结果保留最近 3 对 tool-call/result 及其输入第二级SummarizingCompactionkeep_messages20、preserve_first_user_messageTrue、incrementalTrue仍超目标时用当前模型对更早消息做增量摘要保留最近 20 条消息与第一条用户消息这与文档描述逐字对应It clears older tool results first, retaining the latest three tool-call/result pairs and their inputs. If the history is still over target, the same current model incrementally summarizes older messages while retaining the latest twenty messages and the first user message. 执行时机是每次模型请求之前估算并按需重写历史因此压缩发生在请求链路内部调用方无感知。6.3 运行器的接线读出历史 → 运行 → 回写runner.py 中进入运行图后先通过get_history_layer(run)取层第 361-365 行存在层则读取message_history作为message_history...参数传给 pydantic-ai同时把build_compaction_capability(...)的结果挂到capabilities运行结束后用replace_run_history(history_layer, captured_messages)回写。此外源码还暴露了一个文档未强调的依赖关系deferred_tool_results如 ask-human 延迟工具的续跑输入必须存在 history 层才能工作——缺少时运行器直接报错 Deferred tool results require a history layer with prior message history.因为待匹配的工具调用必须仍在历史状态里。7. 回写规则什么会被存进快照replace_run_history是持久化的唯一入口实现见 runtime/history.py 第 68-78 行def replace_run_history(history_layer, messages) - None: if history_layer is None: return persistent_messages [ replace(message, instructionsNone) if isinstance(message, ModelRequest) else message for message in messages ] history_layer.replace_messages(persistent_messages)关键行为对每条ModelRequest持久化前剥离 run 级instructions即当前 system prompt消息本体parts完整保留。这由测试 test_replace_run_history_persists_full_history_without_instructions 精确验证持久化后的首条请求instructions is None而parts不变且源列表中的对象未被修改source_request.instructions current instructions印证了赋值新对象的无副作用设计。综合文档与源码Dify Agent 的记忆策略是保守的完整规则如下当前 system prompt 以 run 级 pydantic-ai instructions 传入从不入库存储的历史在当前用户 prompt 之前发送给模型当 LLM 层含context_window_tokens时Harness 在模型请求前重写超目标历史见第 6 节一旦 pydantic-ai 在 run capture 中绑定并构建消息完整的捕获历史可能已压缩在成功、失败、超时或取消时都会回写层若失败或取消发生在 capture 尚无任何消息时先前恢复的历史保持原样run 级 system instructions 在持久化前被移除被中断的部分消息保留 pydantic-ai 的stateinterrupted标记供后续独立 Run 修复并续跑失败/取消的 Run 保持其终态状态其快照是检查点而不是把被中断 Run 的终态改成成功。8. 恢复对话会话快照与续跑成功的 Run 会在终态事件terminal event中同时携带最终输出和可续跑的会话快照失败与取消的终态事件也可携带快照作为当前历史的检查点但不会把被中断 Run 的终态改写为成功。客户端侧的标准流程accepted await client.create_run(request) async for event in client.stream_events(accepted.run_id): if event.type run_succeeded: output event.data.output snapshot event.data.session_snapshot break把snapshot传给下一次请求并保持相同的层名与顺序next_request CreateRunRequest( compositioncomposition_with_the_same_layer_names_and_order, session_snapshotsnapshot, )on_exit的默认值是 suspend 各层这正是终态快照可续跑的原因正常记忆流程应保持该默认。这一语义在协议模型 CreateRunRequest 的 docstring 中得到确认on_exitdefaults every active layer to suspend so callers receive a resumable success snapshot unless they explicitly request delete for one or more layers且Resume requests are therefore expected to pair a priorsession_snapshotwith the same logical composition so Agenton can rebuild the same layers and message history。注意两点附加约束来自同一 docstring会话快照不保留 output 层配置——依赖结构化输出的续跑请求必须在composition.layers[]中带上同样的output层以重建输出 schemaDify 租户/用户/run 关联标识必须通过 composition 中的dify.execution_context层提交协议层没有平行的顶层字段。9. 在客户端进程外持久化快照会话快照是 Pydantic 模型可直接序列化为 JSON 存到文件、对象存储等任意介质from pathlib import Path from agenton.compositor import CompositorSessionSnapshot snapshot_path Path(session_snapshot.json) snapshot_path.write_text(snapshot.model_dump_json(), encodingutf-8) restored_snapshot CompositorSessionSnapshot.model_validate_json( snapshot_path.read_text(encodingutf-8) )恢复时务必使用产生该快照的相同层名与顺序否则 Agenton 无法重建一致的层与消息历史。10. 故障排查症状检查项must use reserved layer name history把层改名为history。does not support dependencies从 history 层移除deps。续跑时出现快照生命周期错误使用层处于 suspended 状态的终态快照且层名/顺序保持不变。保存的记忆里看不到 system prompt预期行为当前 system prompt 是临时的不持久化。前两条报错正是 validate_history_layer_composition 抛出ValueError的文案第四条对应replace_run_history剥离instructions的实现属于设计预期而非缺陷。参考路径文档主体history-layer/index.md层实现agenton_collections/layers/pydantic_ai/history.py运行时校验与回写dify_agent/runtime/history.py压缩能力构建dify_agent/runtime/compaction.py运行器接线dify_agent/runtime/runner.py协议模型dify_agent/protocol/schemas.py测试tests/local/dify_agent/runtime/test_runtime_history.py、tests/local/dify_agent/protocol/test_protocol_schemas.py相关层文档ask-human 依赖 history 层续跑ask-human-layer/index.md【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考