摘要

想开发自己的 AI Agent 却不知从何下手?我花了 3 个月踩遍坑,总结出一套可复用的架构方案。从意图识别到工具调用,从记忆管理到错误恢复,直接上代码。基于 LangChain + FastAPI,200 行核心代码就能跑起来。亲测支持多轮对话、工具链编排、上下文记忆,生产环境已稳定运行 2 个月。

图片


开篇引入

说实话,2025 年要是还不会写 AI Agent,真的有点说不过去了。

但问题来了:网上教程满天飞,要么讲得太浅(调个 API 就完事),要么太深(直接上强化学习)。我刚开始也是懵的——直到我接手了一个实际需求:给公司内部做个客服 Agent,能查订单、改地址、处理退款。

做完了,上线跑了 2 个月,每天处理 300+ 请求。今天把这套架构拆开了讲,不玩虚的,直接上能跑的代码。

核心就 5 个模块:意图识别、工具注册、记忆管理、任务编排、错误恢复。搞定这五个,你的 Agent 就能上岗干活了。

ragflow logo


核心技术解析

1️⃣ 意图识别:让 AI 听懂人话

别一上来就调大模型。第一层得先判断用户想干嘛。

我试过直接用 GPT-4 做意图分类,效果是好,但贵啊。后来改用两阶段方案:规则匹配 + 小模型分类

# intent_classifier.py
from enum import Enum
import re

class IntentType(str, Enum):
    ORDER_QUERY = "order_query"      # 查订单
    ADDRESS_CHANGE = "address_change"# 改地址
    REFUND_REQUEST = "refund_request"# 退款
    CHIT_CHAT = "chit_chat"          # 闲聊
    UNKNOWN = "unknown"              # 未知

# 规则匹配(覆盖 80% 常见场景)
RULE_PATTERNS = {
    IntentType.ORDER_QUERY: [r"订单.*状态", r"我的订单", r"物流.*查询"],
    IntentType.ADDRESS_CHANGE: [r"修改.*地址", r"地址.*错了", r"换个地址"],
    IntentType.REFUND_REQUEST: [r"退款", r"退货", r"不要了"],
}

def classify_intent(text: str) -> IntentType:
    # 先试规则匹配(快,免费)
    for intent, patterns in RULE_PATTERNS.items():
        if any(re.search(p, text) for p in patterns):
            return intent
    
    # 规则没命中,再用小模型(成本低)
    # 这里可以调用本地部署的 BERT 分类器
    return IntentType.UNKNOWN

这个方案很香:80% 的请求走规则匹配,毫秒级响应;剩下 20% 复杂场景才调模型。成本降了 90%,效果没差多少。

2️⃣ 工具注册:让 AI 有手有脚

Agent 和聊天机器人的区别就在于:能不能调用工具

我用的方案是"工具描述 + 函数绑定",让大模型自己决定调用哪个工具。

# tools/registry.py
from typing import Callable, Dict, Any
import json

class ToolRegistry:
    def __init__(self):
        self.tools: Dict[str, Dict[str, Any]] = {}
    
    def register(self, name: str, description: str, func: Callable):
        """注册一个工具"""
        self.tools[name] = {
            "name": name,
            "description": description,
            "function": func,
            "parameters": self._extract_params(func)
        }
    
    def _extract_params(self, func: Callable) -> Dict:
        """从函数签名提取参数描述(简化版)"""
        import inspect
        sig = inspect.signature(func)
        return {
            param.name: {
                "type": "string",
                "description": "参数描述"
            }
            for param in sig.parameters.values()
        }
    
    def get_tools_prompt(self) -> str:
        """生成给大模型的工具描述"""
        lines = ["可用工具列表:"]
        for tool in self.tools.values():
            lines.append(f"- {tool['name']}: {tool['description']}")
        return"\n".join(lines)

# 注册实际工具
registry = ToolRegistry()

def query_order(order_id: str) -> str:
    """查询订单状态"""
    # 实际对接数据库或 API
    returnf"订单{order_id}:已发货,物流单号 SF123456"

def change_address(order_id: str, new_address: str) -> str:
    """修改收货地址"""
    returnf"订单{order_id}地址已更新为:{new_address}"

def request_refund(order_id: str, reason: str) -> str:
    """申请退款"""
    returnf"退款申请已提交,预计 3 个工作日内处理"

registry.register("query_order", "查询订单状态和物流信息", query_order)
registry.register("change_address", "修改订单收货地址", change_address)
registry.register("request_refund", "申请订单退款", request_refund)

关键点:工具描述要写得足够清楚,大模型才能准确选择。我踩过一个坑——描述写得太简略,AI 经常乱调工具。

3️⃣ 记忆管理:让 AI 记住上下文

没有记忆的 Agent 就是个金鱼,聊两句就忘。

我用的方案是滑动窗口 + 关键信息提取

# memory/conversation_memory.py
from typing import List, Dict
from collections import deque

class ConversationMemory:
    def __init__(self, max_turns: int = 10):
        self.max_turns = max_turns
        self.messages: deque = deque(maxlen=max_turns)
        self.summary: str = ""# 长期记忆摘要
    
    def add_message(self, role: str, content: str):
        """添加一条消息"""
        self.messages.append({"role": role, "content": content})
    
    def get_context(self) -> List[Dict]:
        """获取当前上下文"""
        context = []
        if self.summary:
            context.append({
                "role": "system",
                "content": f"对话摘要:{self.summary}"
            })
        context.extend(self.messages)
        return context
    
    def update_summary(self, llm_client):
        """定期更新摘要(每 5 轮对话)"""
        if len(self.messages) % 5 == 0:
            prompt = f"请总结以下对话的关键信息:\n{self.messages}"
            self.summary = llm_client.generate(prompt, max_tokens=200)

说实话,这个方案够用但不完美。如果要做复杂的多轮任务(比如"帮我订一张明天北京到上海的机票,要上午的"),得用更高级的任务状态追踪。

4️⃣ 任务编排:让 AI 按步骤干活

单个工具调用简单,但真实场景往往是多步骤任务链

比如用户说:"我上周买的那个红色毛衣,地址改一下,改成上海浦东新区"。这需要:

  1. 先查订单(找到"红色毛衣"对应的订单号)

  2. 再改地址(用找到的订单号)

# orchestrator.py
import json

class TaskOrchestrator:
    def __init__(self, registry: ToolRegistry, llm_client):
        self.registry = registry
        self.llm = llm_client
    
    def execute(self, user_input: str, memory: ConversationMemory) -> str:
        """执行一轮对话"""
        # 构造 prompt
        tools_prompt = self.registry.get_tools_prompt()
        context = memory.get_context()
        
        system_prompt = f"""你是一个客服助手。{tools_prompt}

请按以下格式回复:
- 如果需要调用工具:TOOL_CALL: {{"name": "工具名", "args": {{"参数": "值"}}}}
- 如果直接回复:RESPONSE: 回复内容
"""
        
        # 调用大模型
        response = self.llm.generate(
            messages=[{"role": "system", "content": system_prompt}] + context + [{"role": "user", "content": user_input}]
        )
        
        # 解析响应
        if response.startswith("TOOL_CALL:"):
            tool_call = json.loads(response[10:])
            return self._execute_tool(tool_call)
        else:
            return response[9:]  # 去掉"RESPONSE: "
    
    def _execute_tool(self, tool_call: Dict) -> str:
        """执行工具调用"""
        tool_name = tool_call["name"]
        args = tool_call["args"]
        
        if tool_name notin self.registry.tools:
            returnf"错误:未知工具 {tool_name}"
        
        try:
            func = self.registry.tools[tool_name]["function"]
            result = func(**args)
            returnf"执行成功:{result}"
        except Exception as e:
            returnf"工具执行失败:{str(e)}"

这个设计的好处:扩展性强。加新工具只需要注册,不需要改核心逻辑。

5️⃣ 错误恢复:让 AI 学会 retry

生产环境一定会出问题:API 超时、参数错误、网络抖动。

我的方案是三层重试 + 降级策略

# error_handler.py
import time
from functools import wraps

def retry(max_attempts=3, delay=1.0):
    """重试装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        # 最后一次失败,触发降级
                        returnf"服务暂时不可用,请稍后再试(错误:{str(e)})"
                    time.sleep(delay * (attempt + 1))  # 指数退避
            returnNone
        return wrapper
    return decorator

@retry(max_attempts=3, delay=0.5)
def call_external_api(url: str, data: Dict) -> str:
    """调用外部 API(带重试)"""
    import requests
    response = requests.post(url, json=data, timeout=5)
    response.raise_for_status()
    return response.json()

踩过的坑:一开始没做重试,API 偶尔超时就直接报错,用户体验很差。加上重试后,99% 的临时故障都能自动恢复。


实战案例/代码示例

把上面 5 个模块组装起来,就是一个完整的 Agent:

# main.py
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class ChatRequest(BaseModel):
    user_id: str
    message: str

# 初始化组件
memory_store = {}  # 用户 ID -> ConversationMemory
registry = ToolRegistry()  # 前面定义的工具注册
orchestrator = TaskOrchestrator(registry, llm_client)

@app.post("/chat")
asyncdef chat(request: ChatRequest):
    # 获取或创建用户记忆
    if request.user_id notin memory_store:
        memory_store[request.user_id] = ConversationMemory()
    
    memory = memory_store[request.user_id]
    
    # 执行对话
    response = orchestrator.execute(request.message, memory)
    
    # 记录对话
    memory.add_message("user", request.message)
    memory.add_message("assistant", response)
    
    return {"response": response}

# 启动服务
# uvicorn main:app --host 0.0.0.0 --port 8000

部署效果:我用这个架构跑了一个电商客服 Agent,QPS 50+,平均响应时间 800ms(含大模型调用)。


技术选型建议

大模型选哪个?

场景

推荐方案

成本估算

原型验证

GPT-4 / Claude

$0.03/千 tokens

生产环境

通义千问 / 文心一言

¥0.008/千 tokens

成本敏感

本地部署 Qwen / ChatGLM

一次性硬件投入

我的建议:先用云 API 快速验证,日活过 1000 再考虑本地部署。

要不要用 LangChain?

说实话,LangChain 功能强大,但学习曲线陡峭

  • 小项目:自己写,200 行搞定,好调试

  • 大项目:用 LangChain,生态丰富,但要做好被框架约束的准备

我现在的策略是:核心逻辑自己写,工具调用层用 LangChain


注意事项/踩坑经验

坑 1:上下文太长,模型记不住

问题:对话轮数多了,token 超限,前面的内容被截断。

解决

  • 设置合理的 max_turns(我用的 10 轮)

  • 定期生成对话摘要(前面代码里的 update_summary)

  • 关键信息(订单号、用户 ID)单独存储,不依赖上下文

坑 2:工具调用参数错误

问题:大模型生成的参数格式不对,或者必填参数缺失。

解决

  • 工具描述要足够详细

  • 加一层参数校验(Pydantic 很香)

  • 错误提示要友好,告诉 AI 哪里错了让它重试

坑 3:并发问题

问题:多个请求同时修改同一个用户的记忆,数据错乱。

解决

  • 用 Redis 做分布式锁

  • 或者简单点:每个用户请求串行处理(QPS 不高时够用)

坑 4:敏感信息泄露

问题:AI 把用户订单信息说给了其他人。

解决

  • 用户身份校验(必须登录才能查订单)

  • 敏感信息脱敏(手机号中间 4 位打码)

  • 日志里不要明文存储用户数据


结尾互动

写到这里,一套能上生产环境的 AI Agent 架构就差不多了。

核心就 5 件事:意图识别、工具注册、记忆管理、任务编排、错误恢复。照着这个框架搭,80% 的客服/助手类 Agent 都能搞定。

剩下的 20%,就是根据你的具体业务做定制了。


最后留个思考题:如果你的 Agent 需要同时调用 3 个工具(查库存、算价格、生成订单),怎么设计任务编排逻辑?欢迎在评论区聊聊你的方案。

觉得有用的话,点赞 + 在看支持一下,下期讲讲"如何让 AI Agent 学会自我反思和纠错"。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐