十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Agent Runtime与Agent Harness:彻底分清执行底座与编排控制层

Agent Runtime与Agent Harness:彻底分清执行底座与编排控制层 做 Agent 项目的朋友估计都被 Agent Harness 和 Agent Runtime 这两个词绕晕过同样是“Agent 相关的东西”为什么有人天天问“你基于哪个 Runtime”有人却在说“我自己写了个 Harness”更麻烦的是不同框架文档里的用法还不一样有的把 Harness 说成 Agent 执行器有的把 Runtime 直接当成模型调用客户端。这篇文章不绕概念直接站在工程落地角度把两者的边界、职责、代码形态、排查方式一次说清楚。先说结论Agent Runtime 是 Agent 代码真正执行的环境和基础设施负责模型通信、工具调用、记忆读写、生命周期内的进程与资源管理Agent Harness 则是跑在 Runtime 之上、控制 Agent 行为节奏的编排层它决定 Agent“下一步该推断还是该调用工具、什么时候算完成、异常了怎么纠正”。你可以把 Runtime 理解成摄影棚里的灯光、摄像机和场地把 Harness 理解成导演手里的分镜本——两部戏可以共用一个摄影棚但分镜本完全不同。1. 先把定义钉死两个词到底在解决什么问题1.1 一个马上能记住的类比电影棚和导演我给人讲这个概念时最喜欢用拍电影来比。Runtime 是整个拍摄现场的基础设施摄影机、灯光、录音设备、场务调度它们保证“只要演员站在那里就能被拍下来”。Harness 则是导演手上的剧本分镜、走位要求和喊停规则它决定演员先说哪句台词、走到哪个机位、什么情况下这场戏可以收工。落到 Agent 工程里Runtime 负责“能跑”。模型接口是否可用、工具函数能否在安全容器里执行、上一步的对话历史能不能被读出、运行时的指标和日志能不能被采集这些都是 Runtime 的活。Harness 负责“怎么跑”。先让模型生成一段推理还是先调用检索接口拿到工具返回后要不要再让模型分析一轮已经重复三次还没结果时是否要强行结束这些都是 Harness 的活。这个区分并不是某些框架发明的而是软件工程里“基础设施”和“业务流程”分层思想在 Agent 领域的自然延伸。只要你写过异步任务应该能感受到类似的逻辑消息队列是 Runtime任务编排状态机是 Harness数据库是 Runtime事务脚本是 Harness。1.2 为什么那么多教程把这两个词混着用说句公道话这不能全怪初学者因为不同框架对术语的处理确实不同。有的框架把 Agent Runtime 做成了一个很薄的服务端只处理模型调用和工具执行有的框架则把 Harness 封装成完整的 Agent 类内置了 ReAct 循环、策略注入和记忆压缩文档甚至不出现 Harness 这个词。正因为框架层面的抽象层级不统一才导致“同样的词在不同人嘴里根本不是同一个东西”。更大的混淆来源是早期很多 Agent Demo 是单体脚本运行时环境和控制循环写在同一份代码里。比如 AutoGPT 早期的实现主循环、提示词拼接、文件操作全在一个进程里跑后来社区才逐渐把它拆成不同的组件。在这种“先能用、后分层”的历史背景下你当然很难说清哪个部分属于 Harness、哪个部分属于 Runtime。但从工程化角度分层迟早要做。原因很简单如果你只是想展示一个 Agent Demo单体代码无所谓如果要做多租户、灰度发布、稳定观察和权限隔离你就必须知道哪部分能力应该下沉为公共运行环境哪部分策略应该保留为每个业务可控的编排层。后面所有内容都围绕这个“分层必要性”展开。2. Agent RuntimeAgent 真正“跑”起来的底座2.1 Runtime 不等于“模型 API 封装”它的范围更宽很多人第一反应是Runtime 不就是那个调用 GPT 接口的客户端吗这理解太窄了。模型调用只是 Runtime 的一个子能力完整的 Agent Runtime 至少应该提供四类能力。第一是模型访问抽象。你的业务代码不该写死“openai.ChatCompletion.create”而应该面向一个统一接口能随时从某通义模型切到某个开源私有化模型。第二是工具执行能力。模型输出一个call_search_engine(query...)的指令后Runtime 需要真正把这个指令落到可信环境里执行并且拿到结构化结果返回给上层。第三是状态与记忆能力。Agent 每一轮的消息历史、内部状态、外部记忆向量都需要 Runtime 管理读写和过期策略。第四是可观测性和容错。调用模型失败要重试工具执行超时要熔断每步的 token 和耗时要有 trace这些都是 Runtime 该提供的基础设施能力。你可以回忆一下自己写单体 Agent 时最烦的事切换模型供应商要动业务代码某个工具有毛病把整个进程打挂多轮内存一长上下文爆掉没人处理。这些问题全部指向同一个根源——你把业务控制逻辑和底层执行能力耦合在一起了。引入 Runtime 这一层之后业务控制逻辑只用关心“要什么”不用关心“去哪里取、怎么取更稳”。2.2 Runtime 接口设计的重要原则用名词定义能力不要用流程定义能力我在看开源项目 Runtime 接口时会特别留意一个细节它暴露出来的方法到底是稳定能力还是临时流程。稳定能力比如chat(messages, tools)、run_tool(name, args)、save_recall(text)、retrieve_recall(query)这些可以作为 Runtime 接口。而execute_step(observation)这种带状态推进语义的接口通常更适合放在 Harness 里因为“执行一步”本身就是一种控制策略。用名词定义能力还有个好处可替换性更强。比如今天底层模型不支持 Tool Calling只要模型访问抽象层能把用户指令改写为“请输出JSON格式工具调用”上层 Harness 就不必大改今天工具执行环境从本地 subprocess 改成远端容器只要run_tool接口的入参和返回值不变Harness 也无感知。这就是“底座”该有的样貌。下面给一个最小 Python 接口示意帮助你感受 Runtime 抽象# runtime.py class AgentRuntime: def __init__(self, model_client, tool_registry, memory_store): self.model_client model_client self.tool_registry tool_registry self.memory_store memory_store def chat(self, messages, toolsNone, **kwargs): 调用底层模型并返回完整响应不做决策 return self.model_client.complete(messages, toolstools, **kwargs) def run_tool(self, name, args, timeout10): 在受控环境执行工具并返回结构化结果 executor self.tool_registry.get(name) return executor.run(args, timeouttimeout) def save_memory(self, text): return self.memory_store.save(text) def retrieve_memory(self, query, top_k5): return self.memory_store.search(query, top_ktop_k)看到这里你可能会说这不就是普通函数封装吗对其实就是这么简单。真正复杂的 Runtime 当然还有调度队列、容器隔离、限流和密钥管理但在逻辑上它对外只承诺“我能稳定执行这些原子能力”。原子能力之上如何组合不是 Runtime 的职责。2.3 什么时候该从框架 Runtime 迁到自研 Runtime如果你是刚开始做 Agent 原型的个人开发者建议直接用现成的 Runtime。Python 里可以用 LangChain 的 LCEL 和工具抽象也可以直接用轻量函数实现底层的调用封装端侧可用 Ollama 这类模型运行时它们已经帮你把模型请求标准化了。真正需要自研 Runtime 的信号通常是下面几类你需要对工具执行做更强的安全隔离比如 Agent 生成的要执行的代码不能直接subprocess跑在业务容器里而要进入沙箱容器你需要把 Agent 的调用能力开放给多个业务方每个业务方的模型流量、密钥、限流规则各不相同你需要把记忆存储从“简单的列表保存”升级成多租户隔离向量库并且要求底层存储可运维、可备份、可审计你需要极致的可观测性每次模型调用和工具调用都必须有完整 tracetoken 成本要按客户维度拆分。这些需求出现时如果你依然在 Harness 代码里到处调用模型 SDK、直接操作工具函数那架构基本是一盘散沙。先把 Runtime 能力抽出来再谈上层编排才有的放矢。3. Agent Harness真正控制“智能化行为”的编排层3.1 Harness 的核心是那个循环不是模型本身大模型本身只是一个“单步推理器”给它一段上下文它返回一段文本或一个工具调用。真正让 Agent 持续工作、能分多步完成任务的那个机制是 Harness 里的循环逻辑。经典的 ReAct 循环可以概括为观察当前问题 - 让模型思考 - 如果模型要调用工具就执行 - 把工具结果返回给模型 - 再观察 - 直到模型给出最终答案。Harness 就是这段循环的载体。循环不是越高深越好但必须满足工程要求最大步数、终止条件、异常恢复、历史裁剪。我见过不少项目把 Agent 写得神乎其神最后跑崩的原因却特别低级没有限制最大步数工具调用结果异常后没有让模型感知到或者模型已经连续三次给出同一种错误工具调用却没有打断机制。这些都不是模型能力问题而是 Harness 没有把“套路”写扎实。一个健壮 Harness 的控制伪代码# harness.py class Harness: def __init__(self, runtime, policy, max_steps10): self.runtime runtime self.policy policy self.max_steps max_steps def run(self, task): history [] observation task for step in range(self.max_steps): # 策略层先判断是否可以收手 if self.policy.should_stop(observation, history): return self.policy.build_final_answer(observation, history) # Runtime 负责让模型“想一步” response self.runtime.chat( messagesself.policy.build_messages(observation, history), toolsself.policy.get_tools() ) if response.has_tool_call(): # 执行工具这一步走 Runtime result self.runtime.run_tool( response.tool_call.name, response.tool_call.args ) observation {tool_result: result} else: # 没有工具调用说明模型想直接给答案 if self.policy.is_final(response.text): return response.text observation {agent_message: response.text} history.append({ step: step, response: response, observation: observation, }) raise HarnessTimeoutError(f超过 {self.max_steps} 步仍未得出最终答案)注意这个伪代码里runtime.chat和runtime.run_tool几乎没有业务语义它们只是被动执行而循环次数、什么时候停止、历史怎么传给模型、要不要把上一步的工具结果转成一句话全是 Harness 在管。这就是两者各自的位置。3.2 Harness 同时装着提示词策略和人类规则Harness 不只是写循环它还承载了很多“规则”。同一个 Runtime接不同 Harness可以做出完全不同的 Agent 性格和行为。比如客服 Agent 的 Harness 里会内置“先查订单再安抚用户实在解决不了就转人工”的策略代码助手 Agent 的 Harness 会内置“修改文件前先展示 diff确认后才写入”的审批动作数据分析 Agent 的 Harness 会在模型生成 SQL 之后强制执行“只读检查”禁止DELETE和UPDATE开头。这些策略放不到 Runtime 里因为 Runtime 不知道上层业务目标。但它们非常适合放在 Harness 里通过显式的 Policy 类注入。我在上面代码里写了self.policy就是这个意思。Policy 可以封装系统提示词、工具列表、停止规则、结果校验甚至人工审批回调。把策略从循环代码里拆出来之后测试和复用都方便很多。还有一点容易被忽略错误处理策略也属于 Harness。比如工具调用返回了“权限不足”Harness 要决定是直接反馈给模型让它换个方式还是终止任务并通知管理员。Runtime 只能告诉你工具抛了异常不能替你决定下一步怎么办。这是“编排放控制层”和“执行层”最本质的区别。3.3 开源框架里的 Harness 长什么样为了让你能对号入座我列几个常见框架里“Harness 角色”的具象化框架典型类型对应内容LangGraph图状态机Agent 节点、工具节点、条件边、循环限制CrewAICrew / Agent / Task任务编排、Agent 角色提示词、流程模式AutoGenGroupChatManager对话调度、发言顺序、终止条件Semantic KernelKernel / Planner规划器与函数调用循环自研系统Harness / Controller主循环、策略注入、中断恢复拿 LangGraph 来说StateGraph里的add_node、add_edge本身就是 Harness 逻辑的高度抽象而实际执行invoke model或call tool的是 Runtime 层。很多人误以为 LangGraph 等于 Runtime其实它更偏 Harness真正的底层模型调用还是由 LangChain 的 ChatModel 或者独立 SDK 完成。当你理解了“LangGraph 更像 Harness”之后就不会再犯一个典型错误试图在 LangGraph 里塞过多底层连接池、超时重试等基础设施逻辑。那些逻辑应该在节点内部封装的 Runtime 客户端里否则整个图会变成一张又大又脆的蜘蛛网。4. 两者边界一图流按职责对照与协作流程拆解4.1 高频对比速查表下面这张表可以保存下来当速查卡每次边界模糊时就拿出来对一遍对比维度Agent RuntimeAgent Harness定位执行基础设施控制编排层回答的问题“能不能跑、稳不稳”“下一步做什么、什么时候停”主要封装模型客户端、工具执行器、记忆存储主循环、Policy、提示词策略、终止条件典型载体独立服务、函数库、沙箱进程Agent 类、状态图、编排器对模型影响通过 system 和 tools 传入但不会“替模型决策”决定 model 看到什么、能看到几步历史故障影响崩溃会导致所有 Agent 不可用出 bug 会导致当前任务走偏或死循环可观测对象token 数、调用耗时、工具执行时延决策轨迹、工具选择原因、循环步数测试重点接口稳定性、超时和隔离不同策略下的任务成功率、终止率这不是二选一的关系而是分层依赖关系Harness 依赖 Runtime 提供的原子能力Runtime 不依赖任何 Harness 的业务策略。如果哪天你发现自己的 Runtime 代码里塞了大量“如果用户问了天气就优先调用天气工具”这种逻辑那说明 Harness 里的策略漏到了 Runtime。4.2 一次真实查询的完整路径假设你正在做一个企业内部知识库问答 Agent用户问“帮我总结昨天项目周报并找出风险项。” 我按一次完整执行拆给你看。第一步HTTP 网关收到请求创建 Session初始化 Harness 和绑定给该 Session 的 Runtime。第二步Harness 调 Policy 构造系统提示词把“你是项目经理助理”这类人设和工具说明带进去然后调用 Runtime.chat 把用户问题发给模型。第三步模型返回的不是最终答案而是一个工具调用比如search_docs(query项目周报 2025-04-10, date2025-04-10)。Harness 看到有 tool_call于是转到工具执行阶段调用Runtime.run_toolRuntime 去向量库检索并把文本片段返回。第四步Harness 拿到检索结果后把工具结果拼进 messages再调用 Runtime.chat。模型这次根据检索内容生成了总结和风险点。Harness 判断文本不是最终答案而是“需要再调一次 calendar 工具获取成员日程”的新请求于是再次执行工具。第五步直到模型输出满足 Policy 的终止条件Harness 把结果回给 HTTP 网关。如果第五步发生了工具调用异常Harness 会决定是重试、换工具还是把异常信息返回给模型继续推理。从这五个步骤你应该能感觉到用户感知到的“智能”其实就是 Harness 对 Runtime 多次调用后形成的结果。Runtime 每次执行都很快难的是如何编排这些步骤让模型在正确时机看到正确信息。4.3 边界模糊时用四个问题做判断如果你在代码评审时拿不准某个函数应该属于哪一层可以直接问下面四个问题。第一个问题这段逻辑去掉之后Agent 还能用同一个底层模型和工具吗如果能它多半属于 Harness 策略。第二个问题这段逻辑和具体模型供应商强相关吗比如某个 API 的 tools 参数格式转换这属于 Runtime。第三个问题这段逻辑需要所有 Agent 类型共用吗比如统一的限流、鉴权它是 Runtime 基础设施如果只有财务分析 Agent 需要审批流程那是 Harness。第四个问题如果业务要新接入一个完全不同的 Agent 场景你是否希望复用这段代码希望复用的底层资源和稳定性逻辑放 Runtime不希望复用的场景策略放 Harness。这四句话基本能解决 90% 的归属争论。剩下的 10% 可能属于长期演进产生的中间层比如“会话路由”到底归谁取决于你的产品形态但至少你们讨论时能有一个统一的判断框架而不是靠感觉。5. 日常开发中的高频坑位与排查实录5.1 现象一工具调用一直执行但 Agent 每次都说“没找到结果”这个坑我见得太多了。表面上看 Runtime 日志里工具执行成功了返回内容也打印出来了但模型还是说没找到。排查时先别怀疑模型重点看 Harness 把工具结果回传给模型时是怎么拼装的。常见的错误是Harness 把工具结果保存在了一个局部变量里但下一次调用模型时忘了把这条消息加到 messages或者加了但消息角色写成了user而不是tool对应的角色。工具结果回传属于 Agent 编排的核心细节。模型厂商对工具结果的格式要求可能不同OpenAI 要求用 roletool 的消息并要求提供 tool_call_id其他模型可能只要求在 user 内容里塞结果。这个差异通常在 Runtime 层做适配但 Harness 要保证“每条 tool_call 都有对应结果”。如果你发现工具执行成功但 Agent “失忆”建议在 Harness 插入一步检查统计 messages 里 tool_call 数量和 tool_result 数量是否一致。5.2 现象二Agent 永久不终止费用飙高这大概率不是 Runtime 问题而是 Harness 的终止条件写得太宽松。最常见的情况是 Policy 里只判断“模型输出是否包含 final 标记”但模型在复杂任务里就是不输出这个标记于是一直循环调用工具。我的建议是任何 Harness 都必须同时具备“正向终止”和“强制终止”正向终止由 Policy 判断任务目标是否完成强制终止由兜底步数、时间阈值和 Token 阈值共同实现。上面代码里的max_steps就是强制终止。真实项目里我还会加一个熔断器如果连续三次工具调用都返回相同错误Harness 直接停止并把上下文发给人工处理。这一步能省下大量调试成本。5.3 现象三问题定位时日志到底去 Harness 找还是 Runtime 找很多团队日志混乱就是因为没有按职责划分。我的经验是Harness 日志记录的是“决策轨迹”比如第几步、模型回复了哪段思考、为什么调用某个工具、终止原因是什么Runtime 日志记录的是“执行指标”比如模型接口耗时、Token 消耗、工具执行超时、网络重试次数。如果任务结果不符合预期先查 Harness 的决策轨迹比如“它到底有没有理解用户意图”如果系统整体卡顿或偶发失败再查 Runtime 指标比如“是不是模型 API 超时率变高了”。我把排查顺序整理成了一个速查表现象优先排查层典型原因结论不对Harness提示词策略缺失、上下文裁剪过度、工具信息未回传某一步工具偶发失败Runtime工具超时、服务不可用、鉴权过期任务中途停止Harness异常未处理、终止条件误判整个服务不可用Runtime资源耗尽、下游模型限流未处理响应慢但结果OKRuntime模型调用串行、工具执行慢同一问题多次回答漂移Harness缺少固定的 few-shot、温度未调低这里的重点是不要一出现问题就疯狂打日志定位到每一行而是先判断“是这一层坏了还是上一层用错了”。层与层之间的语义障碍是最大的排查暗礁。5.4 我的一个压箱底调试技巧给 Harness 装“步进器”不知道你会不会遇到这种情况Agent 跑了 20 步之后终于飞了但你想知道它在第 7 步为什么突然检索了一个毫不相关的文档。看完整日志太累直接调模型接口又脱离真实场景。我的建议是给 Harness 设计一个可插拔的 StepListener在每一轮循环开始、模型返回、工具调用前后触发回调把关键信息渲染成可读的 JSONL。这样你可以在本地把一次完整运行保存下来再用脚本按 step 编号逐层查看。实际上这也是一种“Harness 与 Runtime 分离”带来的红利Runtime 只记录底层请求Harness 记录决策链条。把决策链条回放一遍你会瞬间看清是哪条 Prompt 误导了模型而不是在 Runtime 的几百条原始请求日志里大海捞针。这个技巧我几乎在每个生产级 Agent 项目里都会用。6. 架构决策顺序与我的个人心得6.1 小项目可以先不拆服务但逻辑必须拆看到这你可能会紧张是不是必须上容器、上独立 Runtime 服务才算完成了分层大可不必。个人项目或者五个以内的 Agent 场景完全可以跑在同一个 Python 进程里但代码结构上要拆。我推荐的目录结构很简单app/ runtime/ model_client.py tool_executor.py memory_store.py harness/ base_harness.py policies/ customer_service.py analyst.py loop_listeners.py agents/ customer_service_agent.pyruntime下的模块不 importharness下的任何内容harness只通过 Runtime 对外暴露的接口方法使用能力agents负责把某个 Harness 和 Runtime 组合起来注册工具、绑定模型账号。这样就实现了逻辑边界。将来如果某个 Runtime 模块需要独立成服务把它抽出来补一个网络接口层即可不会牵连 Harness。6.2 演进路径通常从“Harness 吃胖”开始要主动给 Runtime 补位在实际项目里还有一个很普遍的趋势一开始 Harness 和 Runtime 确实分了层但随着业务迭代大家图省事开始把“其他模型供应商的适配”“工具执行的沙箱参数”“历史消息的向量化存储”都写进了 Harness。于是 Harness 越来越胖Runtime 变成了空壳所谓架构分层名存实亡。我的建议是每两周做一次代码评审时按第 4.3 节的判断标准重新检查如果同一段“稳定执行能力”被多个 Harness 复制粘贴了就把它下沉到 Runtime如果 Runtime 里出现了业务相关的策略 if-else就把它上提到 Harness。这个动作要持续做而不是只在架构设计时做一次。分层不是一锤子买卖它更像厨房里“灶台”和“菜单”的关系灶台性能稳定菜单却每周都在换。6.3 最后一个选型建议先写坏掉的 Harness再谈框架很多朋友在选择 LangGraph、CrewAI 还是自研时犹豫很久。我的观点是如果你连一个最朴素的while循环 Harness 都没写过直接上抽象框架很容易被框架带着走。先把你想要的一个场景用最原始的 Runtime Policy 循环写出来跑通一次再去看框架内部怎么表达这些概念你才能判断它是在替你做决策还是在限制你的表达。我自己做过两个核心 Agent 系统一个早期重度依赖编排框架后续为了加“人工审批暂停与恢复”花了大半个月改造另一个一开始就用很朴素的 Runtime 接口 Harness 控制循环加新策略反而很快。不是框架不好而是框架自带的那套 Harness 假设未必匹配你的业务状态机。这里没有银弹理解分层逻辑比背出任何一家 API 文档都管用。我个人最后一次分享一个真实体会Agent 项目的复杂度从来不是“模型不够聪明”而是“环境不可控、步骤不可回放、策略不可解释”。把 Runtime 做好解决的是环境稳定性问题把 Harness 做好解决的是步骤可控和策略可解释问题。两者都有价值但如果你今天只打算改一处优先把 Harness 的循环、终止、Policy 和回放日志写得像样因为这是你和“玄学 Agent”之间最重要的一道闸门。
返回列表