之前在接 DeepSeek 的 Agent 类需求时,很多同学会遇到一个很现实的问题:官方文档和开源仓库都看了,模型接口也能通,但一到“让模型自己决定调用哪个工具、把工具结果再喂回去”这一步就卡住了。尤其是想参考 deepseek-ai 相关的 awesome-deepseek-agent 这类资源清单时,面对一堆项目名、示例代码,反而不知道从哪条路径开始落地。这篇文章就把 DeepSeek Agent 从概念到可运行代码完整梳理一遍,包括环境准备、Function Calling 实战、一个可扩展的最小 Agent 骨架,以及前端集成阶段出现过的一个加载报错failed to load plugins client-modules: html did not preload @deepseek-ai/dsh的排查思路。
内容适合三类读者:
- 想用 DeepSeek 做智能客服、Copilot、个人助理的开发者;
- 已经在调 API,但想深入了解 Agent 工具调用和上下文管理的同学;
- 遇到前端插件或客户端模块加载异常,需要快速定位问题的前端同学。
读完你可以掌握 DeepSeek Agent 的完整调用链路,并且拿到一套可以复制到本地跑通的 Python 示例工程。
1. 认识 DeepSeek Agent 与 awesome-deepseek-agent
1.1 DeepSeek 是怎么提供能力的
DeepSeek 对外提供大模型 AI 能力,常见的接入方式分为两类:
- 官方 API 服务:使用 OpenAI 兼容的接口格式,通过
api.deepseek.com调用deepseek-chat等模型; - 开源模型权重:在本地或私有化环境部署,适合对数据有强管控要求的团队。
一般做 Agent 原型验证时最推荐官方 API,因为它省去显卡、部署和运维成本,接口也是主流模型厂商通用的风格。模型名称方面,官方文档会根据版本发布情况调整,所以代码中不要写死依赖某个特定模型版本,最好把模型名收敛到一个配置项里。
这里有一个很容易混淆的点:DeepSeek 有普通对话模型,也有偏推理的模型。做 Agent 时,如果你的任务需要模型自主决定“调用哪个工具”,建议优先选择支持 Function Calling(函数调用)的对话模型。推理模型能想得很深,但工具调用能力和交互稳定性在不同版本上有差异,因此选型前要确认当前模型的官方能力矩阵。
1.2 awesome-deepseek-agent 是什么
在 GitHub 社区里,awesome-*系列仓库通常是指“围绕某个技术主题整理的高质量资源清单”。deepseek-ai 组织以及社区维护的 awesome-deepseek-agent 类仓库,目标就是把 DeepSeek Agent 相关的官方示例、第三方框架、工具插件、Prompt 案例、部署方案汇总到一处。
这类仓库的实际价值可以分为三层:
- 信息索引:你不用再到处搜索“DeepSeek Agent 项目”,清单里已经按类别整理好;
- 快速找案例:当你想看某个场景是否有人实现过,可以直接顺着仓库推荐的项目看源码;
- 了解生态:从仓库条目数量和维护活跃度,能判断 DeepSeek Agent 生态里哪些方向成熟、哪些方向还在早期。
需要注意的是,社区仓库的增长速度非常快,条目会不断变化。把它当成“地图”而不是“标准教材”更合理。真正搭建 Agent 时,核心还是要理解模型 API 的工作原理,以及工具调用、上下文、记忆、异常控制这些通用工程问题。下面就从最核心的原理开始拆解。
2. Agent 应用的基础架构与核心概念
2.1 对话模型和 Agent 的区别
普通对话模型是“一次性交互”:用户提问,模型回答。
Agent 则是在一次任务中,让模型具备“观察 - 决策 - 行动 - 再观察”的能力。最常用的实现模式是 ReAct(Reasoning + Acting)。流程可以理解为:
用户提出需求 ↓ 模型分析:当前需要什么信息或操作 ↓ 模型返回一个“工具调用指令”,而不是最终答案 ↓ 程序执行真实工具(查天气、查数据库、发请求等) ↓ 把工具结果作为新消息送回模型 ↓ 模型根据结果继续推理,或给出最终回答这个过程可能会循环多次,直到模型认为信息足够、不再返回工具调用为止。
为了让模型能“决策”,我们需要给它提供三样东西:
- 工具定义:用 JSON Schema 描述工具名称、用途、参数;
- 当前对话上下文:包括历史消息、上一轮工具执行结果;
- 清晰的系统提示词:规定模型在什么情况下必须调用工具。
2.2 Function Calling 在 Agent 中的位置
Function Calling(函数调用)是 Agent 的技术底座。过去我们要靠 Prompt 约束模型输出 JSON,再自己解析,效果不稳定。现在模型原生支持返回结构化工具调用参数,程序只需要判断tool_calls字段是否存在即可。
一段补全接口返回中,和工具调用相关的关键结构通常是:
message.tool_calls ├── id # 工具调用 ID,后续回传结果时必须带上 └── function ├── name # 要调用的工具名 └── arguments # JSON 字符串格式的参数参数arguments是字符串而不是对象,这一点新人最容易踩坑。拿到后必须json.loads()解析,再传给本地函数。
2.3 Agent 的关键组件
抛开模型本身,一个工程上可用的 Agent 通常包含以下部分:
| 组件 | 作用 | 选型建议 |
|---|---|---|
| 模型接入层 | 统一封装 API 调用、超时重试、密钥管理 | OpenAI SDK 或 requests 直连 |
| 工具注册表 | 管理工具名、描述、执行函数 | 推荐使用装饰器或字典映射 |
| 上下文管理器 | 维护 messages 历史,控制 token 长度 | 记录角色、做截断或摘要 |
| 执行控制层 | 判断是否继续循环、设置最大步数 | 防止模型无限工具调用 |
| 观测日志 | 记录每次请求和工具调用 | JSON 日志、调用链路 ID |
对个人开发者来说,不用第一版就做得很重。一个函数加一个循环,就能跑通最小 Agent;后续再逐步引入记忆、重试、并发和安全控制。
3. 实战前置准备:环境、密钥和项目结构
3.1 运行环境说明
本文代码以 Python 为例,依赖非常少。你不需要搭建本地大模型,只需要能访问 DeepSeek 官方 API。
- Python 版本建议 3.9 及以上;
- 操作系统不限,Windows / macOS / Linux 均可;
- 需要提前申请 DeepSeek API Key,并保证账户有可用额度;
- 示例中使用了 OpenAI Python SDK,因为 DeepSeek 官方提供的接口兼容 OpenAI 格式。
版本方面不需要纠结,示例重点演示的是接口思路,不同 SDK 版本之间参数差异很小。
3.2 安装依赖
先创建虚拟环境,避免污染全局 Python 环境。
python -m venv .venv source .venv/bin/activate # Windows 下使用 .venv\Scripts\activate安装依赖:
pip install openai python-dotenv说明:
openai负责发送请求和解析响应;python-dotenv负责从本地.env文件读取密钥,避免把密钥写死在代码里。
3.3 密钥管理
在项目根目录创建.env文件:
DEEPSEEK_API_KEY=sk-你的密钥再生成.gitignore,一定不要把.env提交到代码仓库:
.env .venv/ __pycache__/密钥安全是红线。若密钥泄露,别人可以消耗你的额度,甚至调用危险操作。生产环境建议使用密钥管理平台或 K8s Secret,而不要依赖.env文件。
3.4 项目结构
后续示例代码建议按下述结构组织:
deepseek-agent-demo/ ├── .env ├── .gitignore ├── requirements.txt ├── deepseek_agent/ │ ├── __init__.py │ ├── client.py # 模型客户端封装 │ ├── tools.py # 工具定义与实现 │ └── agent.py # Agent 主循环 └── run_demo.py # 启动入口对于一个小 Demo 而言,这个结构已经足够清晰。不建议一开始就引入 LangChain 之类的重框架,先把原生调用流程跑通,再决定是否要引入上层抽象。
4. 第一个 DeepSeek Agent:基于 Function Calling 的工具调用实战
4.1 第一步:封装模型客户端
创建deepseek_agent/client.py,统一管理 API Key、基础地址和模型名:
# 文件路径:deepseek_agent/client.py import os from dotenv import load_dotenv from openai import OpenAI load_dotenv() client = OpenAI( api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com", ) # 模型名以官方文档为准,通过变量统一管理,方便后续切换 MODEL_NAME = os.getenv("DEEPSEEK_MODEL", "deepseek-chat")这里把base_url写成 DeepSeek 官方 API 地址。需要注意:当前网络环境必须能够正常访问该域名,如果公司内网有防火墙限制,需要提前在运维侧确认白名单。
4.2 第二步:定义一个工具
为了让示例简单又能说明问题,这里实现一个“根据城市查天气”的工具。真实项目中,这里通常会换成requests请求天气服务或查询内部接口。
# 文件路径:deepseek_agent/tools.py import json def get_weather(city: str) -> str: """模拟查询某个城市的天气情况。 参数 city 由模型解析并传入,真实项目中应替换为天气服务 API。 """ weather_map = { "北京": "晴,25~33℃", "上海": "多云,26~34℃", "深圳": "阵雨,25~30℃", "成都": "阴,22~29℃", } result = weather_map.get(city, "暂未收录该城市天气数据") return json.dumps({"city": city, "weather": result}, ensure_ascii=False)注意两点:
- 工具返回结果最好是字符串,因为它是作为
content塞回给模型的; - 如果结果本身是结构化的,可以先用
json.dumps序列化,既方便模型阅读,也方便日志记录。
4.3 第三步:定义工具 Schema
模型不知道 Python 函数内部怎么实现,它只能通过 JSON Schema 了解“这个工具是干什么的、需要什么参数”。
# 文件路径:deepseek_agent/tools.py TOOL_GET_WEATHER = { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市当天的天气情况", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名,例如:北京", } }, "required": ["city"], }, }, } TOOLS = [TOOL_GET_WEATHER]工具描述越明确,模型就越不会乱调用。例如参数里写清楚“城市名,例如:北京”,可以有效减少模型传省份而不是城市的概率。
4.4 第四步:编写 Agent 主循环
有了模型客户端和工具,接下来完成最核心的 Agent 循环逻辑。
# 文件路径:deepseek_agent/agent.py import json from deepseek_agent.client import MODEL_NAME, client from deepseek_agent.tools import TOOLS, get_weather def call_tool(name: str, arguments: str) -> str: """根据模型返回的工具调用信息,分发到真实函数。""" args = json.loads(arguments) if name == "get_weather": return get_weather(city=args.get("city", "")) # 如果没有匹配到工具,必须返回一个可读结果,而不是抛异常 return json.dumps({"error": f"unknown tool: {name}"}) def run_agent(user_input: str, max_steps: int = 5) -> str: """Agent 主循环:模型决策 -> 执行工具 -> 结果回填 -> 继续或结束。""" messages = [ {"role": "user", "content": user_input}, ] for step in range(1, max_steps + 1): print(f"[step {step}] request to model...") resp = client.chat.completions.create( model=MODEL_NAME, messages=messages, tools=TOOLS, tool_choice="auto", ) message = resp.choices[0].message messages.append(message) # 如果模型没有返回工具调用,说明已经可以生成最终答案 if not message.tool_calls: return message.content # 否则逐个执行工具,并把结果以 tool 角色追加到上下文 for tool_call in message.tool_calls: print(f"[step {step}] tool call: {tool_call.function.name} {tool_call.function.arguments}") tool_result = call_tool( name=tool_call.function.name, arguments=tool_call.function.arguments, ) messages.append( { "role": "tool", "tool_call_id": tool_call.id, "content": tool_result, } ) return "已达到最大执行步数,任务未完成,请简化问题后重试。"这段代码是整个示例的核心,有几处需要重点理解:
messages.append(message):模型返回的 message 对象中包含了tool_calls信息,必须原样保留在上下文里,模型才能知道“自己上一步做了什么决定”;role: "tool"的消息必须携带tool_call_id,用来和模型之前的工具调用 ID 对应;max_steps是安全阀。没有它,模型可能在复杂任务中陷入无限工具调用,既消耗 token,也让用户等待时间无限拉长;- 未知工具不要直接抛异常,因为异常会导致整个对话中断。更优雅的做法是返回可读的错误结果,让模型自己决定下一步。
4.5 第五步:运行验证
创建入口文件:
# 文件路径:run_demo.py from deepseek_agent.agent import run_agent if __name__ == "__main__": answer = run_agent("北京今天天气怎么样?适不适合穿短袖出门?") print("最终回答:", answer)运行:
python run_demo.py预期观察行为如下:
- 控制台先输出模型请求日志;
- 然后输出一次工具调用,例如
tool call: get_weather {"city": "北京"}; - 程序调用本地
get_weather函数拿到模拟结果; - 模型再次收到上下文后,生成最终回答,例如“北京今天晴,25~33℃,适合穿短袖出门,但要注意防晒”。
第一次跑通这个流程,意味着你已经理解 Agent 最核心的机制。后面所有更复杂的能力,都是在这个循环上叠加记忆、更多工具、更多控制策略。
5. 进一步封装:带上下文记忆和任务循环的 Agent 骨架
5.1 把 Agent 封装成可复用类
实际项目里不会只处理单条消息,而是要把多轮对话、工具状态、上下文裁剪都收拢到一起。下面给出一个精简版 Agent 骨架,你可以在此基础上扩展。
# 文件路径:deepseek_agent/agent.py(扩展版) from typing import Callable, Optional TOOL_MAP: dict[str, Callable] = { "get_weather": get_weather, } def _default_tool_caller(name: str, arguments: str) -> str: if name not in TOOL_MAP: return "未知工具,请换一个方式完成任务。" func = TOOL_MAP[name] return func(**json.loads(arguments)) class SimpleAgent: def __init__( self, system_prompt: str, tools: Optional[list[dict]] = None, tool_caller: Optional[Callable] = None, max_steps: int = 5, ): self.system_prompt = system_prompt self.tools = tools or [] self.tool_caller = tool_caller or _default_tool_caller self.max_steps = max_steps self.history: list[dict] = [] def _build_messages(self) -> list[dict]: return [{"role": "system", "content": self.system_prompt}, *self.history] def chat(self, user_input: str) -> str: self.history.append({"role": "user", "content": user_input}) for _ in range(self.max_steps): resp = client.chat.completions.create( model=MODEL_NAME, messages=self._build_messages(), tools=self.tools, tool_choice="auto", ) message = resp.choices[0].message self.history.append(message) if not message.tool_calls: return message.content for tool_call in message.tool_calls: result = self.tool_caller( name=tool_call.function.name, arguments=tool_call.function.arguments, ) self.history.append( { "role": "tool", "tool_call_id": tool_call.id, "content": result, } ) return "已达到最大执行步数,请调整问题后重试。"使用方式:
agent = SimpleAgent( system_prompt="你是智能助手,需要查天气时请使用 get_weather 工具。", tools=TOOLS, ) print(agent.chat("上海呢?")) print(agent.chat("那北京呢?"))通过把history保存为实例属性,实现了同一个 Agent 实例内多轮对话的上下文传递。
5.2 上下文长度与记忆裁剪
模型上下文窗口是有限的。随着 conversation 越来越长,有两个问题会越来越明显:
- token 消耗持续上涨,成本线性增加;
- 超出上下文窗口时,API 会报错,或者早期内容被静默截断。
常用的处理策略有三种:
| 策略 | 做法 | 适用场景 |
|---|---|---|
| 固定窗口裁剪 | 只保留最近 N 条消息 | 简单客服场景 |
| 摘要压缩 | 把早期对话用模型总结成摘要 | 长时间多轮对话 |
| 关键信息抽取 | 从历史中抽取用户偏好、任务状态 | 个性化助手 |
建议第一版先实现固定窗口裁剪。例如只保留最近 20 条消息,超过时丢弃最早的非系统消息:
MAX_HISTORY = 20 def trim_history(history: list[dict]) -> list[dict]: system_msgs = [m for m in history if m["role"] == "system"] other_msgs = [m for m in history if m["role"] != "system"] if len(other_msgs) > MAX_HISTORY: other_msgs = other_msgs[-MAX_HISTORY:] return system_msgs + other_msgs这个方法简单但不完美,因为如果裁掉的消息里包含未被消费的tool结果,模型可能会产生混乱。裁剪时最好从对话边界整体切分,避免把 user、assistant、tool 一组消息拆散。
5.3 工具注册机制
真实项目中工具数量可能很多,把所有工具写进TOOLS列表和TOOL_MAP会很混乱。推荐将工具依赖关系设计为“工具名称作为唯一键”,并提供批量注册能力。
def register_tools(name: str, description: str, parameters: dict, handler: Callable) -> dict: schema = { "type": "function", "function": { "name": name, "description": description, "parameters": parameters, }, } TOOL_MAP[name] = handler return schema这样每新增一个工具,只需要写一个处理函数加一行注册代码,Agent 主循环不需要改动。后续如果要接 LangChain、Dify 或其他框架,这种“注册表”设计也能平滑迁移。
6. 前端集成中的高频报错:failed to load plugins client-modules
6.1 报错现象
在部分 Web 端的 DeepSeek 工具链或社区前端项目中,构建运行时会出现类似下面的报错:
failed to load plugins client-modules: html did not preload @deepseek-ai/dsh这个报错会让页面里的 Agent 面板、工具配置区域或聊天组件完全无法渲染。只看报错文本,容易误以为是大模型接口问题,实际上它属于前端工程化问题,和模型调用没有直接关系。
6.2 报错含义拆解
把报错拆成两部分:
failed to load plugins client-modules:插件加载器在加载“客户端模块”时失败;html did not preload @deepseek-ai/dsh:HTML 页面没有预加载名为@deepseek-ai/dsh的模块。
浏览器在解析模块化 JavaScript 时,期望该模块以<link rel="modulepreload">或类似方式被提前预加载。如果页面 HTML 中没有对应预加载声明,或者声明的资源路径与实际打包产物不一致,运行时就会抛出这个错误。
产生该问题的常见原因有:
- 前端项目引用了
@deepseek-ai/dsh相关的依赖包,但依赖没有安装成功; - 依赖安装成功,但构建产物没有包含该模块,资源 404;
- 构建工具配置了 CDN 路径或
base路径,导致 HTML 中的预加载地址和实际部署路径不匹配; - 本地构建缓存过期,旧 HTML 引用了已不存在的 chunk;
- 插件加载器期望应用在入口 HTML 中显式声明
modulepreload,但当前模板没有配置。
6.3 排查步骤
遇到这个报错不要直接改代码,按下面顺序排查效率更高。
第 1 步:确认依赖是否真实存在。
npm ls @deepseek-ai/dsh如果命令报错或显示missing,说明依赖没有安装成功。重新安装:
npm install如果项目使用 pnpm 或 yarn,先清掉 lock 文件的损坏状态,再重新安装:
pnpm install # 或者 yarn install第 2 步:在源码中检索引用位置。
grep -r "@deepseek-ai/dsh" src --include="*.js" --include="*.jsx" --include="*.ts" --include="*.tsx" --include="*.vue" --include="*.html"确认是哪个文件在运行时 import 了这个模块。有时问题不在自己写的代码,而是某个插件依赖链中间接引入了它。
第 3 步:检查构建产物中是否真的存在对应文件。
# 以 Vite 为例,构建后产物一般在 dist 目录 find dist -name "*dsh*" -o -name "*@deepseek-ai*"如果文件不存在,可能是构建配置将它排除了,或依赖版本不一致。如果文件存在,则继续检查路径匹配。
第 4 步:清理缓存并重新构建。
rm -rf node_modules/.vite dist .nuxt .output npm install npm run build这一步能解决大量“改完代码但页面还走旧资源”的偶发问题。
第 5 步:检查部署后的资源路径。
如果项目设置了独立部署子路径,比如https://example.com/agent/,那么 HTML 里的预加载地址必须带上/agent/前缀。以 Vite 为例:
// vite.config.ts export default defineConfig({ base: process.env.NODE_ENV === "production" ? "/agent/" : "/", build: { modulePreload: { polyfill: true, }, }, });第 6 步:手动预加载兜底。
如果插件加载器要求 HTML 中显式预加载,你可以在入口 HTML 中补上手动声明。注意实际资源名要以构建输出为准:
<link rel="modulepreload" href="/assets/@deepseek-ai_dsh.js" />这里的文件名是示例写法。真实项目中建议先查看dist目录里实际生成的文件名,避免写错。
6.4 这类问题的通用解决思路
前端模块加载类报错,本质上可以归结为三类:
- 模块不存在:依赖没装、装错版本、被 tree-shaking 移除;
- 路径不匹配:
base、CDN 地址、部署子路径不一致; - 时序不一致:HTML 先解析,模块后到达,预加载缺失导致插件系统判定失败。
排查时记住一个原则:不要先怀疑模型代码,先确认“浏览器到底有没有正确拿到这个 JS 文件”。打开开发者工具 Network 面板,看对应请求是不是 404、有没有被缓存、响应头 Content-Type 是否正确,比反复改业务代码有效得多。
7. 常见问题与排查清单
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| API 返回 401 错误 | API Key 错误、环境变量没有加载 | 检查.env是否存在,打印os.getenv确认值 |
| 请求超时或连接失败 | 网络策略不允许访问api.deepseek.com | 先 curl 测试域名连通性,再在运维侧放行白名单 |
| 模型返回的内容里没有 tool_calls | 工具描述不规范、模型不支持函数调用 | 精简描述,检查参数必填项,确认模型支持 Function Calling |
arguments解析报错 | 模型返回的不是合法 JSON | 先打印原始字符串,必要时做 JSON 清洗修复 |
| 工具执行了但模型不基于结果回答 | tool 消息没有附带tool_call_id | 确认每条 tool 消息都回传对应 ID |
| Agent 无限循环 | 缺少最大步数限制 | 设置max_steps,超过次数返回兜底文案 |
| 多轮对话上下文割裂 | 历史消息被随意裁剪 | 裁剪时整组保留 user/assistant/tool 消息 |
前端报did not preload | 依赖缺失、路径不匹配、缓存过期 | 按章节 6.3 的六个步骤排查 |
| 页面加载但 Agent 面板空白 | 前端运行时 JS 报错被吞掉 | 打开控制台查看完整堆栈,不要只看插件提示 |
8. 工程化最佳实践与建议
8.1 Prompt 与工具描述
Agent 的质量上限,往往由工具描述决定。
同一个工具,描述写“查询天气”和写“查询指定城市当天的天气情况,参数城市名使用中文常用名,如北京、上海”,后者的调用准确率会明显更高。建议工具描述遵循如下模板:
该工具用于(功能用途)。当用户提问涉及(触发场景)时调用。 参数说明:xxx 表示(含义),取值示例:xxx。另外不要给模型提供它用不到的工具。工具列表越长,模型做选择时的错误率越高,还会增加每次请求的 token 消耗。按需装配工具是 Agent 工程里最容易被忽视的优化点。
8.2 安全边界
Agent 一旦连上真实工具,就不仅仅是“文本生成”了。如果模型可以触发 SQL 查询、Shell 命令或发送请求,必须做好以下安全措施:
- 工具白名单:只开放明确允许执行的操作;
- 参数校验:模型传入的参数必须二次校验,不能直接拼进 SQL 或 Shell;
- 权限最小化:服务账号只授予必要权限,禁止使用 root / DBA 账号;
- 危险操作确认:删除、更新、转账、发布类操作必须增加人工确认环节;
- 敏感操作审计:记录谁在什么时间调用了什么工具,参数是什么。
特别提醒:不要在 Agent 中实现“帮我删除数据库所有记录”这样的无人确认工具,即使只是测试。生产环境任何变更操作都要走审批和备份流程。
8.3 日志与可观测性
Agent 排错最大的难点在于“模型为什么做了这个决定”。好的日志应该记录:
- 请求 ID 和调用链 ID;
- 每次请求的 messages 摘要;
- 工具调用名称、参数、返回结果;
- token 消耗和执行耗时;
- 是否命中了异常分支。
推荐使用 JSON 结构日志,方便后续接入日志平台。示例:
import logging logger = logging.getLogger("agent") logger.info( "tool_call", extra={ "name": tool_call.function.name, "arguments": tool_call.function.arguments, "trace_id": trace_id, }, )8.4 稳定性设计
模型接口天然存在延迟和不确定性,Agent 工程必须预设以下异常:
- 单次请求超时:设置合理超时时间,并增加重试策略;
- 模型返回格式异常:捕获解析异常并转成可读报错;
- API 限流:遇到限流错误时退避重试;
- 工具执行失败:让模型知道失败原因,而不是直接中断。
重试时需要注意:如果请求已经发给模型并成功执行,但客户端超时了,不能无脑重试。最好为请求增加幂等标识,或者接受“偶尔丢失一次响应”的代价,换取整体稳定。
8.5 从 Demo 到生产
本文的代码可以直接跑通原型,但离生产还有距离。建议按以下优先级改造:
- 配置中心化:密钥、模型名、超时参数从环境变量读取;
- 依赖版本锁定:维护 requirements.txt 或 poetry.lock;
- 增加单元测试:工具分发、JSON 解析、上下文裁剪都是纯函数,可以单测;
- 接入监控:统计调用成功率、平均耗时、token 成本;
- 采用更成熟的 Agent 框架:当需求复杂度超过手写循环能维护的边界时,再引入 LangChain / LlamaIndex / Dify 等方案。
社区里关于 DeepSeek Agent 的第三方框架很多,但不要盲目追逐新框架。你自己能写明白原生循环之后,再去看框架源码,会发现一切都清晰很多。
9. 总结与后续学习建议
到现在为止,你已经完成了 DeepSeek Agent 从零到一的闭环:
- 理解了 Agent 与普通对话模型的区别;
- 知道 Function Calling 在 Agent 循环中的作用;
- 用不到 100 行 Python 代码实现了“模型决策 - 本地工具执行 - 结果回填”的完整流程;
- 了解了上下文记忆裁剪和工具注册机制;
- 掌握了一个前端模块加载报错
failed to load plugins client-modules: html did not preload @deepseek-ai/dsh的系统排查方法。
下一步想继续深入,建议按这个顺序推进:
- 先给 Agent 增加 3 个不同类型工具,例如查天气、查询数据库、发送 HTTP 请求,体会工具分发机制;
- 研究模型返回的
usage字段,学会计算单次任务 token 成本; - 尝试实现一个简单的 RAG,让 Agent 能访问私有文档;
- 把日志、超时重试、安全校验补上,观察系统在生产数据下的表现;
- 再回来重新翻看 awesome 类资源仓库,这时候你已经能评判哪些项目设计得好、哪些只是表面包装了。
大模型 Agent 的工程化还在快速演进,新工具和新框架层出不穷,但“模型负责决策、程序负责执行、上下文负责记忆”这条主线短期不会变。把本文的最小闭环跑通,你就拿到了所有复杂 Agent 系统的基础积木。如果这篇文章对你有帮助,可以收藏备用;实际落地过程中遇到新的报错,也欢迎在评论区把错误信息贴出来,大家一起排查。