最近技术圈有个消息热度很高:前千问负责人林俊旸再次创业,新公司方向直接瞄准 Agent,腾讯跟投。虽然具体产品形态还不算清晰,但资本和顶尖 AI 从业者同时押注 Agent,本身就说明这件事到了值得认真对待的阶段。
对开发者来说,这波热度的核心其实是机会:Agent 开发正在从概念走向工程化。市面上关于 Agent 的资料很多,但大部分要么偏概念、要么只讲框架 API,真正能把原理、手写代码、模型部署、坑点串起来的教程很少。这篇文章以 Agent 开发为主线,结合千问系列开源模型的使用经验,从概念拆解到最小可运行案例,再到本地模型部署和排错清单,完整走一遍。无论你是刚接触大模型开发的学生,还是已经在做 AI 应用的后端工程师,都可以从中找到能直接上手的内容。
读完这篇文章,你会理解 Agent 为什么不是简单的提示词模板;会亲手实现一个能调用工具的 Agent;会掌握在线 API 和本地模型两种接入方式;还会知道本地部署千问这类开源模型时会踩哪些坑。
1. 为什么 Agent 突然成了大模型落地的关键方向
1.1 Agent 是什么,它和聊天机器人有什么区别
很多人刚开始接触 Agent 时,最容易把它和聊天机器人混为一谈。聊天机器人的能力边界是“对话”:用户问一句,模型答一句,即使有多轮上下文,本质上还是在生成文本。Agent 则不一样,它的目标不是“回答”,而是“完成任务”。
一个典型 Agent 的工作方式是这样的:用户提出目标,Agent 先判断需要哪些信息,然后决定调用什么工具、按什么顺序执行,最后根据工具返回结果继续推理,直到给出最终结论或执行完整套动作。比如“帮我查一下北京明后天天气,并制定一份出行计划”,聊天机器人只能根据训练数据或上下文大概说一下,Agent 却可以主动去调用天气 API、读取日历、拆分任务,最后产出一份真正有依据的计划。
所以更准确地说,Agent 是一个以 LLM 为决策核心的自主系统。它把模型的语言理解能力、推理能力和外部工具的执行能力组合在一起,形成“感知 → 决策 → 行动 → 反馈”的闭环。
1.2 Agent 的四个核心要素
一个能完成任务的 Agent,通常包含四个核心要素:
- 规划:把复杂目标拆分成可执行的子任务,决定每一步做什么。常用方式包括思维链、任务分解、ReAct 循环。
- 记忆:保存对话历史、任务状态和长期知识。短期记忆对应模型的上下文窗口,长期记忆通常落在向量数据库或普通数据库里。
- 工具:让 Agent 能和外部世界交互的能力,比如查天气、搜网页、执行代码、操作数据库。工具通常以函数或 HTTP API 的形式暴露给模型。
- 行动:把模型的决策真正执行出去,并把执行结果重新喂给模型,供下一轮决策使用。
这四个要素缺一不可。没有规划,Agent 就是一个单步问答;没有记忆,它无法处理多轮任务;没有工具,它只能纸上谈兵;没有行动闭环,所有决策都无法落地。
1.3 Agent 赛道为什么会受到资本关注
林俊旸此前负责千问相关业务,属于大模型领域非常核心的技术负责人,他再次创业选择 Agent 方向,加上腾讯跟投,说明资本市场对 Agent 的商业化路径已经有共识:大模型本身很难直接变成产品,但“大模型 + 工具 + 流程”的 Agent 可以嵌入真实业务,成为客服、运营、数据分析、代码开发等场景的自动化执行体。
从就业和技术发展角度看,Agent 也正在成为新的岗位方向。搜索热词里已经能看到大量与 agent 相关的需求,比如 agent 开发、agent 框架、agent 安全、agent 记忆、agent 面试题。这意味着市场不再只关心“会不会调 API”,而是关心“能不能用模型构建可靠的多步骤系统”。
1.4 Agent 与工作流、RAG 的边界
在 AI 应用开发中,还有两个经常和 Agent 并列的概念:工作流(Workflow)和 RAG(检索增强生成)。它们之间不是替代关系,而是互补关系。
RAG 解决的是“模型知识不够”的问题。模型训练数据有截止时间,也有领域盲区,RAG 通过引入外部知识库,让模型在回答前先检索相关资料。但 RAG 本身不会做多步决策,它只是给模型喂更多上下文。
工作流解决的是“流程固定”的问题。比如先做意图识别,再查数据库,再调用大模型生成回答,整个过程是预先编排好的,每一步都很确定。工作流适合规则清晰的业务。
Agent 的价值在于“决策不确定”。它面对的任务可能有多条路径,需要模型根据实际情况判断下一步该调用哪个工具、是否需要继续执行。在真实项目里,三者经常组合使用:用 RAG 提供知识,用工作流兜底主流程,用 Agent 处理分支和异常情况。有些框架还会把 Agent 的运行时称为 harness,本质上是把模型、工具、记忆装进一个受控执行环境,你只需要关注决策逻辑,不用关心进程调度和上下文管理。
2. Agent 的核心技术架构拆解
2.1 大模型底座:Agent 的“大脑”
Agent 的决策能力完全来自底层大模型。模型的理解能力、指令跟随能力和函数调用能力,直接决定了 Agent 的上限。
当前开发 Agent 有两种主流路线。第一种是使用在线 API,比如千问 DashScope 的 OpenAI 兼容模式,优点是开箱即用、模型能力较强、不需要自己维护显卡。第二种是本地部署开源模型,比如通过 Ollama、LM Studio、vLLM 加载千问系列模型,优点是数据不出内网、调用成本可控、可以深度定制,但需要自己处理显存、量化和性能问题。
需要特别注意的是,并不是所有模型都适合做 Agent。做 Agent 需要模型能够准确理解工具描述,并输出符合 JSON 结构或函数签名要求的调用结果。如果模型指令跟随能力较弱,经常会出现工具名写错、参数缺失、格式不合法等问题。所以在选型时,要优先选择明确支持 Function Calling 或在 Agent 场景上有过优化的模型。
2.2 规划能力:任务拆解与 ReAct 循环
Agent 最常见的规划模式是 ReAct,也就是 Reason + Act 的循环。它的核心思想是让模型交替进行推理和行动:
- 模型观察当前问题,给出下一步思考。
- 根据思考选择一个工具并给出调用参数。
- 程序执行工具,把结果返回给模型。
- 模型根据新信息继续推理,决定是再调用工具还是给出最终答案。
这种循环不需要提前写好所有分支,模型会根据实际结果动态调整策略。下面是一个简化后的循环过程:
用户:北京今天适合出门吗? 模型:需要先查北京天气。 模型:调用 get_weather(city="北京") 工具:返回“晴,15℃~27℃” 模型:天气晴且温度舒适,适合出门。 模型:适合出门,记得带件薄外套。在代码层面,循环的终止条件通常有两类:一是模型输出最终答案,不再调用任何工具;二是程序设置了最大步数限制,防止模型陷入死循环。后文实战部分会实现这个闭环。
2.3 记忆系统:短期记忆与长期记忆
记忆是 Agent 容易被忽略、却在真实项目中非常重要的部分。短期记忆就是消息序列本身,模型每次决策都要依赖对话历史。问题是上下文窗口有限,超过窗口后最早的信息会被丢弃,Agent 就会“失忆”。
长期记忆用于解决跨会话的问题。比如 Agent 是客服助手,它需要记住用户上次报修的设备型号;Agent 是数据分析助手,它需要记住项目背景信息。长期记忆通常由向量数据库承载,把文本转成向量存储下来,使用时通过语义检索找回相关性最高的内容。
工程上还可以做记忆分层:核心任务状态放内存或 Redis,长期知识放向量库,敏感数据放权限管控的数据库。不要把全部历史都塞进上下文,否则 token 成本会不可控地增长。
2.4 工具调用:Function Calling 的原理
工具调用是 Agent 区别于普通聊天机器人的关键能力。它的原理并不神秘:模型并不会真的去执行函数,它只是根据用户问题和工具描述,输出一个“调用意图”和“参数”,真正的函数执行由外部程序完成。
如果使用原生 Function Calling,模型接口会提供tools参数,开发者把工具的名称、描述和参数结构传给模型,模型返回的tool_calls字段里会包含具体调用信息。程序拿到后执行对应函数,再把结果以tool角色的消息传回模型。
如果不使用原生 Function Calling,也可以采用文本协议:在系统提示词里告知模型所有工具及其参数格式,要求模型输出特定 JSON,比如:
{"name": "get_weather", "arguments": {"city": "北京"}}程序负责解析 JSON、判断工具是否存在、检查参数合法性、执行函数并回填结果。这种方式兼容性更强,适合本地小模型或在线模型未开放 function calling 的场景。
2.5 行动闭环:Agent 如何与环境交互
行动层关注的是“工具执行以后会发生什么”。最简单的行动就是调用 Python 函数,但真实场景里还包括:调用第三方 HTTP 接口、读写数据库、执行 Shell 命令、操作浏览器、发送消息等。
行动层是安全风险最集中的地方。一个 Agent 如果被提示词注入恶意指令,又拥有执行命令的权限,后果会非常严重。所以生产环境的行动层一定要做权限控制:工具最小化授权、敏感操作人工确认、代码执行放在沙箱、所有行动记录日志。这些内容会在第八章展开。
3. 环境准备与版本说明
3.1 开发环境选择
本文的代码示例以 Python 为主,建议使用 Python 3.10 或更高版本。版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。
除了 Python 本身,只需要两个第三方库:
requests:用于调用模型服务接口。python-dotenv:用于读取.env配置文件。
如果你不想安装python-dotenv,也可以直接用环境变量或把配置硬编码在代码里,但生产环境不推荐硬编码密钥。
3.2 模型服务的两种接入方式
为了让实战代码既能连接在线 API,也能连接本地模型,我会使用 OpenAI 兼容接口格式。现在很多模型服务都提供这种格式,包括千问在线 API 和 Ollama 本地服务。
两种方案的对比如下:
| 接入方式 | 服务地址 | 模型名称示例 | 适用场景 |
|---|---|---|---|
| Ollama 本地 | http://localhost:11434/v1 | qwen2.5 | 离线开发、数据敏感、成本敏感 |
| 千问在线 API | 以官方文档兼容模式地址为准 | 模型名以官方列表为准 | 需要更强模型能力、不想维护显卡 |
在下面的最小实战中,默认使用 Ollama 本地服务,这样代码开箱即可运行,在线接入只需要修改环境变量。
3.3 推荐项目结构
整个实战项目建议保持简洁:
agent-demo/ ├── .env ├── requirements.txt ├── tools.py └── agent.py.env保存配置,tools.py定义工具函数,agent.py实现 Agent 主循环。这样拆分的好处是:工具函数可以独立测试,Agent 逻辑保持纯粹,后续新增工具不用频繁改动主程序。
4. 从零实现一个最小 Agent:代码实战
4.1 需求与设计
我们要实现一个能查天气的 Agent。用户会以自然语言提问,比如“北京今天适合出门吗”。Agent 需要完成以下流程:
- 判断是否需要查询天气。
- 如果需要,调用
get_weather工具。 - 拿到工具结果后,结合结果给出最终回答。
- 如果用户问题不需要任何工具,直接回答。
这个需求体现了 Agent 的核心特征:工具调用、结果回填、多轮决策。虽然业务很简单,但代码框架可以复用到更复杂的场景。
4.2 定义工具函数
先创建tools.py,定义一个模拟的天气查询函数。真实项目里可以把这个函数替换成第三方天气 API,返回结构建议保持 JSON 字符串或字典。
# 文件路径:agent-demo/tools.py def get_weather(city: str) -> str: """模拟查询天气。真实项目可替换为第三方 API。""" weather_data = { "北京": "晴,15℃~27℃,西北风3级", "上海": "多云,18℃~25℃,东风2级", "广州": "雷阵雨,20℃~28℃,南风3级", } return weather_data.get(city, f"暂未收录 {city} 的天气数据")为了让工具描述能够自动生成系统提示词,需要在 Agent 中维护一个工具注册表。注册表里包含工具名称、描述、参数格式和对应的执行函数。
4.3 实现 Agent 主循环
接下来创建agent.py,这是整个项目的核心。先看完整代码,然后逐步解释。
# 文件路径:agent-demo/agent.py import json import os import re import requests from dotenv import load_dotenv from tools import get_weather load_dotenv() # 模型服务配置 BASE_URL = os.getenv("BASE_URL", "http://localhost:11434/v1") API_KEY = os.getenv("API_KEY", "ollama") MODEL = os.getenv("MODEL", "qwen2.5") # 工具注册表 TOOLS = [ { "name": "get_weather", "description": "查询指定城市的天气情况", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名,例如北京、上海、广州", } }, "required": ["city"], }, "function": get_weather, } ] MAX_STEPS = 5 def build_system_prompt() -> str: """根据工具注册表自动生成系统提示词。""" lines = [ "你是一个能够调用工具完成任务的 AI 助手。", "如果需要调用工具,请只输出如下 JSON 格式,不要输出任何多余内容:", '{"name": "工具名", "arguments": {"参数名": "参数值"}}', "", "可用工具如下:", ] for tool in TOOLS: lines.append( f"- {tool['name']}: {tool['description']}," f"参数: {json.dumps(tool['parameters'], ensure_ascii=False)}" ) lines.append("") lines.append("拿到工具结果后,请根据结果用自然语言回答用户。") return "\n".join(lines) def extract_json(text: str) -> dict: """从模型输出中解析 JSON,兼容 markdown 代码块包裹的情况。""" text = text.strip() # 去掉可能的 markdown 代码块标记 if text.startswith("```"): text = re.sub(r"^```(?:json)?\s*", "", text) text = re.sub(r"\s*```$", "", text) start = text.find("{") end = text.rfind("}") if start == -1 or end == -1: raise ValueError("输出中没有找到 JSON 对象") return json.loads(text[start : end + 1]) def call_llm(messages: list) -> dict: """调用 OpenAI 兼容的 chat/completions 接口。""" payload = { "model": MODEL, "messages": messages, "temperature": 0.2, } resp = requests.post( f"{BASE_URL}/chat/completions", headers={"Authorization": f"Bearer {API_KEY}"}, json=payload, timeout=60, ) resp.raise_for_status() return resp.json()["choices"][0]["message"] def execute_tool(tool_call: dict) -> str: """根据模型输出的调用信息执行工具。""" name = tool_call.get("name") args = tool_call.get("arguments", {}) for tool in TOOLS: if tool["name"] == name: return str(tool["function"](**args)) return f"未知工具: {name}" def run_agent(user_input: str) -> str: """Agent 主循环。""" messages = [ {"role": "system", "content": build_system_prompt()}, {"role": "user", "content": user_input}, ] for step in range(MAX_STEPS): print(f"[step {step + 1}] 调用模型...") msg = call_llm(messages) content = msg.get("content", "") print(f"[模型输出] {content}") # 尝试解析工具调用,不是 JSON 则视为最终回答 try: tool_call = extract_json(content) except (ValueError, json.JSONDecodeError): return content # 校验工具名是否存在 tool_names = {t["name"] for t in TOOLS} if tool_call.get("name") not in tool_names: return "模型输出了不合法的工具调用:" + content # 执行工具并回填结果 result = execute_tool(tool_call) print(f"[工具结果] {result}") messages.append({"role": "assistant", "content": content}) messages.append( { "role": "user", "content": f"工具 {tool_call['name']} 返回结果:{result}\n" "请根据结果继续回答用户问题。如果信息已足够,请直接给出最终答案。", } ) return "已达到最大步骤数,未能生成最终答案。" if __name__ == "__main__": answer = run_agent("北京今天适合出门吗?") print("\n最终回答:") print(answer)这段代码有几个设计要点:
- 系统提示词由工具注册表自动生成,新增工具时不用手动维护提示词。
extract_json做了容错,兼容了模型输出 markdown 代码块的情况。execute_tool通过工具名查找函数,避免直接执行任意模型输入,降低安全风险。- 主循环里每轮都会把模型输出和工具结果回填到消息列表,让模型在下一轮能感知到完整的执行过程。
4.4 配置环境变量并运行
创建.env文件,默认配置指向本机 Ollama:
BASE_URL=http://localhost:11434/v1 API_KEY=ollama MODEL=qwen2.5如果你使用在线千问 API,可以把BASE_URL改成官方文档中的 OpenAI 兼容模式地址,API_KEY改成你的密钥,MODEL改成对应的模型名称。
创建requirements.txt:
requests python-dotenv安装依赖并运行:
pip install -r requirements.txt python agent.py注意:如果使用本地 Ollama,需要先确保模型已经拉取:
ollama pull qwen2.5 ollama serve4.5 运行结果与原理说明
预期输出大致如下:
[step 1] 调用模型... [模型输出] {"name": "get_weather", "arguments": {"city": "北京"}} [工具结果] 晴,15℃~27℃,西北风3级 [step 2] 调用模型... [模型输出] 北京今天天气晴,气温在15℃到27℃之间,风力不大,比较舒适,适合出门。建议带一件薄外套。 最终回答: 北京今天天气晴,气温在15℃到27℃之间,风力不大,比较舒适,适合出门。建议带一件薄外套。从结果可以看到,Agent 在第一轮没有直接回答,而是先输出了工具调用指令;程序执行工具后把结果回填给模型;模型在第二轮根据真实天气数据给出了有依据的回答。整个过程就是一个最小可用的 ReAct 闭环。
这个示例采用的是“文本 JSON 协议”约定工具调用,不属于模型原生的 function calling。它的好处是兼容各种模型服务,只要模型能输出合法 JSON 即可。生产环境如果模型服务支持原生函数调用,更推荐使用原生方式,比如 OpenAI 兼容接口的tools参数,参数校验和错误处理会更规范。
5. 用框架快速搭建 Agent:框架选型与编排思路
5.1 要不要用框架
手写最小 Agent 能帮助理解原理,但真实项目中通常不建议从零构建。原因是 Agent 涉及很多工程细节:上下文管理、工具调用异常、重试机制、成本统计、可视化调试、多 Agent 协作等,轮子已经有人造好了。
使用框架并不意味着“黑盒调用”。框架的价值在于把通用问题抽象好,让开发者把精力集中在业务工具和提示词上。不过框架 API 变化通常较快,尤其是 LangChain 这类大型框架,升级版本时经常出现接口变动。因此,选择框架时需要评估团队的维护成本,并锁定版本。
5.2 主流框架速览
| 框架/工具 | 定位 | 适用场景 |
|---|---|---|
| LangChain | 通用 LLM 应用框架 | 要灵活编排 Agent、链、RAG 的中大型项目 |
| LlamaIndex | 数据与 RAG 框架 | 知识库问答、文档分析为主,也支持 Agent |
| AutoGen | 多 Agent 对话框架 | 需要多个 Agent 协作解决问题的研究原型 |
| Dify | 开源 LLM 应用平台 | 希望可视化编排、快速交付产品的团队 |
如果你还是新手,建议先用 Dify 这类带界面的平台把 Agent 跑通,观察工具调用链,再回到代码层面深入框架。
5.3 Dify 可视化编排 Agent 的关键步骤
以 Dify 这类开源编排平台为例,核心步骤通常包括:
- 创建应用时选择 Agent 类型,而不是简单的聊天应用。
- 在模型供应商中配置可用模型。因为 Dify 支持 OpenAI 兼容接口,所以本地 Ollama 和千问在线 API 都可以配置。
- 编写系统提示词。提示词里需要明确 Agent 的任务边界、工具使用规则和最终答案输出格式。
- 添加工具。平台通常内置搜索、计算等工具,也支持按 OpenAPI 规范导入自定义 HTTP 工具。
- 调试对话。观察模型每一步的工具调用、参数和返回结果。
- 发布应用。Dify 能生成 Web 页面或 API 服务,方便快速集成到业务系统。
5.4 工作流编排与自主 Agent 怎么选
在实际产品中,不要一上来就全用“自主 Agent”。判断标准很简单:任务路径是否确定性高。如果任务流程固定,比如“先鉴权,再查库存,再生成测试报告”,用工作流更稳定、成本更低。如果任务有较多分支和不确定性,比如“根据用户模糊描述判断客服工单应该转给哪个部门,并生成初步处理方案”,自主 Agent 更合适。
更常见的是混合模式:主流程用工作流控制,遇到分支和异常时调用 Agent 子任务。这样既能保证核心流程稳定,又能利用模型的灵活性处理非确定性问题。
6. 千问模型的本地部署与性能调优
6.1 本地部署三条路线
本地部署开源模型的工具很多,最常见的三条路线是:
- Ollama:安装简单,一条命令就能拉取并启动模型,提供 OpenAI 兼容接口,适合开发调试和个人使用。
- LM Studio:带图形界面,适合不熟悉命令行的开发者,可以直观管理 gguf 模型文件,也能暴露本地 HTTP 服务。
- vLLM:吞吐量高,适合生产环境的模型服务,但部署复杂度也更高,通常需要更精细的显存和并发配置。
对 Agent 开发来说,开发阶段用 Ollama 或 LM Studio 就足够了。生产环境如果对延迟和吞吐有要求,可以切换到 vLLM。
6.2 Ollama 接入千问并启动服务
安装 Ollama 后,先用命令行拉取模型:
ollama pull qwen2.5拉取完成后启动服务:
ollama serve默认情况下,Ollama 的 OpenAI 兼容接口地址是http://localhost:11434/v1。前面章节的agent.py默认配置就是基于这个地址,所以只要模型拉取成功,代码可以直接运行。
6.3 “本地模型很慢”的排查思路
“本地模型很慢”是个高频问题,很多人一上来就以为是显卡不够,其实不一定。建议按以下顺序排查:
- 检查是否真的使用了 GPU。Ollama 默认会自动卸载层到 GPU,但 LM Studio 等工具可能需要手动设置 GPU Offload。如果模型完全跑在 CPU 上,速度会慢很多。
- 检查量化等级。同样一个模型,FP16 精度比 Q4 量化显存占用高很多,速度也更慢。本地消费级显卡建议优先使用 Q4_K_M 或 Q5_K_M 量化。
- 检查上下文长度。如果你把上下文设置成 32K 甚至更长,每步计算量会明显增加。Agent 场景可以先从 4K 或 8K 开始,确认效果后再逐步加长。
- 检查推理线程数。CPU 推理时线程数太少会拉低性能,但线程数设置超过物理核心数也没有意义。
- 检查是否有多进程抢占。如果同一块显卡上同时跑多个模型服务,显存和计算单元都会争抢。
如果模型仍很慢,可以考虑降低模型规模。比如 27B 量级的模型双卡跑不动时,改用 7B 或 14B 量级的量化版本,体验会流畅很多。
6.4 多卡与边缘设备部署注意事项
对于几十 B 量级的开源模型,常见方案是用多张消费级显卡并行推理。比如两张 3090 共 48GB 显存,通常可以跑 27B 量级的量化模型,但需要注意几个问题:
- 多卡并行会引入跨卡通信开销,实际加速比并不是线性增长。
- KV cache 会随上下文长度增长,需要预留足够显存。
- 量化格式要统一,混合使用不同量化策略可能导致推理崩溃或结果异常。
边缘设备部署,比如 RK3588 这样的 ARM 平台,挑战更大。这类设备通常有 NPU,但大模型算子对 NPU 的适配并不完整,很多场景实际是 CPU 推理,内存带宽会成为瓶颈。如果需要边缘部署,一定要先用目标设备实测吞吐和延迟,不要只看规格参数。
6.5 gguf 模型文件选择与下载注意事项
gguf 是本地推理工具最常用的模型格式,Ollama 和 LM Studio 都能直接使用。选择 gguf 文件时要注意以下几点:
- 看清量化等级。文件名里的
Q4_K_M、Q5_K_M表示不同量化精度,精度越低占用显存越小,但效果也会有所下降。 - 确认文件完整性。大文件下载过程中容易损坏,加载失败时可以比对 SHA256 或重新下载。
- 注意模型来源。不要随意下载来路不明的 gguf 文件,尽量选择官方或可信渠道发布的内容,避免恶意权重或后门风险。
- 如果使用 Ollama,不需要手动下载 gguf,直接用
ollama pull拉取即可;Ollama 对模型文件有自己的管理方式。
如果在 CC Switch 等模型管理工具里看不到刚下载的模型,常见原因是模型文件没有放到工具指定的 models 目录,或者工具没有刷新模型列表。把 gguf 文件移动到目录后重启工具,或者手动刷新,通常可以解决。
7. Agent 开发中的高频问题与排查思路
Agent 开发比普通 API 调用更容易出问题,因为它是多步循环,任何一步出错都会影响最终结果。下面整理了一些高频问题。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
Agent 执行时报agent execution provider did not respond in time | 模型服务响应超时,或某一步工具执行时间过长 | 检查模型 API 是否正常,调大超时时间,减少单步任务量,必要时拆分步骤 |
| 模型输出不是合法 JSON,工具调用失败 | 模型没有严格遵循输出格式 | 系统提示词强调格式;换用原生 function calling;加入 JSON 解析容错和重试机制 |
| Agent 陷入重复调用同一个工具的死循环 | 缺乏终止条件或工具结果没有让模型获得新信息 | 设置最大步数;要求模型基于结果直接回答;在工具结果中补充更清晰的信息 |
| 多轮对话后上下文超过模型限制 | 记忆没有做压缩或裁剪 | 实现滑动窗口、摘要压缩、向量检索式长期记忆 |
| 本地模型回答很慢 | 没有用 GPU、量化等级过高、上下文过长、设备带宽不足 | 按 6.3 节的顺序逐项排查 |
| 工具执行成功但结果不理想 | 工具描述不清晰,模型传错参数或理解错意图 | 完善工具 description;参数尽量少并提供默认值;增加参数校验 |
| Agent 在敏感环境执行了风险操作 | 工具权限过大,缺少人工确认 | 最小权限授权;敏感操作需要人工审批;代码放入沙箱执行 |
下面重点展开几个高频问题。
第一个是超时问题。很多 Agent 运行时会在网络层设置默认超时,比如 30 秒。如果模型本身要生成较长内容,或者工具调用链很长,单步很容易超时。解决方案是分层设置超时:模型 API 调用设置一个较宽的超时,比如 60 秒;整个 Agent 流程设置总预算时间和最大步数,超预算后主动返回部分结果给用户,而不是一直阻塞。
第二个是工具调用格式问题。小模型尤其容易出现“模型在 JSON 外面加了说明文字”“工具名拼错”“参数名和工具定义不一致”。处理方式有三层:第一层是在提示词里给出标准示例;第二层是在代码里做容错解析和自动纠错;第三层是解析失败时把错误信息回传给模型,让它自己修正后再试一次。如果模型原生支持 function calling,优先用它,格式正确率会明显更高。
第三个是死循环问题。死循环的根因通常是模型认为“我还需要执行某个工具”,但工具结果并没有带来足够的信息。解决方案是:在系统提示词里明确“当工具结果已返回,且信息足以回答用户时,必须直接输出最终答案,不要重复调用工具”;同时设置最大步数,防止异常情况拖垮整个服务。
第四个是上下文膨胀。Agent 每执行一步,都要把模型输出、工具结果、汇总消息重新塞回 messages。步数多了以后,很快会触达上下文窗口。简单粗暴的滑动窗口会丢失关键任务状态,更稳妥的做法是:关键状态单独存储,上下文只保留最近 N 轮,历史信息按轮次生成摘要后在后续对话中引用摘要。
8. Agent 工程化的最佳实践与安全建议
8.1 工具设计规范
工具是 Agent 与世界的接口,设计质量直接影响成功率。推荐遵循以下规范:
- 命名要语义化。工具名尽量使用“动词 + 名词”的格式,比如
get_weather、create_order,不要用func1这种无意义名称。 - 描述要像写 API 文档。描述里要写清楚工具能做什么、什么场景用、需要哪些参数、参数取值范围。模型完全靠描述决定是否调用工具,描述越模糊,调用越容易出错。
- 参数越少越好。一个工具的参数超过五个,模型传错的概率会显著上升。能合并的参数就合并,能给默认值的就给默认值。
- 返回值要结构化。尽量返回 JSON,而不是一长串人类可读文本。结构化返回值便于模型理解,也便于程序做后续处理。
- 工具要有错误返回。比如查不到城市时,不要抛出异常,而是返回“未查询到该城市天气”,让模型有机会追问用户或改用其他方式处理。
8.2 提示词与模型约束
Agent 的系统提示词比普通问答的提示词更关键,需要做到以下三点:
第一,明确工具调用格式。给出一个标准示例,并说明“除非调用工具,否则不要输出 JSON”。
第二,明确任务边界。说明哪些情况必须调用工具,哪些情况直接回答。比如“当用户问天气时,必须先调用 get_weather