1. 别被“Harness”这个词唬住它其实就是Agent的骨架第一次听到“Harness”这个词很多人会以为是某个新出的框架或者工具库甚至有人把它跟“Agent”本身混为一谈。我刚开始接触AI Agent开发的时候也犯过这个迷糊后来拆了几个开源项目、自己从零搭了两套能跑起来的Agent之后才明白Harness不是Agent它是让Agent真正能干活的那套“骨架系统”。打个比方如果把大模型比作一个脑子很好使但手脚不听使唤的人那Harness就是给他装上的手脚、神经和反射弧。没有Harness模型只能聊天有了Harness它才能读文件、调接口、跑命令、记住上下文、在失败后重试。你看到的那些“AI自动修bug”“AI自己写代码跑测试”“AI帮你操作浏览器”的演示背后真正在干活的就是这套Harness。这篇文章我打算把Harness拆成7个子系统来讲从Agent Loop到LLM Integration从工具调度到状态管理每个子系统我都会说清楚它解决什么问题、为什么这么设计、实际搭的时候要注意什么。不管你是刚入门想搞明白Agent到底怎么运转的还是已经写过一些demo但总觉得“跑不稳”的这篇应该都能帮你把脑子里那团浆糊理清楚。先给个整体认知一个能真正干活的Harness核心就7块——Agent Loop主循环、LLM Integration模型接入层、Tool System工具系统、Context Management上下文管理、State Memory状态与记忆、Execution Sandbox执行沙箱、Observability可观测性。下面逐个拆。2. 七个子系统逐个拆每个都在解决什么要命的问题2.1 Agent Loop整个Harness的心脏决定Agent能不能“自己转起来”Agent Loop是整个Harness里最核心的子系统没有之一。它的职责说起来很简单不断地把当前状态喂给模型拿到模型的输出解析出要执行的动作执行完再把结果塞回去循环往复直到任务完成。但就是这么一个“while循环”里面藏着大量工程细节。我见过很多新手写的Agent Loop是这样的调一次模型拿到回复如果回复里有工具调用就执行执行完再调一次模型如此反复。逻辑上没错但实际跑起来问题一大堆——什么时候该停工具执行失败了怎么办模型一直不输出终止信号怎么办上下文越来越长超出窗口怎么办一个健壮的Agent Loop至少要处理这几件事终止条件判定不能只靠模型说“我完成了”还要有最大轮次限制、超时限制、连续失败次数限制。我一般会设一个max_iterations默认15到25轮超过就强制终止并返回当前结果。动作解析与校验模型输出的工具调用请求要做schema校验参数类型不对、必填字段缺失的直接返回错误信息让模型重试而不是硬着头皮执行。错误回传工具执行报错时不能直接崩掉要把错误信息结构化后塞回上下文让模型自己决定是重试、换工具还是放弃。循环节流连续两轮模型输出完全一样、或者连续调用同一个工具同一个参数要能检测出来并打断否则就是死循环烧token。# Agent Loop 核心骨架示意 def agent_loop(task, max_iterations20): context init_context(task) for i in range(max_iterations): response llm_integration.call(context) action parse_action(response) if action.type finish: return action.result if action.type tool_call: result tool_system.execute(action.tool, action.params) context.append_tool_result(result) if detect_loop(context): context.append_system_hint(检测到重复行为请换思路) return 达到最大轮次限制任务未完成实操心得Agent Loop里最容易被忽略的是“循环检测”。我踩过的坑是一个文件读取工具因为路径写错一直返回空模型就一直重试同一个路径烧了快两万token才发现。后来加了重复动作检测同样动作出现三次就直接注入提示。2.2 LLM Integration模型接入层别小看这层“适配器”LLM Integration这层听起来像是“调个API就完事”但实际做起来它是整个Harness里最脏最累的活。因为你要面对的现实是不同模型提供商的接口格式不一样、function calling的协议不一样、流式输出的分片方式不一样、token计算方式不一样、错误码体系也不一样。这层要解决的核心问题是抽象。上层Agent Loop不应该关心底层用的是哪家模型它只应该看到统一的接口给我一个上下文返回一个结构化的响应包含文本内容、工具调用请求、结束原因。具体要做的事情包括请求格式转换把统一的内部消息格式转成各家API要求的格式。比如OpenAI的tool_calls、Anthropic的tool_use、国内一些模型的function_call字段名和嵌套结构都不同。响应解析归一化把各家返回的工具调用统一解析成内部标准结构包括工具名、参数、调用ID。流式处理流式输出下工具调用的参数是分片到达的要能正确拼接。这个坑很深有些模型的流式工具调用分片顺序不保证需要按index聚合。重试与降级网络抖动、限流、超时都要处理。我一般会做指数退避重试重试三次还不行就切换到备用模型。Token计数与截断不同模型的tokenizer不同要准确计算上下文长度超限前要触发截断或摘要。对比项OpenAI风格Anthropic风格国内部分模型工具调用字段tool_callstool_use content blockfunction_call参数格式JSON字符串直接对象JSON字符串结束原因finish_reasonstop_reasonfinish_reason流式工具调用分片delta分片delta部分不支持注意事项做LLM Integration层的时候一定要把“模型返回了非法JSON”当成常态来处理。我实测下来即使开了JSON mode仍有小概率返回带markdown代码块包裹的JSON解析前要先做清洗。2.3 Tool SystemAgent的手脚工具设计决定能力上限Tool System是Agent真正“干活”的地方。模型再聪明没有工具也只能输出文字。工具系统要解决的核心问题是如何让模型知道有哪些工具可用、每个工具怎么用、用完结果怎么理解。工具系统的设计我总结为三个层次第一层是工具注册与描述。每个工具要有清晰的name、description和参数schema。description写得好不好直接决定模型会不会用、用得对不对。我见过太多人description就写一句“读取文件”结果模型传参老是缺路径。好的description应该包含这个工具做什么、什么时候用、参数含义、返回什么、有什么限制。第二层是工具执行与隔离。工具执行不能直接在主进程里裸跑尤其是文件操作、命令执行这类工具。要有超时控制、资源限制、异常捕获。我一般会给每个工具执行包一层设置独立的超时时间比如文件读取5秒、命令执行30秒。第三层是工具结果处理。工具返回的结果往往很长不能原样塞回上下文。要做截断、摘要、结构化。比如读一个1000行的文件不可能全塞回去要截取关键部分或者做摘要。# 工具注册示例 tools [ { name: read_file, description: 读取指定路径的文件内容。当需要查看文件内容时使用。参数path为绝对路径。返回文件文本内容超过500行会截断。, parameters: { type: object, properties: { path: {type: string, description: 文件的绝对路径} }, required: [path] } } ]实操心得工具数量不是越多越好。我试过给Agent注册30多个工具结果模型选择困难经常选错。后来精简到8个核心工具准确率明显提升。工具多了要么做分组路由要么做动态工具加载。2.4 Context Management上下文管理决定Agent能“记多久”Context Management这层是很多demo级Agent完全忽略的但它是区分“玩具”和“能干活”的关键。大模型的上下文窗口是有限的而Agent干活过程中产生的信息是海量的——每一轮对话、每一次工具调用、每一个工具返回结果都在往上下文里塞东西。这层要解决的核心问题是在有限的窗口里放最有用的信息。具体策略包括滑动窗口保留最近N轮对话更早的丢弃。简单但粗暴容易丢关键信息。摘要压缩把早期的对话做摘要用摘要替代原文。适合长任务。分层记忆把信息分成“当前任务上下文”和“长期记忆”长期记忆存到外部存储需要时检索回来。工具结果裁剪工具返回的大段内容只保留关键部分比如只保留前50行和后50行中间用省略号。系统提示固定系统提示词永远保留在上下文最前面不参与裁剪。我一般会组合使用系统提示固定 最近5轮完整保留 更早的做摘要 工具结果超长裁剪。这样能在有限窗口里塞下尽可能多的有效信息。策略优点缺点适用场景滑动窗口实现简单丢失早期信息短任务摘要压缩保留关键信息摘要本身消耗token长任务分层记忆容量无限检索有延迟超长任务结果裁剪节省空间可能丢细节工具返回超长时2.5 State Memory状态与记忆让Agent不再“失忆”State Memory和Context Management容易混淆但它们是两个不同的东西。Context Management管的是“当前这次模型调用能看到什么”State Memory管的是“跨轮次、跨会话要持久保存什么”。State主要解决任务执行过程中的状态跟踪问题。比如一个多步骤任务当前执行到第几步、哪些步骤完成了、哪些失败了、中间产出了什么文件。这些状态不能只放在上下文里因为上下文会被裁剪要单独存。Memory主要解决跨会话的知识积累。比如用户之前告诉过Agent“我的项目用Python 3.11”下次新会话时Agent应该还记得。这需要把信息持久化到外部存储新会话开始时检索加载。实现上State我一般用一个结构化的JSON对象每轮更新存到内存或Redis。Memory用向量数据库或者简单的KV存储按需检索。# State 结构示例 state { task_id: xxx, current_step: 3, total_steps: 7, completed_steps: [1, 2], failed_steps: [], artifacts: { generated_files: [/tmp/output.py], modified_files: [] }, created_at: 2026-01-01T10:00:00 }注意事项State的更新要原子化避免并发写导致状态错乱。如果Agent支持并行工具调用State更新要加锁或者用乐观锁。2.6 Execution Sandbox执行沙箱安全底线不能破Execution Sandbox这层是很多个人项目会跳过、但企业级应用绝对不能少的。Agent要执行代码、跑命令、操作文件如果不做隔离一个错误的rm -rf或者死循环就能把整个环境搞崩。沙箱要解决的核心问题是让Agent能干活但干不了坏事。具体手段包括文件系统隔离Agent只能访问指定目录不能碰系统文件。可以用chroot、容器挂载或者路径白名单。网络隔离限制Agent能访问的网络范围防止数据泄露或恶意请求。资源限制CPU时间、内存、磁盘写入量都要限制防止资源耗尽。命令白名单只允许执行预定义的命令或者对命令做危险模式检测。超时强杀任何执行超过设定时间就强制终止。我一般用容器做沙箱每个任务起一个临时容器任务结束就销毁。这样隔离最彻底也方便做资源限制。轻量级场景可以用子进程加资源限制的方式。# 用容器做沙箱的简化示例 docker run --rm \ --memory512m \ --cpus1 \ --networknone \ --read-only \ -v /tmp/agent_workspace:/workspace \ agent-sandbox:latest \ python /workspace/task.py2.7 Observability可观测性出问题时能查得动Observability是最后一个子系统但绝不是最不重要的。Agent跑起来之后你面对的是一个黑盒——它为什么这么决策、哪一步慢了、哪一步错了、token花在哪了没有可观测性你根本查不动。这层要解决的核心问题是让Agent的每一步都可见、可查、可分析。具体要做全链路日志每一轮循环的输入、输出、工具调用、执行结果都记下来带时间戳和耗时。Token统计每轮消耗多少token累计多少按模型和工具分类统计。决策追踪模型为什么选这个工具、为什么这么传参把模型的思考过程如果有也记下来。错误聚合把错误按类型聚合方便发现系统性问题。性能指标每轮耗时、工具执行耗时、模型响应耗时找出瓶颈。我一般会用结构化日志每条日志是一个JSON包含trace_id、span_id、event_type、payload。这样方便后续用ELK或者类似工具做分析。观测维度记录内容用途循环轮次每轮输入输出复现问题Token消耗每轮token数成本控制工具调用工具名、参数、结果、耗时性能优化错误信息错误类型、堆栈、上下文问题排查决策路径模型选择理由效果调优3. 从零搭一个最小可用Harness完整实操流程3.1 环境准备与依赖选型搭Harness之前先把环境理清楚。我推荐的技术栈是Python 3.11因为生态最全各家模型SDK都支持得好。核心依赖就几个模型SDKopenai/anthropic等、HTTP客户端httpx、数据校验pydantic、日志structlog、容器操作docker-py如果做沙箱。不需要一上来就上框架。我见过很多人直接上LangChain或者AutoGPT结果出了问题根本不知道是哪层的问题。我的建议是先用最朴素的代码把7个子系统各写一个最小实现跑通之后再考虑用框架替换。# 基础依赖 pip install openai anthropic httpx pydantic structlog目录结构我一般这样组织harness/ loop/ # Agent Loop llm/ # LLM Integration tools/ # Tool System context/ # Context Management state/ # State Memory sandbox/ # Execution Sandbox observability/ # Observability main.py3.2 核心模块实现顺序与关键代码实现顺序很关键我建议按依赖关系来先做LLM Integration最底层再做Tool System独立然后做Context Management和State独立接着做Agent Loop依赖前面几个最后做Sandbox和Observability横切。LLM Integration层的关键是定义好统一接口class LLMClient: def call(self, messages, toolsNone, **kwargs) - LLMResponse: raise NotImplementedError class LLMResponse: def __init__(self, content, tool_calls, finish_reason, usage): self.content content self.tool_calls tool_calls # List[ToolCall] self.finish_reason finish_reason self.usage usageTool System的关键是注册机制和执行隔离class ToolRegistry: def __init__(self): self.tools {} def register(self, name, description, parameters, func, timeout30): self.tools[name] { schema: {...}, func: func, timeout: timeout } def execute(self, name, params): tool self.tools.get(name) if not tool: return ToolResult(errorf工具{name}不存在) try: result run_with_timeout(tool[func], params, tool[timeout]) return ToolResult(contentresult) except TimeoutError: return ToolResult(error执行超时) except Exception as e: return ToolResult(errorstr(e))Agent Loop把上面这些串起来class AgentHarness: def __init__(self, llm, tools, context_mgr, state_mgr, sandbox, observer): self.llm llm self.tools tools self.context_mgr context_mgr self.state_mgr state_mgr self.sandbox sandbox self.observer observer def run(self, task, max_iterations20): self.state_mgr.init(task) for i in range(max_iterations): context self.context_mgr.build(self.state_mgr.get()) response self.llm.call(context, toolsself.tools.schemas()) self.observer.log_llm_call(i, context, response) if response.finish_reason stop: return response.content for tool_call in response.tool_calls: result self.tools.execute(tool_call.name, tool_call.params) self.context_mgr.append_tool_result(tool_call.id, result) self.state_mgr.update_tool_result(tool_call.id, result) self.observer.log_tool_call(tool_call, result) return 任务未完成3.3 参数选择与性能调优几个关键参数我实测下来的经验值max_iterations简单任务10轮够复杂任务20-30轮。设太大浪费token设太小任务做不完。工具超时文件操作5秒命令执行30秒网络请求15秒。超过就杀。上下文窗口使用率不要等到100%才裁剪我一般到80%就触发摘要压缩。重试次数LLM调用重试3次工具执行重试1次有些工具重试有副作用。温度Agent场景建议用低温度0.1-0.3保证决策稳定。性能瓶颈通常在LLM调用上一轮循环里LLM调用占80%以上时间。优化方向用流式输出降低首字延迟、并行执行无依赖的工具调用、缓存重复的LLM请求。4. 踩坑实录那些文档不会告诉你的问题4.1 常见问题速查表问题现象可能原因排查方向解决方案Agent死循环循环检测缺失看日志是否重复动作加重复动作检测工具调用参数错误description不清看模型输出优化工具描述上下文超限裁剪策略不当看token统计加摘要压缩执行卡死无超时控制看工具耗时加超时强杀状态错乱并发写冲突看state更新日志加锁或串行化结果不稳定温度过高对比多次运行降低温度4.2 独家避坑技巧技巧一工具描述里加“反例”。比如read_file的描述里加一句“不要用于读取目录读取目录请用list_dir”。模型看到反例后选错工具的概率明显下降。技巧二错误信息要“可操作”。工具返回错误时不要只返回“执行失败”要返回“路径不存在请检查路径是否正确当前工作目录是/workspace”。模型拿到可操作的错误信息后自我修正的成功率大幅提升。技巧三给Agent一个“放弃”选项。在工具列表里加一个give_up工具描述写“当确认无法完成任务时调用”。这样模型不会为了完成任务而胡乱调用工具而是能体面地放弃。技巧四日志里记录完整上下文快照。出问题时光看日志文字不够要能把当时的完整上下文包括系统提示、历史消息、工具结果dump出来这样才能精确复现。技巧五沙箱里预装常用工具。Agent执行代码时经常需要一些基础库沙箱镜像里提前装好避免每次现装浪费时间。4.3 效果调优的实战经验调优Agent效果我总结下来优先级是工具描述 系统提示 上下文策略 模型选择。很多人一上来就换更强的模型但实际收益远不如把工具描述写清楚。系统提示里我一般会包含角色定义、可用工具概览、工作流程建议、输出格式要求、禁止事项。其中“禁止事项”很重要比如“不要编造工具返回结果”“不要在未确认的情况下删除文件”。上下文策略上我实测“最近5轮完整更早摘要”的组合在大多数任务上效果最好。纯滑动窗口容易丢关键信息纯摘要又容易丢细节。模型选择上不是越强越好。简单任务用便宜模型复杂任务用强模型。我一般会做一个路由根据任务复杂度选择模型能省不少成本。5. 关于Harness和Agent的区别以及后续可以怎么扩展回到标题里的问题——Harness和Agent到底啥区别我现在的理解是Agent是“要做什么”Harness是“怎么做到”。Agent是任务层面的概念Harness是工程层面的实现。你说“我要一个能自动修bug的Agent”这是Agent你说“这个Agent用ReAct循环、注册了8个工具、用容器做沙箱”这是Harness。这7个子系统不是必须全部都有才能叫Harness但缺了哪个Agent的能力就会受限。缺Agent LoopAgent转不起来缺Tool SystemAgent干不了活缺Context ManagementAgent记不住事缺SandboxAgent不安全缺ObservabilityAgent出问题查不动。后续扩展方向我觉得有几个值得深挖一是多Agent协作多个Harness之间怎么通信、怎么分工二是工具的动态发现和组合让Agent能自己写工具三是记忆的长期化和个性化让Agent越用越懂你四是成本优化怎么在保证效果的前提下把token消耗降下来。我自己在实际搭Harness的过程中最大的体会是别追求一步到位先把最小闭环跑通再逐个子系统优化。我第一版Harness只有Agent Loop和Tool System连上下文管理都是简单的滑动窗口但已经能跑通不少任务了。后面遇到什么问题就补什么子系统这样迭代最扎实。
阅读完成 · 觉得有帮助?