依赖:
openai/ 模块:AgentTrace(可观测)、Compaction(上下文压缩)、Persistence(持久化)
一、整体架构
agent.py (~300 行)
├── trace.py ── 可观测性:Span 树 + Trace 日志
├── compaction.py ── 上下文压缩:超长时自动摘要
├── persistence.py ── 持久化:会话保存/恢复 (Store → jsonl)
└── openai ── LLM 调用 (兼容接口)
一条线串起 4 个层次 —— 最简 Agent → 可观测 → 上下文管理 → 持久化 —— 每一层只引入一个类/文件,不搞抽象。
二、工具系统
2.1 工具即函数
两个工具,直白到不需要解释:
def search_web(query: str) -> str:
"""模拟搜索,实际可接 Google/Bing API"""
fake_db = {"北京": "北京今天晴天,25°C~32°C", ...}
return fake_db.get(query, f"未找到'{query}'的相关结果")
def calculate(expression: str) -> str:
"""安全的数学计算"""
...
2.2 JSON Schema 注册表
遵循 OpenAI Function Calling 规范,每个工具的类型、名称、描述、参数 schema 是一个 dict:
TOOLS = [
{"type": "function", "function": {"name": "search_web", "description": "...", "parameters": {...}}},
{"type": "function", "function": {"name": "calculate", "description": "...", "parameters": {...}}},
]
TOOL_MAP = {"search_web": search_web, "calculate": calculate}
设计要点:LLM 只读 TOOLS 的 JSON Schema 做推理,运行时由 TOOL_MAP 分发执行——模型无法直接跑代码,安全隔离。
三、核心循环:run_agent()
3.1 流程图
用户输入
│
▼
┌──────────────────────────────────────┐
│ messages: [system, user_input, ...] │
└──────────────────┬───────────────────┘
▼
┌────────────────┐
│ LLM 思考 │ ← tools=TOOLS, tool_choice="auto"
│ (任意模型) │
└───────┬────────┘
│
┌─────────┴─────────┐
▼ ▼
有 tool_calls? 无 tool_calls?
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 执行工具 │ │ 返回最终答案 │
│ 结果→messages │ └──────────────┘
└──────┬───────┘
│
└──→ 循环 (max_steps=5)
3.2 核心代码(~50 行)
def run_agent(user_input: str, max_steps: int = 5) -> str:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_input},
]
for step in range(max_steps):
response = client.chat.completions.create(
model="your-model-name",
messages=messages,
tools=TOOLS,
tool_choice="auto", # LLM 自己决定要不要调工具
)
msg = response.choices[0].message
if not msg.tool_calls: # 情况 A:直接回答
return msg.content
messages.append(msg) # 情况 B:执行工具
for tool_call in msg.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
result = TOOL_MAP[name](**args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
# 循环回去,LLM 看到工具结果后继续思考
return "⚠️ 达到最大步数限制"
3.3 设计要点
| 要点 | 说明 |
|---|---|
tool_choice="auto" | LLM 自主决策,不强制调用——简单的问候直接答,需要信息才调工具 |
| 对话历史 = 状态 | messages 数组是 Agent 的全部「记忆」,无需额外状态管理 |
| 最大步数 5 | 硬上限防无限循环,适合简单查询,复杂任务可调高 |
| 纯文本交互 | 无 UI 依赖,stdio 打印每步的执行过程 |
四、增强版:run_agent_with_trace()
在基础循环上叠加了三层能力:
4.1 可观测 —— AgentTracer
# 每次 LLM 调用:记录延迟 + 输入/输出
llm_span = tracer.log_llm_call(messages, response, elapsed)
# 每次工具调用:记录参数 + 结果 + 耗时
tool_span = tracer.log_tool_call(name, args, result, elapsed)
# 构建 Span 树
run = tracer.start_run(run_id, user_input)
run.children.append(llm_span)
run.children.append(tool_span)
4.2 上下文压缩 —— ContextManager
# 每轮结束后检查是否需要压缩
messages = ctx.maybe_compact(messages, client)
# 内部逻辑:调用 LLM 生成历史摘要,替换旧的 messages
4.3 持久化 —— PersistenceManager
# 创建新会话
session_id = pm.new_session_id() # 时间戳 + 随机哈希
# 每轮自动保存(防崩溃)
pm.save_messages(session_id, messages)
# 会话完成时保存完整状态
pm.save_session(session_id, messages, ctx.summary, tracer.to_dicts(), user_input)
# 恢复历史会话
state = pm.load_session(session_id) # → {messages, summary, traces}
4.4 会话恢复
def resume_session(session_id, new_input, *, client, ...):
"""恢复历史会话并继续对话"""
state = pm.load_session(session_id)
messages = state["messages"]
ctx.restore(state["summary"])
# 然后走常规 run_agent 循环
4.5 完整版本
def run_agent_with_trace(
user_input: str,
*,
tracer: AgentTracer,
client: OpenAI,
ctx: ContextManager,
pm: PersistenceManager,
session_id: str | None = None,
max_steps: int = 5,
) -> str:
"""
带 可观测 + 压缩 + 持久化 的 Agent 循环。
在原来 run_agent_with_trace 上直接加持久化能力。
"""
# ── 创建或恢复会话 ─────────────────────────────
if session_id:
state = pm.load_session(session_id)
messages = state["messages"]
ctx.restore(state["summary"])
print(f"📂 恢复会话 {session_id},已有 {len(messages)} 条消息")
else:
session_id = pm.new_session_id()
messages = []
# 追加用户输入 + 确保 system prompt 在第一位
messages.append({"role": "user", "content": user_input})
if not messages or messages[0].get("role") != "system":
messages.insert(0, {"role": "system", "content": SYSTEM_PROMPT})
# ── Trace 开始 ─────────────────────────────────
run_id = f"run_{os.urandom(6).hex()}"
run = tracer.start_run(run_id, user_input)
# ── 主循环 ─────────────────────────────────────
for step in range(max_steps):
print(ctx.stats(messages))
t0 = time.time()
try:
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=messages,
tools=TOOLS,
tool_choice="auto",
)
except Exception as e:
llm_err = Span(
span_id=f"llm_{os.urandom(4).hex()}",
type="llm_call",
start_time=t0,
end_time=time.time(),
error=str(e),
)
run.children.append(llm_err)
raise
llm_span = tracer.log_llm_call(messages, response, time.time() - t0)
run.children.append(llm_span)
msg = response.choices[0].message
# ✅ 新增:统一转 dict 再 append
messages.append(to_dict(msg))
# 无需工具 → 输出答案
if not msg.tool_calls:
run.end_time = time.time()
# ✅ 新增:最终保存
pm.save_session(
session_id, messages, ctx.summary,
tracer.to_dicts(), user_input,
)
print(tracer.summary(run))
print(f"💾 会话已保存: {session_id}")
return msg.content or ""
# 执行工具
for tool_call in msg.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
t0 = time.time()
try:
result = TOOL_MAP[name](**args)
except Exception as e:
result = f"工具执行错误: {e}"
tool_span = tracer.log_tool_call(name, args, result, time.time() - t0)
run.children.append(tool_span)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
# ✅ 新增:压缩检查
messages = ctx.maybe_compact(messages, client)
# ✅ 新增:每轮自动保存(防崩溃)
pm.save_messages(session_id, messages)
# 达到最大步数
run.end_time = time.time()
pm.save_session(session_id, messages, ctx.summary, tracer.to_dicts(), user_input)
print(tracer.summary(run))
print(f"💾 会话已保存: {session_id}")
return "⚠️ 达到最大步数限制"
五、整体设计评价
优点
- 极简:核心 Agent 循环 ~50 行,可运行、可调试、可教学
- 渐进增强:基础 → trace → compaction → persistence,每层独立模块化
- 安全:工具执行在 host 端,LLM 只输出 tool_call JSON,无法直接执行代码
- 可恢复:full session 持久化 + 单轮自动保存,崩溃后可续跑
- OpenAI 兼容:
base_url可替换为任何兼容 API(火山引擎 / DeepSeek / 本地 vLLM)
局限
- 无流式输出:所有调用都是非流式的,用户需等完整响应
- 同步阻塞:工具调用串行,不能并发执行多个独立工具
- 工具少:仅 2 个 mock 工具,生产需扩展
- 无工具错误恢复:工具执行失败只返回错误字符串,无重试/降级
适用场景
- 学习 Agent 核心概念(ReAct 循环、工具调用、Function Calling)
- 快速原型验证(有新想法时 5 分钟跑起来)
- 教学演示(每一步可 print 观察)
扩展方向
| 方向 | 改动点 |
|---|---|
| 流式输出 | client.chat.completions 加 stream=True |
| 并行工具执行 | asyncio.gather 并发跑 tool_calls |
| 工具重试 | 错误时追加 "工具执行错误,请重试" 到 messages |
| 多 Agent | 多个 run_agent 实例共享 messages + 角色路由 |
| MCP 集成 | TOOLS 动态拉取 MCP server 的 tools/list |
六、模块依赖图
agent.py
├─ openai (外部)
├─ trace.py
│ └─ dataclass Span (标准库)
├─ compaction.py
│ └─ + openai (调用 LLM 做摘要)
└─ persistence.py
└─ Store (jsonl) (标准库)
全部 4 个文件、< 800 行代码、0 个第三方框架依赖(仅 openai 一个 pip 包)。