ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DeepSeek Agent开发实战:从最小闭环到工具调用

DeepSeek Agent开发实战:从最小闭环到工具调用 你搜索 DeepSeek Agent 时大概率会看到deepseek-ai / awesome-deepseek-agent这类聚合项目。第一反应是“收藏以后看”第二反应往往是“项目太多从哪个开始看都不对”。很多开发者的真实困境是扫了一圈列表概念名词认识了不少但真要写一个能自动查资料、调工具、多轮推理的 Agent手边仍然没有一条清晰的路径。原因很简单聚合仓库解决的是“信息找得到”解决不了“你手上这批代码为什么跑不起来”。这篇文章给出一个明确判断Agent 开发的关键不是模型选得多强也不在于你收藏了多少优秀开源项目而是你是否能构建出一个最小可运行 Agent 回路并理解模型在中间扮演的角色。文章会从概念边界、环境准备、API 调用、函数调用、框架接入、问题排查、工程化建议几个层面展开。目标很清晰读完以后你不只是收藏了一个 awesome 列表而是能自己写出第一个基于 DeepSeek 的 Agent并了解后续如何从示例项目走向生产项目。1. 为什么 Agent 开发值得用 DeepSeek 做一次最小闭环1.1 Agent 不等于聊天机器人很多开发者对 Agent 的第一个误解是把 Agent 等同于“能做多轮对话的聊天机器人”。两者在工程上差别很大。聊天机器人多数时候只有一条路径用户输入 - 模型生成 - 界面输出。Agent 则引入了循环模型生成的不只是最终回答而可能是“下一步动作”比如调用一个天气查询函数、检索一段内部知识库、更新数据库记录、调度另一个模型处理子任务。这意味着 Agent 的可靠程度由三部分组成模型的指令理解能力、工具定义的质量、循环调度代码的健壮性。三者中工具定义和调度代码是可以完全由开发者控制的也是大多数入门者最缺经验的环节。DeepSeek 模型不复杂它提供的是一个能够稳定输出动作的“大脑”而你要学的是给大脑配上手和眼。1.2 入门的核心目标是最小闭环入门时最忌讳“做得太全”。如果一开始就设计多 Agent 协作、复杂记忆、长期任务规划出错后很难定位是模型问题、提示词问题还是框架问题。更快的方式是先用最小闭环跑通整条链路用户请求 - 模型决定调用本地函数 - 代码执行函数并返回结果 - 模型基于工具结果生成最终答案这个链路一旦跑通后面扩展再多的工具、再复杂的记忆机制都是在既有回路上做加法。DeepSeek 提供符合常见大模型接口协议的调用方式因此你不需要先学一套私有 SDK直接用熟悉的 OpenAI SDK 风格代码即可联调。这也是它做 Agent 入门闭环时特别顺手的原因。1.3 聚合仓库的真正价值awesome-deepseek-agent这类仓库的存在至少说明附近生态已经具备几个层次应用层项目桌面端、浏览器扩展、命令行工具、框架层项目与主流编排框架的适配、基础工具层函数调用、提示词、记忆方案。它帮你节省了搜索成本。但从零到一的路径不会给你自动拼好。所以本文不替你去“测评仓库里的每一个项目”而是把结构性问题讲清楚Agent 到底是什么、如何接 API、如何写工具、如何用主流框架、如何排错。当你有了这些基础知识再回头看 awesome 列表时才能真正看出每个项目的定位与设计取舍而不只是“又一个 Agent 仓库”。2. 核心概念与容易混淆的边界2.1 Agent调度者不是执行引擎我给 Agent 一个适合工程开发的定义Agent 是一段以模型为决策核心、以工具为执行手段的循环程序。你可以把它拆成两个部分。决策核心负责“想”根据用户目标、当前上下文、可用工具信息判断下一步该做什么。执行手段负责“做”包括本地函数、API、数据库查询、文件读写、外部服务等。模型不是直接执行者它只是不断输出“下一步动作描述”程序代码负责把动作描述翻译成真实的函数调用。这个拆分的意义在于开发时要明确自己的代码职责构建好函数清单让模型看到你提供了什么把模型的输出解析成结构化动作执行动作并把结果放回上下文让模型继续推理。任何一个环节出现小毛病最终表现都可能是“模型回答不对”但实际上模型很可能是无辜的。2.2 Harness 与 Agent 的差别在 Agent 相关项目里经常看到 Harness 这个词。它最初是“夹具、控制装置”的意思在 Agent 语境里通常指“让 Agent 跑起来的运行时外壳”。可以这样区分Agent 是策略Harness 是承载策略的工程框架。Harness 一般负责管理模型调用、上下文拼接、工具注册与执行、停止条件判断、日志记录等通用功能。你可以把它理解成 Agent 的“驾驶舱”模型在里头只负责动脑而 Harness 处理方向盘、仪表盘和动力系统。很多开源项目会把 Harness 做成独立模块方便你替换不同模型甚至把同一套 Agent 逻辑运行在不同任务中。所以你看到某些项目自称 Harness有些自称 Agent Framework本质上是在强调不同侧重点前者强调运行时执行控制后者强调如何定义 Agent 的决策结构。2.3 Skill 与 Tool、Prompt、Plugin 的区别Skill 是最近很多 Agent 框架大力宣传的概念和 Tool、Prompt、Plugin 有重叠但不完全一样。Tool 是最小可执行单元通常指一个可以被模型调用的函数或服务。Prompt 是指导模型行为的文本指令。Plugin 通常指可以插到宿主程序里的功能模块在 AI Agent 语境里往往附带工具与界面。Skill 则更像是“面向某一类任务打包好的能力集合”它可能包含一段高质量的系统提示词、若干工具定义、默认参数和示例数据。你给 Agent 配置一个“日报助手 Skill”它会把写日报所需的所有步骤、格式要求、数据查询函数一次性带进来。实际开发中不必被概念缠绕。需要记住的原则是把原子能力写成独立 Tool把完整任务流程封装成 Skill用 Harness 把他俩组织起来运行。这样无论任务怎么换只需重新组合不必重写调度主流程。2.4 为什么 DeepSeek 适合作为 Agent 大脑从实际开发成本看DeepSeek 的优势是三点第一API 调用方式高度兼容主流大模型接口迁移成本低第二模型上下文窗口足够支撑多轮工具调用带来的上下文累积第三模型在指令遵循与结构化输出上的表现稳定能比较可靠地按设定函数格式返回。这段结论不是说“多强都能用”而是要建立正确的开发预期模型能力会有波动因此你的调度代码必须做好“模型不调用工具”“模型给出非法参数”这类情况的兜底。开发 Agent 时不要假设模型永远聪明而应该假设它会犯糊涂代码要做的是让它在犯糊涂时能被及时发现并纠正。3. 环境准备与前置条件3.1 准备一个可运行 Python 环境编写 Agent 推荐使用 Python生态比较成熟。建议本地 Python 版本在 3.9 及以上本文演示使用 3.10 左右即可。如果你用 Anaconda可以先创建一个独立环境避免依赖冲突。conda create -n deepseek-agent python3.10 -y conda activate deepseek-agent如果你习惯使用原生 venv也可以python -m venv .venv source .venv/bin/activate3.2 安装 OpenAI SDKDeepSeek 的 API 兼容 OpenAI 格式因此可以直接安装openaiPython 包作为客户端。需要说明的是这里使用openai包只是因为接口协议兼容实际请求地址和模型名称请以你正在使用的服务商配置为准。pip install -U openai python-dotenv安装完成后可以在终端里验证一下包是否可用python -c import openai; print(openai.__version__)3.3 配置 API 密钥与请求入口建议把密钥放到环境变量或.env文件中不要硬编码进 Python 文件防止代码上传仓库时泄露密钥。新建一个.env文件DEEPSEEK_API_KEY你的真实密钥 DEEPSEEK_BASE_URLhttps://api.deepseek.com DEEPSEEK_MODELdeepseek-chat如果使用支持其他厂商的兼容网关就将DEEPSEEK_BASE_URL改为你对应平台的入口地址。密钥申请与模型名称均以你的服务商控制台实际展示为准。这样做的原因是不同服务商的默认模型名可能不同直接在配置层抽离后面换模型时不用改业务代码。4. 核心流程拆解Agent 的最小运行逻辑4.1 单次模型调用只是“翻译官”写 Agent 之前你必须先把“单次模型调用”想清楚。单次调用本质上只做一件事把一段上下文翻译成一段输出。模型不知道你后续会执行什么函数也不关心你代码里有多少分支。它只接收你给的消息输出它认为合理的文本或结构化动作。所以 Agent 开发的核心是把“多轮翻译”包装成“持续执行”。每一轮翻译完成后你的代码要负责判断模型给出了最终回答还是要求调用工具如果是工具调用那么调用哪个工具参数是否合法工具结果应如何回传给模型4.2 Agent 循环的四个阶段一个最小 Agent 循环通常包含四个阶段第一阶段是构建上下文。把系统提示词、历史对话、用户问题、以及可用的工具定义包装好发送给模型。第二阶段是模型决策。模型可能输出最终内容也可能输出一个或多个工具调用请求。第三阶段是工具执行。你解析模型请求调用对应函数拿到结构化或文本结果。第四阶段是结果回填。把工具执行结果作为新的消息追加进上下文再发给模型让它继续推理。这四个阶段不断重复直到模型不再请求继续调用工具直接给最终答案。初学者经常会漏掉第四阶段的“把工具结果追加回消息序列”这一步导致上下文“失忆”模型下一轮根本不知道刚才函数查到了什么因而给出胡乱的猜测。4.3 函数定义的关键要素要让模型稳定调用工具函数定义必须清晰。函数定义不是给人看的文档而是给模型看的“工具说明书”。每个函数应包含函数名、描述、参数结构、参数必填项和类型。函数名建议使用能准确表达动作的命名风格比如get_weather_by_city、search_product_stock。函数描述要写清楚“何时使用、传入什么、返回什么”例如“根据城市名查询当前天气适合用户询问天气时调用”。不要写含义模糊的描述。你写“查询天气”模型不确定要不要传城市表现就是不调用或参数错误你写清楚“如果用户提到北京则 city 传北京”效果会好很多。有一个经验是把容易混淆的边界也写进描述比如“仅支持北京时间不接受英文地名缩写”。4.4 约定优先还是代码优先开发 Agent 会面临两种工具定义风格。一种是约定式提示词直接在系统消息中告诉模型“你只需要输出 JSON格式如下”。另一种是原生函数调用用标准化的 tools 参数把函数结构传给模型模型返回结构化 tool_calls。第二种更推荐原因有两点一是解析逻辑更可靠不必从自由文本中挤出 JSON二是主流 Agent 框架都围绕这种形式做好了上下文与历史管理。在 DeepSeek 场景下你可以优先使用带tools参数的调用方式。只有当平台不支持结构化函数调用时才退回提示词约定模式。下面章节给出可直接运行的完整示例。5. 完整示例带天气查询工具的 DeepSeek Agent5.1 示例目标我们写一个最小可用示例实现以下行为用户输入“北京天气怎么样”Agent 判断需要调用get_weather_by_city函数代码实际执行函数并返回模拟天气数据模型参考函数结果生成自然语言回答。为了减少外部依赖示例中不接入真实天气 API而是用一个本地函数返回一段带说明的文本。真实项目只需要将函数内部替换成 HTTP 请求即可。5.2 完整代码文件路径deepseek_agent_demo.pyimport json import os from dotenv import load_dotenv from openai import OpenAI # 读取 .env 中的配置 load_dotenv() client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlos.getenv(DEEPSEEK_BASE_URL, https://api.deepseek.com), ) MODEL_NAME os.getenv(DEEPSEEK_MODEL, deepseek-chat) # 定义一个真实可执行的函数 def get_weather_by_city(city: str) - str: 根据城市名返回一个模拟天气结果。 # 这里一般是 requests.get(weather_api, params{city: city}) # 再用返回数据拼成一段结构化说明 fake_data { city: city, weather: 晴, temperature: 22, humidity: 40, } return json.dumps(fake_data, ensure_asciiFalse) # 工具注册信息模型只阅读这份说明来决定调用什么函数 tools [ { type: function, function: { name: get_weather_by_city, description: 当用户询问某个城市的天气情况时传入城市名并调用该函数。城市名使用中文例如北京、上海。, parameters: { type: object, properties: { city: { type: string, description: 城市名比如北京、上海、广州 } }, required: [city] } } } ] def run_agent(user_input: str, max_turns: int 5) - str: 最小 Agent 循环调用模型 - 执行工具 - 回填结果。 # 消息历史放在循环外面保证每一轮都能看到之前的上下文 messages [ { role: system, content: 你是一个天气助手会根据用户问题判断是否需要查询天气。 当用户询问天气时必须调用 get_weather_by_city。 }, {role: user, content: user_input}, ] for _ in range(max_turns): response client.chat.completions.create( modelMODEL_NAME, messagesmessages, toolstools, ) message response.choices[0].message # 模型没有请求工具调用说明它认为可以直接给出最终答案 if not message.tool_calls: return message.content or # 如果存在工具调用逐条执行对应结果逐个回填 tool_messages [message] for tool_call in message.tool_calls: if tool_call.function.name get_weather_by_city: arguments json.loads(tool_call.function.arguments) city arguments.get(city, ) result get_weather_by_city(city) else: result json.dumps({error: unknown tool}, ensure_asciiFalse) tool_messages.append( { role: tool, tool_call_id: tool_call.id, content: result, } ) # 把模型这一轮输出和工具执行结果全部追加到历史消息 messages.extend(tool_messages) return 已达到最大轮数未能得到最终答复。 if __name__ __main__: print(run_agent(北京今天天气怎么样))5.3 代码逻辑分步说明在这个文件里比较关键的点有四个。tools列表不是给人看的代码文档而是会随请求发送出去的“模型函数清单”。模型不会真的执行任何 Python 函数它只会根据类型定义去匹配是否能完成用户意图因此函数名、描述、参数约束三者都要认真写。run_agent函数内部使用了循环。循环存在的目的是允许多次工具调用比如“先查北京天气再查上海天气”。如果只写一次模型调用Agent 无法处理多工具链条。tool_call_id必须严格对应原模型返回的id字段不能随便编。服务端是靠它把工具结果关联到之前那条工具调用记录的。拼错或漏掉模型上下文就会错乱。max_turns是一个重要保护机制防止模型陷入“不断请求调用工具”的死循环。入门阶段建议先设成 3 到 5在日志里观察每轮发生什么再调到更大值。5.4 如何运行在.env文件配置好密钥后执行以下命令python deepseek_agent_demo.py预期输出是一段自然语言例如北京今天天气晴气温 22 摄氏度湿度 40%。如果输出结果接近上述内容说明最小 Agent 闭环已经成功。这是你后续扩展一切工具能力的基础。6. 运行结果与效果验证6.1 如何确认 Agent 真的走了工具调用只看到最终自然语言输出还不足以证明 Agent 正确。为了判断整个链路是否真的发生可以在循环里临时加日志把每轮消息打印到终端。一个合理的检查点是模型是否返回了message.tool_calls字段模型选择的函数名是否为get_weather_by_city工具结果是否作为 role 为tool的消息回填到了下一轮。调试时也可以先不打印最终结果而是打印每轮的完整消息结构print(response.choices[0].message.model_dump_json())这样做能看到模型到底输出的是最终内容还是工具调用标记。如果只输出最终内容但没有调用工具日志而你的意图是让模型先查询天气就要优先检查工具注册和提示词了。6.2 验证失败时从哪里看起第一看提示词是不是允许模型不借助工具直接作答。有时模型会“自作聪明”地根据常识直接输出天气比如把北京说成晴这时候不是函数出了问题而是系统提示词没有约束住“必须先调用”。第二看函数参数是否与用户问法匹配。用户说“北京今天天气怎么样”模型应提取 city北京如果模型传了 city北京市也很正常因为描述没有明确禁止带“市”字。如果你希望参数干净一致可以在函数描述中写明参数规范。第三看上下文是否完整保留了工具结果。最简单的方法是把发给模型的 messages 完整打印出来确认 tool 消息确实存在。6.3 预留一个更复杂的测试任务你可以设计一个“查询北京和上海哪个更暖和”的问题来验证多轮工具调用。Agent 理论上的执行顺序应该是先调用一次函数查北京再调用一次函数查上海然后综合两次结果回答。如果程序只调用了一次函数就给出结论基本可以判断循环逻辑没有把工具结果正确保留到上下文。这种测试任务不需要真实联网天气数据用本地模拟返回即可关键在于验证循环与状态管理。真正做生产项目时再把函数体替换成真实 HTTP 调用接日志与监控。7. 常见问题与排查思路问题现象可能原因排查方式解决方案模型完全不调用工具工具注册信息未随请求发送或者提示词允许直接回答打印请求 messages查看是否包含 tools 字段观察返回是否带 tool_calls确认参数确实传入toolstools在系统提示词里强调必须先调用工具函数参数总是非法或缺字段函数描述不够清晰或者模型理解用户表述有歧义查看 tool_calls 中 arguments 字段内容写清参数枚举、默认值要求模型先提取参数再调用增加一段 few-shot 示例工具结果没有参与下一轮推理工具消息未正确回填或 tool_call_id 不匹配打印循环内的全部消息检查 tool 消息位置确保把 assistant 的 tool_calls 消息和所有 tool 消息追加进 messages再发起下一次调用代码抛异常Something went wrong请求格式或模型配置与当前服务商不匹配常见于模型名、base_url 或权限问题查看返回 HTTP 状态码与错误详情检查 .env 配置对应修改模型名和接口地址检查 API 密钥是否有效是否已开通要用的模型过长任务导致上下文超限每轮消息全部追加工具结果太长观察报错信息是否提示超出上下文限制统计 messages 字符数加工工具结果摘要删除早期历史设置消息保留窗口模型在某一步陷入重复调用没有设置停止轮数或模型自身循环了查看 max_turns 是否生效实现轮数上限检测到连续相同动作时直接终止或调整提示词这里有一点需要特别提醒如果 API 请求返回了关于内容策略、输入输出的限制类错误请先不要绕过程序去换私有渠道而是检查你的调用是否完全符合服务商的使用条款。生产中更稳妥的做法是记录失败样本优化提示词或过滤不合规输入而不是寻找绕过机制。8. 从最小示例到工程化更推荐的集成方式8.1 使用消息中间件管理上下文上面的最小示例已经可以运行但真实项目中很少把它直接跑在生产环境里因为当工具数量增多之后messages 的拼接和管理会变得混乱。一个常见的工程化改进是把消息历史放进一个独立的上下文管理器由它只维护最近若干轮记录并为每轮记录打上轮次标识。这样做有三个好处便于查看工具调用链便于统计 Token 消耗后续接入长期记忆时无需改动业务逻辑。另一个值得处理的问题是把工具结果裁剪成摘要。比如搜索服务返回 50 条结果API 一次最多接收几万字的上下文直接把 50 条全丢进去既浪费 Token 又干扰模型。正确的做法是保留前 5 条关键结果并补一句“仍有 45 条未展示”需要时再触发第二轮查询。8.2 接入主流 Agent 框架迁移成本很低如果你已经通过最小示例理解了函数调用与循环原理那么接主流 Agent 框架会简单很多。以常见的 LangChain/LangGraph 生态为例你只需要处理两件事一是将 DeepSeek 模型包装成框架支持的模型对象二是把上一节中get_weather_by_city包装成框架里的工具节点。包装模型的思路通常是借助框架中已有的 OpenAI 兼容类把api_key和base_url改成 DeepSeek 配置即可。框架并不知道你在调用哪一家大模型服务商它只是按照 OpenAI 的接口协议发起请求。因此在没有掌握完整框架细节时先不要着急编排复杂图建议先用一个最小 Chain 验证模型接入项。# 示例片段仅示意将兼容服务包装为框架能识别的模型 # 具体类名与安装方式请以你实际使用的框架版本为准 from langchain_openai import ChatOpenAI import os model ChatOpenAI( modelos.getenv(DEEPSEEK_MODEL, deepseek-chat), api_keyos.getenv(DEEPSEEK_API_KEY), base_urlos.getenv(DEEPSEEK_BASE_URL, https://api.deepseek.com), ) result model.invoke(你好) print(result.content)如果你使用的框架没有提供 OpenAI 兼容封装你也可以直接用自己实现的最小调用函数开发自定义 Agent 工具。很多项目在早期坚持自定义调度代码反而能减少框架升级带来的隐性破坏。8.3 多 Agent 架构不要过早引入单一 Agent 尚未稳定之前多 Agent 协作大概率会放大问题。多 Agent 通常有两种出现原因一是任务确实可以被拆成多个专业角色二是开发团队觉得“Agent 越多越智能”。真实生产项目里问题往往出现在角色之间的交接与信息同步而不是单个 Agent 的智力不足。建议先用一个 Agent 加多个工具完成 80% 的需求只有在出现以下信号时再考虑多 Agent提示词已经超过 5000 字且无法拆分需要不同角色使用不同记忆任务天然要并行承担多个专业域。9. 工程化阶段容易踩的坑9.1 模型上下文不是无限聊天记录很多人把上下文窗口理解成“可以无限放历史记录”。实际上窗口大小决定了模型一次能读多少字符但工程上你不能等到接近上限才处理。因为长上下文会带来更高的推理延迟也会让模型更难集中注意力到用户最近的问题。较好的做法是控制核心上下文总量只保留最近几轮完整消息更早的历史以摘要方式进入系统提示词。如果摘要这一步做得比较粗糙Agent 会丢失关键信息。建议摘要时不只是写“用户之前问过天气”而是把关键事实保留下来“用户当前城市是北京上次查询时间为 2025 年 1 月 20 日”。这样短期记忆转长期记忆时信息损失会少很多。9.2 工具执行结果必须保持可信Agent 中工具的返回内容后续会再次进入模型所以“脏数据”有可能在下一轮被模型当成事实。生产环境里工具函数应当做好字段校验、异常兜底与重试。如果一个工具调用失败不要让 Agent 基于错误信息继续编造结论而是返回一条明确错误让模型告诉用户当前查询失败或让调度代码标记“该工具不可用请走人工处理”。当工具内部会修改外部系统数据时必须增加授权校验与执行确认。Agent 的模型输出本身无法作为“变更审批依据”真实操作数据库、发布版本、发送大量站内信的变更都应有独立于模型的权限检查逻辑。这块是工程底线不能省略。9.3 日志是排查 Agent 问题最重要的手段传统接口开发的日志可能只需要记录入参、出参、耗时Agent 的日志则应该重点记录决策轨迹。每一次模型调用、模型原始返回、工具名、工具参数、工具结果摘要、本轮耗时都要作为一条结构化日志写入日志中心。这样当用户在线上抛出“为什么推荐结果是错的”时可以快速复盘是模型理解错了是工具返回了旧数据还是上下文被历史污染了。给每一天的 Agent 运行增加唯一链路标识trace_id也很重要。有了链路标识前端可以把用户侧的一次操作和后端几十次模型调用串起来排查体验会有本质提升。10. 如何在 DeepSeek Agent 生态中选择学习项目回到开头提到的awesome-deepseek-agent类聚合仓库。当你已经能够写一个最小 Agent 后再看这些项目建议关注四个维度。第一是项目能否离线跑通。仓库提供 example 越多越容易跑通你也能更快把项目跑起来改造。第二是项目可替换性。里面是否把模型调用抽成独立配置是否便于换成其他兼容模型如果模型写死在代码深处复用价值会低很多。第三是工程完整度。好的 Agent 项目会考虑错误处理、日志、并发控制、任务上限而不是只在 README 里放一张架构图。第四是社区维护状态。深入看 issue 里反馈的问题能看出项目是否还在被真实用户使用而不是无人维护的“毕业设计”。参考这些维度不是让你追求某一个“最好的 Agent 项目”而是让你在技术选型时拥有自己的判断标准。收藏一份 awesome 列表很简单难的是知道什么项目值得长期阅读、什么项目只需抽取其中某个工具函数。建议把最小示例中的天气函数替换成你自己业务里的真实工具比如订单查询、知识库检索、工单创建跑通以后你会对 Agent 开发产生完全不同的掌控感。后续值得深入的方向包括函数调用的参数安全校验、外部工具真实 HTTP 调用时的超时与重试策略、基于 RAG 的知识检索工具、把历史任务沉淀为 Skill 的方法以及在深层次任务中如何判断 Agent 的失败原因。顺着这条路径不停积累你面对任何 DeepSeek Agent 开源项目时都能快速判断它的设计逻辑是否与自己的场景匹配。
返回列表