跳到主要内容

04 · 上下文与路由的精华

前几章讲了「图怎么跑」。本章挑出几个让 ChatDev 表达力暴涨、又容易被忽略的机制:上下文清理三态、keep/clear、关键词路由、扇出

1. context_window:节点执行后保留多少输入(三态语义)

它要解决的小问题。 一个 agent 节点跑完,它的输入队列里那堆消息要不要留?留多少?留太多→上下文爆炸/串味;留太少→丢了必要背景。

ChatDev 用一个整数 context_window 表达三种策略(entity/configs/node/node.py:113-121 的字段说明 + clear_input 实现 node.py:238-271):

取值含义
0(默认)执行后清空输入,但保留打了 keep 标记的消息
-1不清,输入全部保留(无限上下文)
N(>0)保留最新的 N 条非 keep 消息(keep 消息额外常驻,且占用 N 的额度)

ChatDev_v1.yaml:角色提示类的 literal 节点都是 context_window: 0(用完即清),而需要「记住整段工具对话」的程序员节点(如 Programmer Code Review)是 context_window: -1(全留)。

清理在每次节点执行后由 _execute_node 调用(workflow/graph.py:594-603)。N>0 的裁剪算法是「keep 消息优先保留并占额度,剩余额度给最新的非 keep 消息」(node.py:255-270)。

2. keep_message 与 clear_context:谁常驻、谁被清

这两个是边上的开关,和 context_window 配合用:

  • keep_message: true(边)→ 搬过去的消息打 keep 标记。配合 context_window: 0,效果是「每轮清桌面,但这份用户任务永远留着」。ChatDev_v1.yaml 里所有 USER → 各 agent 的边都是 keep_message: true——用户任务作为常驻背景。
  • clear_context: true(边)→ 入队先把目标已有的非 keep 输入清掉(_clear_target_context,runtime/edge/conditions/base.py:204-228)。用于「进入新阶段前,先把上一阶段的残留对话抹掉,只带必要的东西过去」。

直觉: context_window 管「我自己跑完留什么」,keep_message/clear_context 管「别人往我这送货时怎么收拾桌面」。两层配合,才能在没有全局对话状态的前提下精确控制每个节点看到的上下文。

3. keyword 条件:不调 LLM 的关键词路由

它要解决的小问题。 「如果复审员说了 <INFO> Finished 就跳出复审循环」——这种路由判断不该再花一次 LLM 调用。

ChatDev 内置 keyword 条件类型(runtime/edge/conditions/keyword_manager.py:36-49),纯字符串/正则匹配:

# 示意,摘自 ChatDev_v1.yaml:924-934
- from: Code Reviewer
to: Test Error Summary Phase Prompt for Assistant
condition:
type: keyword
config:
any: ["<INFO> Finished"] # 上游输出含这串 → 放行(跳出复审,进测试)
none: []
regex: []
case_sensitive: true

求值优先级(keyword_manager.py:36-49):先看 none(命中任一就 False)→ 再看 any(命中任一就 True)→ 再看 regex → 都没配 any/regex 则默认 True。这套优先级让「白名单/黑名单/正则」能自由组合。

另一类条件是 function——调你写在 functions/edge/ 里的 Python 函数做任意判断(runtime/edge/conditions/builtin_types.py:10-15)。所以路由既能零代码(keyword),也能逃生到代码(function)。

4. dynamic map/tree:一条边把任务扇出成并行子单元

它要解决的小问题。 「把一篇长文拆成 10 段,每段并行让 agent 处理,再汇总」——这是 map-reduce。怎么在图里零代码表达?

ChatDev 把「扇出」做成边上的 dynamic 配置(entity/configs/edge/dynamic_edge_config.py:54-183),两种模式:

type语义直觉
map纯扇出:按 split 规则把 payload 切成多份,目标节点并行跑每份,收集结果map
tree扇出 + 逐级归并:切片并行后,按 group_size 迭代 reducemap + reduce

配置含 split(怎么切:按 pattern / json_path 等)、max_parallel(并发上限,默认 10)、tree 的 group_size(默认 3)。

执行时,带 dynamic 配置的入边会让目标节点走特殊路径:GraphExecutor._execute_with_dynamic_config(workflow/graph.py:503-545)把「来自 dynamic 边的输入」拆分、把「静态边的输入」复制给每个子单元,交给 DynamicEdgeExecutor 并行跑。一致性有校验:同一节点的多条 dynamic 入边必须配置完全一致,否则报错(_get_dynamic_config_for_node,workflow/graph.py:444-501)。

精华点: 和 loop_counter 一样,这是「把一种控制流(并行 map-reduce)塞进图的声明里」,执行器按声明展开,用户不写一行并发代码。示例见 yaml_instance/demo_dynamic.yamldemo_dynamic_tree.yaml

5. payload_processor:边上改写消息

边还能挂一个 processor 改写流经的 payload(runtime/edge/processors/,有 regexfunction 两种)。在 EdgeConditionManager.transform_payload(runtime/edge/conditions/base.py:46-86)里被调用——条件放行后、入队目标前,先让 processor 改一手(比如正则抽取、套模板)。这让「A 的输出不直接喂 B,而是抽取/重写后再喂」无需新增节点。


下一章: 代码地图、巧妙之处汇总、边界与横向对比。