Codex Harness 源码课 / 中文
LESSON 03 / 12 20 分钟

回合循环:采样、工具与继续条件

把一次用户回合拆成多次模型请求,追踪继续条件、异步结果与停止路径。

公开源码快照 · 2026-10-13 · f82e365922

学完你能做到

本课目录 · 4 节

接受输入,准备这一轮的工作材料

核心接受操作并保留输入相关元数据。run_turn 收到初始输入、状态和取消信号。开始时可能处理前置与;随后准备本步的、工具计划和历史,再发起。这里的“步”是一次请求所依赖的配置与环境快照,避免中途设置变化让调用失去对应关系。

输入进入回合,整理历史后请求模型;工具结果回到历史,满足继续条件时再次请求。
输入进入回合,整理历史后请求模型;工具结果回到历史,满足继续条件时再次请求。 · 教学示意,省略次要分支;不是完整调用图。

跟着一次调用走

教学模拟:不调用模型、不执行命令。

输出按事件到达

不是一条完整字符串。run_turn 所在模块处理输出项完成、文字增量与请求完成等事件。工具相关输出进入工具执行路径;普通文字则进入消息与历史处理。不能因为已经出现几个文字片段,就断言这一结束。

完成后,结果记录进历史;下一次使用整理后的历史。因此能看到工具返回的真实内容,而不仅是先前的计划。工具错误也可能以结果形式反馈;致命错误和取消则有单独处理路径。

谁决定再来一次?

真实源码 · Apache-2.0 · 未改写
                let (has_pending_input, token_status) = async {
                    let has_pending_input =
                        sess.input_queue.has_pending_input(&sess.active_turn).await;
                    let token_status = super::context_window::context_window_token_status(
                        sess.as_ref(),
                        turn_context.as_ref(),
                    )
                    .await;
                    (has_pending_input, token_status)
                }
                .instrument(trace_span!("run_turn.collect_post_sampling_state"))
                .await;
                let needs_follow_up = model_needs_follow_up || has_pending_input;
                let token_limit_reached = token_status.token_limit_reached;

继续条件同时考虑是否需要后续处理,以及有没有等待中的输入。不要把“出现 assistant 文本”简化成唯一的结束条件。

查看原文及相邻代码 ↗

若需要继续且达到限制,代码可能进入整理历史的分支。若不需要继续,还要运行停止;它可能改变停止行为。用户取消也可以打断网络或工具等待。基础图省略了这些分支,但读源码时必须保留它们。

沿着源码读

以下定位均锁定本课程版本。先读注释与分支,再深入类型定义。

READ & VERIFY / 小练习

实现推演

写一条事件轨迹:用户问测试入口 → 请求读取文件 → 返回文件内容 → 说明测试命令。数一数这至少包含几次请求,再想想读取失败后会发生什么。

阅读练习无需模型账号。示例命令请先确认目录与环境。

自测与边界检查

用户发了一条消息,是否只能有一次请求?

展开自测答案

不是。基础例子至少有两次:一次提出读取请求,一次消费结果并回答。真实运行还可能有重试、更多或历史整理。

为什么 needs_follow_up 还要看 pending input?

展开自测答案

运行过程中可能收到新的用户输入。框架需要把待处理输入纳入推进判断,避免仅根据上一条响应就结束。

学习记录保存在本机

搜索课程

输入关键词,搜索全部课程、词典与来源。

就地词典

在完整词典中查看 →

完整架构总图

放大后横向、纵向滚动阅读;聚焦图示区域后可用方向键滚动。

完整架构职责图,内容与正文总图相同