ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent Harness是什么?原理拆解与从零手写极简实现

Agent Harness是什么?原理拆解与从零手写极简实现 大家在做 AI Agent 相关项目时可能都有过类似的困惑明明网上到处都在讲 Agent说自己要“做一个 Agent”结果一上手发现还要面对工具调用、上下文维护、错误恢复、权限控制一堆问题。更麻烦的是很多人把Agent和Harness混为一谈导致看官方文档时经常绕晕。最近 OpenAI Codex 被反复提及的一句话是 “Codex as a Platform: Build on the Open Agent Harness”把Agent Harness这个概念推到了台前。本文就围绕这个主题从底层原理、核心能力到代码实战完整拆解一遍 Harness Agent 是什么、怎么用、以及如何从零手写一个极简 Harness。内容偏保姆级零基础也能跟着做有后端经验的开发者可以直接跳到第 5 节看代码。1. 背景与核心概念1.1 什么是 Agent先来说 Agent。按最朴素的解释Agent 是一个能感知环境、做出决策、并调用工具执行任务的程序。一个普通脚本和 Agent 的根本区别在于脚本的执行路径是提前写死的而 Agent 的执行路径是根据当前状态动态决策的。举例来说普通脚本读文件 → 统计行数 → 打印结果逻辑固定。Agent收到用户指令“帮我统计一下这个文件有多少行顺便看看有没有重复数据” → 模型决定先读文件、再分析、再调用统计工具 → 根据结果决定下一步。到了大模型时代Agent 的典型构成可以概括为推理内核通常是一个 LLM负责理解指令、拆解任务、决定调用哪个工具。工具集合对外部能力的封装如搜索、计算、读写文件、调用 API 等。记忆与上下文保存历史对话、中间结果、任务目标。执行循环让“决策-行动-观察-再决策”闭环跑起来。所以Agent 解决的核心问题是让程序具备基于目标的自主行动能力。1.2 什么是 HarnessHarness 在英文里的原意是“马具、挽具”作用是把动力源和车身连接起来。在 AI Agent 语境下Harness 可以理解为 Agent 的“驾驶舱”或“运行框架”。更准确一点Harness 是包裹在 Agent 内核之外的一层执行框架负责把模型输出的决策变成真实可执行的行动。一个典型的 Agent Harness 要做的事情包括管理输入输出流维护消息历史和上下文窗口调度工具调用处理循环中的错误与异常执行安全策略与权限限制记录运行日志和可观测指标。为什么最近 Harness 这个概念被频繁提到因为 Codex 已经不只是一个代码补全工具而是慢慢演变成了一个平台。官方把这层支撑“智能体运行”的骨架抽象出来开放给开发者让大家可以基于同一套 Agent Harness 构建自己的编码智能体。这正是 “Codex as a Platform: Build on the Open Agent Harness” 背后的含义LLM 负责智能Harness 负责把智能安全的落地成动作。1.3 为什么开发者需要掌握 Harness很多人在做 Agent 项目时会遇到下面这些典型问题用某个 Agent 框架时模型经常不调用工具而是自己“编答案”上下文稍微一长Token 就爆了工具调用偶发报错整个流程直接中断想限制 Agent 只能访问某几个接口不知道在哪一层做控制。这些问题几乎都出在 Harness 层而不是模型本身。如果能理解 Harness 的原理你就知道工具调用需要注册表、上下文需要预算管理、错误需要回传给模型做自纠、权限需要在策略层统一控制。掌握了这些不管以后用 Codex Agent SDK、LangChain还是自研框架思路都是通用的。2. Harness 与 Agent 的区别这一节解决一个高频疑问Harness 和 Agent 到底有什么区别很多人搜到“harness agent”时会以为这是两个可以互替的名词实际上它们描述的是不同层次的东西。对比维度AgentHarness角色定位决策者执行环境与调度者核心问题下一步做什么如何安全、高效、可控地执行关注点推理、规划、工具选择工具注册、上下文、循环、权限、日志类比驾驶员汽车底盘、仪表盘、刹车系统生产问题模型答错、规划不合理工具超时、上下文溢出、权限绕过简单来说Agent 是大脑Harness 是身体和双手运行的整套机制。再举一个通俗的例子。你让一个助手“帮我查询天气如果下雨就提醒我带伞”。助手脑子里想的是“我需要调用天气查询工具看看要不要提醒”。但真正让这句话落地的是背后那一套流程解析出工具调用、传入城市参数、拿到天气结果、决定是否触发提醒、最后反馈给你。这套流程本身就是 Harness 在做的事。除了跟 Agent 区分还有两个容易混淆的“Harness”Test Harness软件测试领域的“测试脚手架”用于隔离被测代码并驱动测试用例。它和 Agent Harness 都叫 Harness但解决的是完全不同的问题。Harness 公司有一家做 CI/CD 的公司叫 Harness。它跟本文讨论的 Agent Harness 没有直接关系搜索资料时注意区分。理解这个区别之后你再去看官方文档里 “Agent” 和 “Harness” 两个词就不会再晕了。3. Agent Harness 的底层原理拆解3.1 核心组件一个可用的 Agent Harness内部至少包含下面几个组件推理内核Agent Core 负责与 LLM 交互。它把用户输入、系统提示词、历史消息、工具描述拼装成请求发送给模型再解析模型的输出。工具注册表Tool Registry 统一维护“这个 Agent 能调用哪些工具”。每个工具通常包含名称、描述、参数 JSON Schema、执行函数。注册表的作用有两个一是给模型提供工具说明二是限制模型只能调用白名单内的工具。上下文管理器Context Manager 负责维护对话记录。包括 System Prompt、用户消息、助手消息、工具返回结果。它需要处理一个问题大模型上下文窗口有限历史消息不能无限增长。策略引擎Policy Engine 负责权限控制、频率限制、安全检查。比如“计算工具只允许处理数值不允许执行系统命令”“某个工具最多调用 5 次”等约束都放在这里。可观测模块Observer 记录每次决策、工具调用、错误信息、耗时指标。生产环境排障基本靠它。3.2 一次完整执行循环理解 Harness 最好的方式是看一次完整调用链接收用户输入。用户发来一句自然语言指令作为本次任务的起点。组装上下文。Harness 把系统提示词、历史消息、工具描述拼装成一个请求。推理。LLM 接收请求输出结果。结果可能是一个最终回答也可能是一个工具调用意图。解析决策。Harness 从模型输出中解析出结构化指令例如“调用工具 get_current_time参数为空”。执行工具。Harness 根据工具注册表找到对应函数传入参数并执行。回传结果。工具执行完成后结果会作为一条新消息追加到上下文中。再决策。Harness 把带工具结果的上下文再次发送给模型让模型判断任务是否完成。输出或继续。如果模型认为任务完成就输出最终回答否则继续进入工具调用循环。整个过程很像人的工作方式看一眼任务 → 动一下手 → 看结果 → 再决定下一步。3.3 上下文窗口与 Token 预算Harness 里最容易出问题的环节是上下文管理。LLM 的输入长度是有限制的。当历史消息不断累积工具结果不断追加时很快会触达上限。Harness 的上下文管理器一般会做几件事计算当前消息序列的 Token 占用移除最早的非关键历史消息将过长的历史消息摘要压缩限制单次工具结果的大小超长内容只截取一部分回传。所以不要以为 Agent 能“记住”所有历史实际上 Harness 是在有限的上下文窗口里做各种取舍。3.4 错误恢复与回退策略工具调用不可能永远成功。可能的原因包括参数格式不对网络超时权限不足工具内部逻辑抛异常。一个好的 Harness 不会直接崩溃而是把错误信息作为观察结果回传给模型让模型修改参数或换一种策略重新尝试。同时Harness 还要设置最大重试次数和单轮超时时间避免无限循环或长时间卡死。这部分工程细节正是自研 Agent 和直接使用成熟框架时差距最大的地方。4. 环境准备与工程结构在进入代码实战之前先准备好本地环境。4.1 环境要求本文的实战部分会从零实现一个极简 Agent Harness。为了照顾零基础读者设计上做了两个选择不依赖任何外部 LLM API电脑上直接运行优先展示 Harness 的核心机制模型决策部分用可替换的模拟函数代替。环境要求如下Python 3.9 及以上版本无需安装任何第三方依赖如果后续要接入真实大模型再根据所选服务安装对应 SDK。说明一下当前 AI 工具链迭代很快不同版本的 SDK API 差异比较大。本文示例重点在于展示 Harness 的通用设计思路不绑定某个固定版本号。接入真实模型时请以所用服务的官方文档为准。4.2 项目结构为了便于理解我按模块拆分项目mini_harness/ ├── main.py # 入口负责组装各模块并启动交互 ├── harness/ │ ├── __init__.py # 包初始化 │ ├── core.py # Harness 核心循环 │ ├── tools.py # 工具注册表与工具实现 │ └── policy.py # 策略引擎权限与轮数控制 └── requirements.txt # 依赖说明本文示例为空这个结构参考了生产级 Harness 的分层思路工具层、策略层、核心调度层彼此独立。这样后续替换真实模型时只需要修改入口不需要改动核心循环。5. 从零实现一个极简 Agent Harness接下来进入代码实战。我们先定义两个工具再写策略层最后实现核心循环。5.1 工具层实现文件路径harness/tools.py 工具注册表与工具实现。 每个工具由三部分组成 1. 名称 2. 描述信息供模型理解 3. 实际执行的函数 import datetime def get_current_time() - str: 返回当前时间字符串。 now datetime.datetime.now() return now.strftime(%Y-%m-%d %H:%M:%S) def calculate(expression: str) - str: 计算简单的四则运算表达式。 注意这里仅供教学演示实际项目中不要用 eval 处理任意表达式。 # 只允许数字、空格和 - * / ( ) 这些字符 allowed_chars set(0123456789-*/() .) for ch in expression: if ch not in allowed_chars: raise ValueError(f表达式包含非法字符: {ch}) try: result eval(expression, {__builtins__: {}}, {}) return str(result) except Exception as e: raise ValueError(f表达式计算失败: {e}) # 工具注册表一个字典key 是工具名value 是工具元信息 TOOL_REGISTRY { get_current_time: { description: 获取当前日期和时间, function: get_current_time, parameters: {}, }, calculate: { description: 计算数学表达式例如 1 2 或 (3 5) * 2, function: calculate, parameters: {expression: string}, }, }这里有几个设计点需要解释每个工具的描述很重要。真实场景中模型就是靠这些描述决定要不要调用、传什么参数。calculate里的字符白名单是教学用简化方案。实际项目中应该用更安全的表达式解析库不要直接用eval。工具注册表集中管理后续增加新工具只需要往字典里加一项核心循环不用改。5.2 策略层实现文件路径harness/policy.py 策略引擎负责权限控制和执行约束。 class Policy: def __init__(self, allowed_toolsNone, max_turns5): # 允许调用的工具白名单None 表示全部允许 self.allowed_tools allowed_tools # 最大执行轮数防止无限循环 self.max_turns max_turns # 每个工具的调用计数 self.tool_call_count {} def check_tool_allowed(self, tool_name: str) - bool: 检查工具是否被允许调用。 if self.allowed_tools is None: return True return tool_name in self.allowed_tools def before_tool_call(self, tool_name: str) - bool: 工具调用前检查是否允许、是否超次数。 if not self.check_tool_allowed(tool_name): return False current_count self.tool_call_count.get(tool_name, 0) # 每个工具最多调用 3 次 if current_count 3: return False self.tool_call_count[tool_name] current_count 1 return True策略层在 Harness 里的位置很关键。它可以在工具调用前拦截非法操作防止 Agent 失控。真实生产环境里这里还会加上身份校验、敏感操作二次确认、频率限制等逻辑。5.3 核心循环实现文件路径harness/core.py Harness 核心循环 接收用户输入 - 组装上下文 - 调用决策函数 - 执行工具 - 回传结果 - 再次决策 from .tools import TOOL_REGISTRY from .policy import Policy class MiniHarness: def __init__(self, llm_parse_fn, policy: Policy): llm_parse_fn: 决策函数。 输入是消息列表输出是一个字典格式为 { type: tool_call 或 final, tool: 工具名称可选, args: {...}可选, content: 最终回答内容当 type 为 final 时 } self.llm_parse_fn llm_parse_fn self.policy policy self.messages [] # 记录整个执行过程方便观察和日志 self.steps [] def run(self, user_input: str) - str: self.messages.append({role: user, content: user_input}) for turn in range(self.policy.max_turns): # 1. 调用决策函数真实场景中是调用大模型 decision self.llm_parse_fn(self.messages) # 2. 如果模型认为任务完成直接输出最终结果 if decision.get(type) final: final_answer decision.get(content, ) self.messages.append({role: assistant, content: final_answer}) return final_answer # 3. 否则解析工具调用意图 tool_name decision.get(tool) tool_args decision.get(args) or {} # 4. 策略检查是否允许调用 if not self.policy.before_tool_call(tool_name): error_msg f工具 {tool_name} 被策略拦截请换一种方式回答。 self.messages.append({role: tool, content: error_msg}) continue # 5. 执行工具 try: if tool_name not in TOOL_REGISTRY: raise ValueError(f未知工具: {tool_name}) tool_function TOOL_REGISTRY[tool_name][function] result tool_function(**tool_args) result_text str(result) except Exception as e: # 6. 工具执行失败把错误信息回传给决策函数让它自纠 result_text f工具执行失败: {str(e)} self.messages.append({ role: tool, name: tool_name, content: result_text, }) self.steps.append({ turn: turn 1, tool: tool_name, args: tool_args, result: result_text, }) # 超过最大轮数还没有输出最终结果强制结束 return 已达到最大执行轮数任务未能完成。 def load_tool_descriptions() - str: 把注册表信息转成可读的工具说明真实场景中会拼接到 System Prompt。 lines [] for name, meta in TOOL_REGISTRY.items(): params_desc , .join( f{k}: {v} for k, v in meta.get(parameters, {}).items() ) lines.append(f- {name}({params_desc}): {meta[description]}) return \n.join(lines)核心循环的代码量不大但已经把 Harness 的关键机制都覆盖了上下文用messages列表维护决策结果解析用decision字典承载策略检查在执行前拦截工具异常会回传给模型而不是直接中断循环有最大轮数控制。5.4 模拟决策函数文件路径main.py为了让代码不依赖外部 API 也能运行我写了一个mock_llm_parse函数。它用简单的规则模拟大模型的决策 入口文件组装各模块并启动交互。 默认使用模拟决策函数无需外部服务即可运行。 import re from harness.core import MiniHarness, load_tool_descriptions from harness.policy import Policy from harness.tools import TOOL_REGISTRY SYSTEM_PROMPT f你是一个智能助手助手你可以使用以下工具 {load_tool_descriptions()} 请根据用户需求选择是否调用工具。 def mock_llm_parse(messages): 模拟大模型的决策过程。 真实场景中这里会调用 LLM并把模型输出解析成结构化字典。 # 获取最后一条用户消息 last_user_msg for msg in reversed(messages): if msg[role] user: last_user_msg msg[content] break # 规则1包含“时间”或“几点”时调用 get_current_time if 时间 in last_user_msg or 几点 in last_user_msg: return {type: tool_call, tool: get_current_time, args: {}} # 规则2包含“计算”时提取数学表达式并调用 calculate if 计算 in last_user_msg: # 简单提取所有表达式实际场景中由模型解析参数 match re.search(r[\d\-*/() .], last_user_msg) if match: expr match.group().strip() return {type: tool_call, tool: calculate, args: {expression: expr}} return { type: final, content: 我理解你想让我计算但没有找到合法的数学表达式。, } # 规则3兜底情况直接给最终回答 return {type: final, content: f收到你的消息{last_user_msg}。模拟模式下我没有更多工具可调用。} def main(): print( Mini Agent Harness Demo ) print(这是一个极简 Agent Harness模拟模式运行。) print(你可以输入) print( - 现在几点了) print( - 计算 1 2) print( - 计算 (3 5) * 2) print(输入 exit 退出。\n) policy Policy(allowed_toolsNone, max_turns5) harness MiniHarness(llm_parse_fnmock_llm_parse, policypolicy) while True: user_input input( ).strip() if user_input.lower() in (exit, quit): print(再见) break result harness.run(user_input) print(fAssistant: {result}\n) if __name__ __main__: main()mock_llm_parse的目的不是演示模型能力而是把 Harness 的骨架先跑通。当你想接入真实模型时只需要把这个函数替换成真正的 LLM 调用并解析返回值即可。为了让目录结构完整还需要创建文件路径harness/__init__.pyfrom .core import MiniHarness, load_tool_descriptions from .policy import Policy __all__ [MiniHarness, load_tool_descriptions, Policy]文件路径requirements.txt# 本文示例无需第三方依赖 # 接入真实 LLM 时请根据所选服务自行添加6. 运行与验证6.1 运行命令在项目根目录执行python main.py预期交互效果如下 Mini Agent Harness Demo 这是一个极简 Agent Harness模拟模式运行。 你可以输入 - 现在几点了 - 计算 1 2 - 计算 (3 5) * 2 输入 exit 退出。 现在几点了 Assistant: 2025-01-15 20:30:45 计算 1 2 Assistant: 3 计算 (3 5) * 2 Assistant: 16 exit 再见你可以观察到Harness 内部执行了这样几步解析用户输入根据规则决定调用哪个工具执行工具把工具结果作为最终回答输出。6.2 查看执行步骤如果你想知道内部到底调用了哪些工具、结果如何可以在main.py中加一行调试输出result harness.run(user_input) print(fAssistant: {result}\n) print(执行步骤:, harness.steps)这样每次运行后就能看到类似下面的内部记录执行步骤: [ {turn: 1, tool: get_current_time, args: {}, result: 2025-01-15 20:30:45} ]这个steps列表就是最简版的可观测数据。生产环境里这些数据会被格式化写入日志系统或链路追踪平台。6.3 如何接入真实 LLM接入真实模型时核心思路是替换mock_llm_parse将系统提示词、消息历史、工具注册表描述拼装成模型请求调用模型接口不同厂商接口格式不同以官方文档为准解析模型输出返回结构化decision字典。代码骨架大致如下def real_llm_parse(messages): # 伪代码示例具体 API 以所选模型的官方文档为准 response call_llm( systemSYSTEM_PROMPT, messagesmessages, toolsbuild_tools_schema(TOOL_REGISTRY), ) decision parse_model_response(response) return decision这里不锁定任何具体厂商 SDK是因为接口变化太快了。核心要点是无论模型返回什么格式最终都要转换成 Harness 内部的 decision 结构。这样整个执行循环完全不需要变动就完成了从模拟模式到真实模型的切换。7. 常见问题与排查思路做 Agent Harness 开发时下面这些问题是高频出现的。问题现象常见原因解决思路模型不调用工具直接给出编造答案工具描述不清晰或上下文里没包含工具说明检查 System Prompt 是否包含工具列表优化工具描述工具明明注册了却提示未知工具决策函数解析出的工具名与注册表 key 不一致统一工具名建议使用小写英文下划线Agent 进入无限循环一直调用同一个工具没有设置最大执行轮数在 Policy 中增加max_turns单工具调用次数限制上下文越来越长最终请求超过模型限制历史消息和工具结果没有做截断实现滑动窗口、历史摘要压缩工具调用报错后整个流程中断异常没有回传给模型而是直接抛出用 try-except 捕获异常把错误信息追加到上下文中模型返回 JSON 解析失败输出格式不稳定优先使用模型的函数调用机制若无则增加正则或模板兜底权限被绕过工具被非法调用缺少策略层校验所有工具调用前统一走 Policy 检查排查时建议按下面顺序走一遍看日志模型到底输出了什么是没调用工具还是调用格式不对看工具注册表工具名、参数名和决策函数解析出的结果是否完全一致看策略配置是否被白名单或次数限制拦截了看工具实现单独执行这个工具是否能得到正确结果看上下文是不是历史消息太长把关键信息冲掉了这五步覆盖了 Agent Harness 从输入到输出的完整链路能解决绝大部分问题。8. 最佳实践与工程建议8.1 最小权限原则Agent 的能力越强潜在风险就越大。在生产环境里建议遵循最小权限原则只注册业务必需的工具策略层使用白名单控制而不是黑名单敏感操作销毁资源、删除数据、修改权限必须二次确认工具执行前校验参数类型和取值范围。不要把所有工具一股脑塞给 Agent。工具越多模型选错工具的几率越高被恶意指令利用的面也越大。8.2 上下文管理是稳定性的生命线很多 Agent 跑一段时间后效果变差不是模型问题而是上下文管理没做好。建议做到给所有消息标注 role 和来源user、assistant、tool工具结果控制在合理长度超长内容只回传摘要历史消息超过预算时优先压缩旧的工具结果而不是用户指令定期清理无用的中间步骤降低 Token 消耗。8.3 可观测性必须前置第一次写 Harness 时就要预留日志和追踪点位。至少记录每次模型输入的消息条数和 Token 估计每次决策的结构化结果每次工具调用的参数、耗时、返回结果、错误信息策略拦截记录。生产环境里没有日志的 Agent 等于黑盒出问题根本没法定位。8.4 安全边界这里尤其要提醒两件事不要把用户输入直接拼进系统命令或代码执行环境警惕 Prompt Injection。工具返回结果中包含的可疑指令不应被当作系统级指令执行。一个稳妥的做法是工具返回的结果一律视为不可信数据只把它作为普通文本传给模型参考不赋予它调用其他工具的权利。8.5 尽量复用官方 Harness如果是企业项目不建议从零自研完整 Harness。更好的策略是优先使用官方或社区成熟的 Harness 框架通过自定义工具、自定义 Policy 扩展业务能力只对确实不满足需求的部分做二次开发。官方框架经过大量用户验证对极端情况的处理远比我们自己写的要完善。本文手写极简 Harness目的是帮助理解原理而不是鼓励大家在生产环境全部自己造轮子。9. 总结与学习路线这篇文章从概念、原理、代码实现、问题排查到工程建议完整覆盖了 Harness Agent 的核心知识。读完你应该能回答下列几个问题Agent 和 Harness 有什么区别Harness 的核心组件有哪些一次工具调用循环是如何流转的如何从零实现一个最小可运行的 Harness上下文管理、策略控制、可观测性为什么重要接下来可以沿着这个方向继续深入学习 Function Calling 机制理解模型如何输出结构化工具调用了解 MCP 等工具协议看看工具注册如何标准化读一读 Codex Agent SDK 等官方 Harness 的源码或文档对照本文的 MiniHarness理解生产级设计的差距尝试给 MiniHarness 加上上下文压缩、日志落盘、真实模型接入把它扩展成自己的练习项目。如果你正在把自己的流程封装成 Agent我的建议是先从 Harness 入手而不是一上来就堆功能。先把最小闭环跑通再把工具、策略、可观测性一层层加进去这个过程会比直接接手大而全的框架清晰得多。如果本文对你有帮助可以先收藏备用动手写代码时拿出来对照。欢迎在评论区聊聊你在 Agent 落地过程中遇到的坑。
返回列表