1. 五分钟跑通第一个 DeepSeek 回合到底在做什么
你可能已经在各种文章里看过 DeepSeek Agent Harness 的架构图,六层、八模块、工具调用链路,越看越觉得门槛高。但真正让你从"看客"变成"玩家"的,其实只有一件事:让模型对你开口说第一句话,并且这句话是通过你自己写的代码、走你自己的通道回来的。
这一节要做的,就是这件事。用 uv 起一个 Python 3.12 项目,装一个 openai 兼容客户端,把 endpoint 和 Key 指向 TaoToken 统一通道,然后跑一次带工具调用的对话回合。整个过程五分钟,不需要你懂框架,不需要你搭项目结构,就一个 .py 文件。
为什么强调"第一个回合"?因为 Agent 的本质是"多回合 + 工具调用 + 状态管理",但如果你连单回合都没亲手跑通过,后面所有关于 Harness 的讨论对你来说都是悬空的。我见过太多人卡在"我知道原理但没跑过"的状态,一遇到 401 或者 model not found 就懵了。所以这一节的目标很明确:让你亲眼看到返回结构里 choices、usage、tool_calls 长什么样。
TaoToken 在这里扮演的角色是"统一通道"。你可以把它理解成一个兼容 OpenAI 协议的入口,你的代码里 base_url 指向它,api_key 用它发的 Key,模型名填 DeepSeek 系列,剩下的调用方式和直连官方完全一致。这样做的好处是:以后你想换模型、想加工具、想接 Agent 框架,业务代码一行不用动,只改配置。
适合谁读?零基础但愿意动手的人。你不需要会 Python 高级特性,能复制粘贴、能看懂 print 输出就行。如果你已经跑过 OpenAI 的 hello world,那这一节对你来说是"换通道 + 加工具调用"的升级版,同样值得跟一遍,因为工具调用的返回结构是后面所有 Agent 内容的地基。
我试过把这套流程给完全没接触过大模型 API 的朋友走一遍,从装 uv 到看到工具调用返回,最快的一次是四分钟出头。所以别怕,跟着下面的步骤走就行。
2. TaoToken 前置准备:Key、通道与 uv 环境
在写代码之前,你需要先把三样东西准备好:uv、TaoToken 的 API Key、以及一个明确的项目目录。这一节把这三件事一次说清,避免你跑到一半发现缺东西。
先说 uv。它是目前 Python 生态里管理虚拟环境和依赖最快的工具之一,装完之后你不需要再手动 python -m venv、不需要 pip install 一堆东西。macOS 和 Linux 下用官方脚本安装:
curl -LsSf https://astral.sh/uv/install.sh | shWindows PowerShell 下用:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.sh | iex"装完之后新开一个终端,输入uv --version能看到版本号就说明成功了。如果提示找不到命令,多半是 PATH 没刷新,重开终端即可。
接下来是 TaoToken 的 Key。打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,注册登录后进入控制台,在 API Keys 页面创建一个新的 Key。创建时建议给它起个能认出来的名字,比如deepseek-first-turn,方便以后排查是哪个项目在用。Key 只会完整显示一次,复制下来先存到安全的地方,别直接写进代码提交到 Git。
关于通道地址,记住两个:
- 官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
- API 基地址:https://taotoken.net/api
注意 API 基地址后面不加任何 UTM 参数,代码里填的就是这个干净的地址。很多新手会把带参数的官网地址填进 base_url,结果请求路径拼错,报 404,这是第一个高频坑。
模型名方面,DeepSeek 系列在 TaoToken 通道上通常用deepseek-chat或deepseek-reasoner这类标识,具体以你控制台里模型列表显示的为准。这一节我们用deepseek-chat作为示例,因为它对工具调用的支持比较稳定。
最后建项目目录。随便找个你放代码的地方,执行:
uv init first_turn && cd first_turn uv python pin 3.12 uv add openai这三条命令分别做了:初始化项目、锁定 Python 3.12、安装 openai 客户端。装完之后你会看到目录里多了 pyproject.toml 和 .python-version,这就是 uv 帮你管好的环境描述文件。后面所有依赖都往 pyproject.toml 里加,不用手动维护 requirements.txt。
到这里前置就齐了。你可以先不急着写代码,把 Key 和 base_url 在脑子里过一遍:Key 来自 TaoToken 控制台,base_url 是 https://taotoken.net/api,模型名是 deepseek-chat。这三个东西填对,后面基本不会出大问题。
3. 可复制配置:pyproject、环境变量与最小 Harness 脚本
这一节给你三份可以直接抄的东西:pyproject.toml 的依赖片段、环境变量设置方式、以及一个带工具调用的最小 Harness 脚本。抄完就能跑。
先看 pyproject.toml。uv init 之后它已经有一个基础结构,你只需要确认 dependencies 里有 openai。执行过uv add openai之后,它大概长这样:
[project] name = "first-turn" version = "0.1.0" requires-python = ">=3.12" dependencies = [ "openai>=1.40.0", ]如果你后面想加日志或者 dotenv,可以继续uv add python-dotenv,它会自动写进这个文件。不要手动去改版本号,让 uv 帮你解析依赖树,避免版本冲突。
环境变量这块,推荐用 .env 文件管理,但这一节为了让你少装依赖,先用系统环境变量。macOS/Linux:
export TAOTOKEN_API_KEY="sk-你的key"Windows PowerShell:
$env:TAOTOKEN_API_KEY="sk-你的key"注意变量名我用的是 TAOTOKEN_API_KEY,不是 OPENAI_API_KEY,这样以后你同时接多个通道时不会互相覆盖。Key 里不要带空格,复制的时候前后别多选字符,这是 401 的常见原因。
下面是核心的最小 Harness 脚本,保存为first_turn.py:
""" 五分钟跑通第一个 DeepSeek 回合(带工具调用)。 前置:设置环境变量 TAOTOKEN_API_KEY。 运行:uv run python first_turn.py """ import os import sys import json import datetime from openai import OpenAI BASE_URL = "https://taotoken.net/api" MODEL = "deepseek-chat" # 定义一个最简单的工具:查询当前时间 TOOLS = [ { "type": "function", "function": { "name": "get_current_time", "description": "获取当前的日期和时间", "parameters": { "type": "object", "properties": {}, "required": [], }, }, } ] def get_current_time() -> str: return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") def main() -> None: key = os.getenv("TAOTOKEN_API_KEY") if not key: sys.exit("请先设置环境变量 TAOTOKEN_API_KEY") client = OpenAI(base_url=BASE_URL, api_key=key) messages = [ {"role": "system", "content": "你是一个会调用工具的助手。"}, {"role": "user", "content": "现在几点了?请调用工具获取。"}, ] # 第一回合:模型决定是否调用工具 resp = client.chat.completions.create( model=MODEL, messages=messages, tools=TOOLS, tool_choice="auto", ) msg = resp.choices[0].message print("第一回合 finish_reason:", resp.choices[0].finish_reason) print("第一回合 tool_calls:", msg.tool_calls) # 如果模型要求调用工具,我们执行并把结果回传 if msg.tool_calls: messages.append(msg) for call in msg.tool_calls: if call.function.name == "get_current_time": result = get_current_time() messages.append({ "role": "tool", "tool_call_id": call.id, "content": result, }) # 第二回合:模型基于工具结果生成最终回答 resp2 = client.chat.completions.create( model=MODEL, messages=messages, tools=TOOLS, ) print("最终回答:", resp2.choices[0].message.content) print("usage:", resp2.usage) if __name__ == "__main__": main()这份脚本的关键点有三个。第一,base_url 指向 https://taotoken.net/api,不是官网首页。第二,tools 参数用标准的 OpenAI 工具定义格式,DeepSeek 兼容这套结构。第三,工具调用是两回合的:第一回合模型返回 tool_calls,你执行工具后把结果以 role=tool 追加进 messages,再发第二回合拿最终回答。这个"两回合"结构就是 Agent 循环的最小雏形,后面所有复杂 Harness 都是在这个骨架上加东西。
如果你只想先跑一个不带工具的纯对话,把 tools 和 tool_choice 去掉,直接打印resp.choices[0].message.content就行。但既然这一节叫"第一个 DeepSeek 回合",我建议你直接跑带工具的版本,因为看到 tool_calls 返回的那一刻,你对 Agent 的理解会实打实地上一个台阶。
4. 验证请求:跑起来看返回结构与工具调用链路
配置写完,现在跑起来。在项目目录下执行:
uv run python first_turn.py第一次运行 uv 会解析依赖并创建虚拟环境,可能要等十几秒。之后就是秒起。你会看到类似下面的输出:
第一回合 finish_reason: tool_calls 第一回合 tool_calls: [ChatCompletionMessageToolCall(id='call_abc123', function=Function(arguments='{}', name='get_current_time'), type='function')] 最终回答: 现在是 2026-01-15 14:32:07。 usage: CompletionUsage(completion_tokens=18, prompt_tokens=142, total_tokens=160)看到finish_reason: tool_calls就说明模型正确识别了工具并决定调用。看到tool_calls列表里有get_current_time和它的 id,说明工具定义被正确解析。最终回答里带上了时间,说明第二回合把工具结果吃进去并生成了自然语言回复。usage 里能看到 token 消耗,这是留痕的起点。
把这条链路拆开看,实际发生了这些事:
你的代码组装 messages 和 tools,POST 到 https://taotoken.net/api/chat/completions。TaoToken 通道把请求转发给 DeepSeek 模型。模型推理后返回一个 JSON,choices[0].message 里没有 content,但有 tool_calls。你的代码解析 tool_calls,本地执行 get_current_time,把结果作为 role=tool 的消息追加。再次 POST,模型这次拿到工具结果,生成最终文本。整个过程两次请求,一次工具执行,这就是一个完整的 Agent 回合。
这里有个细节值得注意:第一回合的 message 对象直接 append 进 messages 是合法的,因为 openai 客户端返回的 message 本身就是可序列化的结构。如果你手动构造 dict,要确保 tool_calls 字段格式一致,否则第二回合会报 400。
再验证一下纯对话路径。把脚本里的 tools 参数临时去掉,user 消息改成"用一句话解释什么是 Agent",重新跑:
uv run python first_turn.py你会看到finish_reason: stop,message.content 里是一段完整回答,usage 里 prompt_tokens 和 completion_tokens 都有值。这说明通道对普通对话和工具调用两种模式都正常支持。
如果你想更直观地看原始返回,可以在 create 之后加一行:
print(json.dumps(resp.model_dump(), ensure_ascii=False, indent=2))这样能看到完整的响应 JSON,包括 id、object、created、model 等字段。排查问题时这行很有用,能一眼看出是通道返回的还是模型返回的。
跑通之后,建议你把这次调用的 base_url、model、usage 记一笔。不用复杂,print 一行就行:
print(f"[留痕] {BASE_URL} | {MODEL} | in={resp.usage.prompt_tokens} out={resp.usage.completion_tokens}")这个习惯越早养成越好。等你后面跑 Agent 任务,一次几十次调用,没有留痕你根本不知道钱花在哪。
5. 本篇常见错排查:401、404、tool_calls 解析失败
第一次跑大概率不会一次全绿。我把这一节最常见的几类报错和对应解法列出来,你对着改就行。
401 Unauthorized / invalid api key
这是最高频的。原因通常是三个:Key 没设置、Key 带空格、环境变量没生效。先确认echo $TAOTOKEN_API_KEY(Windows 用echo $env:TAOTOKEN_API_KEY)能打印出你的 Key。如果打印为空,说明环境变量没设上,重开终端再设。如果打印出来前后有空格,重新复制。还有一种情况是你把 Key 写进了代码但用了错误的变量名,检查os.getenv里的名字和设置时是否一致。
404 Not Found / model not found
两个原因:base_url 写错,或者模型名写错。base_url 必须是https://taotoken.net/api,不要带末尾斜杠,不要带 UTM 参数。模型名以你控制台模型列表为准,这一节用deepseek-chat。如果你填了deepseek-flash但通道里没有这个标识,就会报 model not found。遇到这个错,先去控制台确认可用模型名,再回来改 MODEL 常量。
local proxy failed / connection error
这类报错说明请求根本没发出去,通常是本地网络环境问题。检查你的网络是否能正常访问 https://taotoken.net/api,可以用 curl 试一下:
curl -I https://taotoken.net/api如果 curl 也失败,说明是网络层的问题,不是代码问题。如果 curl 成功但 Python 失败,检查是否有全局代理设置干扰了 openai 客户端。openai 客户端默认会读 HTTP_PROXY 环境变量,如果你本地有代理配置,可能需要临时 unset 掉再跑。
reading choices / KeyError 'choices'
这个报错说明返回的 JSON 里没有 choices 字段,通常是通道返回了一个错误对象,但你的代码直接去取 choices 了。解法是在 create 之后先判断:
if not resp.choices: print("异常返回:", resp.model_dump()) sys.exit(1)这样能把真实的错误信息打出来,而不是被 KeyError 掩盖。
tool_calls 解析失败 / 第二回合 400
如果你手动构造 tool 消息,常见错误是 tool_call_id 对不上,或者 role 写成了 assistant。正确格式是:
messages.append({ "role": "tool", "tool_call_id": call.id, "content": result, })tool_call_id 必须和第一回合返回的 call.id 完全一致。另外,第一回合的 assistant message 要原样 append 进去,不能只 append content,否则模型看不到自己发起的工具调用。
OAuth / 认证方式混淆
如果你看到 OAuth 相关的报错,说明你可能误用了某些需要 OAuth 的客户端配置。这一节用的是标准 API Key 认证,不需要 OAuth。检查你的 OpenAI 客户端初始化是否只传了 base_url 和 api_key,没有多余的 auth 参数。
把这几类错对着排查,基本能覆盖 95% 的首次运行问题。剩下的如果还搞不定,把完整报错和你的 base_url、模型名(不要贴 Key)发出来,一般一眼就能定位。
6. 从这一回合出发:下一步该往哪走
跑通这一个回合之后,你手里其实已经有了 Agent 的最小骨架:消息组装、模型调用、工具定义、工具执行、结果回传、二次调用。后面所有的 Harness 内容,都是在这个骨架上做加法。
如果你想继续深入,我建议按这个顺序走。先把工具数量加到三个以上,观察模型在多工具场景下怎么选择。然后引入多轮对话,让 messages 在多次用户输入之间保持。再往后是状态管理、错误重试、日志落盘,这些就是 Harness 要解决的问题。
通道这块,你现在用的是 TaoToken 的统一入口。以后想换模型,只改 MODEL 常量;想加新工具,只改 TOOLS 列表;想接 Agent 框架,base_url 和 Key 不用动。这种"业务代码和通道解耦"的结构,是你后面做任何 Agent 项目都该保持的习惯。
如果你还没创建 Key,现在就可以去 https://taotoken.net/api-keys 建一个,然后回到这一节的脚本重新跑一遍。跑通之后,把 usage 那行留痕保留下来,这是你 Agent 之路的第一笔账。
下一节我们会从"调 API"往"造系统"迈,讲清楚 OpenAI 兼容协议为什么能成为事实标准,以及它对你后面选型意味着什么。但在此之前,先确保你亲手跑通过这一个回合。没跑通的原理都是别人的,跑通的才是你自己的。