DeepSeek Agent开发实战:从Function Calling到前端集成排错
发布时间:2026/9/4 20:16:25来源:尧图网络
之前在接 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-agent1.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 则是在一次任务中让模型具备“观察 - 决策 - 行动 - 再观察”的能力。最常用的实现模式是 ReActReasoning 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_KEYsk-你的密钥再生成.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_keyos.getenv(DEEPSEEK_API_KEY), base_urlhttps://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_asciiFalse)注意两点工具返回结果最好是字符串因为它是作为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(cityargs.get(city, )) # 如果没有匹配到工具必须返回一个可读结果而不是抛异常 return json.dumps({error: funknown 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( modelMODEL_NAME, messagesmessages, toolsTOOLS, tool_choiceauto, ) 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( nametool_call.function.name, argumentstool_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( modelMODEL_NAME, messagesself._build_messages(), toolsself.tools, tool_choiceauto, ) 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( nametool_call.function.name, argumentstool_call.function.arguments, ) self.history.append( { role: tool, tool_call_id: tool_call.id, content: result, } ) return 已达到最大执行步数请调整问题后重试。使用方式agent SimpleAgent( system_prompt你是智能助手需要查天气时请使用 get_weather 工具。, toolsTOOLS, ) 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-modules6.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/dshHTML 页面没有预加载名为deepseek-ai/dsh的模块。浏览器在解析模块化 JavaScript 时期望该模块以link relmodulepreload或类似方式被提前预加载。如果页面 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 relmodulepreload 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 Callingarguments解析报错模型返回的不是合法 JSON先打印原始字符串必要时做 JSON 清洗修复工具执行了但模型不基于结果回答tool 消息没有附带tool_call_id确认每条 tool 消息都回传对应 IDAgent 无限循环缺少最大步数限制设置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 系统的基础积木。如果这篇文章对你有帮助可以收藏备用实际落地过程中遇到新的报错也欢迎在评论区把错误信息贴出来大家一起排查。
网站建设高端定制企业官网