Hermes Agent 设计了一个很重要的特性——模型无关(Model-Agnostic)。它内部有一套 Transport 适配器层,把不同厂商的 API 差异封装在内部,对上层暴露统一接口。
这意味着切换模型不需要改代码,只需要改配置。
目前 Hermes Agent 支持的 API 模式:
| API 模式 | 适用模型 |
|---|---|
| chat_completions | OpenAI、DeepSeek、OpenRouter、小米、智谱…… |
| anthropic_messages | Anthropic Claude、MiniMax 的 Anthropic 兼容接口 |
| codex_responses | OpenAI Codex、xAI |
| bedrock_converse | AWS Bedrock |
DeepSeek V4 提供的是标准的 OpenAI 兼容 API,所以走 chat_completions 模式,这是最简单的接入路径。
说白了就三步:
1. 配置中指定 provider 为 deepseek
2. 配置中指定 model 为 deepseek-v4-flash(或 deepseek-v4-pro)
3. 设置 DEEPSEEK_API_KEY 环境变量
就这么多。不需要改一行 Python 代码。
开始之前,确认以下条件:
| 项目 | 要求 |
|---|---|
| Hermes Agent | 已克隆到本地(二开仓库 main 分支) |
| Python 环境 | venv 可用(./venv/Scripts/python.exe) |
| DeepSeek 账号 | 已注册并创建 API Key |
| 网络 | 能访问 https://api.deepseek.com |
如果你用的我们的二开仓库,前两个条件已经满足。
步骤 1:注册 DeepSeek 账号,获取 API Key
打开 https://platform.deepseek.com/api_keys,注册账号,创建一个 API Key。
创建成功后你会看到一串以 sk- 开头的密钥,复制它。
步骤 2:设置环境变量
找到项目根目录的 .env 文件,添加一行:
|
1 2 3 |
DEEPSEEK_API_KEY=sk-你的密钥 # 可选:自定义 API 地址(默认 https://api.deepseek.com/v1,通常不需要改) # DEEPSEEK_BASE_URL=https://api.deepseek.com/v1 |
注意:.env 文件默认被 .gitignore 忽略,不会提交到仓库,密钥是安全的。
步骤 3:修改 config.yaml
编辑 hermes_workspace/config.yaml,找到 model: 配置段,修改为:
|
1 2 3 4 |
model: default: deepseek-v4-flash # 使用 V4 Flash 版本 provider: deepseek # 提供商设为 deepseek base_url: https://api.deepseek.com/v1 |
三个字段的含义:
| 字段 | 值 | 作用 |
|---|---|---|
| default | deepseek-v4-flash | 告诉 Hermes 用哪个模型 |
| provider | deepseek | 告诉 Hermes 走哪个提供商的认证和路由 |
| base_url | https://api.deepseek.com/v1 | 告诉 Hermes API 地址在哪 |
三个字段缺一不可,少一个 Hermes 就无法确定怎么连。
步骤 4:验证 API 连通性
在终端运行以下命令,确认 DeepSeek API 能正常响应:
|
1 2 3 |
curl -s https://api.deepseek.com/v1/models \ -H "Authorization: Bearer $DEEPSEEK_API_KEY" \ -H "Content-Type: application/json" |
正常返回:
|
1 2 3 4 5 6 7 |
{ "object": "list", "data": [ {"id": "deepseek-v4-flash", "object": "model", "owned_by": "deepseek"}, {"id": "deepseek-v4-pro", "object": "model", "owned_by": "deepseek"} ] } |
能看到两个模型 ID,说明 API Key 有效,网络连通。
再测试一下对话:
|
1 2 3 4 5 6 7 8 |
curl -s https://api.deepseek.com/v1/chat/completions \ -H "Authorization: Bearer $DEEPSEEK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-v4-flash", "messages": [{"role": "user", "content": "你好,请用一句话介绍自己"}], "max_tokens": 100 }' |
如果返回包含 choices[0].message.content,说明模型正常响应。
步骤 5:启动 Hermes Agent 测试
激活虚拟环境,启动交互式会话:
|
1 2 3 4 5 6 7 8 |
# Windows .\venv\Scripts\activate
# Linux/Mac source venv/bin/activate
# 启动 Hermes python hermes |
输入任意问题测试。如果你看到了正常回复,说明配置成功!
预期结果:
|
1 2 3 4 5 6 |
Provider: deepseek API mode: chat_completions Model: deepseek-v4-flash
你: 你好 Hermes: 你好!有什么我可以帮你的吗? |
如果任务需要更强的推理能力(复杂数学、长文档分析),改一行即可:
|
1 2 3 4 |
model: default: deepseek-v4-pro # 从 flash 改为 pro provider: deepseek base_url: https://api.deepseek.com/v1 |
Pro 的激活参数是 Flash 的 3.8 倍(49B vs 13B),推理深度更强,但价格也贵 12 倍。
如果你已经在用 OpenRouter,可以不切换 provider,直接改模型名:
|
1 2 3 4 |
model: default: deepseek/deepseek-v4-flash # 注意 OpenRouter 要用 vendor/model 格式 provider: openrouter # 保持 openrouter 不变 base_url: https://openrouter.ai/api/v1 |
OpenRouter 上的路由会自动把请求转发到 DeepSeek。
DeepSeek 也提供了 Anthropic 兼容的 API 端点,如果你习惯了 Anthropic 的接口格式,可以用这个:
|
1 2 3 4 |
model: default: deepseek-v4-flash provider: deepseek base_url: https://api.deepseek.com/anthropic # 注意这个 URL |
当 base_url 以 /anthropic 结尾时,Hermes Agent 会自动检测并切换到 anthropic_messages 模式。
这是最经济的用法——日常对话用 Flash(极低成本),遇到复杂推理时手动切换到 Pro。
在 Hermes 会话中通过 /model 命令切换:
|
1 2 3 4 |
你: /model deepseek-v4-pro Hermes: 模型已切换为 deepseek-v4-pro 你: 帮我分析这篇论文的数学推导... Hermes: [使用 Pro 进行深度推理] |
用完后切回 Flash:
|
1 |
你: /model deepseek-v4-flash |
这样可以大幅降低成本——Flash 的价格只有 Pro 的 1/12。
接下来的内容,我们深入 Hermes Agent 的源码,看一条消息从"用户输入"到"DeepSeek 响应"的完整旅程。
涉及的源码文件和关键行号:
| 环节 | 文件 | 关键行 |
|---|---|---|
| 配置加载 | run_agent.py AIAgent.init | L833-L1059 |
| Provider 识别 | run_agent.py api_mode 判断 | L977-L1008 |
| 模型名归一化 | hermes_cli/model_normalize.py | L147-L179 |
| 凭证加载 | agent/credential_pool.py | L1262-L1338 |
| Transport 选择 | agent/transports/__init__.py | L14-L57 |
| API 调用构建 | agent/transports/chat_completions.py | L75-L162 |
| Reasoning 处理 | run_agent.py | L7826-L7885 |
当你运行 python hermes 时:
1. CLI 入口(cli.py)读取 hermes_workspace/config.yaml
2. 从 config 中提取 model.default → "deepseek-v4-flash"
3. 从 config 中提取 model.provider → "deepseek"
4. 从 config 中提取 model.base_url → "https://api.deepseek.com/v1"
5. 将这些参数传给 AIAgent.__init__()
简化后的伪代码:
|
1 2 3 4 5 6 7 8 |
# cli.py(简化) config = load_config("hermes_workspace/config.yaml") model_cfg = config.get("model", {}) agent = AIAgent( model=model_cfg.get("default", "deepseek-v4-flash"), provider=model_cfg.get("provider", "deepseek"), base_url=model_cfg.get("base_url", "https://api.deepseek.com/v1"), ) |
AIAgent.__init__() 收到这三个参数后,会做一系列关键判断。我们重点关注几个核心属性的赋值:
|
1 2 3 4 |
# run_agent.py 第 941-1008 行(简化) self.model = model # "deepseek-v4-flash" self.base_url = base_url or "" # "https://api.deepseek.com/v1" self.provider = provider.strip().lower() # "deepseek" |
这是最关键的一步。Hermes Agent 根据 provider 和 base_url 来决定使用哪种 API 模式。
api_mode 决定了使用哪个 Transport 适配器。判断逻辑在 run_agent.py 第 977-1008 行:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# run_agent.py 第 977 行 if api_mode in {"chat_completions", "codex_responses", "anthropic_messages", "bedrock_converse"}: # 如果调用者明确指定了 api_mode,直接用 self.api_mode = api_mode elif self.provider == "openai-codex": self.api_mode = "codex_responses" elif self.provider == "xai": self.api_mode = "codex_responses" elif self.provider == "anthropic": self.api_mode = "anthropic_messages" elif self._base_url_lower.rstrip("/").endswith("/anthropic"): # URL 以 /anthropic 结尾 → Anthropic 兼容模式 self.api_mode = "anthropic_messages" elif self.provider == "bedrock": self.api_mode = "bedrock_converse" else: # 以上都不匹配 → 默认走 chat_completions self.api_mode = "chat_completions" |
DeepSeek 走的是哪条分支?
我们的配置是:
对照判断链:
chat_completions 模式使用的是 OpenAI SDK,而 DeepSeek 的 API 是 OpenAI 兼容的,所以天然适配。
如果错误地走了 anthropic_messages 模式,Hermes 会用 Anthropic SDK 去发请求,但 DeepSeek 的 OpenAI 端点不认识 Anthropic 的请求格式,会返回 400 错误。
小结:判断链的本质是一个路由表。每个 provider 按其特征(名称、URL)被路由到正确的 API 模式。DeepSeek 的特征是"OpenAI 兼容但没有特殊标识",所以走了默认的 chat_completions。
你可能注意到,我们的配置写的是 deepseek-v4-flash,但早期 Hermes Agent 版本只认识 deepseek-chat。为什么现在可以直接用 V4 模型名?
早期的 Hermes Agent 把所有非推理的 DeepSeek 输入都折叠为 deepseek-chat,这个模型在 DeepSeek 自己的 API 上对应 V3。也就是说:
|
1 |
用户写 deepseek-v4-flash → 被折叠为 deepseek-chat → 实际调用的是 V3 |
这对用户来说是个隐蔽的坑——配置了 V4,结果用的还是 V3。
这个问题在最近的版本中已经修复。现在的归一化逻辑在 hermes_cli/model_normalize.py 第 147-179 行:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
_DEEPSEEK_CANONICAL_MODELS = frozenset({ "deepseek-chat", # V3 "deepseek-reasoner", # R1 "deepseek-v4-pro", # V4 Pro "deepseek-v4-flash", # V4 Flash }) _DEEPSEEK_V_SERIES_RE = re.compile(r"^deepseek-v\d+([-.].+)?$") def _normalize_for_deepseek(model_name: str) -> str: bare = _strip_vendor_prefix(model_name).lower() # 规则 1:已经是规范的模型名 → 原样返回 if bare in _DEEPSEEK_CANONICAL_MODELS: return bare # 规则 2:匹配 V 系列模式(deepseek-v<数字>...)→ 原样返回 if _DEEPSEEK_V_SERIES_RE.match(bare): return bare # 规则 3:包含推理关键词(r1, think, reasoning...)→ deepseek-reasoner for keyword in _DEEPSEEK_REASONER_KEYWORDS: if keyword in bare: return "deepseek-reasoner" # 规则 4:其他所有 → deepseek-chat(V3) return "deepseek-chat" |
为什么我们的配置能正确传递?来看匹配过程:
同样,deepseek-v4-pro 也是规范名,也会原样传递。
|
1 2 3 4 5 6 7 8 |
# run_agent.py 第 1017-1024 行 from hermes_cli.model_normalize import normalize_model_for_provider # 如果 provider 不是聚合器(OpenRouter 那种),就走归一化 if self.provider not in _AGGREGATOR_PROVIDERS: self.model = normalize_model_for_provider(self.model, self.provider) # → normalize_model_for_provider("deepseek-v4-flash", "deepseek") # → _normalize_for_deepseek("deepseek-v4-flash") # → "deepseek-v4-flash"(原样返回) |
小结:模型名归一化确保用户输入被映射为 API 能识别的模型 ID。V 系列 ID(v4-pro、v4-flash 和未来的 v5-*)会原样传递,只有模糊输入(如 deepseek-r1)才会被映射到规范名。
改了配置,加了环境变量,但 Hermes Agent 是怎么知道要用 DEEPSEEK_API_KEY 的?
在 hermes_cli/auth.py 中,有一个提供商注册表 PROVIDER_REGISTRY,记录了每个提供商的信息:
|
1 2 3 4 5 6 7 8 |
# hermes_cli/auth.py 第 272 行(简化) ProviderConfig( name="deepseek", api_key_env_vars=("DEEPSEEK_API_KEY",), # 从哪个环境变量读 key base_url_env_var="DEEPSEEK_BASE_URL", # 可选:自定义 base_url inference_base_url="https://api.deepseek.com/v1", # 默认 API 地址 auth_type="api_key", # 认证方式 ) |
这里的关键信息是:DeepSeek 的 API Key 从 DEEPSEEK_API_KEY 环境变量读取。
当 Hermes Agent 需要调用 API 时,会触发凭证加载:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# agent/credential_pool.py 第 1262 行(简化) def _seed_from_env(provider, entries): # 查询 PROVIDER_REGISTRY 获取该提供商的配置 pconfig = PROVIDER_REGISTRY.get(provider) # provider = "deepseek" # 遍历所有可能的环境变量名 for env_var in pconfig.api_key_env_vars: # → ("DEEPSEEK_API_KEY",) token = os.getenv(env_var, "").strip() # → 从环境变量读取 if not token: continue # 将凭证写入凭证池 entries.append({ "source": f"env:{env_var}", "auth_type": "api_key", "access_token": token, # 你的 sk-xxx "base_url": pconfig.inference_base_url, }) |
Hermes Agent 读取凭证的顺序:
1. 环境变量(DEEPSEEK_API_KEY)
2. ~/.hermes/.env 文件
3. 项目根目录 .env 文件
4. hermes auth 命令手动添加的凭证
如果多个来源都有值,优先级高的覆盖优先级低的。
运行以下命令查看凭证池状态:
|
1 2 3 4 5 6 7 |
python -c " from hermes_cli.env_loader import load_hermes_dotenv from pathlib import Path load_hermes_dotenv(hermes_home=Path.home()/'.hermes', project_env=Path.cwd()/'.env') import os print('DEEPSEEK_API_KEY loaded:', bool(os.getenv('DEEPSEEK_API_KEY'))) " |
如果输出 True,说明凭证加载成功。
小结:凭证加载是自动的。Hermes Agent 根据 provider: deepseek 查找注册表,找到对应的环境变量名 DEEPSEEK_API_KEY,然后从环境中读取。你只需要确保环境变量已设置即可。
确定了 api_mode = "chat_completions" 后,Hermes Agent 需要选择一个 Transport 来处理 API 请求。
Transport 采用注册表模式,每个 api_mode 对应一个 Transport 类:
|
1 2 3 4 5 6 7 8 9 10 |
# agent/transports/__init__.py _REGISTRY = { "chat_completions": ChatCompletionsTransport, "anthropic_messages": AnthropicTransport, "codex_responses": CodexTransport, "bedrock_converse": BedrockTransport, } def get_transport(api_mode): cls = _REGISTRY.get(api_mode) return cls() # 返回一个 Transport 实例 |
每个 Transport 遵循相同的四步管道:
|
1 |
convert_messages → convert_tools → build_kwargs → normalize_response |
对于 ChatCompletionsTransport,消息和工具已经是 OpenAI 格式,所以转换步骤几乎是恒等变换:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# agent/transports/chat_completions.py class ChatCompletionsTransport(ProviderTransport): def convert_messages(self, messages, **kwargs): # 消息已经是 OpenAI 格式,只需清理 Codex 残留字段 return messages # 几乎就是原样返回 def convert_tools(self, tools): # 工具定义已经是 OpenAI 格式 return tools # 原样返回 def build_kwargs(self, model, messages, tools, **params): # 组装 API 调用参数 kwargs = { "model": model, # "deepseek-v4-flash" "messages": messages, # OpenAI 格式消息 "tools": tools, # 工具定义 "max_tokens": ..., "temperature": ..., "extra_body": ..., # 提供商特定参数 } return kwargs def normalize_response(self, response): # 将 DeepSeek 的响应标准化为统一格式 return NormalizedResponse( content=response.choices[0].message.content, tool_calls=tool_calls, finish_reason=response.choices[0].finish_reason, reasoning=getattr(response.choices[0].message, "reasoning_content", None), usage=Usage( prompt_tokens=response.usage.prompt_tokens, completion_tokens=response.usage.completion_tokens, ), ) |
build_kwargs 是 Transport 中最复杂的方法。对于 DeepSeek,它需要处理几个特殊逻辑:
max_tokens 默认值:DeepSeek 不强制要求 max_tokens,但 Hermes 会设置一个合理的默认值。
temperature:DeepSeek 支持 temperature 参数。Hermes 会根据配置传入。
extra_body:有些提供商需要在 extra_body 中传递额外参数。对于 DeepSeek,通常是空的。
小结:Transport 层封装了所有 API 差异。ChatCompletionsTransport 是最简单的 Transport,因为 DeepSeek 和 OpenAI 的接口几乎一致。如果换成 Anthropic 的 Transport,消息格式转换就复杂得多。
实际发请求的是 OpenAI SDK:
|
1 2 3 4 5 6 |
# run_agent.py(简化) from openai import OpenAI client = OpenAI( api_key=credential.access_token, # 你的 sk-xxx base_url="https://api.deepseek.com/v1", # 从 config.yaml 读的 ) |
一条消息从用户输入到 DeepSeek API 的完整路径:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
用户输入 "你好" → CLI 读取输入 → AIAgent.run_conversation("你好") → 构建 messages 列表 [{"role": "user", "content": "你好"}] → 调用 self._call_llm() → 获取 Transport: ChatCompletionsTransport → transport.build_kwargs(model, messages, tools) → 组装 kwargs → client.chat.completions.create(**kwargs) → HTTP POST https://api.deepseek.com/v1/chat/completions → 请求体: {"model": "deepseek-v4-flash", "messages": [...], ...} → DeepSeek API 返回响应 → transport.normalize_response(response) → 提取 content, tool_calls, usage → AIAgent 处理返回结果 → 输出到终端 |
Hermes Agent 有自动重试机制。当 API 返回错误时:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# 重试策略(简化) retry_on_status = {429, 502, 503, 529} # 限流、网关超时、服务不可用 for attempt in range(max_retries): # 默认最多重试 3 次 try: response = client.chat.completions.create(**kwargs) return response except APIError as e: if e.status_code in retry_on_status: wait = jittered_backoff(attempt) # 退避等待 time.sleep(wait) else: raise # 非重试错误直接抛出 |
DeepSeek V4 支持"思考模式"——在返回最终答案之前,模型会先输出推理过程。这些推理内容放在 reasoning_content 字段中。
|
1 2 3 4 5 6 7 8 9 |
{ "choices": [{ "message": { "content": "OK", "reasoning_content": "用户要求只回复 OK,所以直接回答 OK" }, "finish_reason": "stop" }] } |
在 run_agent.py 第 7826 行,有一个专门的检测方法:
|
1 2 3 4 5 6 7 8 9 |
def _needs_deepseek_tool_reasoning(self) -> bool: """检测当前是否使用 DeepSeek 的思考模式""" provider = (self.provider or "").lower() model = (self.model or "").lower() return ( provider == "deepseek" # provider 是 deepseek or "deepseek" in model # 模型名包含 deepseek or base_url_host_matches(self._base_url_lower, "api.deepseek.com") # 域名匹配 ) |
这个检测在三个地方被使用:
|
1 2 3 4 5 |
# 在消息持久化前,补充 reasoning_content(简化) if self._needs_deepseek_tool_reasoning(): if assistant_msg.get("tool_calls"): # DeepSeek 要求在 tool_calls 消息中也有 reasoning_content api_msg["reasoning_content"] = "" |
| 检测方式 | 示例 | 匹配 |
|---|---|---|
| provider == "deepseek" | config 中 provider 设为 deepseek | ? |
| "deepseek" in model | 模型名含 deepseek | ? |
| api.deepseek.com 域名 | base_url 指向 deepseek 域名 | ? |
三条路径只要有一条匹配,就会触发 reasoning_content 处理。这也是为什么我们配置 provider: deepseek 后,推理内容能正常显示。
小结:DeepSeek 的 reasoning_content 不是标准 OpenAI 格式的字段,但 Hermes Agent 通过专门的检测逻辑,把它正确地保留下来了。这对于 Agent 场景很重要——推理轨迹在多轮工具调用中是关键上下文。
把前面的所有环节串起来,一条消息从"用户输入"到"DeepSeek 响应"的完整链路:
┌─────────────────────────────────────────────────────────────────────┐
│ 用户终端 (CLI) │
│ 输入: "帮我查一下今天的天气" │
└──────────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ CLI 入口 (cli.py) │
│ • 读取 config.yaml → model: deepseek-v4-flash │
│ • 读取 config.yaml → provider: deepseek │
│ • 读取 config.yaml → base_url: https://api.deepseek.com/v1 │
│ • 读取 .env → DEEPSEEK_API_KEY │
│ • 创建 AIAgent(model, provider, base_url) │
└──────────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ AIAgent.__init__() (run_agent.py) │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ ① Provider 识别 │ │
│ │ provider="deepseek" → 不匹配任何特殊条件 │ │
│ │ → api_mode = "chat_completions" │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ ② 模型名归一化 │ │
│ │ normalize_model_for_provider("deepseek-v4-flash", "deepseek")│ │
│ │ → _normalize_for_deepseek("deepseek-v4-flash") │ │
│ │ → "deepseek-v4-flash" (原样返回) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ ③ 凭证加载 │ │
│ │ PROVIDER_REGISTRY["deepseek"] → env_var: "DEEPSEEK_API_KEY" │ │
│ │ os.getenv("DEEPSEEK_API_KEY") → "sk-xxx" │ │
│ │ 写入凭证池 → 后续 API 调用使用 │ │
│ └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ AIAgent.run_conversation() │
│ • 构建 system prompt (Hermes 的默认身份) │
│ • 构建 messages: [{role: "user", content: "帮我查一下今天的天气"}] │
│ • 调用 _call_llm() │
└──────────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Transport 层 (chat_completions) │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ ChatCompletionsTransport.build_kwargs() │ │
│ │ → convert_messages(): 已经是 OpenAI 格式,几乎不变 │ │
│ │ → convert_tools(): 已经是 OpenAI 格式,几乎不变 │ │
│ │ → 组装 kwargs: model, messages, tools, max_tokens, ... │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ OpenAI SDK client.chat.completions.create(**kwargs) │ │
│ │ → HTTP POST https://api.deepseek.com/v1/chat/completions │ │
│ └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ DeepSeek API Server │
│ • 接收请求 → 模型推理 → 生成响应 │
│ • 返回: {"choices": [{"message": {"content": "...", │
│ "reasoning_content": "..."}}], "usage": {...}} │
└──────────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ AIAgent 处理响应 │
│ • ChatCompletionsTransport.normalize_response() │
│ → 提取 content, tool_calls, reasoning_content │
│ → 标准化为 NormalizedResponse │
│ • 如果有 tool_calls → 执行工具 → 继续循环 │
│ • 如果只有文本 → 返回给用户 │
│ • DeepSeek 特殊处理: │
│ → _needs_deepseek_tool_reasoning() → True │
│ → 保留 reasoning_content 到消息历史 │
│ → 工具调用消息补充 reasoning_content │
└──────────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 用户终端 (CLI) │
│ 输出: "今天天气晴朗,气温 22-28°C,适合户外活动。" │
└─────────────────────────────────────────────────────────────────────┘
这个流程中的关键检查点:
| 检查点 | 做什么 | 如果错了 |
|---|---|---|
| Provider 识别 | 确定 api_mode | API 格式不匹配,返回 400 |
| 模型名归一化 | 确保模型名 API 能识别 | 路由到错误的模型版本 |
| 凭证加载 | 获取 API Key | 401 Unauthorized |
| Transport 构建 | 组装请求参数 | 请求格式错误 |
Q1:启动时提示 “Provider ‘deepseek’ not found”
原因:config.yaml 中 provider 拼写错误。
解决:检查 provider 是否为 deepseek(全小写)。
Q2:API 返回 401 Unauthorized
原因:DEEPSEEK_API_KEY 未设置或设置错误。
排查步骤:
|
1 2 3 4 5 6 7 8 9 |
# 1. 检查环境变量是否已加载 echo $DEEPSEEK_API_KEY
# 2. 如果为空,检查 .env 文件 grep DEEPSEEK_API_KEY .env
# 3. 确保 .env 文件格式正确(不要有多余空格或引号) # 正确: DEEPSEEK_API_KEY=sk-xxx # 错误: DEEPSEEK_API_KEY = "sk-xxx" |
Q3:API 返回 400 Bad Request
原因:请求格式错误。可能是 api_mode 选错了。
排查:检查运行日志中的 api_mode 是否为 chat_completions。如果显示 anthropic_messages,说明 base_url 可能以 /anthropic 结尾,导致模式误判。
Q4:响应很慢或超时
原因:可能是网络问题,或者模型在处理复杂推理。
解决:
Q5:模型明明配置了 V4,但感觉能力像 V3
原因:如果通过 OpenRouter 路由,deepseek-chat 会映射到 V3。如果直接调用 DeepSeek API,deepseek-chat 也可能指向旧版本。
解决:始终使用 V 系列 ID:deepseek-v4-flash 或 deepseek-v4-pro,不要用 deepseek-chat。
Q6:工具调用(Tool Calls)失败
原因:DeepSeek 的工具调用格式可能与标准 OpenAI 有细微差异。
解决:检查是否启用了 Hermes 的 DeepSeek 特殊处理(_needs_deepseek_tool_reasoning)。如果没有,可能需要确保 provider 正确设置为 deepseek。
操作回顾
接入 DeepSeek V4 只需要两步配置 + 一个环境变量:
| 改动项 | 位置 | 内容 |
|---|---|---|
| 添加 API Key | .env | DEEPSEEK_API_KEY=sk-xxx |
| 修改模型配置 | hermes_workspace/config.yaml | provider: deepseek |
| default: deepseek-v4-flash | ||
| base_url: https://api.deepseek.com/v1 |
源码层面的三个核心检测点
| 检测点 | 位置 | 作用 |
|---|---|---|
| Provider 识别 | run_agent.py:977-1008 | 确定 api_mode → 决定 Transport |
| 模型名归一化 | model_normalize.py:147-179 | 确保模型名被 API 识别 |
| 凭证加载 | credential_pool.py:1262-1338 | 从环境变量读取 API Key |
一句话总结
Hermes Agent 接入 DeepSeek V4,本质就是在三个核心检测点(Provider → Model → Credential)上提供了正确的信息,让框架的自动路由机制找到正确的 API 路径。
这不仅是 DeepSeek 的接入方式,也是 Hermes Agent 接入任何新模型的标准模式——理解了这个流程,你就能轻松接入任何 OpenAI 兼容的模型。