大家好,我是张大鹏,10 年全栈开发经验,目前在做 AI 在线教育培训。上一篇文章介绍了 DeepSeek V4 的模型能力,这篇我们实操——把 Hermes Agent 接到 DeepSeek V4 上,并且深入到源码层面,让小白也能理解每一步"为什么这么配"。


写在前面

这篇文章分为上下两篇:

  • 操作篇(第 1-4 章):跟着步骤做就行,不需要懂代码
  • 原理篇(第 5-12 章):深入源码,理解背后机制

小白可以先按操作篇跑起来,有兴趣再读原理篇。


操作篇

一、背景:为什么要在 Hermes Agent 中用 DeepSeek V4

1.1 Hermes Agent 是模型无关的

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.2 我们要做什么?

说白了就三步:

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 文件,添加一行:

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: 配置段,修改为:

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 能正常响应:

curl -s https://api.deepseek.com/v1/models \
  -H "Authorization: Bearer $DEEPSEEK_API_KEY" \
  -H "Content-Type: application/json"

正常返回:

{
  "object": "list",
  "data": [
    {"id": "deepseek-v4-flash", "object": "model", "owned_by": "deepseek"},
    {"id": "deepseek-v4-pro", "object": "model", "owned_by": "deepseek"}
  ]
}

能看到两个模型 ID,说明 API Key 有效,网络连通。

再测试一下对话:

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 测试

激活虚拟环境,启动交互式会话:

# Windows
.\venv\Scripts\activate

# Linux/Mac
source venv/bin/activate

# 启动 Hermes
python hermes

输入任意问题测试。如果你看到了正常回复,说明配置成功!

预期结果:

Provider: deepseek
API mode: chat_completions
Model: deepseek-v4-flash

你: 你好
Hermes: 你好!有什么我可以帮你的吗?

四、进阶配置

4.1 切换到 Pro 版本

如果任务需要更强的推理能力(复杂数学、长文档分析),改一行即可:

model:
  default: deepseek-v4-pro       # 从 flash 改为 pro
  provider: deepseek
  base_url: https://api.deepseek.com/v1

Pro 的激活参数是 Flash 的 3.8 倍(49B vs 13B),推理深度更强,但价格也贵 12 倍。

4.2 通过 OpenRouter 中转

如果你已经在用 OpenRouter,可以不切换 provider,直接改模型名:

model:
  default: deepseek/deepseek-v4-flash    # 注意 OpenRouter 要用 vendor/model 格式
  provider: openrouter                    # 保持 openrouter 不变
  base_url: https://openrouter.ai/api/v1

OpenRouter 上的路由会自动把请求转发到 DeepSeek。

4.3 使用 Anthropic 兼容接口

DeepSeek 也提供了 Anthropic 兼容的 API 端点,如果你习惯了 Anthropic 的接口格式,可以用这个:

model:
  default: deepseek-v4-flash
  provider: deepseek
  base_url: https://api.deepseek.com/anthropic    # 注意这个 URL

base_url/anthropic 结尾时,Hermes Agent 会自动检测并切换到 anthropic_messages 模式。

4.4 双模型策略:默认 Flash,复杂任务升 Pro

这是最经济的用法——日常对话用 Flash(极低成本),遇到复杂推理时手动切换到 Pro。

在 Hermes 会话中通过 /model 命令切换:

你: /model deepseek-v4-pro
Hermes: 模型已切换为 deepseek-v4-pro
你: 帮我分析这篇论文的数学推导...
Hermes: [使用 Pro 进行深度推理]

用完后切回 Flash:

你: /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

五、配置加载流程:从 config.yaml 到 AIAgent

5.1 CLI 启动时发生了什么

当你运行 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__()

简化后的伪代码:

# 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"),
)

5.2 AIAgent 初始化

AIAgent.__init__() 收到这三个参数后,会做一系列关键判断。我们重点关注几个核心属性的赋值:

# 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"

六、Provider 识别:如何确定走哪个 API

这是最关键的一步。Hermes Agent 根据 providerbase_url 来决定使用哪种 API 模式。

6.1 api_mode 判断链

api_mode 决定了使用哪个 Transport 适配器。判断逻辑在 run_agent.py 第 977-1008 行:

# 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 走的是哪条分支?

我们的配置是:

  • provider = "deepseek"
  • base_url = "https://api.deepseek.com/v1"

对照判断链:

  1. ❌ api_mode 未明确指定
  2. ❌ provider != “openai-codex”
  3. ❌ provider != “xai”
  4. ❌ provider != “anthropic”
  5. ❌ base_url 不以 /anthropic 结尾
  6. ❌ provider != “bedrock”
  7. 所有条件都不满足 → 走 else → chat_completions

6.2 为什么这很重要?

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 能直接用

你可能注意到,我们的配置写的是 deepseek-v4-flash,但早期 Hermes Agent 版本只认识 deepseek-chat。为什么现在可以直接用 V4 模型名?

7.1 历史包袱

早期的 Hermes Agent 把所有非推理的 DeepSeek 输入都折叠为 deepseek-chat,这个模型在 DeepSeek 自己的 API 上对应 V3。也就是说:

用户写 deepseek-v4-flash  → 被折叠为 deepseek-chat → 实际调用的是 V3

这对用户来说是个隐蔽的坑——配置了 V4,结果用的还是 V3。

7.2 归一化规则

这个问题在最近的版本中已经修复。现在的归一化逻辑在 hermes_cli/model_normalize.py 第 147-179 行:

_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"

为什么我们的配置能正确传递?来看匹配过程:

  1. bare = "deepseek-v4-flash"(去掉可能的前缀)
  2. 检查是否在 _DEEPSEEK_CANONICAL_MODELS → 在!因为 deepseek-v4-flash 是规范名之一
  3. 匹配 V 系列正则 → 也匹配,但规则 1 先命中
  4. 返回 "deepseek-v4-flash" 原样

同样,deepseek-v4-pro 也是规范名,也会原样传递。

7.3 调用链

# 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)才会被映射到规范名。


八、凭证加载:DEEPSEEK_API_KEY 是如何被找到的

改了配置,加了环境变量,但 Hermes Agent 是怎么知道要用 DEEPSEEK_API_KEY 的?

8.1 提供商注册表

hermes_cli/auth.py 中,有一个提供商注册表 PROVIDER_REGISTRY,记录了每个提供商的信息:

# 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 环境变量读取

8.2 凭证池加载

当 Hermes Agent 需要调用 API 时,会触发凭证加载:

# 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,
        })

8.3 读取优先级

Hermes Agent 读取凭证的顺序:

1. 环境变量(DEEPSEEK_API_KEY)
2. ~/.hermes/.env 文件
3. 项目根目录 .env 文件
4. hermes auth 命令手动添加的凭证

如果多个来源都有值,优先级高的覆盖优先级低的。

8.4 验证凭证是否加载成功

运行以下命令查看凭证池状态:

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,然后从环境中读取。你只需要确保环境变量已设置即可。


九、Transport 选择:chat_completions 模式详解

确定了 api_mode = "chat_completions" 后,Hermes Agent 需要选择一个 Transport 来处理 API 请求。

9.1 Transport 注册表

Transport 采用注册表模式,每个 api_mode 对应一个 Transport 类:

# 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 实例

9.2 Transport 管道

每个 Transport 遵循相同的四步管道:

convert_messages → convert_tools → build_kwargs → normalize_response

对于 ChatCompletionsTransport,消息和工具已经是 OpenAI 格式,所以转换步骤几乎是恒等变换:

# 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,
            ),
        )

9.3 build_kwargs 的 Provider 特定配置

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,消息格式转换就复杂得多。


十、API 调用构建:请求是怎么组装并发出的

10.1 客户端创建

实际发请求的是 OpenAI SDK:

# 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 读的
)

10.2 完整请求链

一条消息从用户输入到 DeepSeek API 的完整路径:

用户输入 "你好"
    → 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 处理返回结果
    → 输出到终端

10.3 重试与错误处理

Hermes Agent 有自动重试机制。当 API 返回错误时:

# 重试策略(简化)
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 的特殊处理

11.1 reasoning_content

DeepSeek V4 支持"思考模式"——在返回最终答案之前,模型会先输出推理过程。这些推理内容放在 reasoning_content 字段中。

{
  "choices": [{
    "message": {
      "content": "OK",
      "reasoning_content": "用户要求只回复 OK,所以直接回答 OK"
    },
    "finish_reason": "stop"
  }]
}

11.2 Hermes 如何保留推理内容

run_agent.py 第 7826 行,有一个专门的检测方法:

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. 响应标准化时:从 reasoning_content 字段提取推理过程,存入消息的 reasoning 字段
  2. 工具调用消息回显时:DeepSeek 的 API 要求在工具调用消息中也要包含 reasoning_content 字段,否则会报 400 错误
  3. 持久化时:推理内容会被保存到对话历史中,保证跨会话的连续性
# 在消息持久化前,补充 reasoning_content(简化)
if self._needs_deepseek_tool_reasoning():
    if assistant_msg.get("tool_calls"):
        # DeepSeek 要求在 tool_calls 消息中也有 reasoning_content
        api_msg["reasoning_content"] = ""

11.3 三条检测路径

检测方式 示例 匹配
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.yamlprovider 拼写错误。

解决:检查 provider 是否为 deepseek(全小写)。

Q2:API 返回 401 Unauthorized

原因DEEPSEEK_API_KEY 未设置或设置错误。

排查步骤

# 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:响应很慢或超时

原因:可能是网络问题,或者模型在处理复杂推理。

解决

  • 检查网络连通性:curl -I https://api.deepseek.com
  • 如果不需要推理,可以在请求中关闭思维链
  • 切换到 Flash 版本,响应速度更快

Q5:模型明明配置了 V4,但感觉能力像 V3

原因:如果通过 OpenRouter 路由,deepseek-chat 会映射到 V3。如果直接调用 DeepSeek API,deepseek-chat 也可能指向旧版本。

解决:始终使用 V 系列 ID:deepseek-v4-flashdeepseek-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 兼容的模型。


参考


作者:大鹏 AI 教育团队
日期:2026-04-28
声明:本文基于 HermesAgent main 分支(commit e8ecacda)的源码分析,项目仍在活跃开发中,部分细节可能随版本更新而变化。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐