ARTICLE DETAIL

资讯详情

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

AI Agent Harness 七子系统:从零搭建稳定智能体骨架

AI Agent Harness 七子系统:从零搭建稳定智能体骨架 1. 拆开 AI Agent 的“驾驶舱”Harness 到底管什么很多人第一次听到 Harness 这个词脑子里浮现的是汽车线束或者测试框架。放在 AI Agent 的语境里它其实更接近“驾驶舱”或者“总装线”——模型是发动机工具是车轮记忆是油箱而 Harness 是把这些东西串起来、让 Agent 真正跑起来的那套骨架。你单独拿一个 LLM 出来它只能聊天你给它挂上工具、加上循环、塞进上下文管理它才开始“干活”。这中间的胶水层、调度层、状态层合起来就是我理解的 Harness。我接触过不少团队做 Agent早期都容易犯一个错把注意力全放在 Prompt 调优和模型选型上结果 Demo 很惊艳一上真实任务就崩。问题往往不出在模型而出在 Harness 没搭好。模型再强如果循环控制写死、工具返回没做归一化、上下文无限膨胀跑三轮就开始胡言乱语。所以这篇文章我想把 Harness 拆成 7 个子系统来讲每个子系统解决什么问题、为什么这么切、实操中怎么落地尽量说透。这 7 个子系统不是某个框架的官方定义而是我在多个 Agent 项目里反复验证后总结出的一套分层方式。它覆盖了从“模型怎么被调用”到“任务怎么被拆解”再到“结果怎么被验证”的完整链路。适合正在从 0 到 1 搭建 Agent 的开发者也适合已经有一个能跑的 Demo、但想把它做成稳定产品的团队。读完你至少能判断自己手头的 Agent 缺的是哪一块以及补这块大概要花多少功夫。2. 七个核心子系统逐个拆解2.1 Agent Loop整个 Harness 的心跳Agent Loop 是 Harness 里最核心的子系统没有之一。它决定了 Agent 是“一问一答”还是“持续行动”。最简单的 Loop 长这样接收用户输入拼上下文调模型解析输出如果输出里有工具调用就执行工具把结果塞回上下文再调模型直到模型输出最终答案或者达到最大轮次。听起来简单但坑非常多。第一个坑是终止条件。我见过太多 Agent 因为终止条件写得太松陷入无限循环烧掉大量 token 还出不来。常见的做法是设最大轮次比如 10 轮或 15 轮超过就强制中断并返回当前状态。但光有轮次限制不够还要有“无进展检测”——如果连续两轮工具调用返回的结果高度相似或者模型输出开始重复就应该主动打断。第二个坑是错误处理。工具执行失败时是把错误信息原样塞回上下文还是包装成结构化错误我的经验是包装成结构化错误包含错误类型、错误信息、建议的重试方式。这样模型下一轮能更好地决策而不是被一堆堆栈信息搞晕。第三个坑是并发。有些任务可以并行调多个工具但 Loop 本身如果是串行的就会浪费大量时间。可以在 Loop 内部加一个并发调度层把无依赖的工具调用并行化但要注意结果合并的顺序和一致性。# 一个简化但可用的 Agent Loop 骨架 def agent_loop(task, max_turns12): context build_initial_context(task) for turn in range(max_turns): response llm_call(context) if response.is_final: return response.content tool_calls parse_tool_calls(response) if not tool_calls: context.append(response.content) continue results execute_tools(tool_calls) context.append(format_tool_results(results)) if no_progress(context): return 任务未完成已中断 return 达到最大轮次注意最大轮次不是越大越好。我实测下来大部分任务 8 到 12 轮足够超过 15 轮还没结果基本是任务定义或工具设计有问题继续跑只是烧钱。2.2 LLM Integration别把模型当黑盒LLM Integration 这个子系统管的是“怎么跟模型说话”。很多人以为这就是调个 API其实远不止。首先是模型选择不同任务适合不同模型。推理密集型任务用强推理模型格式转换类任务用轻量模型就够。我通常会在 Harness 里做一个模型路由层根据任务类型、上下文长度、成本预算动态选模型。其次是 Prompt 组装。System Prompt、历史消息、工具定义、当前任务这几块怎么拼、顺序如何、各自占多少 token都会影响效果。我的经验是把工具定义放在 System Prompt 之后、历史消息之前这样模型在生成时能优先看到可用工具。历史消息要做截断或摘要不能无限堆。上下文窗口再大也有上限而且越长越贵、越慢、越容易丢关键信息。第三个点是输出解析。模型返回的文本要解析成结构化动作比如工具调用、最终答案、追问。解析器要足够鲁棒能处理模型偶尔的格式偏差。我一般会要求模型输出 JSON但同时在解析层做容错比如用正则兜底、用 JSON 修复库处理不完整 JSON。还有一个细节是流式输出如果前端需要实时展示Harness 要支持流式解析不能等整个响应结束再处理。# 模型路由的简化逻辑 def select_model(task_type, context_length, budget): if task_type reasoning and budget 0.5: return strong-reasoning-model if context_length 8000: return long-context-model return lightweight-model2.3 Tool Registry工具不是越多越好Tool Registry 管的是“Agent 能用哪些工具、怎么用”。我见过最夸张的一个项目给 Agent 挂了 40 多个工具结果模型选择困难经常调错。工具数量超过 15 个之后选择准确率会明显下降。所以我的建议是分层核心工具常驻扩展工具按需加载。每个工具的定义要包含名称、描述、参数 schema、返回值格式、错误码。描述要写得像给新人看的文档说清楚“什么时候用这个工具”“输入什么”“输出什么”。参数 schema 用 JSON Schema 定义方便模型理解也方便做校验。返回值格式要统一比如都返回{status, data, error}结构这样 Loop 层处理起来一致。工具执行层要做超时控制、重试、熔断。外部 API 不稳定是常态不能让一个工具卡死整个 Agent。我通常给每个工具设 10 到 30 秒超时失败重试 1 到 2 次连续失败就熔断并返回结构化错误。还有一个容易被忽略的点是工具权限有些工具只能读、有些能写、有些能删Harness 要在执行前做权限校验避免 Agent 误操作。工具类型典型数量加载方式超时建议核心工具5-8 个常驻10-15 秒扩展工具10-20 个按需20-30 秒危险工具1-3 个二次确认30 秒以上2.4 Memory Context让 Agent 记住该记的Memory 子系统解决的是“Agent 怎么记住东西”。短期记忆就是当前会话的上下文长期记忆是跨会话的知识。短期记忆的管理核心是“什么该留、什么该丢”。我的做法是分层最近 N 轮完整保留更早的做摘要再早的只保留关键实体和结论。摘要不是简单截断而是用模型生成一段压缩后的上下文。比如把前 10 轮对话压缩成 200 字的关键信息。这样既保留了语义又控制了 token。长期记忆可以用向量库存储检索时按相似度召回。但要注意召回的内容要经过相关性过滤不能一股脑塞进上下文否则会干扰模型判断。还有一个实践是“工作记忆”和“情景记忆”分开。工作记忆是当前任务的临时状态任务结束就清空情景记忆是历史任务的记录可以跨任务复用。我通常会在 Harness 里维护一个任务状态对象记录当前目标、已完成步骤、待办事项、关键发现。这个对象每轮更新作为上下文的一部分传给模型。提示上下文不是越长越好。我实测发现超过 6000 token 的上下文模型对中间部分的注意力会明显下降。关键信息要放在开头或结尾中间放次要内容。2.5 Planning Decomposition把大任务拆成小步骤Planning 子系统管的是“Agent 怎么把复杂任务拆开”。没有 Planning 的 Agent 就像无头苍蝇东一榔头西一棒子。常见的 Planning 方式有三种一次性规划、逐步规划、混合规划。一次性规划是让模型先输出完整步骤列表然后按步骤执行。优点是全局清晰缺点是如果第一步就错了后面全错。逐步规划是每轮只决定下一步做什么灵活但容易迷失方向。混合规划是我最常用的先让模型输出一个粗粒度计划比如 3 到 5 个大步骤然后每个大步骤内部再逐步细化。拆解的时候要注意粒度。太粗了执行不了太细了效率低。我的经验是每个子任务应该能在 1 到 3 轮内完成超过 3 轮就继续拆。子任务之间要有明确的依赖关系能并行的并行不能并行的串行。还要有回退机制如果某个子任务失败能回到上一步重新规划。# 混合规划的简化实现 def plan_task(task): coarse_plan llm_call(f把任务拆成3-5个大步骤{task}) steps parse_steps(coarse_plan) for step in steps: fine_plan llm_call(f细化这个步骤{step}) execute_step(fine_plan) if step_failed(): replan()2.6 Execution Sandbox让 Agent 安全地动手Execution 子系统管的是“Agent 怎么真正执行动作”。这包括代码执行、文件操作、API 调用、浏览器操作等。核心问题是安全。Agent 执行代码时不能让它直接跑在宿主机上必须放在沙箱里。沙箱要限制网络访问、文件系统访问、CPU 和内存使用。我通常用容器做沙箱每个任务一个独立容器任务结束就销毁。容器内预装常用依赖但禁止访问宿主机文件系统。网络访问要白名单控制只允许访问必要的 API。执行结果要捕获 stdout、stderr、退出码结构化返回给 Loop 层。文件操作也要小心。Agent 写文件时要限制在指定工作目录内不能越界。删除操作要二次确认或者先移到回收站。API 调用要加限流和重试避免把外部服务打挂。还有一个细节是执行日志每一步操作都要记录方便排查问题和审计。执行类型沙箱方案限制项日志级别代码执行容器CPU/内存/网络DEBUG文件操作工作目录隔离路径白名单INFOAPI 调用代理层限流/重试INFO浏览器操作无头浏览器域名白名单DEBUG2.7 Evaluation Feedback让 Agent 知道自己干得怎么样Evaluation 子系统管的是“怎么判断 Agent 干得好不好”。没有评估的 Agent 就像没有考试的学校不知道学生学没学会。评估分两层过程评估和结果评估。过程评估看每一步是否合理比如工具选择对不对、参数传得对不对、有没有绕弯路。结果评估看最终输出是否满足要求比如任务完成度、准确性、格式合规性。我通常会用规则加模型的方式做评估能用规则判断的用规则比如格式校验、关键词匹配规则判断不了的用模型比如语义正确性、逻辑一致性。反馈要能影响后续行为。如果评估发现某一步错了要能触发重试或重新规划。如果整体结果不达标要能给出改进建议。评估结果还要记录下来用于后续优化 Prompt、调整工具、改进规划策略。我一般会维护一个评估日志记录每次任务的成功率、平均轮次、常见错误类型定期复盘。# 简单的评估逻辑 def evaluate(task, result, trace): format_ok check_format(result) semantic_ok llm_judge(task, result) efficiency len(trace) / expected_turns return { format: format_ok, semantic: semantic_ok, efficiency: efficiency, passed: format_ok and semantic_ok }3. 从零搭一个最小可用 Harness 的实操路径3.1 环境准备与依赖选型搭 Harness 不需要一开始就上重型框架。我的建议是先用手写 Python 把核心 Loop 跑通再逐步引入组件。Python 版本用 3.10 以上依赖主要几个openai或对应模型 SDK、pydantic做数据校验、httpx做异步 HTTP、dockerSDK 做沙箱、chromadb或qdrant做向量存储。目录结构我习惯这样分core/放 Loop 和调度llm/放模型集成tools/放工具定义和执行memory/放记忆管理planning/放规划逻辑sandbox/放沙箱eval/放评估。每个模块对外暴露清晰接口模块之间通过事件或消息传递降低耦合。配置管理用 YAML 或环境变量把模型密钥、超时时间、最大轮次、沙箱镜像这些可调参数外置。这样换模型、调参数不用改代码。日志用结构化日志每条记录包含时间戳、模块、级别、任务 ID、轮次、耗时方便后续分析。# config.yaml 示例 llm: default_model: lightweight-model reasoning_model: strong-reasoning-model max_tokens: 4096 loop: max_turns: 12 no_progress_threshold: 2 sandbox: image: agent-sandbox:latest timeout: 30 memory_limit: 512m3.2 核心 Loop 的编码与调试先写一个最简 Loop只支持文本输入输出不挂工具。跑通之后加一个 echo 工具测试工具调用链路。再加一个计算器工具测试参数解析和结果回传。每加一个功能都写一个对应的测试用例确保回归时不会破坏已有功能。调试的时候把每轮的上下文、模型输出、工具调用、工具结果都打印出来。我通常会写一个 trace 查看器把整个执行链路可视化。这样出问题时能快速定位是哪一轮、哪个环节出的错。常见问题包括模型不按格式输出、工具参数解析失败、上下文超长、循环不终止。# 最小 Loop 的调试版本 def debug_loop(task): context [{role: user, content: task}] for turn in range(5): print(f--- Turn {turn} ---) print(fContext length: {len(str(context))}) response llm_call(context) print(fResponse: {response}) context.append({role: assistant, content: response}) if FINAL in response: break return context3.3 工具接入与沙箱配置工具接入从最简单的开始一个读文件的工具、一个写文件的工具、一个执行 shell 命令的工具。每个工具定义好 schema写好执行函数注册到 Tool Registry。执行函数里加超时和异常捕获返回统一格式。沙箱用 Docker 起一个容器把工作目录挂载进去限制网络和资源。执行命令时通过 Docker API 发送捕获输出。容器可以复用但每个任务结束后要清理临时文件。如果任务涉及敏感操作可以每个任务起一个新容器用完即销毁。# 工具注册示例 def register_tool(name, description, schema, func): TOOLS[name] { description: description, schema: schema, func: func } register_tool( read_file, 读取指定路径的文件内容, {path: {type: string}}, lambda path: open(path).read() )3.4 记忆与规划模块的集成记忆模块先做短期记忆用列表存消息超过阈值就做摘要。摘要用模型生成把最早的一批消息压缩成一段话。长期记忆可以后面再加先用文件或 SQLite 存任务记录。规划模块先做一次性规划让模型输出步骤列表然后按步骤执行。执行过程中如果某步失败记录失败原因继续下一步或中断。等一次性规划跑稳了再升级到混合规划。集成的时候注意模块之间的数据流。Loop 从 Memory 拿上下文从 Planning 拿下一步动作从 Tool Registry 拿工具定义从 Sandbox 拿执行结果从 Evaluation 拿反馈。每个模块只做自己的事通过清晰的接口交互。4. 实操中最容易踩的坑与排查手册4.1 循环不终止的三种典型场景第一种是模型一直调用工具但不给最终答案。这通常是因为 Prompt 里没明确要求“完成后输出 FINAL”或者工具返回的结果让模型觉得还需要继续查。解决办法是在 System Prompt 里强调终止条件并在 Loop 层加轮次限制。第二种是工具调用失败后模型反复重试同一个工具。这通常是因为错误信息没给够模型不知道该怎么改。解决办法是把错误信息结构化包含错误类型和建议让模型能调整参数或换工具。第三种是模型输出格式不对解析器一直解析失败。这通常是因为 Prompt 里的格式要求不够明确或者模型能力不够。解决办法是加 few-shot 示例或者在解析层做容错实在解析不了就当作普通文本处理。现象可能原因排查方法解决方案一直调工具终止条件不明确看 System Prompt加 FINAL 要求反复重试错误信息不足看工具返回结构化错误解析失败格式要求模糊看模型输出加示例/容错上下文超长记忆没压缩看 token 数加摘要层4.2 工具调用失败的排查思路工具调用失败分几种参数解析失败、执行超时、执行报错、权限不足。参数解析失败通常是 schema 定义和模型输出不匹配检查 schema 是否太复杂或者模型是否理解不了。执行超时看工具本身耗时加超时或优化工具实现。执行报错看错误日志定位是工具代码问题还是外部依赖问题。权限不足检查沙箱配置和工具权限设置。我一般会做一个工具调用日志记录每次调用的输入、输出、耗时、状态。出问题时按任务 ID 查日志能快速定位。还有一个技巧是给工具加 dry-run 模式先不真正执行只校验参数确认没问题再实际执行。4.3 上下文爆炸的预防与处理上下文爆炸是 Agent 跑长任务时的常见问题。预防手段有几个一是每轮结束后检查 token 数超过阈值就触发摘要二是工具返回结果做截断只保留关键信息三是历史消息分层最近几轮完整保留更早的摘要再早的只留实体。处理已经爆炸的上下文可以用模型做一次压缩把整个上下文压缩成一段简短描述然后重新开始。但这样会丢失细节所以最好还是预防为主。我通常会在 Harness 里设一个 token 预算比如 8000 token超过就自动触发压缩。注意压缩上下文时任务目标、当前状态、关键发现这三类信息必须保留其他可以丢。丢了这三类Agent 会迷失方向。4.4 评估结果不稳定的应对评估结果不稳定通常是因为评估标准模糊或者模型评估本身有波动。解决办法是把评估标准量化能用规则判断的不用模型。比如格式校验用正则关键词匹配用字符串包含只有语义判断才用模型。模型评估时加多次采样取多数或者用更强的模型做评估。还有一个问题是评估和实际需求脱节。评估通过了但用户不满意说明评估指标没覆盖真实需求。这时候要回头调整评估标准把用户反馈纳入评估体系。我一般会定期人工抽检评估结果校准评估标准。5. 关于 Harness 设计的一些个人体会Harness 这个东西说到底是把 Agent 从“玩具”变成“工具”的关键。模型能力再强没有好的 Harness也只能做 Demo。我见过太多团队在模型上砸钱却在 Harness 上省功夫结果产品化阶段寸步难行。七个子系统里我觉得最重要的是 Agent Loop 和 Evaluation。Loop 是骨架Evaluation 是眼睛。没有 LoopAgent 动不起来没有 EvaluationAgent 不知道自己动得对不对。其他子系统可以逐步完善但这两个必须一开始就设计好。还有一个体会是Harness 的设计要“可观测”。每一步在做什么、为什么这么做、结果如何都要能追踪。这样出问题能排查优化有依据。我通常会把 trace 做成可视化任务执行完能回放整个链路像看录像一样。最后说一个细节Harness 的配置要外置不要硬编码。模型、超时、轮次、沙箱参数这些都要能通过配置文件或环境变量调整。这样换环境、调参数不用改代码部署和实验都方便。我踩过这个坑早期把参数写死在代码里后来调一次参数就要重新部署效率极低。
返回列表