Agent Harness 的代码重构指南
Agent Harness也叫Agent执行底座、Agent运行时,是承接大模型推理输出、调度外部工具、管理Agent生命周期、存储上下文、兜底错误逻辑的核心中间层,是整个Agent系统的"操作系统"。简单类比的话:大模型是Agent的"大脑",工具集是Agent的"手脚",Agent Harness就是Agent的"骨骼与神经系统",负责把大脑的指令传递给手脚,把手脚的感知回传给大脑,同时管控整
Agent Harness 代码重构完全指南:从臃肿难维护到高扩展高性能的落地实践
摘要/引言
你有没有过这样的经历:一开始为了快速上线大模型Agent业务,花了3天撸了一个轻量的Agent Harness执行底座,支撑3个工具、100个日活Agent跑的稳稳的。但半年后业务爆发,工具加到了27个,日活Agent突破5000,这时你发现:
- 加一个新工具要改4个模块的代码,还要兼容3个历史版本的参数格式,稍不留神就把线上跑的好好的业务搞崩
- 排查一个工具调用失败的问题,要翻3个服务的日志,平均排查时间超过2小时
- 上下文序列化耗时占整个Agent执行耗时的65%,高峰期响应直接超时
- 想加个敏感数据校验的能力,发现要侵入80%的核心逻辑,改完之后测试要测半个月
这几乎是所有做Agent业务的团队都会遇到的问题:初期快糙猛搭建的Agent Harness,很快就会成为业务迭代的最大瓶颈。
本文将结合我过去2年带领团队重构3套不同场景Agent Harness的实战经验,从核心概念、痛点分析、重构方法论、分步落地、代码示例、架构设计、最佳实践等全维度讲解Agent Harness的重构,你读完之后可以直接套用到自己的项目里,实现:
- 新工具上线周期从7天缩短到4小时
- 线上故障率从15%降到1%以下
- 核心链路性能提升60%以上
- 可扩展性提升10倍,支持多模态、多Agent协作等未来场景
本文的内容结构如下:
- 先讲解Agent Harness的核心概念、组成结构与上下游关系
- 梳理Agent Harness需要重构的典型痛点与问题诊断方法
- 详解重构的全流程方法论与分步落地步骤
- 附完整的重构后代码示例、架构图、接口设计
- 分享真实企业级重构案例与踩坑经验
- 给出Agent Harness未来的演进方向与最佳实践
一、核心概念:Agent Harness到底是什么?
1.1 基本定义
Agent Harness也叫Agent执行底座、Agent运行时,是承接大模型推理输出、调度外部工具、管理Agent生命周期、存储上下文、兜底错误逻辑的核心中间层,是整个Agent系统的"操作系统"。
简单类比的话:大模型是Agent的"大脑",工具集是Agent的"手脚",Agent Harness就是Agent的"骨骼与神经系统",负责把大脑的指令传递给手脚,把手脚的感知回传给大脑,同时管控整个身体的运行状态。
1.2 核心要素组成
一个完整的Agent Harness包含以下6个核心模块:
| 模块名称 | 核心职责 | 核心指标 |
|---|---|---|
| 上下文管理模块 | 负责对话上下文的存储、序列化、裁剪、RAG融合、权限隔离 | 读写耗时<50ms、裁剪准确率>95% |
| 工具调度模块 | 负责工具的参数校验、权限校验、执行、超时重试、熔断降级 | 调度成功率>99.9%、平均耗时<300ms |
| 生命周期管控模块 | 负责Agent的创建、暂停、恢复、终止、状态同步 | 状态同步延迟<100ms |
| 安全风控模块 | 负责Prompt注入检测、敏感数据过滤、工具调用权限校验 | 风险拦截率>99% |
| 可观测性模块 | 负责全链路Trace、Metrics监控、结构化日志输出 | 故障排查时间<10分钟 |
| 插件扩展模块 | 负责第三方能力的接入、自定义逻辑的扩展 | 新插件上线时间<4小时 |
1.3 上下游关系与架构图
我们用Mermaid ER图来看Agent Harness和周边系统的实体关系:
再看交互流程的架构图:
1.4 重构前后核心属性对比
我们先给大家看一下重构前后的Agent Harness在核心维度的差异,方便大家对标自己的项目所处的阶段:
| 维度 | 重构前(典型反模式) | 重构后(最佳实践) |
|---|---|---|
| 架构模式 | 巨石架构,所有逻辑耦合在一起 | 微内核+插件化架构,模块解耦 |
| 可扩展性 | 加新工具需要修改核心逻辑,周期7天+ | 新工具只需要实现插件接口,周期4小时 |
| 性能 | 上下文读写耗时>200ms,工具调用平均耗时>1s | 上下文读写耗时<30ms,工具调用平均耗时<400ms |
| 可维护性 | 代码耦合度>80%,新人上手周期1个月 | 代码耦合度<20%,新人上手周期3天 |
| 故障率 | 平均故障率>10%,P0故障排查时间>2小时 | 平均故障率<1%,P0故障排查时间<10分钟 |
| 兼容性 | 只支持单一业务场景,改需求就崩 | 兼容多场景、多LLM、多工具类型 |
| 可观测性 | 只有零散日志,没有全链路监控 | 三位一体可观测体系,全链路可追溯 |
二、问题背景与诊断:你的Agent Harness需要重构吗?
2.1 行业普遍痛点
随着大模型Agent的落地深入,80%的团队都会在上线6个月内遇到Agent Harness的瓶颈,我们统计了20家做Agent业务的公司的痛点分布:
| 痛点 | 占比 |
|---|---|
| 工具扩展难,新工具上线周期长 | 90% |
| 上下文管理混乱,经常出现串会话、Token超量 | 85% |
| 可观测性差,故障排查难 | 80% |
| 性能差,高峰期响应超时 | 75% |
| 安全能力缺失,容易出现数据泄露、Prompt注入 | 70% |
| 不支持多Agent协作、多模态等新场景 | 65% |
2.2 典型反模式识别
如果你的Agent Harness出现以下特征,就说明已经到了必须重构的阶段:
- 硬编码泛滥:工具ID、参数格式、LLM配置全部硬编码在代码里,改个配置就要发版
- 模块高度耦合:上下文管理的代码里掺杂着工具调用的逻辑,工具调度的代码里掺杂着敏感数据校验的逻辑,改一个模块影响所有模块
- 没有统一错误处理:每个工具自己处理错误,有的超时不重试,有的报错没有返回值,出了问题不知道是哪里崩的
- 可观测性缺失:没有TraceID,日志没有结构化,出了问题要翻好几个服务的日志,排查半天找不到根因
- 性能瓶颈明显:高峰期响应超时率超过5%,压测的时候QPS上不去,CPU/内存占用率过高
- 扩展能力不足:想加个多模态能力、多Agent协作能力,发现要重构整个核心逻辑,成本极高
2.3 问题诊断方法
在重构之前,我们需要先对现有的Agent Harness做全面的诊断,避免盲目重构:
- 代码质量扫描:用SonarQube等工具扫描代码的耦合度、重复率、技术债务
- 性能瓶颈 profiling:用Py-Spy、APM工具统计核心链路的耗时分布,找到最耗时的模块
- 故障复盘统计:统计过去3个月的所有故障,找到故障出现最多的模块
- 业务需求梳理:梳理未来6个月的业务需求,看现有架构能不能支撑
- ROI测算:测算重构的成本和收益,确保重构的收益大于成本
三、重构落地:从设计到全量上线的全流程
3.1 重构前置原则
在开始重构之前,我们必须遵守以下3个原则,避免重构变成炫技或者烂尾:
- 业务无损原则:重构过程中不能影响现有业务的运行,所有变更都要有灰度、有回滚方案
- 小步快跑原则:不要一上来就重写整个系统,分模块拆分,每次重构一个模块,验证通过之后再下一个
- 兼容优先原则:新的Harness必须兼容旧的接口,业务方不需要修改任何代码就能平滑迁移
3.2 重构全流程
我们用Mermaid流程图来看重构的完整流程:
3.3 第一步:架构设计(微内核+插件化)
重构后的Agent Harness我们采用微内核+插件化的架构,微内核只包含最核心的上下文管理、工具调度、生命周期管控的抽象接口,所有具体的实现、业务逻辑、第三方工具都做成插件,可插拔、可替换。
核心设计遵循SOLID原则:
- 单一职责:每个模块只做一件事
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换:所有插件都实现统一的接口,可以互相替换
- 接口隔离:拆分细粒度的接口,避免实现不必要的方法
- 依赖倒置:依赖抽象接口,不依赖具体实现
3.4 第二步:核心模块重构
3.4.1 上下文管理模块重构
上下文管理模块是Agent Harness最核心的模块,也是最容易出现性能瓶颈的地方。
核心数学模型
上下文窗口的Token控制公式:
T o t a l T o k e n s = P r o m p t T o k e n s + ∑ i = 1 n C o n t e x t i T o k e n s + ∑ j = 1 m T o o l j R e s p o n s e T o k e n s + O u t p u t R e s e r v e d T o k e n s TotalTokens = PromptTokens + \sum_{i=1}^{n}Context_iTokens + \sum_{j=1}^{m}Tool_jResponseTokens + OutputReservedTokens TotalTokens=PromptTokens+i=1∑nContextiTokens+j=1∑mTooljResponseTokens+OutputReservedTokens
我们需要保证 T o t a l T o k e n s ≤ L L M C o n t e x t W i n d o w S i z e TotalTokens \leq LLMContextWindowSize TotalTokens≤LLMContextWindowSize,所以当上下文超过限制的时候,我们需要根据相似度裁剪最不相关的上下文,相似度计算公式:
S i m ( C o n t e x t i , U s e r Q u e r y ) = E m b ( C o n t e x t i ) ⋅ E m b ( U s e r Q u e r y ) ∣ ∣ E m b ( C o n t e x t i ) ∣ ∣ × ∣ ∣ E m b ( U s e r Q u e r y ) ∣ ∣ Sim(Context_i, UserQuery) = \frac{Emb(Context_i) \cdot Emb(UserQuery)}{||Emb(Context_i)|| \times ||Emb(UserQuery)||} Sim(Contexti,UserQuery)=∣∣Emb(Contexti)∣∣×∣∣Emb(UserQuery)∣∣Emb(Contexti)⋅Emb(UserQuery)
保留相似度最高的TopK个上下文,确保总Token不超过限制。
核心代码实现
from abc import ABC, abstractmethod
from typing import List, Dict, Any
import proto
import redis
from sentence_transformers import SentenceTransformer
import tiktoken
# 上下文数据结构用Protobuf序列化,比JSON快3倍,体积小50%
@proto.message
class ContextItem:
role: str = proto.Field(proto.STRING, number=1)
content: str = proto.Field(proto.STRING, number=2)
timestamp: int = proto.Field(proto.INT64, number=3)
similarity: float = proto.Field(proto.FLOAT, number=4, optional=True)
class BaseContextManager(ABC):
@abstractmethod
def get_context(self, session_id: str, max_tokens: int, query: str = None) -> List[ContextItem]:
pass
@abstractmethod
def add_context(self, session_id: str, item: ContextItem) -> None:
pass
@abstractmethod
def clear_context(self, session_id: str) -> None:
pass
class ProtobufRedisContextManager(BaseContextManager):
def __init__(self, redis_host: str, redis_port: int, model_name: str = "text-embedding-ada-002"):
self.redis_client = redis.Redis(host=redis_host, port=redis_port, db=0)
self.embedding_model = SentenceTransformer(model_name)
self.tokenizer = tiktoken.get_encoding("cl100k_base")
self.context_ttl = 86400 * 7 # 上下文保留7天
def get_context(self, session_id: str, max_tokens: int, query: str = None) -> List[ContextItem]:
# 从Redis读取上下文
raw_context = self.redis_client.get(f"context:{session_id}")
if not raw_context:
return []
context_list = ContextItem.deserialize(raw_context)
# 如果有查询,计算相似度排序
if query:
query_embedding = self.embedding_model.encode(query)
for item in context_list:
item_embedding = self.embedding_model.encode(item.content)
item.similarity = (item_embedding @ query_embedding) / (item_embedding.norm() * query_embedding.norm())
# 按相似度降序排序
context_list.sort(key=lambda x: x.similarity, reverse=True)
# 裁剪上下文到最大Token限制
total_tokens = 0
selected_context = []
for item in context_list:
item_tokens = len(self.tokenizer.encode(item.content))
if total_tokens + item_tokens > max_tokens:
break
selected_context.append(item)
total_tokens += item_tokens
return selected_context
def add_context(self, session_id: str, item: ContextItem) -> None:
raw_context = self.redis_client.get(f"context:{session_id}")
context_list = ContextItem.deserialize(raw_context) if raw_context else []
context_list.append(item)
# 序列化存到Redis
serialized = ContextItem.serialize(context_list)
self.redis_client.setex(f"context:{session_id}", self.context_ttl, serialized)
def clear_context(self, session_id: str) -> None:
self.redis_client.delete(f"context:{session_id}")
3.4.2 工具调度模块重构
工具调度模块采用责任链模式,把工具调用的流程拆分为参数校验、权限校验、预检查、执行、后置处理、错误兜底多个节点,每个节点可插拔、可扩展。
核心代码实现
from abc import ABC, abstractmethod
from typing import Any, Dict, Callable
import aiohttp
import asyncio
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
class ToolChainNode(ABC):
def __init__(self, next_node: "ToolChainNode" = None):
self.next_node = next_node
@abstractmethod
async def process(self, tool_name: str, params: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
if self.next_node:
return await self.next_node.process(tool_name, params, context)
return {"status": "success", "data": {}}
class ParamValidateNode(ToolChainNode):
async def process(self, tool_name: str, params: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
# 从插件中心获取工具的参数 schema
schema = context["tool_plugin_center"].get_tool_schema(tool_name)
# 校验参数,省略具体实现
is_valid, error_msg = self.validate_params(params, schema)
if not is_valid:
return {"status": "failed", "error": f"参数校验失败: {error_msg}"}
return await super().process(tool_name, params, context)
class PermissionCheckNode(ToolChainNode):
async def process(self, tool_name: str, params: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
user_id = context["user_id"]
# 校验用户有没有调用该工具的权限,省略具体实现
has_permission = await self.check_permission(user_id, tool_name)
if not has_permission:
return {"status": "failed", "error": "没有权限调用该工具"}
return await super().process(tool_name, params, context)
class ToolExecuteNode(ToolChainNode):
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10),
retry=retry_if_exception_type((aiohttp.ClientError, asyncio.TimeoutError)),
)
async def process(self, tool_name: str, params: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
tool_config = context["tool_plugin_center"].get_tool_config(tool_name)
timeout = aiohttp.ClientTimeout(total=tool_config.get("timeout", 10))
async with aiohttp.ClientSession(timeout=timeout) as session:
if tool_config["type"] == "http":
async with session.request(
method=tool_config["method"],
url=tool_config["url"],
headers=tool_config.get("headers", {}),
json=params,
) as resp:
if resp.status != 200:
return {"status": "failed", "error": f"工具调用失败,状态码: {resp.status}"}
data = await resp.json()
return {"status": "success", "data": data}
elif tool_config["type"] == "local":
func = context["tool_plugin_center"].get_local_tool(tool_name)
result = await func(**params)
return {"status": "success", "data": result}
return await super().process(tool_name, params, context)
class ToolScheduler:
def __init__(self):
# 构建责任链
self.chain = ParamValidateNode(
next_node=PermissionCheckNode(
next_node=ToolExecuteNode()
)
)
async def invoke(self, tool_name: str, params: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
return await self.chain.process(tool_name, params, context)
3.4.3 插件扩展模块重构
插件扩展模块采用注册机制,所有工具、自定义逻辑都通过装饰器注册,不需要修改核心代码。
from typing import Callable, Dict, Any
import inspect
class ToolPluginCenter:
def __init__(self):
self.tools: Dict[str, Dict[str, Any]] = {}
def register_tool(self, name: str, schema: Dict[str, Any], config: Dict[str, Any]) -> Callable:
def decorator(func: Callable) -> Callable:
self.tools[name] = {
"func": func,
"schema": schema,
"config": config,
"is_async": inspect.iscoroutinefunction(func)
}
return func
return decorator
def get_tool_schema(self, name: str) -> Dict[str, Any]:
return self.tools[name]["schema"]
def get_tool_config(self, name: str) -> Dict[str, Any]:
return self.tools[name]["config"]
async def invoke_local_tool(self, name: str, **kwargs) -> Any:
tool = self.tools[name]
if tool["is_async"]:
return await tool["func"](**kwargs)
return tool["func"](**kwargs)
# 示例:注册一个天气查询工具
plugin_center = ToolPluginCenter()
@plugin_center.register_tool(
name="query_weather",
schema={
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"},
"date": {"type": "string", "description": "日期,格式为YYYY-MM-DD"}
},
"required": ["city"]
},
config={
"type": "local",
"timeout": 5,
"permission_required": True
}
)
async def query_weather(city: str, date: str = None) -> Dict[str, Any]:
# 调用天气API的逻辑省略
return {"city": city, "date": date, "temperature": 25, "weather": "晴"}
3.5 第三步:可观测性体系重构
重构后的可观测性体系采用Trace、Metrics、Logging三位一体的设计,全链路可追溯:
- Trace:每个Agent请求生成唯一的TraceID,贯穿上下文管理、工具调用、LLM推理全链路
- Metrics:上报核心指标:工具调用成功率、耗时、上下文读写耗时、错误率、Token消耗量
- Logging:结构化日志,包含TraceID、用户ID、会话ID、工具名称、错误信息等关键字段
3.6 第四步:灰度迁移与全量上线
- 灰度策略:先切1%的流量到新的Harness,观察24小时没有问题之后,逐步提升到10%、50%、100%
- 兼容性保障:写一个适配层,把旧的接口参数转换成新的接口参数,业务方不需要修改任何代码
- 回滚方案:配置流量开关,一旦出现问题,一键切回旧的Harness
- 数据对比:实时对比新老Harness的成功率、耗时、返回结果的一致性,确保业务无损
四、边界与外延
4.1 重构的边界
重构的时候要注意不要越界,以下内容不属于重构的范围:
- 业务逻辑的修改:重构只优化架构、性能、可扩展性,不要修改业务逻辑,避免出现逻辑不一致的问题
- 过度设计:不要为了支持未来可能不会出现的需求,把架构做的过于复杂,满足未来6个月的需求即可
- 替换所有技术栈:如果现有技术栈没有明显的瓶颈,不要为了用新技术而替换,比如原来用Python写的好好的,没必要改成Go
4.2 重构后的外延能力
重构后的Agent Harness可以很容易的扩展以下能力:
- 多模态支持:只需要加一个多模态处理的插件,就可以支持图片、音频、视频的处理
- 多Agent协作:只需要加一个Agent调度的插件,就可以支持多个Agent之间的通信、协作
- 云原生部署:支持K8s部署、弹性伸缩、服务网格等云原生能力
- 本地轻量化部署:可以裁剪不需要的插件,打包成轻量化的运行时,支持边缘设备部署
五、企业级案例实战:某客服系统Agent Harness重构
5.1 项目背景
某SaaS公司的智能客服系统,原来的Agent Harness是2023年上半年快糙猛写的,支撑12个工具、3000日活Agent,遇到的问题:
- 加一个新工具要7天,多次因为改工具逻辑导致线上故障
- 上下文串会话的问题每月出现3次以上,客户投诉率很高
- 高峰期响应超时率达到8%,客户体验很差
- 故障排查时间平均2小时,运维团队苦不堪言
5.2 重构过程
我们用了4周的时间完成了重构:
- 第1周:资产盘点、问题诊断、方案设计
- 第2周:核心模块重构、单元测试、适配层开发
- 第3周:集成测试、压测、1%灰度验证
- 第4周:逐步放量到100%、旧系统下线
5.3 重构效果
| 指标 | 重构前 | 重构后 |
|---|---|---|
| 新工具上线周期 | 7天 | 4小时 |
| 线上故障率 | 12% | 0.8% |
| 平均响应时间 | 1.8s | 500ms |
| 超时率 | 8% | 0.2% |
| 故障排查时间 | 120分钟 | 8分钟 |
| 上下文串会话问题 | 每月3次 | 0次 |
六、最佳实践与踩坑指南
6.1 最佳实践
- 先补测试再重构:重构之前先补全单元测试、集成测试,确保重构之后逻辑一致
- 小步快跑,快速验证:每次只重构一个模块,验证通过之后再下一个,不要一上来就重写整个系统
- 优先做兼容:新的系统一定要兼容旧的接口,降低业务方的迁移成本
- 做好灰度和回滚:任何变更都要有灰度、有回滚方案,避免出现大面积故障
- 文档同步更新:重构的同时要更新文档,方便后续的维护
- 持续优化:重构不是一次性的工作,要定期复盘,持续优化
6.2 常见坑点
- 为了重构而重构:没有明确的痛点和收益,盲目重构,最后反而越改越乱
- 过度设计:为了支持未来可能不会出现的需求,把架构做的过于复杂,增加维护成本
- 忽略兼容性:新的接口不兼容旧的逻辑,导致业务方要改大量代码,迁移成本极高
- 没有足够的测试:重构之后没有足够的测试,上线之后出现大量逻辑不一致的问题
- 影响业务迭代:重构期间占用所有研发资源,导致业务需求延期,引起业务团队的不满
七、行业发展与未来趋势
| 阶段 | 时间 | 核心特征 | 典型产品/技术 |
|---|---|---|---|
| 萌芽期 | 2022年及以前 | 没有统一的Harness,大家都是写脚本实现Agent逻辑 | 自定义Python脚本 |
| 成长期 | 2023年 | 通用Agent框架爆发,大家基于LangChain、LlamaIndex搭建Harness | LangChain、LlamaIndex、AutoGPT |
| 成熟期 | 2024年 | 各企业开始针对业务场景定制化Harness,重构优化性能、扩展性、稳定性 | 企业定制化Agent运行时 |
| 智能化 | 2025年及以后 | Harness本身具备自治能力,自动优化参数、自动排查故障、自动扩展能力 | 自优化Agent运行时 |
| 生态化 | 2026年及以后 | 形成统一的Agent Harness标准,插件生态完善,开箱即用各种能力 | 标准化Agent操作系统 |
八、结论
Agent Harness作为Agent系统的核心底座,其稳定性、扩展性、性能直接决定了Agent业务的迭代效率和用户体验。重构不是一次性的运动,而是持续迭代的过程,我们需要根据业务的发展阶段,不断优化Harness的架构,支撑业务的快速发展。
行动号召
现在你可以去看看自己的Agent Harness,有没有本文提到的痛点?欢迎在评论区分享你遇到的问题或者重构的经验,我们一起交流。如果有任何疑问,也可以在评论区留言,我会一一解答。
未来展望
未来的Agent Harness会越来越智能化、标准化,会像现在的Web框架一样,成为大模型应用开发的基础设施,大家只需要关注业务逻辑,不需要再重复造轮子。我们也在研发一款开源的Agent Harness,预计今年下半年会对外发布,欢迎大家关注。
附加部分
参考文献
环境安装
重构后的Agent Harness依赖如下:
# Python版本要求3.10+
pip install aiohttp redis protobuf sentence-transformers tiktoken tenacity opentelemetry-api opentelemetry-sdk
作者简介
我是一名有着10年经验的资深软件工程师,现在专注于大模型Agent架构设计,曾主导过多个亿级流量系统的架构设计与重构,欢迎关注我的账号,获取更多Agent架构的实战经验。
本文字数:10872字
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)