华为云Flexus+DeepSeek征文|Dify 多 Agent 会话管理与上下文工程实战:让智能体“记住该记住的,忘掉该忘掉的“

一、引言:用户只问了一句话,八个 Agent 各说各话

先看一个真实场景。你基于 Dify 搭了一套企业智能客服系统,里面跑了八个子 Agent:订单查询、物流跟踪、售后理赔、优惠计算、库存查询、支付咨询、发票开具、人工转接。用户发来一句:"我上周买的那台电脑,物流怎么还没到?退款的话多久能到账?"

这句话里有两个诉求:查物流、问退款。于是编排层把任务拆给物流 Agent 和售后 Agent。物流 Agent 查完说"在途,预计明天到",售后 Agent 说"退款 3-5 个工作日到账"。看起来没问题,但用户紧接着追问:"那我能同时申请退款吗?还是等收到货再退?"

问题来了:这句话该由谁回答? 物流 Agent 知道订单在途,售后 Agent 知道退款规则,但没有一个 Agent 同时知道"订单状态 + 退款政策 + 用户此前的诉求",上下文散落在不同 Agent 的会话里。最终要么是某个 Agent 瞎猜,要么是编排层把两份对话历史全塞给某个 Agent------token 翻倍、噪声翻倍、还可能答非所问。

这就是多 Agent 系统里最隐蔽也最致命的工程问题:会话状态与上下文管理。单 Agent 系统里,会话就是一份 message 数组,追加、截断、完事;多 Agent 系统里,一份会话被拆成多份,状态要跨 Agent 传递、同步、恢复,任何一环出问题,用户体感就是"它失忆了"或"它精神分裂了"。

这一期,我们就把多 Agent 的会话管理讲透:状态怎么建模、上下文怎么传、冲突怎么解决、断线怎么续接,最后落到 Dify 工作流里的工程实现。上一期我们让系统"越炸越稳",这一期我们让系统"记得住、传得对、接得上"。

二、从"消息列表"到"共享工作记忆":会话管理的三层模型

2.1 单 Agent 时代的会话:一个数组打天下

单 Agent 聊天机器人里,会话上下文就是一个消息数组:

复制代码
messages = [
    {"role": "system", "content": "你是订单助手"},
    {"role": "user", "content": "查一下订单 12345"},
    {"role": "assistant", "content": "订单 12345 已发货,预计明天送达"},
]

每次调用模型,把整个数组塞进 prompt,超出窗口就做截断或摘要。逻辑简单,状态只有一份,不存在同步问题。这是所有聊天应用的起点,但也是多 Agent 系统里最不该照搬的模型------因为一份数组承载不了多个执行者的状态

2.2 多 Agent 时代的会话:四层状态模型

多 Agent 系统里,"会话"至少包含四个互相独立又彼此关联的状态层:

状态层 内容 生命周期 谁读写
用户会话层 用户画像、偏好、历史对话 长期(跨会话) 入口 Agent
任务上下文层 当前任务目标、约束、中间结果 短期(本次任务) 执行 Agent
协作上下文层 Agent 间交接信息、调用链记录 任务期 编排 Agent
共享黑板层 多 Agent 共同读写的状态(订单状态、用户意图) 任务期+ 所有相关 Agent

这四层各有各的读写方、各有各的生命周期。把四层混成一份 message 数组,是绝大多数多 Agent 上下文事故的根源------物流 Agent 的中间推理混进了用户看到的对话,售后 Agent 的私有草稿被当成事实传给用户,共享黑板的订单状态被两个 Agent 同时覆盖。

2.3 一个关键心智模型:上下文是"工作记忆",不是"聊天记录"

很多团队把"上下文管理"等同于"聊天记录管理",这是认知偏差。多 Agent 系统的上下文,本质上是团队的工作记忆:谁在做什么、做到哪一步、发现了什么、答应了用户什么。聊天记录只是工作记忆的一部分输入。

用这个心智模型看问题,很多设计决策就清晰了:

  • 该记什么?------工作记忆里该放的是"结论、承诺、进度、约束",而不是"每一句原始对话";
  • 该忘什么?------临时推理、已完成的中间结果、与当前任务无关的历史,都是该清理的工作记忆垃圾;
  • 该怎么传?------传的是"交接单",不是"会议录音"。

三、会话状态建模:哪些该记、哪些该忘、记多久

3.1 状态分类:先给状态打标签

在设计会话状态之前,先对系统里所有可能的状态做一次分类盘点。我的建议是按"敏感度 × 生命周期 × 作用域"三个维度打标签:

复制代码
@dataclass
class SessionState:
    key: str            # 状态键,如 order_status
    scope: str          # user / task / coordination / blackboard
    ttl: int            # 生命周期(秒),-1 表示永不过期
    sensitive: bool     # 是否敏感(手机号、地址、支付信息)
    writer: str         # 主写者 Agent,如 order_agent
    readers: list       # 可读 Agent 列表
    version: int = 0    # 版本号,用于冲突检测

举个例子,一次"查物流 + 问退款"的会话里:

  • user_preference(用户偏好):scope=user,ttl=-1,sensitive=False,writer=entry;
  • order_status(订单状态):scope=blackboard,ttl=3600,writer=order_agent,readers=logistics, after_sale
  • refund_policy_hit(退款政策命中结果):scope=task,ttl=600,writer=after_sale;
  • user_phone(用户手机号):scope=user,sensitive=True,只允许售后 Agent 在必要环节读取。

给状态打标签的最大价值:让"该记/该忘"从拍脑袋变成可执行规则------scope 决定谁能读写,ttl 决定何时过期,sensitive 决定谁有权限碰。

3.2 遗忘策略:没有遗忘机制的记忆系统是垃圾场

长期记忆系统最常见的病是"只进不出"。多 Agent 会话状态必须有三道遗忘闸门:

  1. TTL 过期:任务上下文层的状态(如"退款政策命中结果")任务结束即失效,设短 TTL 自动清除;
  2. 容量上限:共享黑板设最大条目数,超出按"最后写入时间 + 优先级"淘汰最不相关的;
  3. 相关性清理:会话进行中,定期评估每条状态与当前任务的相关性,低于阈值就归档到冷存储。

遗忘不是丢数据,是把短期工作记忆和长期记忆分开:工作记忆保持轻量,长期记忆负责沉淀用户画像和业务规则。

3.3 Dify 里的落地:会话变量与会话 ID

Dify 原生提供了两个关键机制:

  • 会话变量(Conversation Variables):在应用级定义,跟随会话存在,所有工作流节点都可以读写。这天然就是"共享黑板层"的载体;
  • 会话 ID(Conversation ID):每次用户消息都会带上会话 ID,用它把多轮消息串联成同一份上下文。

工程上的做法:把 3.1 的状态模型映射到会话变量,命名规范用 scope_key 前缀(如 blackboard_order_statustask_refund_hit),写一个统一的状态读写工具节点,所有 Agent 通过它访问状态,而不是各写各的变量------这就为后面的"一致性"章节打下了基础。

四、上下文传递:传结论不传对话,传引用不传副本

4.1 三种传递方式:全量、摘要、引用

多 Agent 交接上下文,常见有三种策略,成本与保真度依次变化:

策略 做法 token 成本 信息保真 适用场景
全量传递 把完整对话历史塞给下一个 Agent 最高 单 Agent 深度长对话
摘要传递 用 LLM 生成结构化摘要再传递 高(有损) 跨 Agent 任务交接
引用传递 传会话 ID + 查询接口,按需取数 可控 共享黑板、知识库、数据库

全量传递在多 Agent 场景下几乎总是错误选择:假设八个 Agent 每人 2000 token 的对话历史,汇聚到编排层就是 16000 token 的噪声堆。上一期成本治理我们算过,多 Agent 系统的 token 消耗中,上下文膨胀通常占 40% 以上,而膨胀的根源就是全量传递。

4.2 摘要传递:分层摘要(Rolling Summary)的正确姿势

摘要传递不是简单让 LLM 总结一段话,而是要做分层

  • 细节层(保留最近 2-3 轮原始消息):保证近因信息不丢失;
  • 摘要层(更早的历史压缩成要点):只保留"结论、承诺、状态变更",丢弃"过程性对话";
  • 事实层(结构化字段):订单号、金额、时间这些必须精确的,绝不能靠 LLM 摘要(会编造),直接从状态库取值。

一个可落地的分层摘要实现:

复制代码
import json
from typing import List, Dict

def rolling_summary(messages: List[Dict], llm, keep_recent: int = 3,
                    summary_so_far: str = "") -> Dict:
    """分层摘要:近 N 轮保原文,更早的并入摘要,结构化事实单独提取"""
    recent = messages[-keep_recent:]
    old = messages[:-keep_recent]

    # 1. 事实层:从消息里正则提取结构化字段(不经过 LLM,保证精确)
    facts = extract_facts(old)  # {"order_id": "12345", "amount": 8999.0}

    # 2. 摘要层:只把"无结构化落点"的信息交给 LLM
    if old:
        prompt = (
            "以下是多轮对话的早期部分,请压缩为要点清单,只保留:"
            "①已确认的事实 ②对用户的承诺 ③状态变更 ④未决问题。"
            "丢弃寒暄、重复、过程性思考。\n\n" + json.dumps(old, ensure_ascii=False)
        )
        new_summary = llm.chat(prompt)
        summary_so_far = merge_summary(summary_so_far, new_summary)

    # 3. 组装返回:事实层 + 摘要层 + 细节层
    return {
        "facts": facts,
        "summary": summary_so_far,
        "recent": recent,
    }

关键点:结构化事实绝不进 LLM 摘要。LLM 摘要适合"压缩语义",不适合"保存精确值"------它会在你不知道的时候把 8999 改成 8900。订单号、金额、地址、时间,全部走事实层。

4.3 引用传递:上下文即查询

引用传递的思路是:上下文不搬家,给 Agent 一个"门牌号" 。交接时传 session_id + 查询协议,接收方需要什么再按需取:

  • 会话历史 → 调历史查询接口(只取需要的片段);
  • 订单状态 → 查共享黑板或业务库;
  • 用户画像 → 查用户档案服务。

这在 Dify 里的落地非常自然:Agent 的工具节点本身就是"按需取数"的通道,把"读上下文"设计成工具而不是把上下文塞进 prompt。引用传递的额外好处是天然解决一致性问题------大家读的是同一份数据源,而不是各自拷贝的过期副本。

五、一致性:两个 Agent 同时改一份状态怎么办

5.1 冲突从哪来

多 Agent 并发写同一个状态,是必然发生的事。典型场景:

  1. 用户同时问了"物流"和"退款",物流 Agent 更新 order_status 为"在途",售后 Agent 同时读取它准备回答,读到的是旧值;
  2. 两个 Agent 分别给用户承诺了不同的送达时间;
  3. 编排层把同一任务分发给了两个候选 Agent(超时重试导致的重复执行)。

5.2 三个治理手段

手段一:主写者模式(Single Writer) 。每个状态只允许一个 Agent 写,其他 Agent 只读。order_status 归订单 Agent 写,物流 Agent 和售后 Agent 都只读。冲突在源头消失------不是靠锁,是靠"职责单一"。这是最推荐的第一道防线,成本最低、效果最好。

手段二:乐观锁版本号。允许并发写,但每次写都带版本号:

复制代码
def update_state(store, key: str, new_value, expected_version: int) -> bool:
    """乐观锁:版本不匹配则拒绝写入,返回 False 由调用方重读重试"""
    current = store.get(key)
    if current.version != expected_version:
        return False  # 冲突,调用方需重新读取后合并再写
    store.put(key, State(new_value, version=current.version + 1))
    return True

写失败的一方执行"重读 → 合并 → 重试",合并规则可以是"以最新为准"或"以主写者为准"。

手段三:事件溯源(Event Sourcing) 。不存最终状态,存状态变更事件流(order_status_changed: in_transit@14:32),任何时刻的状态由事件流重放得出。优点是天然有审计轨迹、可回滚;缺点是实现重,适合订单、资金这类高价值状态。

5.3 冲突仲裁规则

当合并不可避免时,仲裁规则要有明确的优先级,并写进文档:

  1. 事实类冲突(订单号、金额):以业务系统为准,LLM 输出绝不参与仲裁;
  2. 承诺类冲突(给用户的承诺):以先承诺者为准,后承诺者必须引用前文("按之前客服的说法...");
  3. 状态类冲突(任务进度):以主写者为准;
  4. 仲裁失败:宁可转人工/明确说"我需要再确认",也不许编造。

六、会话续接:让用户感觉"它一直记得我"

6.1 断点从哪来

多 Agent 任务链路长,中断是常态:子 Agent 超时、模型 API 限流、编排进程重启、用户离开几小时再回来。用户回来问"刚才说到哪了",系统如果一脸茫然,前面所有上下文工程都白做了。

6.2 会话快照 + 状态重建

工程上要保证两件事:会话可快照、状态可重建

复制代码
def snapshot_session(session_id: str) -> str:
    """把会话的完整状态打成快照,返回快照 ID"""
    state = collect_states(session_id)          # 四层状态全量收集
    recent_msgs = fetch_recent_messages(session_id)
    snapshot = {
        "states": state,
        "recent_messages": recent_msgs,
        "created_at": now(),
    }
    return persist_snapshot(session_id, snapshot)

def restore_session(session_id: str, snapshot_id: str) -> None:
    """恢复:先重建状态,再重放关键消息,最后验证一致性"""
    snap = load_snapshot(snapshot_id)
    restore_states(session_id, snap["states"])   # 1. 恢复四层状态
    replay_to_agents(session_id, snap["recent_messages"])  # 2. 通知相关 Agent 重放
    verify_consistency(session_id)               # 3. 校验黑板状态无冲突

注意恢复顺序:先状态后消息。如果先重放消息再恢复状态,Agent 在重放过程中读到的是空状态,可能产生错误中间结果。

6.3 超时任务的"诚实交接"

如果任务确实中断且无法恢复(比如订单 Agent 宕机超过 TTL),系统要做的不是假装记得,而是诚实地说"我需要重新确认"------这比编造一个上下文强一百倍。我们的多 Agent 故障演练(上一期)里验证过:用户对"系统明确说不知道"的容忍度,远高于"系统编造了一个错误的订单状态"。

七、Dify 工程实战:一套完整的会话上下文管理方案

7.1 架构总览

把上面的理论落到 Dify 工作流,我推荐的结构是:

复制代码
入口节点
  ├─ 会话恢复检查(用户回来?→ 从快照恢复状态)
  ├─ 状态读取节点(读会话变量 → 组装上下文包)
  ├─ 路由节点(按意图分发到子 Agent)
  │    ├─ 订单 Agent / 物流 Agent / 售后 Agent ...
  │    └─ 每个 Agent 执行后:写回状态节点(乐观锁)
  ├─ 上下文压缩节点(对话超长 → 分层摘要)
  ├─ 响应组装节点(结论 + 引用来源)
  └─ 会话快照节点(异步保存)

7.2 会话变量设计规范

在 Dify 的"会话变量"里按规范定义:

复制代码
blackboard_order_status   # 共享黑板:订单状态(订单 Agent 主写)
blackboard_refund_hit     # 共享黑板:退款政策命中(售后 Agent 主写)
task_current_intent       # 任务层:当前用户意图
user_phone                # 用户层:敏感,脱敏存储
ctx_facts                 # 事实层:结构化字段 JSON
ctx_summary               # 摘要层:滚动摘要文本

命名即契约:作用域_业务名,谁主写、谁能读,写进 README 并在工作流里用"变量赋值节点"统一收敛写入口,禁止子 Agent 直接改别人的域。

7.3 上下文压缩节点:Dify 里的 Rolling Summary

Dify 工作流里可以用"代码节点"实现 4.2 的分层摘要。注意两个工程细节:

  1. 触发条件:不要每轮都摘要,设阈值------最近消息数 > 15 或估算 token > 窗口 60% 才触发;
  2. 摘要用独立会话调用:摘要生成使用单独的 LLM 调用(低温度、专用 prompt),不走主对话,避免污染主上下文。

7.4 完整链路示例:物流+退款的多轮会话

跑一遍完整流程验证设计:

  • 第 1 轮 :用户问物流。路由 → 物流 Agent,读 blackboard_order_status(空)→ 查业务库写状态"在途",把事实写入 ctx_facts,回复"预计明天送达";
  • 第 2 轮:用户问退款。路由 → 售后 Agent,读黑板拿到订单状态"在途",结合退款政策回复"建议先收货再申请退货";
  • 第 3 轮:用户问"收货后退款多久到账"。路由 → 售后 Agent,此时上下文包里:事实层(订单号、状态)、摘要层(前两轮的结论与承诺)、细节层(本轮),直接给出精确回答,全程无需重查订单------因为订单 Agent 已经把状态写进黑板,售后 Agent 读的是同一份最新数据,而不是自己上一轮的拷贝。

这个链路里,四个设计点各司其职:黑板消除状态孤岛、主写者消除写冲突、分层摘要控制 token、会话快照保证可恢复。

八、踩坑清单:多 Agent 会话管理的六个坑

  1. 把聊天记录当上下文全量塞:token 爆炸 + 噪声淹没关键信息,必须分层(事实/摘要/细节);
  2. 让 LLM 摘要订单号:LLM 会"合理"地编造精确值,结构化字段一律正则/工具提取;
  3. 所有 Agent 直接写共享变量:没有主写者约束,状态互相覆盖,加版本号 + 单写者;
  4. 恢复时先重放消息后恢复状态:Agent 在空状态下产生错误中间结果,顺序必须是先状态后消息;
  5. 敏感字段明文进上下文:手机号、地址进了共享黑板就可能被任意 Agent 读走,必须脱敏 + 最小授权;
  6. 只记不忘:任务层状态不清理,会话越拖越臃肿,TTL 和容量上限要设成默认值而非可选项。

九、FAQ:六个高频问题

Q1:多 Agent 上下文一定要用共享黑板吗?单传引用不行吗?

引用传递解决"读一致性",黑板解决"写一致性"。如果只有读没有写,引用就够了;一旦多个 Agent 都要更新同一状态(订单、进度),就必须有黑板 + 主写者。

Q2:分层摘要会丢信息吗?

会,但丢的是"过程性信息",保的是"结论性信息"。验收标准:用户追问"你刚才说的那个承诺",系统必须答得出来------答不出就是摘要丢了承诺,摘要质量不合格。

Q3:会话快照多久打一次?

按业务价值定。高频高价值会话(涉及支付、订单)每轮异步快照;普通问答 3-5 轮一次即可。快照是异步的,别阻塞主链路。

Q4:子 Agent 之间的上下文会不会互相污染?

会,所以要"作用域隔离":任务层状态随任务销毁,黑板层状态按业务域隔离(blackboard_order_* 只允许订单域 Agent 读写)。命名规范 + 读写白名单是两道闸。

Q5:Dify 会话变量能存大对象吗?

能存但别存大。会话变量适合存结构化状态(事实层、进度),大段文本走外部存储(Redis/数据库),变量里只放引用。

Q6:用户隐私数据在共享黑板里怎么保护?

三层:脱敏存储(手机号存 138****1234)、最小授权(白名单限定读者)、任务结束即清理(TTL 设短)。敏感字段永远不进 LLM 摘要。

十、总结:上下文工程的三个铁律

这一期把多 Agent 会话管理与上下文工程讲完了。如果只能带走三条:

  1. 分层:事实层保精确、摘要层保语义、细节层保近因,三层各司其职,绝不混装;
  2. 定界:每个状态有作用域、有主写者、有 TTL------该谁读、该谁写、该活多久,先定清楚再写代码;
  3. 可恢复:任何时刻的会话都能快照、都能重建,恢复顺序先状态后消息,恢复不了就诚实承认。

回想开头的场景:八个 Agent 各说各话的智能客服,在分层上下文 + 共享黑板 + 主写者 + 快照恢复这套体系下,用户追问"能不能同时申请退款",售后 Agent 读到黑板里的最新订单状态和此前的承诺,给出的是"建议先收货再退货,退款 3-5 个工作日到账"------上下文连贯、状态一致、承诺不冲突。用户感知是:这个客服记得我说过什么。

多 Agent 系统的工程能力,就藏在这些"看不见的记得"里。

下一期我们聊聊多 Agent 系统的安全边界与权限治理------当智能体越来越多、能调的工具越来越强,怎么防止一个被攻破的 Agent 成为整个系统的突破口。


📚 延伸阅读

如果你对 DeepSeek 的实战用法感兴趣,推荐阅读我的另一篇文章:

👉 DeepSeek 实战指南:提示词工程、API 集成与效率提升全攻略

这篇文章系统地拆解了 DeepSeek 的提示词工程技巧、API 封装方法以及日常效率提升场景,全文代码可直接运行,适合已经上手 DeepSeek 但希望更高效使用的开发者。

Dify 多 Agent 实战系列:


本文是"华为云Flexus+DeepSeek征文"系列文章之一。该系列基于华为云 Flexus 云服务器与 MaaS 平台 DeepSeek 推理服务,结合 Dify 工作流引擎,从部署、Agent 开发、评测、成本治理到稳定性建设,系统拆解企业级 AI 应用的工程实践。

相关推荐
疯狂的金桔1 小时前
从零理解 Milvus:从向量数据库到 RAG、Hybrid Search 与 Reranker
人工智能
番茄不是西红柿kk1 小时前
什么是Token?
人工智能·ai·chatgpt·agent·token·codex·deepseek
Lambert2811 小时前
2026 年了,Java 程序员做 AI 为什么不用转 Python
aigc
地平线开发者1 小时前
量化为什么会有损失:从量化误差到精度下降
算法
代码简单说1 小时前
Codex 常见错误排查指南:Stream disconnected、400、401、403、429、502、503 解决方法
人工智能
Databuff1 小时前
workbuddy 企业版与 openocta 企业版 功能对比
人工智能
Cobyte1 小时前
Claude Code 的 Task System 的实现原理
后端·aigc·ai编程
xqqxqxxq1 小时前
AI Agent学习:MCP与工具生态:工具选择的挑战(李博杰《深入理解 AI Agent》4.3观后总结)
人工智能·学习
疯狂的金桔1 小时前
不要再把 Agent Memory 当成聊天记录:从 LangGraph 到 Deep Agents 的完整记忆架构
人工智能
腾讯数据架构师1 小时前
壁仞 GPU 怎么接入 Kubernetes 和 AI 平台?CubeStudio 壁仞算力适配实操
人工智能·容器·kubernetes·cube-studio·ai平台