ARTICLE DETAIL

资讯详情

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

DeepSeek-Harness:从手写Agent循环到多智能体编排的实践指南

DeepSeek-Harness:从手写Agent循环到多智能体编排的实践指南 上个月我在重构一个基于 DeepSeek-R1 的本地问答机器人时明显感觉到代码已经膨胀到失控工具调用逻辑散落在各个模块里上下文拼接全靠手写想让第二个智能体加入协作就得把主循环整个重写一遍。后来我把主循环抽出来做成了可复用的调度层朋友看了一眼说这不就是社区里说的 harness 吗。顺着这个思路我找到了 DeepSeek-Harness 这个开源 SDK也就是很多人常说的 harness-sdk它把智能体运行、技能加载、多智能体编排都收拢成了标准接口我才意识到之前那些手写编排的代码全是在重复造轮子。这篇文章聊一聊我是怎么理解它、部署它以及如何在真实项目里用它搭起多智能体协作给正在纠结“我的 Agent 代码到底该怎么组织”的朋友一份参考。1. 先从没救的手写 Agent 循环说起为什么需要 Harness 这层“调度器”1.1 亲手写一遍主循环你就会明白问题出在哪我第一次写 Agent 的时候思路非常直白既然 Agent 的核心就是一个“循环调用大模型、解析结果、执行工具、再把结果喂回去”的过程那我直接写一个 while 循环不就行了于是就有了下面这类代码。它不算错但它只是“能跑”messages [{role: system, content: SYSTEM_PROMPT}] while True: user_input input( ) messages.append({role: user, content: user_input}) while True: response llm.chat(messagesmessages, toolsTOOL_SCHEMAS) if response.tool_calls: for call in response.tool_calls: result execute_tool(call.name, call.arguments) messages.append({ role: tool, tool_call_id: call.id, content: result }) continue else: print(response.content) messages.append({role: assistant, content: response.content}) break这套代码在Demo阶段完全没问题可一旦进入真实业务你很快会发现四件事第一上下文管理完全失控。不同的工具调用会往里塞不同类型的结果有的返回 JSON有的是 Markdown 文本有的是超长日志。当这些内容混在同一段 messages 里时模型很容易被历史噪声干扰而且 Token 消耗会指数级上升。我当初没有做压缩和裁剪结果跑了半小时单轮对话的 Token 就逼近了模型窗口上限。第二工具注册逻辑重复且脆弱。每加一个新工具我得同时改 TOOL_SCHEMAS 列表、execute_tool 的分支、以及提示词里的说明三个地方有一处没同步模型就会在调用时给出错误的参数。这个“三角同步”问题在工具数量超过五个之后基本就是噩梦。第三多 Agent 协作无从下手。想让第二个 Agent 加入不是简单再开一个循环就行。你需要设计两个 Agent 之间怎么传递任务、怎么共享上下文、怎么避免它们互相覆盖状态。自己写这套消息路由难度不亚于重写一个消息队列。第四没有插件化能力。我希望 Agent 能按需加载某个技能而不是启动时把一切全部装上。手写的时候这个能力基本做不出来只能靠 if-else 硬堆。这些痛点不是代码写得烂而是我缺了一层负责“调度、状态、能力管理”的运行时。这层运行时就是 harness。1.2 Harness 解决的是什么层级的问题把 Agent 比作一个员工harness 就是公司员工负责思考和干活公司负责办公场地、流程制度、任务分发和部门协作。没有公司你再强的员工也只能单打独斗没有 harness你的 Agent 再聪明也只是一个孤立的循环。具体到代码层面harness 主要替我解决了四件事运行时的状态管理Agent 的生命周期、对话历史的维护、上下文的裁剪与压缩策略都由 harness 统一处理我不需要自己写一堆全局变量。能力加载Skill 可以做到按需加载、可插拔。某个任务需要代码检索能力就挂载对应的 Skill不需要让所有 Agent 启动时都载入全部工具。编排逻辑多个 Agent 之间怎么分工、谁先执行、结果如何回传harness 提供了标准化的消息传递机制我只需要声明协作关系不用手写路由。宿主接入通过 Plugin 机制harness 可以接到命令行、HTTP 服务、聊天平台等不同宿主上。我开发时用 CLI 跑上线时换一个 Plugin 就行核心代码不动。所以我现在的结论是如果你只是做一次性的脚本调用手写循环没问题但只要是打算把 Agent 用在真实项目里直接用 harness-sdk 这类现成运行时比什么都自己造要稳得多。2. 拆开 Harness 的肚皮Agent、Skill、Plugin 到底在干什么2.1 先把概念对齐别再把 Harness 和 Agent 混为一谈网上搜 harness 相关的内容常看到“harness 和 agent 区别”这类问题。我一开始也搞混过后来用一个类比彻底理清了。Agent 是“大脑加手脚”它负责理解任务、拆解步骤、调用工具Harness 是承载 Agent 的“工作台”它负责把任务交给合适的 Agent给 Agent 提供技能工具再把结果收回来。用一个表格对比更清楚概念角色类比典型职责Agent执行者员工调用大模型、生成推理、决定下一步动作Skill能力单元员工掌握的技能具体执行某个任务比如搜索、查库、生成图表Harness运行时与调度层公司管理生命周期、分发任务、维持状态、加载技能Plugin宿主集成办公室环境把 harness 接进 CLI、API Server、聊天平台这四层各有各的边界。Agent 不应该关心“我的技能是存在哪个目录下的 YAML 文件”那是 Skill 管理的事Skill 也不应该关心“现在对话进行到第几轮”那是 Harness 的状态管理。我见过很多项目把 Agent 写得特别“重”一个类里面既做模型调用又做上下文拼接还做工具执行最后还直接操作文件系统。这其实就是把 harness 的职责全部并进 Agent 了。早期可以跑等到要扩展的时候改动一个功能往往会牵连一大片。2.2 一次请求在 Harness 里走完了哪些流程我基于自己的使用经验把一次请求在 harness-sdk 里的流转过程整理成一条链路。虽然具体实现因版本而异但整体流程可以作为你理解框架的锚点用户请求进入之后Harness 先做任务拆解和路由判断这个任务应该交给哪个 Agent。接着 Agent 开始加载它需要的 Skill并把 Skill 的说明注入到当轮提示词里。这里有个很关键的动作Skill 不是“全量注入”而是把 Skill 的声明信息用途、参数、输入输出格式提供给模型让模型决定何时调用。模型一旦决定调用某个 SkillHarness 就接管执行过程把调用参数交给 Skill 运行运行结果再作为新的上下文回传给模型。模型根据结果决定是继续调用下一个 Skill还是结束并生成最终回复。整个过程里Agent 其实是在“思考”和“决策”真正的脏活累活都是 Skill 在执行。而 Harness 负责在中间传递消息、维护状态、清理异常。这种分层最大的好处是你可以单独替换任何一个 Skill或者让多个 Agent 共享同一个 Skill而不会让代码牵一发动全身。这里有一个我在实际使用中比较意外的点Harness 对上下文的处理比我自己写的循环要聪明得多。它不会把工具返回的内容原封不动地塞进历史里而是会根据后续是否还需要该结果决定是保留原始内容还是只保留摘要。这个能力在长对话场景里非常救命。2.3 为什么它对推理型模型特别友好我用的主力模型是 DeepSeek-R1 这类推理型模型它们的特点是会产生比较长的思维链reasoning trace而不是像普通模型那样直接给答案。这个特性让它更强但也带来了新的问题思维链本身也是 Token如果每轮都完整保留窗口消耗极快。Harness 对这种场景的处理方式我看下来是这样的它会区分“推理内容”和“最终回答”。对模型内部推理过程Harness 可能只保留必要的中间状态甚至丢弃已经结束的推理链对面向用户的最终回答才完整保留。这样既能保证模型的推理质量又不会让历史记录被思维链刷爆。另外Skill 的声明式输入输出约束对推理型模型其实很有帮助。因为推理模型擅长一步一步想而你给它的 Skill 如果定义了清晰的参数结构——比如“输入问题输出包含三个字段的 JSON”——它在推理时就不需要猜测该传什么参数而是直接按照声明来组织调用。我体感这种“显式约束”能把工具调用的成功率拉高不少。3. 本地部署与版本选型从 pip install 到锁定 v0.1.5-rc.23.1 我的安装环境与首次安装我是在一台 Ubuntu 22.04 的机器上部署的Python 用的 3.10。建议不要用系统自带的 Python 直接装而是先建一个虚拟环境避免污染全局依赖python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip接下来就是安装 harness-sdk。不同项目的安装方式略有差异我采用的是从源码克隆安装因为我需要看源码排查问题git clone https://github.com/你的目标仓库地址/DeepSeek-Harness.git cd DeepSeek-Harness pip install -e .装完之后可以用下面的命令确认版本python -c import harness; print(harness.__version__)如果你只是想在应用里调用不想折腾源码也可以直接pip install对应包名但如果你后面可能要改 Skill 加载逻辑我建议还是保留源码目录排起错来会方便很多。注意安装过程中最容易出错的是依赖冲突。harness-sdk 通常依赖 pydantic、pyyaml 这类基础库如果你之前装过别的大模型框架建议在虚拟环境里重新装一份不要图省事用全局环境。3.2 为什么有经验的人要把版本锁在 v0.1.5-rc.2网上经常看到有人问“deepseek harness 怎么退回到 v0.1.5-rc.2”一开始我不理解直到自己升级到新版踩了坑。新版本一般会调整 Skill 的加载机制比如对插件的目录结构要求更严格或者把某些默认行为改了。我遇到的情况是升级之后原来能正常加载的 Skill 全部报 failed to load plugins排查半天发现是版本里对 Plugin 入口文件的命名和位置有了新约定。所以现在我的建议很直接如果你在跑生产环境优先锁定稳定版本而不是追最新版。锁版本的操作也很简单cd DeepSeek-Harness git checkout v0.1.5-rc.2 pip install -e .或者对于 pip 安装方式直接指定版本号pip install deepseek-harness0.1.5rc2锁版本不是保守而是给“可控”留出空间。新功能可以放在测试环境里玩但线上环境稳定比新特性重要得多。3.3 装完怎么验证安装完成后不要急着写代码先跑一遍官方示例。harness-sdk 一般会带一个 examples 目录我会这么做cd examples python simple_agent.py --skill ../skills/demo如果能正常对话说明运行时和 Skill 加载链路是通的。接下来再做自己的项目。验证时重点看两点第一日志里有没有 failed to load 之类的字样第二Skill 是否真的被模型感知到了。你可以直接问模型“你有哪些技能”看它能不能回答出你刚加载的技能名。如果它答不上来说明 Skill 声明文件有问题后面我会专门讲排查链路。4. 动手搭第一个多智能体工作流文档助手 代码检索的协作4.1 场景定义与分工理论讲再多不如跑通一个实际场景。我搭的第一个多智能体工作流是这样的用户抛出一个问题如果问题涉及项目里的文档就由“文档助手”Agent 负责回答如果问题涉及代码逻辑就交给“代码检索”Agent让它搜索代码库并返回相关代码片段。这里的分工原则其实很重要不要试图让一个 Agent 什么都会而是根据能力边界切分。文档助手只需要能读取 Markdown 文件代码检索 Agent 只需要能调用 grep 或文件搜索工具。职责单一Skill 也更稳定。4.2 编写两个 Skill 的最小实现Harness 里的 Skill 通常是一个独立的目录目录里包含声明文件和实现文件。我习惯用这样的结构skills/ ├── doc_reader/ │ ├── skill.yaml │ └── main.py └── code_search/ ├── skill.yaml └── main.pyskill.yaml 里最关键的是声明这个 Skill 的用途和参数因为模型要根据这份声明决定什么时候调用它。一个最小化的声明长这样name: doc_reader description: 读取本地 Markdown 文档内容适用于回答文档相关问题时使用 inputs: doc_path: type: string description: 要读取的文档路径 outputs: content: type: string description: 文档文本内容main.py 里就是普通 Python 函数的实现负责真正读文件import sys def run(doc_path: str) - dict: with open(doc_path, r, encodingutf-8) as f: content f.read() return {content: content} if __name__ __main__: # harness 会传入 JSON 格式的参数 import json args json.loads(sys.argv[1]) result run(**args) print(json.dumps(result))另一个 code_search 的 Skill 同理只是把 read 换成了 grep 或路径遍历。我在这两个 Skill 里都用到了同一个经验不要自己定义一套复杂的参数协议直接用 JSON 字符串进出。这样 Harness 调用你的时候只需要把参数字典传给命令行返回结果也是 JSON解析成本最低。4.3 在配置里把两个 Agent 编排起来Skill 写好了接下来是配置 Agent 和编排逻辑。我用的方式是在一个 YAML 配置文件里声明两个 Agentagents: doc_assistant: model: deepseek-r1 skills: - doc_reader system_prompt: 你是文档助手只负责回答文档相关问题。 coder_assistant: model: deepseek-r1 skills: - code_search system_prompt: 你是代码检索助手只负责回答代码逻辑相关问题。然后建立一个入口脚本把用户输入交给 Harness 路由from harness import Harness harness Harness( agents_configagents.yaml, skills_dirskills, ) def handle(user_input: str): # 简单路由包含“代码”字样就交给 coder_assistant agent_name coder_assistant if 代码 in user_input else doc_assistant response harness.run(agent_nameagent_name, user_inputuser_input) return response if __name__ __main__: while True: q input( ) if q.strip() exit: break print(handle(q))实际跑起来之后我观察到模型确实按照 system_prompt 的约束在做决策问“用户注册流程是什么”会触发 doc_reader问“注册接口的校验逻辑在哪个文件”会触发 code_search。而且两个 Agent 可以各自维持自己的上下文不会互相污染。这个案例虽然简单但它证明了最关键的一件事多智能体编排的本质是把不同的能力边界用声明式配置切分清楚而不是在代码里写一堆 if-else。5. 踩坑实录failed to load plugins 的完整排查链路5.1 现象与现场日志我遇到过最典型的报错就是这个ERROR: failed to load plugin: doc_reader更让人崩溃的是这个报错不会告诉你具体是哪一行出的问题。第一次遇到时我以为是 Skill 代码写错了但 main.py 单独运行明明没问题。这种“单独跑正常放进框架里就报错”的故障最考验排查思路。5.2 排查路径从路径到版本我一步步做了什么我总结了完整的排查链路建议你也按这个顺序走第一步查路径。Harness 加载 Skill 时会从配置的 skills_dir 目录里扫描子目录。如果目录结构不对——比如多套了一层嵌套文件夹——它就会找不到 skill.yaml。我先在项目根目录跑find . -name skill.yaml确认所有声明的 Skill 文件都在预期的位置。第二步查声明文件语法。skill.yaml 里的字段名必须和框架要求的完全一致。我踩过的坑是把description写成了desc框架解析时读不到描述字段直接判定该 Skill 无效。用 yaml 解析工具本地校验一遍python -c import yaml; datayaml.safe_load(open(skills/doc_reader/skill.yaml)); print(data.keys())第三步查入口文件名称和可执行性。harness 会发现 skill.yaml 后按约定去加载 main.py。如果 main.py 没有run函数或者 run 函数不是可调用对象插件加载就会失败。我用一行命令验证python -c import sys; sys.path.insert(0, skills/doc_reader); import main; print(callable(main.run))第四步查依赖缺失。Skill 的 main.py 里如果 import 了一个虚拟环境里没装的库加载时会抛 ImportError但 harness 把它包装成了 failed to load。这一步比较容易漏。解决方法是在虚拟环境里手动执行一遍 main.py看有没有 import 报错python skills/doc_reader/main.py {doc_path: README.md}第五步查版本差异。如果以上四步都正常那就考虑是不是 SDK 版本改动导致的兼容性问题。我那次最后就是在这一步定位到的新版本要求 Skill 的入口文件是entry.py而不是main.py我改个文件名就好了但更稳妥的方案还是直接回到了 v0.1.5-rc.2。5.3 修复方案与后续预防结合我几次踩坑经验给你一张排查对照表症状排查重点常见原因failed to load plugins路径skill.yaml 位置不在扫描目录内模型感知不到 Skillskill.yaml 字段description 字段缺失或字段名不一致加载时报 ImportErrorskill 内部依赖虚拟环境缺少第三方库升级后全部 Skill 失效版本兼容新版改动了入口文件约定或插件协议后续预防我有几条习惯一是每次改完 Skill 先单独验证 run 函数再放进 harness二是不用最新的 SDK 版本跑生产三是写一个简单的测试脚本启动时打印已加载的 Skill 列表。不要小看这个测试脚本它能帮你把问题挡在进业务之前。6. 把 Harness 接进现有工程以及我踩过版本坑之后的体会6.1 扩展点与外部 SDK 和自定义 Skill 的集成思路Harness 的价值不只在它自身的能力还在于它是一个开放的扩展框架。我看到有开发者把阿里云的 Skill 封装成可以识别的组件也有人在研究怎么跟 Claude Code SDK 之类的现有工具链联动。从我自己的实践来看接入外部能力最通用的方式就是把外部 SDK 封装成一个 Skill。假如你有一个内部的接口服务只要在 main.py 里调用对应的 SDK 或 HTTP 客户端然后把结果包装成 JSON 返回Harness 就可以通过 skill.yaml 的描述让模型自动发现并使用。同样地你也可以做一个 HTTP 插件把 Harness 本身暴露成 API这样前端或者别的服务就能通过 REST 调用来驱动你的 Agent 集群。这个扩展模式我认为是 harness-sdk 最值得投入时间理解的部分它不强绑定任何一家模型或工具核心就是“技能声明 标准输入输出”。你手上有多少业务能力都可以用统一的描述语言塞进去。6.2 我的维护建议和版本管理习惯走到这一步我已经在几个真实的内部工具里用到了 harness-sdk。有一个印象很深的体感把原来 800 多行的手写 Agent 逻辑重构到 harness 上之后核心代码缩减到不到 200 行而且逻辑清晰很多。排错的时候不再需要翻上下文拼接的代码直接看 Skill 的声明和实现就行。最后给你三条我的个人建议第一锁定版本。不要追新尤其不要在周五下午升级。我现在所有项目的依赖文件里都明确写死了版本号。第二Skill 要做单测。Harness 本身只是调度框架真正干活的是 Skill。给每个 Skill 的 run 函数写一个简单测试会省下大量联调时间。第三日志要留全。Harness 的日志会打印出模型调用、Skill 加载、工具返回等关键节点出问题时先把日志级别调到 DEBUG很多看似诡异的问题都能从日志里找到答案。我自己经历过从手写循环到 harness 的重构这个过程的感触是Agent 开发真正成熟的标志不是代码写得多么精巧而是框架边界足够清晰、能力可以自由拆装。希望这篇分享能帮你在“用 harness 还是手写”之间做出更明确的判断。
返回列表