书名:Harness工程实战 从零开始驾驭AI软件工程
ISBN:978-7-115-70180-0
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
著 张建飞
责任编辑 卜一凡
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
本书基于AI技术对软件开发范式的颠覆性影响,系统构建了Harness工程理论体系与实践框架。全书共分为4部分:第一部分梳理Agent基础认知,包括Function Calling(函数调用)、MCP标准及Coding Agent等核心概念;第二部分阐述Harness工程理论,包括Harness工程的缘起、演进(从Prompt工程到Context工程再到Harness工程),以及Harness工程方法论;第三部分提供Harness工程实践,包括通过经典微服务、全新“绿地项目”、遗留“棕地项目”等场景,提供涵盖测试、环境管理、持续交付的Harness工程实战指南;第四部分展望未来人类工程师的新定位与软件工程的新形态。
本书适合正在使用或计划使用AI进行辅助开发的软件工程师、技术领导者/架构师、技术管理者/CTO,以及对AI时代软件工程变革感兴趣的研究者与教育者阅读。
我们正站在一个历史性的拐点上。
过去20年,我们历经了敏捷开发、DevOps、云原生的浪潮,每一次跃迁都伴随着工程方法的迭代。而今天,AI带来的不仅仅是工具的升级,更是工作范式的颠覆。
在本书中,作者敏锐地捕捉到:软件工程的下一个十年,不再是如何写出更好的代码,而是如何为AI设好缰绳、构建闭环、实现可控演进。
我深感这本书与华为云在AI原生(AI Native)领域的战略思考高度同频。
在华为云,我们相信:真正的AI原生,不是单点能力的堆砌,而是基础设施、开发工具与工程方法的体系化重塑。为此,华为云打造了面向AI时代的软件工程飞轮。
• 以ModelArts模型训练平台为基座,提供高效、可信、可复现的模型精调与持续学习能力,让行业知识沉淀为可迭代的数字资产。
• 以模型即服务(Model as a Service,MaaS)为枢纽,打通顶尖模型与业务场景的“最后一公里”,让算力与智能按需调用、即插即用。
• 以AgentArts智能体平台为中枢,支持多智能体编排、工程化部署、权限治理与持续演进,让复杂系统的构建从“手工作坊”走向“自动化产线”。
• 更以CodeArts代码智能体为核心,将AI深度嵌入需求分析、架构设计、代码生成、测试验证与运维反馈的全链路,实现开发者与智能体的无缝协同。
这四者并非孤立产品,而是一套面向AI原生的工程基础设施。它们不替代开发者,而是拓宽开发者的架构思维与创新边界。
技术浪潮奔涌向前,唯有以工程化思维驾驭变革,方能行稳致远。愿这本书成为每一位开发者、架构师与技术决策者的“航海图”。华为云将持续深耕AI原生基础设施,坚持开放生态、共建标准、赋能一线,与产业同人一道,让智能真正融入软件的每一行代码、每一次迭代、每一场创新。
未来已来,驾驭者胜。
华为云CEO 周跃峰
2026年7月
近年来,大语言模型(Large Language Model,LLM)(以下简称“大模型”)正以不可阻挡之势冲击着各行各业。其中遭受冲击最猛烈的,可能恰恰是软件行业。
OpenAI宣称数百万行代码级别的项目已能完全交由AI生成;硅谷科技巨头开始批量取消传统研发岗位,裁员潮席卷而来。“程序员即将失业”的论调甚嚣尘上,制造了前所未有的焦虑。
作为一名在软件行业摸爬滚打多年的“老兵”,我想在开篇先递上一颗“定心丸”:AI或许能取代低端“码农”,但高阶的软件工程师绝不会消失。
大模型能大幅降低软件的偶然复杂度——很好地完成查阅文档、修正语法错误、编写CRUD(Create、Retrieve、Update、Delete,创建、查询、更新、删除)模板这些体力活,但它无法独自跨越软件的本质复杂度——核心业务逻辑、领域建模、边界划分,以及在利益冲突中进行战略权衡。
布鲁克斯(Brooks)在《人月神话》一书中提出的“没有银弹”的铁律(指的是没有任何一种单一的技术或管理能够独立地显著提高软件的生产率、可靠性和简洁性),在AI时代依然有效。AI是一把好剑,但不是另一颗“银弹”。
我们在享受AI高速发展带来的好处的同时,也在制造大量缺乏设计、维护困难的代码,欠下高昂的“认知债”与“技术债”。对需要持续演进数年的复杂系统来说,无序的代码熵增终将演化为灾难。
但只要沉下心来,回归软件工程的理性底色,你就会发现——AI时代的软件工程与过去半个世纪并无本质不同,因为软件工程的终极使命从来不是“写代码”,而是“管理复杂度”。
软件工程的五大基石并未发生巨大变化,便是铁证。
• 设计美学没变:分而治之、模块化、信息隐蔽等设计思想,目的都是控制系统复杂度。在AI时代,总体的设计美学没有变,只是落地形式发生了变化——通过Context工程(Context Engineering)来精简信息,适配AI有限的上下文窗口。
• 架构定义没变:结构严密、职责明确的架构,本身就是AI友好的架构。COLA(Clean Object-oriented and Layered Architecture,整洁面向对象分层架构)等在AI时代展现出更强的生命力。
• 规范要求没变:传统代码规范旨在降低人的认知成本。AI时代更需要规范,只是换成了提示词(Prompt)编写规则与技能集。
• 审查作用没变:代码审查依然是质量闸门。以前的审查主要是为了规避人的疏忽造成的问题,现在的审查还须关注大模型的幻觉。
• 质量追求没变:测试依然是保障产品质量的关键。只不过,以前测试用例、测试代码多由人工编写,现在可由AI生成;而测试环境难题,今天依然需要攻克。
将上文提到的变化用当下最精准的概念来概括,就是Harness工程(Harness Engineering)。
“harness”一词本意为马具。在Harness工程中,它的核心寓意是:在人机协同的研发生态下,重构软件工程方法论,让开发者从被动的编码者(Coder),转变为攥紧缰绳、驾驭AI的系统构建者(Builder)。以前系统复杂度失控是因为人本身的能力不足,现在系统复杂度失控则主要是因为人驾驭AI的能力不足。
技术外壳日新月异,但底层的软件精神从未动摇——那是我在《代码精进之路:从码农到工匠》中追求的工匠精神,也是我在《程序员的底层思维》中淬炼的思维模型。
我写这本书的初衷,不是追逐时髦或吹捧概念,而是希望把传统软件工程的精髓融入AI的语境,让它们重放光彩,帮助每一位同行在时代浪潮中实现华丽转身。
本书面向正在使用或计划使用AI进行辅助开发的软件工程师、技术领导者/架构师和技术管理者/ CTO,以及对AI时代软件工程变革感兴趣的研究者与教育者。
• 软件工程师:已在使用Coding Agent,但觉得代码质量不稳定,希望系统化提升AI协作效率。
• 技术领导者/架构师:关注团队工程效率,担心技术债问题,希望建立组织级AI开发规范。
• 技术管理者/ CTO:评估AI驱动开发的回报,识别其中的机遇、风险和投资方向。
• 研究者与教育者:希望关注AI时代软件工程的方法论变革。
本书假定读者具备基础开发经验,了解常规软件工程实践(版本控制、持续集成、单元测试等)。但读者不需要具备AI或机器学习背景——我们关注如何使用模型,而非如何训练模型。
全书分为4部分,共11章,将带领你完成从“码农”到“Harness工程师”的进阶。
第一部分:Agent基础认知(第1~3章)
在挥动缰绳之前,你必须先读懂“马”。我将深入剖析Agent的底层逻辑:从Function Calling(函数调用)这一行动之源讲起,帮你理解AI如何与物理世界“握手”;接着武装你的知识库,帮你掌握MCP、Skills与SubAgent协同作战的艺术;最后将目光聚焦于Coding Agent,讨论新一代的AI研发利器如何重塑软件开发的未来。
第二部分:Harness工程理论(第4~6章)
当基础打牢后,我将引入本书的核心思想——Harness工程。你将了解这一工程学的缘起,理解它为何是软件工程范式跃迁的结果,并掌握一套完整的方法论。在这里,我们将把氛围编程(Vibe Coding)转变为严谨的工程实践,探讨如何设计接口、管理上下文,以及构建容错机制。
第三部分:Harness工程实践(第7~10章)
理论只有落地才能释放出更大的价值。在这一部分,我们将进入本书的主战场。无论你是在搭建经典的微服务,还是在全新的“绿地项目”中“开垦”,抑或是在面对令人头疼的遗留“棕地项目”,本书都将提供具体的战术指导。我将教你如何构建坚固的测试、环境与交付堡垒,确保AI系统在生产环境中依然“温顺”可控。
第四部分:未来展望与挑战(第11章)
在全书的最后,我会将目光投向更远的未来,探讨随着技术演进,软件工程将面临的全新挑战与机遇:AI的终点在哪里?人类工程师的角色将如何演变?
如果时间有限,可以只阅读第4、6、8、9、10章,快速建立对Harness工程的整体认知。
• 运行代码辅助阅读:本书配套仓库(https://github.com/significant- frank/HarnessEngineering)收录了书中出现过的所有代码示例,建议你亲自运行和修改。
• 边读边实践:建议你每读完一章,尝试在你当前的项目中落地一项实践。
• 带着批判性思维去思考:书中的方法和工具是我在特定场景下的经验总结,请根据实际情况调整。
• 关注原则而非工具:AI工具演进速度很快,因此本书力求聚焦于不依赖特定工具的原则和方法。
感谢我的团队(张静、傅斌杰、李川、孙志磊等)。两年来,我们一起踩坑、复盘、迭代,才形成了今天这套方法论。特别感谢敢于在生产项目中“第一个吃螃蟹”的工程师。
感谢所有开源社区和AI工具背后的开发者。
感谢人民邮电出版社优秀的编辑团队。
感谢我的家人——在我沉迷于“与AI对话”的无数个深夜,你们的理解和支持是我坚持下去的动力。
张建飞
2026年4月于杭州
第一部分 Agent基础认知如果说传统AI是一本只会按目录回答问题的“问答手册”,那么Agent[1]则是一位能感知环境、自主决策并熟练使用工具的“数字员工”。它不再被动等待你的下一行提示词,而是主动理解你的架构意图,调用你定义的API,甚至在代码报错时展现出近乎直觉的自我反思与修正能力。
[1] Agent指能感知并行动的广义实体(含规则脚本);AI Agent特指以大模型为大脑,具备推理、规划与工具调用能力的智能体,是Agent在AI时代的高级形态。为了表述简洁,本书所提到的Agent均指AI Agent。
本章将带你打破对话框的“囚笼”,从最基础的Function Calling出发,逐步深入理解Agent的核心运作机制——Agent Loop与ReAct框架,最终引导你亲手构建一个具备读写文件与执行命令能力的简易版编程助手。
Agent是一种具备环境感知、自主规划与任务执行能力的智能系统。与基于固定规则的标准作业程序(Standard Operating Procedure,SOP)不同,传统自动化是逻辑预设的(如if-then条件分支),而Agent是目标驱动的,不再死守预设的步骤。
一个完整的Agent系统需构建以下支撑体系。
• LLM:相当于Agent的“大脑”,通过其强大的语义理解、推理与泛化能力,让Agent在动态环境下自主规划路径、调用工具并实时修正策略。
• 感知模块:相当于Agent的“感官”,通过API、实时检索,与文件系统接入多维外部信息。
• 工具集:相当于Agent的“四肢”,通过Function Calling与MCP,将逻辑影响力延伸至真实软件环境。
• 记忆模块:相当于Agent的“经验库”,利用向量数据库或记忆流(Memory Stream)维护上下文连贯性,支撑长周期的决策规划。
简单来说,Function Calling并非让LLM直接运行代码,而是让LLM能够根据用户意图,结构化地选择工具并生成调用参数。它让LLM从“文本生成”走向“逻辑执行”。
在没有Function Calling之前,AI只能在已有的训练数据中“脑补”答案(即大模型的“幻觉”)。通过该接口,我们实现了交互范式的升维。相较于传统API调用,Function Calling提供了更大的灵活性,两者的对比如表1-1所示。
表1-1 传统API调用与Function Calling的对比
|
维度 |
传统API调用 |
Function Calling |
|---|---|---|
|
执行逻辑 |
如 |
根据上下文对话按需选择工具 |
|
参数感知 |
依赖严格格式,正则解析困难 |
语义提取,能从模糊指令中提炼参数 |
|
异常恢复 |
接口报错则程序崩溃 |
模型根据报错信息反思并修正参数重试 |
|
扩展成本 |
每添加一个功能都要改主进程 |
仅需注册工具Schema,模型无缝接入 |
构建Agent的第一步是选择一个在服从性与逻辑性之间取得平衡的大模型。目前,DeepSeek凭借其对工程逻辑的深度理解和极高的Token性价比,成为开发者构建Agent的首选。
我们需要在DeepSeek开放平台完成注册,并获取API密钥(API Key)。这将支撑我们完成前期的基础代码生成与简单的Agent任务。具体步骤如下。
(1)访问DeepSeek开放平台(platform.deepseek.com)。
(2)完成账号注册并进入控制台。
(3)在“API Keys”页面创建一个新的密钥,并妥善保存。
以下是“Hello World”程序,我们可以先通过简单的Python脚本验证环境是否就绪。注意,为了适应某些网络环境,我们使用了httpx库并暂时禁用了SSL证书验证。
import os
import httpx
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
http_client=httpx.Client(verify=False) # 禁用SSL证书验证
# (仅用于测试)
)
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role":"system", "content":"You are a helpful
assistant"},
{"role":"user", "content":"Hello"},
],
max_tokens=1024,
temperature=0.7,
stream=False # 关闭流式输出(逐步返回生成的文本),简化处理逻辑
)
print(response.choices[0].message.content)
注意,DEEPSEEK_API_KEY需放在当前目录下的.env文件中,请读者自行配置。运行脚本(输入指令python deepseek.py),如果看到如下输出,则说明已经可以正常调用大模型了。
Hello! How can I assist you today?
如果没有Function Calling,那么当你问AI“北京今天天气怎么样?”时,它可能会回答训练数据中的旧信息。而有了Function Calling后,交互流程就会包含如下4个环节。
• 意图识别:LLM判断出用户需要实时数据。
• 工具检索:LLM从你提供的tools列表中找到get_weather函数。
• 参数映射: LLM将“北京”映射为函数参数{"city": "Bei jing"}。
• 执行闭环:外部系统运行函数,将结果传回,LLM润色后输出人类可读的回答。
实现该流程的代码示例如下。
import os
import json
import httpx
import requests
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
http_client=httpx.Client(verify=False),
)
tools = [
{
"type":"function",
"function":{
"name":"get_weather",
"description":"获取指定城市的天气信息",
"parameters":{
"type":"object",
"properties":{
"city":{
"type":"string",
"description":"城市名称,支持中文或英文",
}
},
"required":["city"],
},
},
}
]
def get_weather(city):
url = f"https://wttr.in/{city}?format=3&lang=zh-cn"
try:
response = requests.get(url)
response.raise_for_status()
return response.text.strip()
except Exception as e:
return f"获取天气信息失败:{e}"
def run(messages):
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools,
tool_choice="auto",
)
message = response.choices[0].message
if message.tool_calls:
tool_call = message.tool_calls[0]
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
print(f"模型调用函数:{fn_name}({fn_args})")
if fn_name == "get_weather":
result = get_weather(**fn_args)
else:
result = f"未知函数:{fn_name}"
messages.append(message)
messages.append(
{
"role":"tool",
"tool_call_id":tool_call.id,
"content":result,
}
)
final_response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
)
return final_response.choices[0].message.content
return message.content
if __name__ == "__main__":
messages = [
{"role":"system", "content":"你是一个有用的助手,可以查询天气信息。"},
{"role":"user", "content":"北京今天天气怎么样?"},
]
answer = run(messages)
print(answer)运行上述代码,由于使用了Function Calling,我们可以获得北京实时的天气情况。LLM除了返回天气信息,还给出了出行建议,具体如下。
根据查询结果,北京今天的天气情况如下。 -**温度**:+15℃ -**天气状况**:晴朗 北京今天天气不错,温度适宜,适合外出活动。建议您根据个人体感适当增减衣物。
Function Calling赋予了Agent“伸手”的能力,但要让它在复杂任务中“走得稳、不迷路”,我们还需要Agent Loop。
在1.2.3节的示例中,Function Calling虽然可以访问工具,但只能进行一次对话。然而,现实中的任务很少像查询天气这样,一次调用就能完成。因此,我们需要引入Agent Loop,通过循环机制来处理复杂的用户任务。
Agent Loop(Agent循环)可以将复杂目标拆解为可追踪的执行步骤:每次完成动作后,会实时捕获工具返回的结果,让模型进行“反思”(Reflection),并据此动态调整后续路径。这种循环使Agent变成了“目标驱动型的执行引擎”。
Agent Loop是Agent(如CodeArts、Claude、Codex、OpenCode、OpenClaw等)必须具备的核心运作机制。
下面让我们通过一个支持“乘法”与“除法”的计算器Agent示例,来一步步解释Agent Loop是如何运作的。其核心逻辑在于通过XML标签(如<tool>)来控制执行流。Agent Loop本质上是一个循环,其执行逻辑的伪代码如下。
while iteration < max_iterations:
# 1. 发送包含历史记录的messages给LLM
response = call_model(messages)
# 2. 检查AI是否要求使用工具
if tool_call := parse_tool(response):
# 执行工具并获得观察值
observation = execute(tool_call)
# 将工具结果回传给历史记录,准备下一次循环
messages.append({"role":"user", "content":
f"<observation>{observation}</observation>"})
# 3. 检查是否有最终答案
elif final_answer := parse_final(response):
return final_answer完整代码请参阅本书配套GitHub仓库中的源码chapter1/calculator_agent.py。Agent Loop的执行逻辑如图1-1所示。

图1-1 Agent Loop的执行逻辑
我们以算式123×45÷23为例,了解Agent经历的3次“意识觉醒”——每一次模型调用,都是一次从“无知”到“有知”的跃迁。
第一阶段:意图识别——“我需要先做什么?”
Agent接收到用户的指令后,并不急于给出一个可能错误的答案,而是首先进行意图识别。
当第一次调用模型时,Agent将包含System Prompt(系统提示词)和工具定义的上下文发送给LLM。Agent发送给LLM的内容如下。
[
{
"role":"system",
"content":" 当用户问计算相关问题……<工具使用说明>"
},
{
"role":"user",
"content":"123*45/23"
}
]LLM的思维黑盒如下。
“用户输入了一个混合运算表达式。根据四则运算规则(先乘除后加减,从左至右),我必须优先处理最左侧的乘法部分。我不能直接算出最终结果,我需要调用multiply工具。”
LLM输出的决策是<tool>multiply(123,45)</tool>。
第二阶段:环境反馈与状态衔接——“乘法已完成,接下来处理除法。”
在此阶段,Agent会在本地拦截multiply指令,运行Python函数计算出5535.0。紧接着,Agent将这一环境反馈作为新的用户消息,连同之前的对话历史,再次“回传”给LLM。
当第二次调用模型时,Agent发送给LLM的内容如下。
[
{
"role":"system",
"content":" 当用户问计算相关问题……<工具使用说明>"
},
{
"role":"user",
"content":"123*45/23"
},
{
"role":"assistant",
"content":"<tool>multiply(123,45)</tool>"
},
{
"role":"user",
"content":"<tool_output>5535.0</tool_output>"
}
]LLM的思维黑盒如下。
“我回顾了一下历史记录。刚才我请求了乘法,现在系统告诉我结果是5535.0。很好,原算式123*45/23现在简化为5535/23。为了得到最终答案,我需要进行下一步:除法运算。”
LLM输出的决策是<tool>divide(5535,23)</tool>。
第三阶段:任务收敛与最终交付——“所有步骤都已完成,生成最终报告。”
随着divide指令的发出,Agent再次介入执行,得到结果240.652。当这个结果被送回LLM时,闭环即将完成。
当第三次调用模型时,Agent发送给LLM的内容如下。
[
{
"role":"system",
"content":" 当用户问计算相关问题……<工具使用说明>"
},
{
"role":"user",
"content":"123*45/23"
},
{
"role":"assistant",
"content":"<tool>multiply(123,45)</tool>"
},
{
"role":"user",
"content":"<tool_output>5535.0</tool_output>"
},
{
"role":"assistant",
"content":"<tool>divide(5535.0,23)</tool>"
},
{
"role":"user",
"content":"<tool_output>240.652</tool_output>"
}
]LLM的思维黑盒如下。
“现在的上下文非常完整。乘法做了,除法也做了,最后的结果是240.652。我不再需要调用任何工具,任务链条已闭合。现在,我可以向用户汇报最终结果了。”
LLM输出的最终决策是<final_result>240.652</final_result>。
通过这3次交互,Agent成功地将一个数学问题拆解为“思考—行动—观察”的循环。
LLM之所以识别到<final_result>就知道任务已经完成,是因为我们在System Prompt中使用了如下提示词,其中<final_result>明确标识了最终答案。
# 工具使用说明
tool_use = """
当用户询问计算相关话题时,若有必要可以使用工具,每个工具都是函数。
使用工具的方式为输出 "<tool>使用工具指令</tool>"。
使用工具后,你要等待回传结果 "<tool_output>工具回传的结果
</tool_output>"。
最后,输出最终结果"<final_result>最终答案</final_result>"
可用工具:
multiply(a,b):返回 a 乘 b
divide(a,b):返回 a 除以 b
"""而Agent之所以知道如何结束任务,是因为我们在Agent Loop中编写了如下代码。通过搜索<final_result>,可以判断任务是否结束。
# 检查是否有最终结果
final_match = re.search(r'<final_result>([^<]*)</final_result>', assistant_content)
if final_match:
final_answer = final_match.group(1).strip()
print(f"\n最终答案:{final_answer}")这虽然是一个简单的示例,却展示出了Agent与LLM的基本交互模式。本案例使用XML形式来标识工具调用,你也可以使用JSON形式。
本节中的案例揭示了一个事实:LLM没有任何“记忆”。每一次调用模型时,LLM看到的都是一个全新的上下文窗口。Agent维护这个窗口的完整性——确保每一次调用模型时,窗口中都涵盖了到目前为止发生的所有事情。
这种设计既有代价,又有收益。代价是每次都要重复发送历史对话,Token消耗呈线性增长;收益则是只要历史对话还在上下文窗口里,就能随时回溯之前的每一步操作。
细心的读者可能已经发现,1.3节的计算器Agent中已经隐含着ReAct(Reasoning and Acting,先推理后行动)的基本范式,当LLM输出<tool>multiply(123,45)</tool>时,它完成了“思考”(识别出需要先算乘法);当Agent返回<tool_output>后,LLM再次输出<tool>divide(5535,23)</tool>,这正是ReAct中“观察后重新思考”的体现。
然而,该实现仍缺失关键一环:我们从未让LLM显式输出思考过程。而ReAct框架的核心贡献就是将推理过程暴露出来,让Agent的决策可解释、可调试。
本节将正式介绍ReAct框架(以下简称ReAct),并在1.3节示例的基础上,将其真实的“思考”过程显式输出。
ReAct的思想源自经典论文“ReAct: Synergizing Reasoning and Acting in Language Models”(Yao et al., 2022)。该论文指出,人类在解决复杂问题时,并非直接行动,而是在大脑中搭建一个“内心小剧场”(Inner Speech)进行推演。
想象一下你正在修理一台不熟悉的设备。
“屏幕不亮,但电源灯是亮的。可能是背光模组坏了。先拆开后盖,检查排线。排线没有松动。那可能是驱动板的问题,用万用表测一下电压。电压正常。难道是屏幕本身坏了?”
在这个过程中,你并非机械地执行“拆后盖→查排线→测电压”的固定步骤,而是在每一轮行动之后推理,根据观察结果动态调整下一步计划。这种“推理—行动—观察—再推理”的循环,正是人类解决复杂问题的核心机制。
ReAct的核心贡献是将这种“内心小剧场”显式引入LLM的推理过程。传统思维链(Chain-of-Thought,CoT)只进行推理,不与外部环境交互;传统仅行动模式(Act-Only)只执行动作,不进行显式推理。ReAct将两者结合,使LLM在生成推理轨迹的同时,通过调用工具与外部世界交互。
ReAct将Agent的思维过程从“文字创作”约束为标准化的逻辑循环。在该循环中,思考(Thought)、行动(Action)、观察(Observation)构成三位一体的逻辑链。
• 思考:深度思考。Agent暂停执行,开始思考两个问题。
① 进度对齐:根据已有执行记录,当前进展到了哪一步?
② 差距分析:为了达成目标,最欠缺什么信息?
• 行动:精准执行。这是Agent连接现实世界的唯一方式。基于思考结论,从工具箱中调用具体指令。行动必须满足两个要求。
① 确定性:必须是具体的、可参数化的命令(如read_file、run_test),严禁模糊意图。
② 最小化:倾向于执行“最小原子操作”,以便出错时快速回溯。
• 观察:环境反馈。这是Agent的“数字触觉”,也是消除幻觉的关键。观察具有两重价值。
① 客观真实:反馈来自编译器报错、测试失败日志或文件系统的真实状态,而非模型推测。
② 输入转化:模型接收原始反馈后,将其转化为下一轮思考的关键变量。
三者形成闭环:思考决定行动,行动产生观察,观察驱动下一轮思考。这个循环持续进行,直至任务完成或人工中断。
为了直观理解ReAct的价值,我们用一个传统模型几乎无法完成的任务作对比:获取训练数据截止日期之后的最新信息。假设用户提出:“总结iPhone 16的3项核心技术革新。”在非ReAct模式和ReAct模式下,LLM将输出截然不同的结果。
在纯文本生成逻辑下,模型仅依赖内部权重(Internal Weight)进行预测。由于无法感知外部环境,模型为维持对话连贯性,会基于iPhone 14或iPhone 15的信息进行推测,从而产生如“搭载A17 Pro芯片”(实际为A18芯片)或“潜望式长焦全面普及”等的幻觉。其缺陷在于模型被困在知识的“时间胶囊”里,但它不知道自己“不知道”。
开启ReAct模式后,Agent能将任务拆解为多轮实验。
(1)思考1(识别盲区):用户需要最新机型信息,当前知识库存在时滞风险。决策:必须调用搜索工具获取一手资料。
(2)行动1(主动干预):Search["iPhone 16官方发布会参数"]。
(3)观察1(接收反馈):获取关于“相机控制按键”和“A18芯片”的零散信息。
(4)思考2(评估缺口):已知有新按键和新芯片,但“第三项”核心技术革新尚不明确(是屏幕刷新率还是电池?)。决策:避开二手新闻,直接深入阅读官方技术文档。
(5)行动2(精准深度读取):Read_Page["Apple官网iPhone 16详情页"]。
(6)观察2(锁定事实):确认基于生成式AI的“Apple Intelligence”是软硬件结合的重头戏。
(7)最终答案:基于上述观察结果,给出精准总结。
通过这个案例,我们可以清晰地看到:思考为行动提供方向,行动为推理提供依据,观察为认知提供反馈。三者构成的闭环使Agent能够在不确定的信息环境中持续挖掘真相,而不是在幻觉的泥潭中越陷越深。
本节将基于ReAct框架,使用Python从零开始构建一个具备读写本地文件与执行终端命令能力的简易版编程助手。
Agent的所有理智与行为边界,都源于其System Prompt。
为了让LLM摆脱纯文本生成的惯性,我们需要定义一套严格的XML协议,强制模型遵循“思考—行动—观察”的循环来输出。以下System Prompt声明了Agent的ReAct模式,以及Agent与LLM之间的通信协议。
react_system_prompt_template = """
你是一个具备文件操作能力的Coding Agent。请按以下流程解决问题。
1. <thought>:分析当前环境,复盘已完成的步骤,计划下一步。
2. <action>:从工具箱中选择工具并执行。格式:tool_name("arg1", "arg2")
3. <observation>:观察工具执行返回的真实结果。
4. <final_answer>:所有任务完成后,向用户提交最终结果。
可用工具:
${tool_list}
当前环境:
操作系统:${operating_system} | 目录:${working_directory}
目录下文件:${file_list}
"""上述提示词构建了一个结构化的交互闭环。
• 强制思考:要求模型在执行任何具体操作前,必须先进行逻辑推演。这不仅能有效减少幻觉,还能让开发者清晰地看到AI的决策路径,极大地提升了系统的透明度与可调试性。
• 标准化行动:通过规定tool_name("arg")的格式,将自然语言转化为机器可解析的指令,为后续Python代码解析并执行真实的系统命令奠定了基础。
• 环境感知:在提示词中动态注入当前的操作系统、工作目录及文件列表,让Agent能根据真实的物理环境做出准确判断。
ReActAgent类是访问LLM并利用ReAct循环处理函数调用的类。其核心执行流程包含以下4个步骤。
(1)初始化:加载DeepSeek API等底层大模型服务。
(2)渲染:将当前目录下的真实文件列表动态注入提示词,让Agent具备环境感知能力。
(3)循环:实时监测LLM的输出,一旦检测到<action>标签,就利用Python内置函数getattr或函数映射机制执行本地工具。
(4)解析:精准处理多行字符串转义,确保模型写入的代码格式完全正确。
主程序的文件名是simple_claude_agent.py,它通过Python函数为Agent定义了以下本地工具。
• read_file(path):使Agent具备读取现有代码库的能力。
• write_to_file(path,content):使Agent具备编写文件(编码)的能力。
• run_terminal_command(cmd):赋予Agent在Shell中运行代码、安装依赖的最高权限。
这些Function Calling核心功能的完整实现代码如下。
import os
import click
from react_agent import ReActAgent
def read_file(file_path):
"""用于读取文件内容"""
with open(file_path, "r", encoding="utf-8") as f:
return f.read()
def write_to_file(file_path, content):
"""将指定内容写入指定文件"""
with open(file_path, "w", encoding="utf-8") as f:
f.write(content.replace("\\n", "\n"))
return "写入成功"
def run_terminal_command(command):
"""用于执行终端命令"""
import subprocess
run_result = subprocess.run(command, shell=True, capture_output=True, text=True)
return "执行成功" if run_result.returncode == 0 else run_result.stderr
@click.command()
@click.argument('project_directory',
type=click.Path(file_okay=False, dir_okay= True))
def main(project_directory):
# 确保项目目录存在,如果不存在则创建
if not os.path.exists(project_directory):
os.makedirs(project_directory)
project_dir = os.path.abspath(project_directory)
tools = [read_file, write_to_file, run_terminal_ommand]
agent = ReActAgent(tools=tools, model="deepseek-chat", project_directory= project_dir)
task = input("请输入任务:")
final_answer = agent.run(task)
print(f"\n\n Final Answer:{final_answer}")
if __name__ == "__main__":
main()在这段代码中,我们不仅完成了工具的注册,还通过click库初始化了项目目录。
接下来,我们通过命令行正式启动我们亲手打造的简易版编程助手,见证其从零生成一个完整的贪吃蛇游戏的过程。
首先,我们在终端启动Agent。
# 设置API密钥(如果还没设置) export DEEPSEEK_API_KEY="your-api-key" # 开启调试模式运行(可以看到每次请求的详细内容) python simple_claude_agent.py ~/Desktop/code/temp/ --debug
紧接着,在控制界面输入任务指令。
请输入任务:写一个贪吃蛇游戏,使用HTML、CSS和JS实现,代码分别放在不同的文件中。
按“回车键”后,终端将清晰展示Agent的完整思考与执行链路。
思考:用户需要我写一个贪吃蛇游戏,包含三个独立文件。我需要先检查目录下是否已有相关文件,避免重复生成。
执行:run_terminal_command("ls")
观察:index.html style.css script.js
思考:发现已存在三个文件。我应该先读取它们,检查其是否已经完整实现了贪吃蛇游戏。
执行:read_file("/Users/xxx/Desktop/code/temp/index.html")
观察:<!DOCTYPE html>……(文件内容)
思考:index.html 已经包含了游戏结构。继续检查CSS和JS文件……
(略)
思考:所有三个文件都已存在且功能完整。无需重复生成。
最终答案:贪吃蛇游戏已经完整实现,用户可以使用方向键控制蛇的移动……如果在空目录中运行,Agent执行ls发现目录为空后,会直接调用write_to_file依次生成index.html、style.css和script.js这3个文件。打开生成的index.html,即可在浏览器中运行完整的贪吃蛇游戏(见图1-2)。
值得注意的是,由于LLM生成的结果具有随机性,如果你多次运行该任务,会发现每次生成的页面风格,甚至代码实现细节都会有所不同。

图1-2 生成的贪吃蛇游戏
为了进一步验证Agent的智能程度,我们来进行一个有趣的对照实验。在贪吃蛇游戏的代码已经完整生成的情况下,我们在相同的目录下再次运行完全相同的任务指令。
传统的自动化脚本会无差别地覆盖已有的代码文件。然而,我们编写的Agent表现出了令人惊喜的“自我意识”,它默默进行了如下思考。
“用户要求创建贪吃蛇游戏。但在当前环境中,我感知到script.js已经存在。我需要先执行read_file检查它的功能是否完备,而不是盲目重写。”
这种不需要硬编码就能实现的动态逻辑判断,正是Agent独有的“柔性处理”能力。它不再是一个只会执行命令的机器,而是像一个经验丰富的程序员一样,先观察环境现状,再决定下一步动作。
该实验包含了编程助手的全部核心要素:通过ls命令感知环境(观察),判断文件是否存在(思考),以及决定是读取已有文件还是生成新文件(行动),然后执行读写操作,最终交付可运行的游戏。整个循环透明可见,每一轮的思考、行动、观察都被输出,这让Agent的行为不再是“黑盒魔法”,而是成为可追踪、可理解、可调试的工程过程。这正是ReAct的魅力所在。
本章完成了一场从思维到实践的深度重构。
思考、行动、观察为Agent注入了一个动态的“内心小剧场”。这种将推理与行动紧密耦合的模式,已成为解决复杂工程问题、实现柔性决策的标准范式。
通过从零开始构建简易版编程助手,我们深刻体会到了传统编程与AI编程的本质区别。
• 传统编程(硬编码驱动):需要预定义所有可能的分支,用if-else逻辑去定义现实边界。
• AI编程(目标驱动):只需赋予LLM清晰的目标、基础的信息与得力的工具,它便能在动态环境中自主感知、试错并自我修正,直至达成目标。
这种从“规定每一步怎么走”到“只需告知要去哪里”的范式跃迁,是Agent带给软件工程领域最激动人心的成果。接下来,我们将继续深入探索,看看如何让Agent拥有更强大的记忆能力并为其配备更精密的工具,使其能更轻松地攻克复杂业务难题。