在 AI 智能体开发的世界里,我们经常会遇到这样的困境:为了让大模型调用一两个简单的天气查询或数据库接口,不得不引入庞大的第三方框架。随之而来的是深达数十层的堆栈调用、晦涩难懂的中间件类继承、黑盒一般的内部提示词注入,以及版本升级带来的 API 破裂。
其实,所有第三方 Agent 框架的本质,都是别人封装好的“运行底座”(Harness)。剥离掉所有花哨的概念包装后,一个智能体的底层运行逻辑极其质朴。只有亲手实现过一遍最核心的 Agent 循环,你在评估 LangChain、Semantic Kernel 或 AutoGen 时,才能一眼看穿框架在做什么,并清晰判断何时该用框架,何时自建最可控。
Agent 的底层运行本质:带状态的决策循环
从控制论和图灵机的视角看,任何单智能体系统的核心运行拓扑就是一个带反馈的有限状态循环:
flowchart TD
UserInput["用户输入 User Input"] --> InitHistory["初始化上下文 History"]
InitHistory --> LoopStart["进入决策循环 (Iteration <= max_turns)"]
LoopStart --> CallLLM["调用底层大模型 (带系统提示词与工具 Schema)"]
CallLLM --> CheckStop{"模型停机原因 (Stop Reason)"}
CheckStop -- "tool_use (申请调用工具)" --> ParseCalls["解析工具名称与参数入参"]
ParseCalls --> ExecTools["分发执行本地函数 ToolRegistry.call()"]
ExecTools --> AppendResults["将调用结果注入 History (Role: User/Tool)"]
AppendResults --> LoopStart
CheckStop -- "end_turn (自然语言回答完成)" --> ExtractText["提取模型文本内容"]
ExtractText --> Finish(["返回最终回答,终止流程"])
LoopStart -- "达到最大步数限制" --> Fallback(["熔断保护,抛出超限告警"])
用极简的伪代码描述,仅需几行:
1 2 3 4 5 6 7
| while not reached_max_steps: decision = llm.generate(history + system_prompt, tools=tool_schemas) if decision.wants_tool: result = execute_tool(decision.tool_name, decision.tool_args) history.append(result) else: return decision.final_text
|
所有框架无非是在这个循环周围,加上了重试机制、状态检查点、可观测性埋点和序列化支持。
完整实现:200 行纯 Python 手写 Agent Harness
我们不需要安装任何第三方 Agent 库,仅依赖官方原厂 SDK(此处以 Anthropic Claude API 为例,OpenAI SDK 逻辑完全同构),实现一个生产级的 Agent 运行底座。
首先实现一个通过装饰器注册工具、自动收集 Schema 并统一派发调用的中枢管理器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import inspect from typing import Any, Callable
class ToolRegistry: """轻量级工具注册中枢:负责 Schema 管理、动态分发与安全防护"""
def __init__(self): self._schemas: dict[str, dict] = {} self._handlers: dict[str, Callable] = {}
def register(self, name: str, description: str, parameters: dict, func: Callable): """显式注册工具""" self._schemas[name] = { "name": name, "description": description, "input_schema": parameters, } self._handlers[name] = func
def tool(self, name: str, description: str, parameters: dict): """装饰器注册语法糖""" def decorator(func: Callable): self.register(name, description, parameters, func) return func return decorator
def get_schemas(self) -> list[dict]: """导出所有提供给大模型的工具 JSON Schema 列表""" return list(self._schemas.values())
def call(self, name: str, args: dict) -> Any: """根据名称动态执行目标本地函数,带有完善的异常兜底""" if name not in self._handlers: return f"执行失败:未找到名为 '{name}' 的已注册工具" try: handler = self._handlers[name] return handler(**args) except Exception as e: return f"工具 '{name}' 运行时抛出异常: {e}"
registry = ToolRegistry()
|
2. 定义实际业务工具
我们定义三个典型的原子工具:气象查询、安全数学运算与轻量知识库检索:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| @registry.tool( name="get_weather", description="查询指定城市的实时气象数据", parameters={ "type": "object", "properties": { "city": {"type": "string", "description": "城市名称,例如北京、上海"}, }, "required": ["city"], }, ) def get_weather(city: str) -> str: mock_db = { "北京": "晴空万里,23°C,北风二级", "上海": "阴有阵雨,19°C,空气湿度 85%", "深圳": "多云,27°C,体感舒适", } return mock_db.get(city, f"{city} 暂无气象台站数据")
@registry.tool( name="calculate", description="高精度数学表达式求值计算器", parameters={ "type": "object", "properties": { "expression": {"type": "string", "description": "标准数学运算表达式,例如 '12 * 8 + 4'"}, }, "required": ["expression"], }, ) def calculate(expression: str) -> str: allowed_chars = set("0123456789+-*/()., ") if not all(c in allowed_chars for c in expression): return "安全校验失败:表达式包含不合法字符" try: res = eval(expression, {"__builtins__": None}, {}) return str(res) except Exception as e: return f"数学运算错误: {e}"
@registry.tool( name="query_knowledge", description="在本地系统技术文档库中检索指定名词或规范", parameters={ "type": "object", "properties": { "keyword": {"type": "string", "description": "需要查询的关键词"}, }, "required": ["keyword"], }, ) def query_knowledge(keyword: str) -> str: docs = { "agentscope": "AgentScope 是阿里巴巴达摩院开源的多智能体框架,主打以消息(Msg)为一等公民和 RPC 分布式部署。", "semantic kernel": "Semantic Kernel 是微软主导的企业级 AI SDK,深度集成 C#/.NET 与依赖注入插件体系。", "eino": "Eino 是字节跳动开源的 Go 语言 AI 框架,针对生产级高并发与全链路流式设计。", } kw_lower = keyword.lower() for k, v in docs.items(): if k in kw_lower: return v return f"文档库中未收录与 '{keyword}' 匹配的知识条目"
|
3. Agent 运行循环核心容器
接下来编写驱动大模型与工具交互的 Agent 核心类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| import os from anthropic import Anthropic
class AgentHarness: """轻量级 Agent 运行底座"""
def __init__( self, system_prompt: str, model: str = "claude-opus-4-5", max_iterations: int = 8, verbose: bool = True, ): self.client = Anthropic() self.system_prompt = system_prompt self.model = model self.max_iterations = max_iterations self.verbose = verbose self.history: list[dict] = []
def _log(self, text: str): if self.verbose: print(text)
def run(self, user_prompt: str) -> str: """执行单次任务直至生成最终结论或触发步数熔断""" self.history.append({"role": "user", "content": user_prompt})
for step in range(1, self.max_iterations + 1): self._log(f"\n[🔄 步数 {step}/{self.max_iterations}] 正在等待大模型规划决策...")
response = self.client.messages.create( model=self.model, max_tokens=2048, system=self.system_prompt, tools=registry.get_schemas(), messages=self.history, )
if response.stop_reason == "tool_use": tool_results = [] for block in response.content: if block.type == "tool_use": self._log(f" 👉 [调用工具] {block.name}(参数: {block.input})") exec_res = registry.call(block.name, block.input) self._log(f" 👈 [工具输出] {exec_res}")
tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": str(exec_res), })
self.history.append({"role": "assistant", "content": response.content}) self.history.append({"role": "user", "content": tool_results})
elif response.stop_reason == "end_turn": final_answer = "" for block in response.content: if hasattr(block, "text"): final_answer += block.text
self.history.append({"role": "assistant", "content": final_answer}) self._log(" ✅ [任务完成] 模型已生成最终答复。") return final_answer
warning = "⚠️ [熔断触发] 已达到最大迭代步数限制,当前任务被强制中断。" self._log(warning) return warning
def reset(self): """清空会话历史""" self.history = []
|
运行与实测验证
我们初始化 Agent 并测试跨多个工具的复合多步推理能力:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| if __name__ == "__main__": harness = AgentHarness( system_prompt="""你是一名严谨的智能技术助手。 你可以自主调用天气查询、数学计算与技术知识库检索工具来协助回答问题。 必须基于工具返回的事实给出答案,严禁凭空捏造。""", verbose=True, )
query = ( "帮我查一下北京现在的天气;" "另外算一下 128 乘以 3.5 加上 42 等于多少;" "最后在文档库里检索一下 AgentScope 是什么。" )
print("=" * 60) print("用户指令:", query) print("=" * 60)
final_result = harness.run(query)
print("\n" + "=" * 60) print("智能体最终输出:\n", final_result) print("=" * 60)
|
在运行日志中,你可以直观地观察到:
- 第 1 步:模型识别出 3 个相互独立的目标,单次响应中并发输出了 3 个
tool_use 请求(get_weather、calculate、query_knowledge)。
- 工具执行:
ToolRegistry 依次安全执行每个本地函数,构造标准化 tool_result 切片回传。
- 第 2 步:模型读取到包含 3 个结果的新上下文,进入
end_turn 分支,合成结构化的完整中文答复。
进阶强化:多工具并发加速
当大模型在一次决策中请求多个独立的 I/O 工具(例如并发抓取多个网页或查询不同城市天气)时,单线程顺序执行会徒增首字等待时间。我们可以利用 Python concurrent.futures 轻松实现并发提速:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import concurrent.futures
def execute_tools_parallel(tool_calls: list) -> list[dict]: """多线程并发执行工具列表""" results = [] with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: future_map = { executor.submit(registry.call, tc.name, tc.input): tc for tc in tool_calls } for future in concurrent.futures.as_completed(future_map): tc = future_map[future] try: res = future.result() except Exception as e: res = f"线程池执行失败: {e}" results.append({ "type": "tool_result", "tool_use_id": tc.id, "content": str(res), }) return results
|
框架与手写 Harness 的概念映射表
通过手写这 200 行代码,再去看成熟框架,你会发现它们的本质是一一对应的:
| 智能体运行时要素 |
手写 Harness 的对应代码 |
常见商业 / 开源框架中的包装 |
| 工具定义与元数据 |
ToolRegistry.register / @registry.tool |
LangChain @tool / Semantic Kernel KernelFunction |
| 核心执行拓扑 |
for step in range(max_iterations) 循环 |
LangChain AgentExecutor / Mastra Workflow.step |
| 工具派发路由 |
registry.call(name, args) |
LangGraph ToolNode / Eino ToolComponent |
| 上下文状态维护 |
self.history.append(...) |
LangGraph StateGraph(messages) / AutoGen ChatResult |
| 防死循环熔断保护 |
max_iterations = 8 判定退出 |
框架内部隐藏的 max_turns 参数 |
决策准则:何时手搓 Harness,何时选用框架?
推荐手搓自研 Harness 的场景
- 核心逻辑清晰单一:业务只需挂载 3~5 个确定的内部微服务 API,无需复杂的学术图算法。
- 生产环境对黑盒零容忍:金融、医疗或交易核心链路要求每一行代码可断点调试、日志可追溯、无第三方未审计依赖。
- 性能敏感与依赖极简:希望避免安装臃肿的几百兆三方库,减少部署包体积与安全漏洞扫描风险。
推荐选用成熟框架的场景
- 多智能体复杂群聊/辩论:需要编排像 AutoGen、AgentScope 那样涉及十几个 Agent 的动态协作与路由。
- 长生命周期人机协同:需要 LangGraph 级别的会话检查点持久化(Checkpoint)、时间旅行回溯与人工审批阻断。
- 团队统一全栈基建:如前端 TypeScript 团队直接使用 Mastra / Vercel AI SDK,与 Next.js 生态无缝对接。
关联导航