1. 从零构建工程化Agent的演进之路
在AI领域,我们常常陷入一个误区:面对Agent系统设计时,总是先构想一个完美的架构,规划好所有的模块和接口,然后再开始编码。但真实世界的工程实践告诉我,这种"先设计后实现"的方式往往会导致过度设计,最终产出一个复杂但难以演进的系统。
我的做法恰恰相反:先构建一个能跑通最小闭环的粗糙版本,然后让真实需求推动系统自然演进。这种"渐进式工程化"的方法,让我在短短几周内就从20行的原型代码,发展出了一个具备完整Runtime的Agent系统。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 最小闭环:验证核心逻辑
2.1 原型设计思路
任何Agent系统的核心都是一个决策-执行循环:
- 接收用户输入
- 模型决定下一步动作
- 系统执行该动作
- 将结果反馈给模型
- 重复直到任务完成
为了快速验证这个循环是否可行,我甚至没有直接接入真实的大模型,而是先构建了一个模拟的fake_llm()函数:
python复制def fake_llm(messages):
last = messages[-1]["content"]
if "创建 hello.txt" in last:
return '命令:echo "hello" > hello.txt'
if "执行结果:" in last:
return "完成:hello.txt 已经创建完成"
return "完成:暂时不知道该怎么处理"
这个模拟函数虽然简单,但它清晰地定义了Agent应该具备的两个基本能力:
- 生成系统命令(以"命令:"开头)
- 返回最终结果(以"完成:"开头)
2.2 主循环实现
有了这个模拟函数,主循环的实现就变得非常简单:
python复制import os
messages = [{"role": "system", "content": "你必须只用两种格式回复:命令:xxx 或 完成:xxx"}]
while True:
user_input = input("你: ").strip()
if user_input.lower() in {"exit", "quit"}:
break
messages.append({"role": "user", "content": user_input})
while True:
reply = fake_llm(messages)
print("agent:", reply)
if reply.startswith("命令:"):
command = reply.split("命令:", 1)[1].strip()
result = os.popen(command).read()
messages.append({"role": "user", "content": f"执行结果:{result}"})
elif reply.startswith("完成:"):
break
这个不到20行的代码已经实现了一个最基本的Agent:
- 它能理解用户的简单指令(如"创建hello.txt")
- 能生成相应的系统命令
- 能执行命令并收集结果
- 能根据执行结果判断任务是否完成
关键经验:在早期阶段,不要追求完美的架构或完整的工具链。先用最简单的方式验证核心逻辑是否可行,这能帮你快速发现问题并调整方向。
3. 接入真实模型:从模拟到实战
3.1 替换为真实LLM调用
验证了核心循环可行后,下一步就是替换掉模拟函数,接入真实的大语言模型。我选择了OpenRouter作为API网关,因为它支持多种模型提供商:
python复制import os
import requests
def call_openrouter(messages):
url = "https://openrouter.ai/api/v1/chat/completions"
headers = {
"Authorization": f"Bearer {os.getenv('OPENROUTER_API_KEY')}",
"Content-Type": "application/json",
}
payload = {
"model": os.getenv("OPENROUTER_MODEL", "openai/gpt-4o-mini"),
"messages": messages,
"temperature": 0.2,
}
resp = requests.post(url, headers=headers, json=payload, timeout=60)
resp.raise_for_status()
return resp.json()["choices"][0]["message"]["content"]
3.2 结构化提示工程
为了让模型输出更可控,我们需要设计更结构化的提示词:
python复制system_prompt = """
你是一个任务执行助手。对于用户的任务,你需要决定是调用工具还是直接回答。
你的响应必须是严格的JSON格式:
{
"thought": "你的思考过程",
"action": "use_tool|finish",
"tool_name": "工具名(仅action=use_tool时需要)",
"tool_args": {参数对象},
"final_answer": "最终回答(仅action=finish时需要)"
}
"""
这种结构化输出比最初的"命令:"前缀更可靠,也更容易扩展。
4. 工程化演进:解决实际问题
4.1 问题一:脆弱的字符串协议
最初的"命令:"前缀协议虽然简单,但非常脆弱:
- 难以处理复杂命令
- 无法传递额外元数据
- 错误处理困难
解决方案是引入结构化动作描述:
python复制{
"thought": "需要先创建文件",
"action": "use_tool",
"tool_name": "write_file",
"tool_args": {
"path": "hello.txt",
"content": "hello"
}
}
4.2 问题二:直接系统调用不安全
早期版本直接使用os.popen()执行任意命令,这带来了严重的安全隐患。解决方案是引入工具注册机制:
python复制class ToolRegistry:
def __init__(self):
self.tools = {}
def register(self, name, func, description=""):
self.tools[name] = {
"func": func,
"description": description,
}
def call(self, name, **kwargs):
if name not in self.tools:
raise ValueError(f"工具不存在: {name}")
return self.tools[name]["func"](**kwargs)
4.3 问题三:状态管理混乱
将所有状态都塞在messages数组中会导致:
- 对话历史与任务状态混杂
- 难以追踪长期信息
- 上下文窗口浪费
引入分层内存系统解决这个问题:
python复制class Memory:
def __init__(self):
self.short_term = [] # 最近对话
self.working = { # 当前任务状态
"plan": None,
"tool_results": [],
"notes": []
}
self.long_term = {} # 持久化记忆
5. 进阶功能:规划与执行分离
5.1 简单规划器实现
对于复杂任务,我们需要先制定计划再执行:
python复制def create_plan(user_input):
# 实际项目中这里会调用LLM生成计划
return {
"goal": user_input,
"steps": [
"理解任务需求",
"收集必要信息",
"执行核心操作",
"验证结果",
"返回最终答案"
]
}
5.2 运行时调度器
将各个模块整合成一个完整的运行时系统:
python复制class AgentRuntime:
def __init__(self):
self.llm = LLMClient()
self.tools = ToolRegistry()
self.memory = Memory()
self.planner = Planner()
def handle_task(self, user_input):
# 1. 创建计划
plan = self.planner.create_plan(user_input)
self.memory.set_plan(plan)
# 2. 执行循环
while not self.is_task_complete():
# 构建上下文
context = self.memory.build_context()
# 获取下一步决策
decision = self.llm.decide_next_step(context)
# 执行动作
if decision["action"] == "use_tool":
result = self.tools.call(
decision["tool_name"],
**decision["tool_args"]
)
self.memory.add_tool_result(result)
# ...其他处理逻辑
6. 工程实践:从脚本到系统
6.1 项目结构演进
随着功能增加,代码组织也需要相应调整:
code复制agent_runtime/
├── main.py # 入口点
├── config.py # 配置管理
└── agent/ # 核心模块
├── llm.py # 模型交互
├── memory.py # 状态管理
├── planner.py # 任务规划
├── runtime.py # 主循环
├── tools.py # 工具系统
└── schemas.py # 数据模型
6.2 配置管理最佳实践
使用环境变量和配置文件管理敏感信息:
python复制# config.py
import os
from pydantic import BaseSettings
class Settings(BaseSettings):
openrouter_key: str = os.getenv("OPENROUTER_API_KEY")
model_name: str = "anthropic/claude-sonnet-4.6"
class Config:
env_file = ".env"
settings = Settings()
7. 部署与运行
7.1 环境准备
创建虚拟环境并安装依赖:
bash复制python -m venv venv
source venv/bin/activate # Linux/Mac
# venv\Scripts\activate # Windows
pip install requests python-dotenv pydantic
7.2 运行Agent
设置环境变量后启动系统:
bash复制export OPENROUTER_API_KEY="your_key_here"
python -m agent_runtime.main
8. 经验总结与避坑指南
8.1 关键决策点
- 先验证再优化:不要一开始就追求完美架构
- 结构化胜过字符串:尽早引入JSON等结构化数据格式
- 明确边界:不要让模型直接操作系统资源
- 状态分离:区分对话历史、工作记忆和长期记忆
8.2 常见问题解决
问题:模型不遵循输出格式
- 解决方案:在提示词中强调格式要求,添加解析后验证
问题:工具调用失败
- 解决方案:实现自动重试机制,记录详细错误日志
问题:上下文窗口爆炸
- 解决方案:实现智能上下文窗口管理,优先保留关键信息
9. 未来演进方向
- 分布式工具调用:支持远程工具和服务调用
- 记忆压缩与检索:实现长期记忆的高效存储和检索
- 多Agent协作:构建能够分工合作的Agent团队
- 性能监控:添加详细的指标收集和分析
这个项目最让我深刻的体会是:好的系统架构不是设计出来的,而是演进出来的。从最初的20行脚本到现在这个可扩展的Runtime,每一次改进都是由真实需求驱动的。这种渐进式工程化的方法,既能保证早期快速验证,又能确保系统随着需求增长而自然演进。
