☰
WorkBuddy_AI Harness架构解析1_什么是AI_Harness
2026/10/15 3:41:46 网站建设 项目流程

什么是AI Harness

摘要

在人工智能领域,我们经常听到"智能体(Agent)"这个词。但智能体究竟是什么?一个常见的公式是:智能体 = 模型 + Harness。模型是大脑,而Harness则是让这个大脑能够"行动"的支架与基础设施。本文将深入探讨AI Harness的概念、组成、工作原理,并通过完整的代码示例展示如何从零构建一个简易的Harness,帮助读者真正理解这一关键概念。

引言:从一个公式说起

当我们谈论GPT-4、Claude、Llama这些大语言模型(LLM)时,我们往往把它们等同于"AI"。但实际上,一个裸的模型本身并不能做太多事情——它只能接受输入文本,输出文本。它不能读取文件、不能调用API、不能记住对话、不能执行代码、不能访问互联网。

那么,为什么ChatGPT可以帮我们查天气、写代码、分析文档?为什么Claude Code可以修改我们的代码库?

答案就在于:模型外面包裹着一层"支架",这层支架就是Harness。

智能体(Agent) = 模型(Model) + Harness
  • 模型:提供推理、理解、生成能力(“思考”)
  • Harness:提供工具调用、记忆管理、循环控制、上下文组装等能力(“行动”)

Harness 这个词本意是"马具"——它不提供马力,但让马的力量能够被驾驭、被导向具体的目标。同样,AI Harness 不提供智能,但它让模型的智能能够作用于真实世界。

正文

一、为什么模型需要 Harness?

1.1 裸模型的能力边界

让我们先看看一个裸模型能做什么、不能做什么。

裸模型能做的:

  • 接收一段文本(prompt)
  • 输出一段文本(completion)

裸模型不能做的:

  • 记住上一次对话(无状态)
  • 调用外部工具(无手无脚)
  • 执行多步骤任务(无循环)
  • 访问实时信息(知识截止)
  • 操作文件系统、数据库、浏览器
1.2 一个直观的例子

假设你问裸模型:“北京现在天气怎么样?”

裸模型只能根据训练数据回答:“我无法获取实时天气信息。” 因为它没有联网能力。

但如果有一个 Harness,它会这样做:

1. 接收用户问题 2. 让模型判断:需要调用天气API 3. Harness 执行 API 调用,拿到结果 4. 把结果塞回给模型 5. 模型生成自然语言回答

这就是 Harness 的价值——它是模型与外部世界之间的桥梁。

二、AI Harness 的核心组成

一个完整的 AI Harness 通常包含以下几个核心模块:

模块职责
循环控制器(Loop Controller)驱动"思考-行动-观察"的循环
工具注册表(Tool Registry)管理模型可调用的工具
上下文管理器(Context Manager)组装 prompt、管理对话历史
记忆系统(Memory)短期/长期记忆存储与检索
解析器(Parser)解析模型输出的工具调用指令
执行器(Executor)实际执行工具并返回结果
错误处理(Error Handler)处理工具失败、重试、回退

下面这张流程图展示了典型的 Harness 工作循环:

┌─────────────────────────────────────────┐ │ 用户输入 │ └──────────────┬──────────────────────────┘ ▼ ┌───────────────┐ │ 组装 Prompt │◄──────────┐ └───────┬───────┘ │ ▼ │ ┌───────────────┐ │ │ 调用模型 │ │ └───────┬───────┘ │ ▼ │ ┌───────────────┐ │ │ 解析输出 │ │ └───────┬───────┘ │ ▼ │ ┌──────────────┐ │ │ 是工具调用? │ │ └──┬────────┬──┘ │ 是 │ │ 否 │ ▼ ▼ │ ┌──────────┐ ┌──────────┐ │ │ 执行工具 │ │ 返回答案 │ │ └────┬─────┘ └──────────┘ │ │ │ └─── 结果加入上下文 ───────┘

三、从零实现一个最小 Harness

理论讲完了,我们来动手实现一个最小可用的 Harness。为了聚焦核心概念,我们使用伪模型接口,但结构完全对应真实场景。

3.1 定义工具接口
# harness.pyfromtypingimportCallable,AnyimportjsonclassTool:"""工具封装:名称、描述、参数 schema、执行函数"""def__init__(self,name:str,description:str,parameters:dict,func:Callable):self.name=name self.description=description self.parameters=parameters self.func=funcdefto_schema(self)->dict:"""转成模型能理解的 JSON Schema"""return{"name":self.name,"description":self.description,"parameters":self.parameters,}defrun(self,**kwargs)->Any:returnself.func(**kwargs)classToolRegistry:"""工具注册表:管理所有可用工具"""def__init__(self):self._tools:dict[str,Tool]={}defregister(self,tool:Tool):self._tools[tool.name]=tooldefget(self,name:str)->Tool|None:returnself._tools.get(name)defschemas(self)->list[dict]:return[t.to_schema()fortinself._tools.values()]
3.2 实现几个示例工具
# tools.pyimportdatetimeimportmathdefget_current_time()->str:"""获取当前时间"""returndatetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")defcalculate(expression:str)->str:"""安全地计算数学表达式(仅演示,生产需沙箱)"""allowed={k:getattr(math,k)forkindir(math)ifnotk.startswith("_")}allowed["__builtins__"]={}try:result=eval(expression,allowed)returnstr(result)exceptExceptionase:returnf"计算错误:{e}"defread_file(path:str)->str:"""读取文件内容"""try:withopen(path,"r",encoding="utf-8")asf:returnf.read()exceptExceptionase:returnf"读取失败:{e}"
3.3 注册工具
# setup_tools.pyfromharnessimportTool,ToolRegistryfromtoolsimportget_current_time,calculate,read_filedefbuild_registry()->ToolRegistry:registry=ToolRegistry()registry.register(Tool(name="get_current_time",description="获取当前系统时间",parameters={"type":"object","properties":{},"required":[]},func=get_current_time,))registry.register(Tool(name="calculate",description="计算数学表达式,例如 '2 + 3 * 4'",parameters={"type":"object","properties":{"expression":{"type":"string","description":"数学表达式"}},"required":["expression"],},func=calculate,))registry.register(Tool(name="read_file",description="读取本地文件内容",parameters={"type":"object","properties":{"path":{"type":"string","description":"文件路径"}},"required":["path"],},func=read_file,))returnregistry
3.4 实现 Harness 主循环
# agent.pyimportjsonfromharnessimportToolRegistryclassHarness:"""AI Harness:驱动模型与工具的循环"""MAX_STEPS=10# 防止无限循环def__init__(self,model_client,registry:ToolRegistry,system_prompt:str=""):self.model=model_client self.registry=registry self.system_prompt=system_prompt self.history:list[dict]=[]def_build_prompt(self,user_input:str)->list[dict]:"""组装发送给模型的消息列表"""messages=[]ifself.system_prompt:messages.append({"role":"system","content":self.system_prompt})messages.extend(self.history)messages.append({"role":"user","content":user_input})returnmessagesdef_parse_tool_call(self,model_output:str)->dict|None:"""解析模型输出中的工具调用指令 约定模型输出格式: {"tool": "tool_name", "args": {...}} 或纯文本(表示最终答案) """text=model_output.strip()# 尝试提取 JSON 块iftext.startswith("```"):text=text.strip("`").replace("json\n","",1).strip()try:data=json.loads(text)ifisinstance(data,dict)and"tool"indata:returndataexceptjson.JSONDecodeError:passreturnNonedefrun(self,user_input:str)->str:"""执行完整的 Agent 循环"""forstepinrange(self.MAX_STEPS):messages=self._build_prompt(user_input)# 关键:把工具 schema 也告诉模型model_output=self.model.chat(messages=messages,tools=self.registry.schemas(),)tool_call=self._parse_tool_call(model_output)iftool_callisNone:# 没有工具调用 → 最终答案self.history.append({"role":"user","content":user_input})self.history.append({"role":"assistant","content":model_output})returnmodel_output# 有工具调用 → 执行tool_name=tool_call["tool"]args=tool_call.get("args",{})tool=self.registry.get(tool_name)iftoolisNone:observation=f"错误:工具{tool_name}不存在"else:try:observation=str(tool.run(**args))exceptExceptionase:observation=f"工具执行失败:{e}"# 把工具调用与结果写入历史self.history.append({"role":"user","content":user_input})self.history.append({"role":"assistant","content":model_output})self.history.append({"role":"user","content":f"工具{tool_name}返回:{observation}"})# 下一轮循环时,user_input 已被吸收进 historyuser_input="请基于以上工具结果继续回答。"return"达到最大步数限制,任务未完成。"
3.5 模拟一个模型客户端

为了让你能直接运行,我们写一个"脚本化"的假模型,模拟真实 LLM 的行为:

# fake_model.pyimportjsonclassFakeModel:"""一个按照预设脚本响应的假模型,用于演示 Harness 流程"""def__init__(self,scripted_responses:list[str]):self.scripted=scripted_responses self.calls=0defchat(self,messages,tools):# 打印当前看到的工具,验证 Harness 是否传入ifself.calls==0:print(f"[模型] 收到{len(tools)}个工具定义")resp=self.scripted[self.calls]self.calls+=1returnresp# 场景:用户问"现在几点?顺便算一下 15 * 24 是多少?"script=[# 第 1 步:调用时间工具json.dumps({"tool":"get_current_time","args":{}}),# 第 2 步:调用计算工具json.dumps({"tool":"calculate","args":{"expression":"15 * 24"}}),# 第 3 步:给出最终答案"现在是 2025-01-15 14:30:00,15 * 24 = 360。",]model=FakeModel(script)
3.6 组装并运行
# main.pyfromagentimportHarnessfromsetup_toolsimportbuild_registryfromfake_modelimportFakeModel,script registry=build_registry()model=FakeModel(script)harness=Harness(model_client=model,registry=registry,system_prompt="你是一个有用的助手,可以调用工具来完成任务。",)answer=harness.run("现在几点?顺便算一下 15 * 24 是多少?")print("最终答案:",answer)

运行输出:

[模型] 收到 3 个工具定义 最终答案: 现在是 2025-01-15 14:30:00,15 * 24 = 360。
3.7 这个最小 Harness 揭示了什么?

虽然只有一百多行代码,但它已经包含了 Harness 的所有关键要素:

  1. 循环控制:for step in range(MAX_STEPS)
  2. 工具注册:ToolRegistry
  3. 上下文组装:_build_prompt拼接 system/history/user
  4. 输出解析:_parse_tool_call
  5. 工具执行:tool.run(**args)
  6. 结果回灌:把 observation 写回 history
  7. 终止条件:模型不再调用工具,或达到最大步数

真实的 Harness(如 LangChain、AutoGPT、Claude Code、OpenAI Assistants API)本质上都是这个循环的复杂化版本。

四、真实世界中的 Harness 长什么样?

4.1 主流 Harness 对比
Harness特点典型场景
LangChain / LangGraph组件化、图结构编排通用 Agent 开发
AutoGPT / BabyAGI自主任务分解、长期记忆探索性自主智能体
OpenAI Assistants API托管式、内置代码解释器与文件检索快速构建应用
Claude Code / Cursor深度集成 IDE、代码库上下文编程助手
MCP(Model Context Protocol)标准化工具/资源协议跨模型工具互通
4.2 一个真实 Harness 需要考虑的额外问题

上面的最小实现回避了很多真实问题,而这些问题恰恰是 Harness 工程的核心难点:

  • 上下文窗口管理:历史太长怎么办?截断、摘要、向量检索?
  • 工具调用的鲁棒性:模型输出格式错误如何修复?
  • 并发与流式:多个工具能否并行?如何流式返回给用户?
  • 权限与沙箱:工具执行是否安全?文件/网络访问如何限制?
  • 可观测性:如何追踪每一步的 token 消耗、延迟、失败率?
  • 多智能体协作:多个 Agent 之间如何通信与分工?
4.3 用 LangGraph 重写同一逻辑

作为对比,我们用 LangGraph(一个流行的 Harness 框架)实现同样的功能:

# langgraph_demo.pyfromlanggraph.graphimportStateGraph,ENDfromlanggraph.prebuiltimportToolNodefromlangchain_core.toolsimporttoolfromlangchain_openaiimportChatOpenAIfromtypingimportTypedDict,Annotatedimportoperator@tooldefget_current_time()->str:"""获取当前系统时间"""importdatetimereturndatetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")@tooldefcalculate(expression:str)->str:"""计算数学表达式"""importmathreturnstr(eval(expression,{"__builtins__":{}},vars(math)))tools=[get_current_time,calculate]tool_node=ToolNode(tools)model=ChatOpenAI(model="gpt-4o").bind_tools(tools)classState(TypedDict):messages:Annotated[list,operator.add]defcall_model(state:State):return{"messages":[model.invoke(state["messages"])]}defshould_continue(state:State):last=state["messages"][-1]return"tools"iflast.tool_callselseEND graph=StateGraph(State)graph.add_node("agent",call_model)graph.add_node("tools",tool_node)graph.set_entry_point("agent")graph.add_conditional_edges("agent",should_continue,{"tools":"tools",END:END})graph.add_edge("tools","agent")app=graph.compile()result=app.invoke({"messages":[("user","现在几点?15*24 等于多少?")]})print(result["messages"][-1].content)

可以看到,LangGraph 把我们在Harness类里手写的循环、解析、执行、回灌,抽象成了图节点与边。但底层逻辑完全一致——这就是 Harness 的本质。

五、Harness 的设计哲学

理解了 Harness 的机制后,我们再来思考几个设计层面的问题。

5.1 模型越强,Harness 越重要还是越不重要?

有一种观点认为:“模型越来越强,Harness 会越来越薄。”

这只对了一半。模型确实会内化一些能力(比如原生函数调用、更长上下文),但新的能力边界也会不断外扩:

  • 模型能调用工具了 → 但工具生态需要 Harness 管理
  • 模型能处理长上下文了 → 但记忆检索、压缩仍需 Harness
  • 模型能自我反思了 → 但多步规划、错误恢复仍需 Har

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询