构建生产级 AI Agent:工具调用与记忆架构实战指南

前言

从 ChatGPT 插件生态到 Claude Tool Use,从 LangChain Agents 到各种 AutoGPT 变体,AI Agent(人工智能代理)已经成为大模型落地最重要的应用形态。但真正将 Agent 做到生产级别并不简单------工具调用的稳定性、记忆系统的可靠性、多轮对话的上下文管理,每一项都是工程上的硬骨头。

本文将聚焦三大核心议题,带你从「能跑」走向「能上线」:

  1. 工具调用(Tool Calling)------如何让 Agent 稳定调用外部工具
  2. 记忆架构(Memory Architecture)------如何管理短期/长期记忆,避免「聊着聊着忘了」的问题
  3. 实战示例------用 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+$' )] ```

三条核心原则

  1. description 即 Prompt ------ description 是 LLM 理解工具用途的唯一依据,要具体说明输入输出,不能模糊
  2. 参数加约束 ------ 用 `Annotated` + `Field` 加 `min_length`、`max_length`、`pattern` 等约束,减少 LLM 幻觉
  3. 每个工具单一职责 ------ 不要设计「万能工具」,一个工具只做一件事

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」到「生产系统」,差距在于容错、监控、安全这些工程细节。希望本文提供的架构和代码模板,能帮助你少走弯路。

如果你有更好的实践方案,欢迎在评论区交流!

相关推荐
kyriewen2 小时前
2026 年了,还在用 Node.js?Bun 迁移实战:20 分钟搞定,附踩坑记录
前端·javascript·node.js
青山Coding4 小时前
Cesium应用(八):物体运动的实现思路
前端·cesium
用户41659673693554 小时前
Android WebView 加载 file:// 离线页面调试教程
android·前端
Asmewill4 小时前
curl命令学习笔记一
前端
我是一只快乐的小螃蟹4 小时前
1.2 ArrayList 源码解析
前端
星栈4 小时前
我用 Rust + Dioxus 做了个全栈跨平台笔记应用:再把新建、编辑和交付补上
前端·rust·前端框架
我是一只快乐的小螃蟹4 小时前
1.1 HashMap (JDK1.8) 源码解析
前端
爱勇宝7 小时前
小红花成长新版:模板来了,鼓励也更容易开始
前端·后端·程序员
竹林8187 小时前
Solana前端开发:我在一个NFT铸造页面上被@solana/web3.js的Connection和Transaction签名坑了两天
前端