它说“我来运行”,然后呢?
你把一句话交给编程助手:
运行
main.py,只报告结果。
假设文件里只有一行:
print(10 / 0)这段代码会触发除零错误。不过,你让助手做的是运行文件,再报告这次运行的结果。它要怎样调用程序、拿到结果,再给出回答?
模型可以提出执行 python main.py,实际启动程序还需要工具实现。在编程助手里,负责调用模型、执行工具请求、保存结果的外围运行框架称为 Harness。模型决定请求什么动作,Harness 调用相应的工具,再把结果交回模型。
下面沿用 Learn Claude Code 第一课的循环思想,用这个假设案例追踪一次完整的执行过程。
先跟着这句话走一圈
Harness 首先把你的问题交给模型,同时提供工作约定和工具说明。工作约定告诉模型在哪里工作;工具说明告诉它可以请求哪些操作、参数怎样填写。在这个案例里,工具名是 bash,参数 command 接收要执行的命令。
点“下一步”,顺着亮起的箭头看:这次是谁交给谁,交了什么。
Harness 将你的问题、工作约定和工具说明一起送给模型。
- 工作约定
- 在当前目录使用工具。
- 工具说明
- bash(command):执行命令,返回输出或错误。
- 把问题交给模型Harness→模型
Harness 将你的问题、工作约定和工具说明一起送给模型。
随问题一起提供- 工作约定
- 在当前目录使用工具。
- 工具说明
- bash(command):执行命令,返回输出或错误。
- 模型请求运行文件模型→Harness
模型选择 bash 工具,并给出参数。Harness 保存这次请求;命令还没有执行。
工具请求- 工具
bash- 参数
command: "python main.py"
- Harness 真正执行命令Harness→Python 程序
Harness 把参数交给 bash 的实现,启动 Python,运行当前目录的 main.py。
实际执行- 命令
python main.py
- 程序把错误带回来Python 程序→Harness
10 / 0 触发除零异常。执行工具收集错误并返回给 Harness;模型此时还没看到它。
返回的错误末行- 错误
ZeroDivisionError: division by zero
- 带着结果再问模型Harness→模型
Harness 将错误关联到刚才的请求并保存,再带着完整对话调用同一个模型。
第二次调用带上- 对话
- 你的原问题 → bash 请求 → 除零错误
- 调用约定
- 工作约定与工具说明仍然提供。
- 模型回答你的问题模型→Harness
你只要求报告结果,因此本例回复运行失败。Harness 把回复显示给你,这轮结束。
你看到的回答- 回答
- 运行失败:除零错误。
教学推演,未连接真实模型与 Shell。
这次往返中,模型被调用了两次:第一次提出工具请求,第二次读到执行结果后回答你。两次调用之间,Harness 完成了命令执行和结果保存。
每当模型请求工具,Harness 就执行工具、保存结果,再调用模型判断下一步。这种反复执行和判断的过程称为 Agent Loop。本例只需要执行一次;如果模型提出新的工具请求,Harness 就继续同样的过程。
工具请求怎样变成真正的执行
先看图中的第二、三步。两步里都有 python main.py,但第二步只是模型提出请求,第三步才真正执行命令。
模型返回的工具请求包含两项信息:工具名 bash,以及参数 command: "python main.py"。Harness 读取这份请求,找到对应的工具实现,再把命令传给它。因此,普通回复里写一句“你可以运行 python main.py”,并不会自动触发执行。
在 第一课的固定版本源码中,TOOLS 是提供给模型的工具说明,run_bash() 是 Harness 调用的执行函数。前者描述工具名称和参数,后者启动命令、收集输出并返回结果。
运行到 10 / 0 时,Python 产生除零错误。工具把错误返回给 Harness,图中的第四步就走完了。可是,模型不会自动收到这个结果:Harness 还需要把它加入对话记录,并发起第二次调用。
为什么第二次还要带上原问题?
既然已经拿到了 ZeroDivisionError,第二次调用只发送这一行错误,够不够?
试着换一下最初的要求,文件和错误都保持不变:
| 你最初说的话 | 看到同一个错误以后,该做什么 |
|---|---|
| 只报告结果 | 告诉你运行失败,发生除零错误 |
| 找到报错位置 | 继续查看能定位错误行的代码或输出 |
| 修好它,再运行一次 | 查清预期行为,修改代码并取得新的运行结果 |
错误相同,接下来该做的事却不同。除零错误说明这次运行发生了什么,原问题说明你希望助手做到哪一步。本例要求“只报告结果”,如实报告失败就可以了;看到错误并不意味着需要修改文件。
所以,Harness 在第二次调用时,会一起提供原问题、模型先前的工具请求,以及对应的执行结果。这些内容保存在对话记录 messages 中,工作约定和工具说明则随调用继续提供。模型再次判断时,就能同时看到你要它运行什么、它刚才请求了什么,以及那次请求得到了什么结果。
执行结果也要关联到发起它的请求。只有一条命令时,这个对应关系很直观;若一次请求了多个工具,就需要明确每份结果属于哪次调用。课程源码通过工具调用标识完成关联。
把这次往返缩成一段短代码,就可以停在结果写入记录的位置,观察 messages 的变化。
在代码里停住,看看结果何时写入记录
为了只观察这段循环,下面用 choose_action() 的固定规则代替模型判断,用直接计算 10 / 0 代替 Shell 运行文件。除零异常由 Python 真实产生,最终回答由规则返回。
代码可以直接运行,也可以保存后用 python3 执行。浏览器代码块使用 Pyodide 0.29.0;这个短例子只用 Python 3 标准语法,不读取 main.py。
def choose_action(messages):
# 教学用的固定规则,替代真实模型调用。
if messages[-1][0] == "tool":
return "answer", "运行失败:除零错误"
return "tool", "10 / 0"
messages = [("user", "运行 main.py,只报告结果")]
while True:
kind, value = choose_action(messages)
messages.append(("assistant", value))
if kind == "answer":
print(value)
break
try:
# 直接执行算式,替代启动 Shell 和读取文件。
result = str(10 / 0)
except ZeroDivisionError as error:
result = f"{type(error).__name__}: {error}"
messages.append(("tool", result))
print("记录条数:", len(messages))点击行号设置断点,再点“调试”;也可以直接运行。
先点“运行”,会看到两行输出:
记录条数: 3
运行失败:除零错误第一行在工具结果写入后输出:此时三条记录分别是用户问题、工具请求和执行结果。循环随后再次调用 choose_action(messages),固定规则读到工具结果,返回回答,才有第二行输出。
要看清这两个时刻,可以设置两个断点:
- 在
messages.append(("tool", result))这一行点击行号,再点“调试”。停住时,result已经保存除零错误,messages里还只有用户问题和工具请求。点一次“下一步”,结果才成为第三条记录。 - 再给
kind, value = choose_action(messages)设置断点。第二次停在这里时,上一轮的工具结果已经在记录中;继续执行,固定规则会返回回答,循环随后走到break。
调试时,高亮的是尚未执行的那一行;变量和输出来自这段代码真实执行轨迹的回放。
除零异常被 except 捕获后,变成了可以保存和读取的信息,所以循环能继续到第二次判断。回答也会写入记录,最终 messages 共有四条。你可以把这两次判断对照图中的第一次模型调用和第二次模型调用,观察执行结果怎样影响下一步。
运行失败了,这一轮却可以结束
短代码最后走到了 break,因为固定规则读到工具结果后返回了 answer。真实 Agent Loop 也需要一个停止条件:第一课的最小循环在模型响应中有工具调用时执行并回传,没有新的 tool_use 块时返回。
循环停止,只说明当前没有新的工具请求。是否完成任务,还要对照用户的原始要求和执行证据。本例要求“运行 main.py,只报告结果”,执行失败也是运行结果,助手如实报告错误,就回答了你的问题。
如果要求改成“修好并验证”,只报告错误显然还不够。模型可能需要继续请求查看代码、修改文件和重新运行;每次请求都要经过 Harness 执行,再把结果交回模型判断。
回看开头那句话,一次有依据的回答需要这几次交接完整发生:模型提出请求,Harness 实际执行,结果进入对话,模型再根据结果和原问题回答。读 agent_loop() 时,可以沿着这条顺序追踪模型调用、run_bash() 和工具结果的保存位置。
图中的执行过程是教学推演,短代码验证的是固定规则下的记录和循环。要把这些交接接到真实程序上,可以继续读实践|写出第一个可控的循环。实践篇沿用 main.py,补上模型接口、工具结果和停止控制;想先理解后续思想,也可以之后再回来动手。