前言
从 ChatGPT 插件生态到 Claude Tool Use,从 LangChain Agents 到各种 AutoGPT 变体,AI Agent(人工智能代理)已经成为大模型落地最重要的应用形态。但真正将 Agent 做到生产级别并不简单------工具调用的稳定性、记忆系统的可靠性、多轮对话的上下文管理,每一项都是工程上的硬骨头。
本文将聚焦三大核心议题,带你从「能跑」走向「能上线」:
- 工具调用(Tool Calling)------如何让 Agent 稳定调用外部工具
- 记忆架构(Memory Architecture)------如何管理短期/长期记忆,避免「聊着聊着忘了」的问题
- 实战示例------用 Python 从零实现一个具备记忆的工具调用 Agent
一、工具调用:Agent 的手脚
1.1 什么是 Tool Calling
Tool Calling(工具调用)是让大模型主动调用外部函数或 API 的能力。本质上,它解决的是「LLM 不能做什么」的问题------LLM 训练数据有截止日期,无法实时搜索网络,无法读写本地文件,无法执行代码,而 Tool Calling 弥补了这些短板。
主流模型的 Tool Calling 实现方式有两种:
| 方式 | 代表模型 | 特点 |
|---|---|---|
| Function Calling | OpenAI GPT-4/4o、Anthropic Claude | 结构化 JSON 输出,工具 schema 预定义 |
| Tool Use | Anthropic Claude 3.5+ | 更自然的指令式调用 |
1.2 工具调用的完整流程
一个完整的工具调用循环如下:
``` 用户输入 → LLM 理解意图 → 判断是否需要工具 → 选择工具 → 生成参数 ↓ 执行工具 → 获取结果 → 反馈给 LLM → 再次理解 → 判断是否还需工具 ↓ 最终回复用户 ```
这个循环可能执行多轮,直到 LLM 判断不需要再调用工具为止。
1.3 工具 Schema 的设计原则
好的工具 Schema 直接影响调用成功率。以下是生产级工具的设计规范:
```python from typing import Annotated, Literal from pydantic import BaseModel, Field
class SearchWebTool(BaseModel): """搜索互联网获取最新信息""" query: Annotatedstr, Field( description="搜索关键词,需要简洁明确,避免过长", min_length=2, max_length=100 ) max_results: Annotatedint, Field( description="最大返回结果数", default=5, ge=1, le=20 )
class CalculatorTool(BaseModel): """执行数学计算(支持加减乘除、幂运算)""" expression: Annotatedstr, Field( description="数学表达式,如 '2 + 3 \* 4' 或 '10 \*\* 2'", pattern=r'\^\[0-9+\\-\*/().%\\s+$' )] ```
三条核心原则:
- description 即 Prompt ------ description 是 LLM 理解工具用途的唯一依据,要具体说明输入输出,不能模糊
- 参数加约束 ------ 用 `Annotated` + `Field` 加 `min_length`、`max_length`、`pattern` 等约束,减少 LLM 幻觉
- 每个工具单一职责 ------ 不要设计「万能工具」,一个工具只做一件事
1.4 工具执行层的容错设计
生产环境中,工具调用失败是常态。需要设计完善的容错层:
```python import asyncio from typing import Any, Callable from functools import wraps
class ToolExecutionError(Exception): """工具执行异常""" def init (self, tool_name: str, original_error: Exception): self.tool_name = tool_name self.original_error = original_error super().init(f"Tool '{tool_name}' execution failed: {original_error}")
async def safe_execute_tool( tool_fn: Callable, max_retries: int = 3, backoff_base: float = 1.0 ) -> Any: """ 带重试的工具执行包装器
css
指数退避策略:
- 第1次失败:等待 1s 后重试
- 第2次失败:等待 2s 后重试
- 第3次失败:返回错误结果
"""
last_error = None
for attempt in range(max_retries):
try:
result = await tool_fn()
return {"success": True, "data": result, "attempts": attempt + 1}
except Exception as e:
last_error = e
if attempt < max_retries - 1:
await asyncio.sleep(backoff_base * (2 ** attempt))
# 所有重试均失败
return {
"success": False,
"error": str(last_error),
"attempts": max_retries
}
```
二、记忆架构:Agent 的大脑
2.1 为什么记忆系统至关重要
没有记忆的 Agent 是「金鱼」------每轮对话都从零开始,历史信息全部丢失。这在简单问答场景中可以接受,但在复杂任务中不可接受。
典型的记忆分层架构:
``` ┌─────────────────────────────────────┐ │ Working Memory(工作记忆) │ ← 当前会话上下文,Token 窗口内 ├─────────────────────────────────────┤ │ Short-Term Memory(短期记忆) │ ← 最近 N 轮对话摘要 ├─────────────────────────────────────┤ │ Long-Term Memory(长期记忆) │ ← 持久化存储,向量检索 └─────────────────────────────────────┘ ```
2.2 短期记忆:滑动窗口摘要
最简单的短期记忆实现是「滑动窗口」------保留最近 N 条消息,超出的部分做摘要压缩:
```python from dataclasses import dataclass, field from typing import List, Optional import json
@dataclass class ShortTermMemory: """短期记忆:基于消息条数的滑动窗口""" max_messages: int = 20
python
# 原始消息列表
messages: List[dict] = field(default_factory=list)
# 摘要(当消息超长时压缩)
summary: Optional[str] = None
def add_message(self, role: str, content: str) -> None:
"""添加一条消息"""
self.messages.append({
"role": role,
"content": content,
"timestamp": self._get_timestamp()
})
# 超过窗口大小时,压缩历史
if len(self.messages) > self.max_messages:
self._compress()
def _compress(self) -> None:
"""压缩历史消息:保留摘要 + 最近几条"""
recent_count = 5
recent = self.messages[-recent_count:]
# 生成摘要(这里可以用 LLM API,也可以用简单的模板)
history_for_summary = self.messages[:-recent_count]
if history_for_summary:
self.summary = self._generate_summary(history_for_summary)
# 只保留摘要 + 最近消息
self.messages = recent
def _generate_summary(self, messages: List[dict]) -> str:
"""生成对话摘要"""
# 简化实现:提取关键信息
total_tokens = sum(len(m["content"]) for m in messages)
topics = set()
for m in messages:
words = m["content"].split()[:3]
topics.update(words)
return f"[摘要] 共{len(messages)}条消息,约{total_tokens}字,涉及: {', '.join(topics)}"
def get_context(self) -> List[dict]:
"""获取完整的上下文"""
context = []
if self.summary:
context.append({
"role": "system",
"content": f"【历史摘要】{self.summary}"
})
context.extend(self.messages)
return context
@staticmethod
def _get_timestamp() -> str:
from datetime import datetime
return datetime.now().isoformat()
```
2.3 长期记忆:向量检索 + 知识图谱
短期记忆解决「最近忘了什么」的问题,长期记忆解决「过去学了什么」的问题。
生产级长期记忆通常用向量数据库(如 Milvus、Qdrant、Chroma)做语义检索:
```python from dataclasses import dataclass from typing import List, Tuple import numpy as np
@dataclass class LongTermMemory: """长期记忆:向量语义检索"""
python
def __init__(self, embedding_model: str = "text-embedding-3-small"):
self.embedding_model = embedding_model
self.vectors: List[np.ndarray] = []
self.texts: List[str] = []
self.metadata: List[dict] = []
def add_memory(
self,
content: str,
metadata: dict = None
) -> None:
"""添加一条长期记忆"""
# 1. 生成向量嵌入
vector = self._embed(content)
# 2. 存储向量 + 原始文本 + 元数据
self.vectors.append(vector)
self.texts.append(content)
self.metadata.append(metadata or {})
def retrieve(
self,
query: str,
top_k: int = 5,
score_threshold: float = 0.7
) -> List[Tuple[str, float, dict]]:
"""
语义检索相关记忆
Returns:
[(记忆文本, 相似度分数, 元数据), ...]
"""
query_vector = self._embed(query)
# 计算余弦相似度
scores = []
for vec in self.vectors:
score = self._cosine_similarity(query_vector, vec)
scores.append(score)
# 取 Top-K
indexed_scores = list(enumerate(scores))
indexed_scores.sort(key=lambda x: x[1], reverse=True)
results = []
for idx, score in indexed_scores[:top_k]:
if score >= score_threshold:
results.append((self.texts[idx], score, self.metadata[idx]))
return results
def _embed(self, text: str) -> np.ndarray:
"""调用嵌入模型(需替换为实际 API)"""
# 这里使用模拟实现
# 实际项目请使用 OpenAI embeddings 或本地 embedding 模型
import hashlib
seed = int(hashlib.md5(text.encode()).hexdigest()[:8], 16)
np.random.seed(seed % (2**32))
return np.random.rand(1536) # text-embedding-3-small 维度
@staticmethod
def _cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
dot = np.dot(a, b)
norm_a = np.linalg.norm(a)
norm_b = np.linalg.norm(b)
return dot / (norm_a * norm_b + 1e-8)
```
2.4 记忆的「遗忘」机制
人类大脑会遗忘,Agent 也需要遗忘机制,否则记忆库会无限膨胀且信噪比下降。
实用的遗忘策略:
```python class MemoryConsolidation: """记忆整合与遗忘"""
python
@staticmethod
def should_forget(memory: dict, usage_count: int, last_used: str) -> bool:
"""
判断是否应该遗忘某条记忆
遗忘条件(满足任一即遗忘):
1. 长期未使用(超过 30 天)
2. 使用频率极低(< 3 次)且超过 60 天
3. 被标记为低价值(用户明确表示不感兴趣)
"""
from datetime import datetime, timedelta
last_used_dt = datetime.fromisoformat(last_used)
days_since_use = (datetime.now() - last_used_dt).days
if days_since_use > 30:
return True
if usage_count < 3 and days_since_use > 60:
return True
return memory.get("low_value", False)
@staticmethod
def merge_similar(memories: List[dict]) -> List[dict]:
"""
合并相似记忆,减少冗余
保留每组中最新、最完整的那条记忆
"""
if len(memories) < 2:
return memories
# 简化实现:按主题分组
groups = {}
for m in memories:
topic = m.get("topic", "default")
if topic not in groups:
groups[topic] = []
groups[topic].append(m)
result = []
for topic, group in groups.items():
# 每组保留最新
group.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
result.append(group[0])
return result
```
三、实战:从零构建生产级 AI Agent
3.1 整体架构
```python from typing import List, Optional, Literal from dataclasses import dataclass import json
@dataclass class Message: """对话消息""" role: Literal"user", "assistant", "system", "tool" content: str tool_call_id: Optionalstr = None name: Optionalstr = None
class ToolCallAgent: """具备工具调用和记忆系统的生产级 Agent"""
python
def __init__(
self,
llm_client, # LLM API 客户端
tools: List[dict], # 工具 schema 列表
short_term_memory: ShortTermMemory,
long_term_memory: Optional[LongTermMemory] = None,
max_turns: int = 10 # 最大工具调用轮次,防止无限循环
):
self.llm = llm_client
self.tools = tools
self.short_term = short_term_memory
self.long_term = long_term_memory
self.max_turns = max_turns
async def chat(self, user_input: str) -> str:
"""主对话入口"""
# Step 1: 检索长期记忆(如果启用)
relevant_memories = []
if self.long_term:
relevant_memories = self.long_term.retrieve(user_input, top_k=3)
if relevant_memories:
memory_context = "\\n".join(
f"- {text}" for text, _, _ in relevant_memories
)
user_input = f"【相关记忆】\\n{memory_context}\\n\\n【当前问题】\\n{user_input}"
# Step 2: 添加用户消息到短期记忆
self.short_term.add_message("user", user_input)
# Step 3: 工具调用循环
for turn in range(self.max_turns):
response = await self._call_llm(self.short_term.get_context())
# 检查是否有工具调用
if response.get("tool_calls"):
await self._handle_tool_calls(response["tool_calls"])
else:
# 没有工具调用,直接回复
final_answer = response["content"]
self.short_term.add_message("assistant", final_answer)
return final_answer
return "已达到最大交互轮次,请重新描述您的问题。"
async def _call_llm(self, messages: List[dict]) -> dict:
"""调用 LLM"""
# 适配 OpenAI / Claude 等主流 API
return await self.llm.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=self.tools,
tool_choice="auto"
)
async def _handle_tool_calls(self, tool_calls: List[dict]) -> None:
"""处理工具调用"""
for tc in tool_calls:
tool_name = tc["function"]["name"]
tool_args = json.loads(tc["function"]["arguments"])
tool_call_id = tc["id"]
# 执行工具(带容错)
result = await safe_execute_tool(
lambda: self._execute_tool(tool_name, tool_args)
)
if result["success"]:
# 成功:添加工具结果到上下文
self.short_term.add_message(
"tool",
str(result["data"]),
tool_call_id=tool_call_id
)
else:
# 失败:返回错误信息
self.short_term.add_message(
"tool",
f"【工具执行失败】{tool_name}: {result['error']}",
tool_call_id=tool_call_id
)
def _execute_tool(self, name: str, args: dict) -> str:
"""执行具体工具"""
tool_map = {
"search_web": self._tool_search_web,
"calculator": self._tool_calculator,
"get_weather": self._tool_weather,
}
if name not in tool_map:
raise ValueError(f"Unknown tool: {name}")
return tool_map[name](**args)
@staticmethod
def _tool_calculator(expression: str) -> str:
"""计算器工具"""
try:
result = eval(expression) # 生产环境请用 ast.literal_eval 或数学库
return f"计算结果:{expression} = {result}"
except Exception as e:
return f"计算错误:{e}"
@staticmethod
def _tool_search_web(query: str, max_results: int = 5) -> str:
"""搜索工具(需接入真实搜索 API)"""
return f"搜索「{query}」,找到 {max_results} 条相关结果(请接入真实搜索 API)"
@staticmethod
def _tool_weather(city: str) -> str:
"""天气查询工具(需接入真实天气 API)"""
return f"{city} 今日天气:晴,28°C(请接入真实天气 API)"
```
3.2 使用示例
```python import asyncio
初始化组件
short_memory = ShortTermMemory(max_messages=20) long_memory = LongTermMemory()
添加一些先验知识到长期记忆
long_memory.add_memory( "用户偏好使用 Python 3.11+,喜欢类型提示", metadata={"topic": "user_preference", "timestamp": "2025-01-15"} )
定义工具
tools = { "type": "function", "function": { "name": "calculator", "description": "执行数学计算(支持加减乘除、幂运算)", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "数学表达式,如 '2 + 3 \* 4' 或 '10 \*\* 2'" } }, "required": \["expression" } } }, { "type": "function", "function": { "name": "search_web", "description": "搜索互联网获取最新信息", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词,需要简洁明确" }, "max_results": { "type": "integer", "description": "最大返回结果数", "default": 5 } }, "required": "query" } } } ]
async def main(): # 这里需要传入实际的 LLM client # agent = ToolCallAgent(llm_client=..., tools=tools, ...) print("Agent 初始化完成,可开始对话") # response = await agent.chat("帮我计算 (12 + 8) * 3 的结果") # print(response)
asyncio.run(main()) ```
四、生产环境注意事项
4.1 安全性
```python
危险工具必须加权限校验
class SafeToolRegistry: """安全工具注册表"""
python
def __init__(self):
# 高危工具需要二次确认
self.dangerous_tools = {"delete_file", "execute_code", "send_email"}
self.allowed_tools = set()
def register_tool(self, name: str, dangerous: bool = False):
if name in self.dangerous_tools and not dangerous:
raise PermissionError(f"Tool '{name}' requires dangerous=True to register")
self.allowed_tools.add(name)
def can_execute(self, tool_name: str) -> bool:
return tool_name in self.allowed_tools
```
4.2 性能优化
- 批量工具:多个独立工具调用可并行执行(`asyncio.gather`)
- Token 预算:设置 `max_tokens` 上限,防止异常响应超长
- 流式输出:大模型响应建议用流式(Streaming),提升感知体验
4.3 监控与可观测性
```python @dataclass class AgentMetrics: """Agent 关键指标""" total_requests: int = 0 tool_call_count: int = 0 avg_turns_per_request: float = 0.0 error_rate: float = 0.0
python
def record_request(self, turns: int, success: bool):
self.total_requests += 1
self.tool_call_count += turns
self.error_rate = (self.error_rate * (self.total_requests - 1)
+ (0 if success else 1)) / self.total_requests
self.avg_turns_per_request = self.tool_call_count / self.total_requests
```
总结
本文从工程落地的角度,系统介绍了生产级 AI Agent 的三大核心能力:
| 能力 | 关键技术 | 核心挑战 |
|---|---|---|
| 工具调用 | Function Calling / Tool Use schema 设计 | 工具可用性、参数准确性、执行容错 |
| 短期记忆 | 滑动窗口 + 摘要压缩 | 压缩质量、上下文长度管理 |
| 长期记忆 | 向量检索 + 知识图谱 | 嵌入质量、检索精度、遗忘策略 |
从「玩具 demo」到「生产系统」,差距在于容错、监控、安全这些工程细节。希望本文提供的架构和代码模板,能帮助你少走弯路。
如果你有更好的实践方案,欢迎在评论区交流!