Hermes Agent接入DeepSeek V4的完整指南

Written by

in

文章目录
  • 开始之前,确认以下条件: 项目 要求 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 .venvScriptsactivate # Linux/Mac source venv/bin/activate # 启动 Hermes python hermes 输入任意问题测试。如果你看到了正常回复,说明配置成功! 预期结果: Provider: deepseek API mode: chat_completions Model: deepseek-v4-flash 你: 你好 Hermes: 你好!有什么我可以帮你的吗?
  • 这是最关键的一步。Hermes Agent 根据 provider 和 base_url 来决定使用哪种 API 模式。
  • 你可能注意到,我们的配置写的是 deepseek-v4-flash,但早期 Hermes Agent 版本只认识 deepseek-chat。为什么现在可以直接用 V4 模型名?
  • 改了配置,加了环境变量,但 Hermes Agent 是怎么知道要用 DEEPSEEK_API_KEY 的?
  • 确定了 api_mode = "chat_completions" 后,Hermes Agent 需要选择一个 Transport 来处理 API 请求。
  • 把前面的所有环节串起来,一条消息从"用户输入"到"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. 检查环境变量是否已加载 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-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 兼容的模型。 以上就是Hermes Agent接入DeepSeek V4的完整指南的详细内容,更多关于Hermes Agent接入DeepSeek V4的资料请关注风君子博客其它相关文章!
  • 目录
    • 一、背景:为什么要在 Hermes Agent 中用 DeepSeek V4
      • 1.1 Hermes Agent 是模型无关的
      • 1.2 我们要做什么
    • 二、准备工作
      • 三、具体配置步骤
        • 四、进阶配置
          • 4.1 切换到 Pro 版本
          • 4.2 通过 OpenRouter 中转
          • 4.3 使用 Anthropic 兼容接口
          • 4.4 双模型策略:默认 Flash,复杂任务升 Pro
        • 五、配置加载流程:从 config.yaml 到 AIAgent
          • 5.1 CLI 启动时发生了什么
          • 5.2 AIAgent 初始化
        • 六、Provider 识别:如何确定走哪个 API
          • 6.1 api_mode 判断链
          • 6.2 为什么这很重要?
        • 七、模型名归一化:为什么 deepseek-v4-flash 能直接用
          • 7.1 历史包袱
          • 7.2 归一化规则
          • 7.3 调用链
        • 八、凭证加载:DEEPSEEK_API_KEY 是如何被找到的
          • 8.1 提供商注册表
          • 8.2 凭证池加载
          • 8.3 读取优先级
          • 8.4 验证凭证是否加载成功
        • 九、Transport 选择:chat_completions 模式详解
          • 9.1 Transport 注册表
          • 9.2 Transport 管道
          • 9.3 build_kwargs 的 Provider 特定配置
        • 十、API 调用构建:请求是怎么组装并发出的
          • 10.1 客户端创建
          • 10.2 完整请求链
          • 10.3 重试与错误处理
        • 十一、响应处理:DeepSeek 的特殊处理
          • 11.1 reasoning_content
          • 11.2 Hermes 如何保留推理内容
          • 11.3 三条检测路径
        • 十二、图解:一条消息的完整旅程
          • 十三、常见问题排查
            • 十四、总结

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

              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
              .venvScriptsactivate
              
              # Linux/Mac
              source venv/bin/activate
              
              # 启动 Hermes
              python hermes
              

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

              预期结果:

              Provider: deepseek
              API mode: chat_completions
              Model: deepseek-v4-flash
              
              你: 你好
              Hermes: 你好!有什么我可以帮你的吗?
              

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

              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,直接改模型名:

              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 的接口格式,可以用这个:

              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 命令切换:

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

              当你运行 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"),
              )

              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"

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

              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

              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。也就是说:

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

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

              这个问题在最近的版本中已经修复。现在的归一化逻辑在 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-vd+([-.].+)?$")
              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 也是规范名,也会原样传递。

              # 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,记录了每个提供商的信息:

              # 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 时,会触发凭证加载:

              # 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 命令手动添加的凭证

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

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

              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 类:

              # 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 遵循相同的四步管道:

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

              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:

              # 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 的完整路径:

              用户输入 "你好"
                  → 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 返回错误时:

              # 重试策略(简化)
              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 字段中。

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

              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"] = ""

              检测方式 示例 匹配
              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 兼容的模型。

              以上就是Hermes Agent接入DeepSeek V4的完整指南的详细内容,更多关于Hermes Agent接入DeepSeek V4的资料请关注风君子博客其它相关文章!

              站内搜索