从零搭建Agent智能体:函数调用、记忆与知识检索的企业级实践指南
2026/9/8 9:13:56 网站建设 项目流程

在大模型应用落地过程中,Agent智能体已经成为团队从单轮问答走向真实业务系统的关键一步。很多项目Demo阶段很顺利,模型会调用工具、回答也很像那么回事,但一旦进入企业级开发,工具调用失败怎么暴露、历史对话怎么管理、私有知识怎么检索、权限边界怎么控制、线上故障怎么排查,这些问题会立刻浮出来。下面按一条从入门到企业级实战的路径展开:先理解Agent的核心组成,再基于大模型函数调用写一个最小可运行Agent,接着加入记忆和知识检索,最后补齐生产落地需要的可观测性、权限、成本和多Agent协作模式。读完以后,你可以照着同样的思路搭建自己的Agent项目,也清楚线上出问题时从哪一层开始查。

1. 先理解Agent智能体是什么,不只是“会调工具的聊天机器人”

1.1 从一次问答差异理解普通LLM程序与Agent的区别

普通的大模型程序通常是一次问答闭环:用户提问,模型生成回复,程序显示结果。整个过程没有状态,模型也不负责操作外部系统。比如用户问“今天几号”,如果只是纯LLM程序,模型要么凭训练数据猜测,要么直接说自己不知道,因为它拿不到系统时间。

Agent智能体则不同。它的核心变化是:模型不只是生成文本,而是在一个循环里做决策、调用工具、读取结果、修正下一步,直到完成任务。同样问“今天几号”,Agent会意识到这不是靠记忆能回答的问题,而是需要调用一个时间查询工具;工具返回数据之后,模型再基于这个数据组织成最终回答。

这个差异对开发者的启发很明显:写Agent时,你真正要做的事情不是“写一个提示词让模型更聪明”,而是设计一套让模型能安全、可控、可观测地调用外部能力的执行框架。理解这一点之后,很多后续问题都有了定位方向。

1.2 Agent的五个核心模块:模型、规划、记忆、工具、执行环境

一个可以落地的Agent,至少由五部分组成:

模块解决什么问题常见实现学习阶段重点
大模型理解用户意图并做出决策兼容OpenAI协议的模型服务选模型、写提示词、控制温度
规划把目标拆解成可执行步骤ReAct、Plan-and-Execute、编排框架设计循环终止条件
记忆维护对话上下文和长期知识会话消息列表、向量库、关系数据库控制上下文长度
工具让Agent触达外部系统函数调用、API封装、数据库连接定义工具schema和参数解析
执行环境运行Agent的服务与沙箱Python服务、任务队列、权限容器部署、隔离、审计

这五个模块不是所有项目一开始都要完整实现。学习阶段可以先只跑通“模型+工具”的最小闭环,再加入记忆,最后才考虑执行环境的权限和隔离。

1.3 ReAct循环:Agent最常见的思考-行动-观察模式

ReAct是当前Agent最基础的运行模式,它模拟的是人解决问题的过程:先思考,再行动,观察结果,然后决定继续还是结束。

一次完整的ReAct循环通常包含四步:

  1. 思考:模型根据当前问题判断下一步该做什么。
  2. 行动:模型输出一个工具调用指令,比如调用计算函数或查询接口。
  3. 观察:程序真正执行工具,并把执行结果返回给模型。
  4. 决策:模型根据观察结果决定是继续调用工具,还是生成最终回答。

举一个最小例子:用户输入“现在是几点”。

模型先思考,发现需要系统时间,于是输出调用get_current_time的指令。程序执行该函数,得到2026-01-15 14:30:00这样的结果,再把结果放回会话。模型看到结果后,生成“当前时间是2026年1月15日14点30分”的最终回复。

这里最容易误解的一点是:工具调用并不是模型自己执行的。模型只负责输出一个结构化的调用请求,真正执行函数的是你写的框架代码,执行结果再以观察结果的形式回到模型。排查问题时,必须先确定到底是一层出错,否则容易把模型决策问题误判成工具实现问题。

2. 开发前准备:环境、依赖、项目结构和模型接入

2.1 学习环境与生产环境的最小要求

学习阶段不需要复杂基础设施,一台能装Python虚拟环境的开发机就够了。生产环境则还需要考虑模型网关、日志中心、消息队列、配置中心和密钥管理。两者的差异可以用一张表看明白:

项目学习环境生产环境
Python版本3.10及以上3.10及以上或对应Docker镜像
大模型API兼容OpenAI协议的可用服务统一网关、限流、超时、重试、降级
依赖管理pip + requirements.txt锁定依赖版本或用镜像构建
密钥管理本地 .env 文件环境变量或密钥管理服务
数据存储SQLite / 内存向量列表独立数据库、向量库、对象存储
日志print结构化日志、链路追踪

这里不推荐把课程里的版本号原样复制到生产。落地前先确认所依赖SDK、目标模型和你使用的兼容服务都支持你计划用的函数调用特性。

2.2 创建项目结构和虚拟环境

建议从agent-starter这样的目录开始,避免在全局环境里装依赖。

mkdir agent-starter cd agent-starter python -m venv .venv source .venv/bin/activate pip install -U pip pip install openai python-dotenv

Windows下激活虚拟环境使用.venv\Scripts\activate。安装完成后,项目里可以先建立这样的目录结构:

agent-starter/ ├── .env ├── requirements.txt └── agent/ ├── __init__.py ├── core.py ├── tools.py └── memory.py

.env存密钥和模型配置,tools.py放Agent可以调用的工具函数,core.py放ReAct循环,memory.py放记忆和检索逻辑。

2.3 模型接入的通用配置方式

无论使用哪家模型服务,只要接口兼容OpenAI协议,调用方式都类似。关键是把api_keybase_url、模型名放在配置里,而不是写死在代码中。例如.env文件:

OPENAI_API_KEY=你的key OPENAI_BASE_URL=https://你的服务地址 MODEL_NAME=你的模型名 EMBEDDING_MODEL=你的embedding模型名

对应的Python初始化代码:

import os from dotenv import load_dotenv from openai import OpenAI load_dotenv() client = OpenAI( api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"), ) MODEL = os.getenv("MODEL_NAME", "gpt-4o-mini") EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "text-embedding-3-small")

注意:.env文件不要提交到代码仓库。生产环境建议通过部署平台的密钥管理能力注入环境变量,而不是维护一个明文配置。

3. 用函数调用做一个最小可运行的Agent

3.1 明确第一个Agent的任务边界

第一个Agent不要做成“什么都会”。任务边界越小,越容易验证功能。这里设计两个工具:一个负责时间查询,一个负责数学表达式计算。Agent需要根据用户问题决定是否调用工具,并基于工具结果回答。

函数调用机制可以简单理解为:你在请求中声明“有哪些工具可用”,模型根据需要决定要不要使用这些工具。模型返回的不是直接执行结果,而是一个tool_calls结构,里面包含函数名和参数JSON。框架代码负责把JSON解析成Python参数,调用真正的函数,再把结果作为role: "tool"的消息放回会话。

3.2 安全实现两个工具函数

计算工具这里不使用eval,因为直接执行用户输入的表达式存在明显风险。用抽象语法树解析会更安全一些,同时只放行常见运算。

# agent/tools.py import ast import operator import time _ALLOWED_OPS = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Mod: operator.mod, ast.Pow: operator.pow, ast.USub: operator.neg, ast.UAdd: operator.pos, } def _eval_expr(node): if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value if isinstance(node, ast.BinOp) and type(node.op) in _ALLOWED_OPS: return _ALLOWED_OPS[type(node.op)](_eval_expr(node.left), _eval_expr(node.right)) if isinstance(node, ast.UnaryOp) and type(node.op) in _ALLOWED_OPS: return _ALLOWED_OPS[type(node.op)](_eval_expr(node.operand)) raise ValueError("不支持的表达式") def calculate(expression: str) -> str: tree = ast.parse(expression, mode="eval") result = _eval_expr(tree.body) return str(result) def get_current_time() -> str: return time.strftime("%Y-%m-%d %H:%M:%S")

这里要注意:即使使用了AST解析,operator.pow也存在超大数计算风险。生产环境需要增加指数上限、长度限制和超时保护。

3.3 注册工具并实现ReAct循环

工具函数的Python实现只解决“执行”,还要给模型提供一份机器可读的工具schemas。下面是工具注册部分:

# agent/tools.py 追加 TOOLS = [ { "type": "function", "function": { "name": "calculate", "description": "计算数学表达式,支持加减乘除、括号和幂运算。", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "合法的数学表达式,例如 (2 + 3) * 4" } }, "required": ["expression"] } } }, { "type": "function", "function": { "name": "get_current_time", "description": "获取当前日期和时间。", "parameters": {"type": "object", "properties": {}} } } ] TOOL_MAP = { "calculate": calculate, "get_current_time": get_current_time, }

核心循环放在core.py,逻辑是:发送消息和工具列表,检查模型是否需要调用工具;如果没有tool_calls,说明模型已经可以直接回复,就把文本返回;如果有,则逐个执行工具,并把工具结果追加到消息列表,再进入下一轮请求。

# agent/core.py import json from openai import OpenAI from .tools import TOOLS, TOOL_MAP client = OpenAI() MODEL = "gpt-4o-mini" def run_agent(user_input: str, max_steps: int = 5) -> str: messages = [ { "role": "system", "content": "你是智能助手。如果用户的问题需要时间、计算等能力,请调用对应工具,获取结果后再回答。" }, {"role": "user", "content": user_input}, ] for _ in range(max_steps): response = client.chat.completions.create( model=MODEL, messages=messages, tools=TOOLS, tool_choice="auto", ) message = response.choices[0].message messages.append(message) if not message.tool_calls: return message.content for tool_call in message.tool_calls: fn = TOOL_MAP[tool_call.function.name] args = json.loads(tool_call.function.arguments) try: result = fn(**args) content = result except Exception as e: content = f"工具执行失败: {e}" messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": content, }) return "轮次已达上限,没有拿到最终结果。"

tool_choice="auto"表示允许模型自己决定是否调用工具。如果设置成"none",模型不会使用工具;如果指定具体函数名,则强制模型调用该函数。调试阶段打印message.tool_calls能确认模型是否真的发起了调用。

3.4 运行验证

写一个简单入口验证最小闭环:

from agent.core import run_agent print(run_agent("现在几点?")) print(run_agent("计算 (23 + 45) * 2 的结果"))

正常情况下,第一个问题会触发get_current_time,第二个问题会触发calculate,然后模型返回基于工具结果的最终答案。调试时建议临时打印每次请求后的tool_calls,确认模型决策符合预期。如果模型直接回答了计算结果,说明它没有把计算任务交给工具,这时需要确认工具schema描述是否清晰。

4. 给Agent补上记忆、知识检索和对话管理

4.1 短期记忆和长期记忆,Agent需要哪一种

短期记忆指当前会话内的上下文。实现方式最简单:把用户消息、模型回复、工具调用结果都放进同一个messages列表,下一轮继续使用。

长期记忆指跨会话保存的信息,比如用户偏好、历史事实和私有文档知识。这类数据不适合每轮都塞进上下文,通常需要向量化后存入向量库,在需要时按相似度检索。

判断需要哪种记忆,看你的Agent任务:只做单轮工具调用,短期记忆就够;做客服、销售助手、私人助理这类需要记住用户历史的场景,才需要长期记忆。

4.2 让Agent能连续多轮对话

最小Agent每轮都会新建messages,这会导致模型忘记前面的对话。改造方法也很直观:把消息列表提升为会话状态。

session_messages = [] def ask(question: str) -> str: global session_messages session_messages.append({"role": "user", "content": question}) # 复用 run_agent 的循环,但传入 session_messages 而不是新建 return run_agent(session_messages)

实际项目还要考虑用户会话隔离:同一个消息列表不能给所有用户用,必须按session_id维度隔离存储。

4.3 用向量检索接入私有知识库

如果Agent需要回答私有文档问题,可以先把文档切成片段,嵌入成向量,查询时做相似度检索,把最相关的片段作为额外上下文注入模型。

下面是一个便于理解的最小实现,使用embedding接口和NumPy计算余弦相似度,生产环境可以替换为专门的向量数据库:

# agent/memory.py import numpy as np class VectorMemory: def __init__(self, client, embedding_model): self.client = client self.embedding_model = embedding_model self.chunks = [] self.vectors = [] def _embed(self, text: str): response = self.client.embeddings.create( model=self.embedding_model, input=text ) return response.data[0].embedding def add(self, text: str): vector = self._embed(text) self.chunks.append(text) self.vectors.append(np.array(vector)) def search(self, query: str, top_k: int = 3): query_vec = np.array(self._embed(query)) scores = [(np.dot(query_vec, vec), idx) for idx, vec in enumerate(self.vectors)] scores.sort(reverse=True) return [self.chunks[idx] for _, idx in scores[:top_k]]

实际使用时,不能简单把检索结果直接拼进用户问题,而是应该告诉模型“以下是从知识库中检索到的资料,回答时优先参考它”。这样模型才知道这些内容的来源和用途。

4.4 记忆模块的工程注意点

记忆模块最容易出问题的不是算法,而是边界控制:

  • 上下文窗口有限,不能无限累积历史消息。
  • 每轮请求都会消耗Token,冗长历史会直接推高成本。
  • 用户A的数据不能出现在用户B的检索结果中。
  • 敏感信息需要脱敏后再进入模型上下文或向量库。

常见策略包括:旧消息摘要、截断工具调用细节、只把最近几轮完整消息保留,更早内容写入向量库按需检索。

5. 从Demo走向企业级:可观测性、权限、成本与多Agent模式

5.1 学习环境与生产环境的差异

把Agent从Demo搬到生产,往往不是代码行数变多,而是多了一整套围绕模型的工程保护。核心差异如下:

维度学习Demo企业级要求
模型调用直接请求统一网关、限流、超时、重试、降级
工具执行本机函数权限沙箱、白名单、审计
记忆内存列表持久化存储、多租户隔离
可观测性print链路追踪、结构化日志、指标
安全不敏感任务提示词注入防护、敏感信息过滤、参数校验
版本管理改完就跑模型版本、Prompt版本、工具版本可回滚

5.2 企业级Agent的模块化改造

第一件事是配置外置化。模型名、提示词模板、工具开关、最大轮数等都不应写死在代码里,应该放到配置中心或环境变量,便于灰度发布和回滚。

第二件事是异步化。Agent的单次任务可能涉及多次模型调用和工具调用,耗时会从几百毫秒膨胀到几十秒。用HTTP同步请求会造成网关超时,生产环境建议把长任务放入消息队列,由Worker执行并回传结果。

第三件事是可观测性。给每个Agent任务分配一个trace_id,记录用户输入、模型决策、工具调用、耗时和Token消耗。有了链路数据,才能回答“昨天还好好的,今天为什么效果变差了”。

import logging import uuid logger = logging.getLogger("agent") def run_agent_with_trace(user_input, session_id=None): trace_id = uuid.uuid4().hex logger.info("start trace_id=%s session_id=%s input=%s", trace_id, session_id, user_input) try: result = run_agent(user_input) logger.info("finish trace_id=%s result=%s", trace_id, result) return result except Exception as e: logger.exception("failed trace_id=%s error=%s", trace_id, e) raise

第四件事是权限收敛。Agent能调用的工具必须是最小权限集合。比如允许查询订单,不代表允许删除订单;允许读取用户信息,不代表允许导出全量数据。工具API在设计时要带用户身份和租户维度,服务端再做鉴权。

5.3 多Agent协作的常见模式

当业务复杂度超过单个Agent能力时,才考虑多Agent协作。常见模式有三种:

  • 监督者模式:主Agent负责任务分发,判断子Agent能力边界,汇总结果。
  • 流水线模式:上一个Agent的输出作为下一个Agent的输入,适合固定顺序的流程。
  • 共享工具模式:多个Agent复用同一套受限工具集,由统一网关做鉴权和审计。

这里要强调:单Agent还不够稳定时,不要急着引入多Agent。多Agent会放大错误传播,也会让排查链路变得复杂。先把一个Agent在真实任务上的稳定性和可观测性做起来,再逐步扩展。

6. 高频故障排查:现象、原因和解决路径

6.1 排查链路总顺序

Agent问题的排查和普通后端问题不一样,因为错误可能来自模型、工具、上下文或部署环境。建议按以下顺序排查:

  1. 用户输入本身是否清晰,是否在模型能力范围内。
  2. tools参数是否真的传到了模型请求。
  3. 模型是否支持函数调用,tool_choice是否设置正确。
  4. 模型返回的arguments是否为合法JSON,字段和函数签名是否一致。
  5. 工具执行是否抛错,异常信息是否作为tool消息返回到会话。
  6. 上下文是否过大,导致模型忽略了工具或历史信息。
  7. 环境变量、密钥、服务地址和部署权限是否正常。

6.2 高频问题表格

问题现象可能原因检查方式处理建议
模型从不调用工具模型不支持函数调用;工具描述不清;tool_choice设置错误打印请求中的tools和响应中的tool_calls更换支持该特性的模型;细化工具描述;必要时强制指定工具
tool_call参数解析失败模型生成了非法JSON;字段名与函数参数不一致打印原始arguments捕获JSON异常并把错误返回给模型修正;统一参数命名
工具执行报错后Agent反复重试异常没有返回给模型;工具输入校验不明确查看messages中的tool消息返回明确错误原因;限制最大轮数
多轮对话后上下文超限历史消息无限增长检查Token使用和API报错摘要历史、截断旧消息、接入检索式长期记忆
同一问题不同环境结果差异大模型版本、temperature、Prompt版本不一致固定模型名和参数并记录版本用评估集回归,锁定版本
生产突然失效密钥过期、配额耗尽、依赖版本变化、接口地址变更查看配置和监控日志配置外置、增加告警、做好灰度

6.3 三个典型场景的排查展开

场景一:Agent不调用工具。先确认请求里确实带上了toolstool_choice;再换一个有函数调用能力的模型;最后检查工具description是否说清了“什么时候该用”。不要一上来就改提示词,先分清是哪一层问题。

场景二:工具调用成功,但模型没有使用结果。检查工具结果是否通过role: "tool"正确返回,以及tool_call_id是否匹配。如果结果很长,模型可能被其他上下文干扰,需要精简工具返回内容。

场景三:测试通过,上线后偶发失败。优先怀疑环境差异,比如本地有某个文件或环境变量但生产没有;其次怀疑模型版本或服务配置是否一致;最后检查工具依赖的下游接口是否存在超时和服务抖动。

7. 从入门到落地的实践清单与下一步

7.1 推荐学习路径清单

如果你是从零开始学习Agent开发,建议按下面顺序推进:

  1. 先掌握纯LLM调用:消息角色、system prompt、temperature、max_tokens。
  2. 熟悉函数调用机制:tools定义、tool_call解析。
  3. 用Python手写一个最小ReAct循环,并在每一步打印日志。
  4. 给Agent加短期记忆,支持连续多轮对话。
  5. 接入文档向量检索,让Agent能回答私有知识问题。
  6. 设计评测集,记录每次运行的工具调用序列和最终答案。
  7. 完成异步化、配置外置、日志监控和权限隔离。
  8. 再研究多Agent协作和编排框架。

7.2 发布前检查清单

在把一个Agent服务发布到生产前,可以对照这份清单检查:

  • [ ] 工具白名单是否完整,是否限制最低权限。
  • [ ] 工具参数是否做了长度、类型、范围校验。
  • [ ] 单次Agent最大轮数和Token成本是否有限制。
  • [ ] 是否记录链路追踪、工具调用日志和Token消耗。
  • [ ] 会话是否按用户和租户做了数据隔离。
  • [ ] Prompt、模型版本是否可回滚。
  • [ ] 是否配置了超时、重试、熔断机制。
  • [ ] 是否用真实场景的评测集跑过回归。

7.3 下一步可以研究的扩展方向

跑通基础Agent之后,值得继续深入的方向有三个:第一是RAG工程化,包括文档分块、召回排序和对检索质量的评估;第二是Agent评估体系,把“效果不错”变成一组可量化回归用例;第三是Agent安全,重点是提示词注入防护和工具权限边界。

最值得坚持的做法,是给每个Agent任务都保留完整日志和失败样本。只有数据积累起来,你才能判断问题是模型能力、工具设计还是Prompt表达,才不会在调参和改结构之间反复横跳。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询