配置模型与提供方

Tether 的 LLM 抽象建立在 Tether.Llm 服务定义之上,支持接入真实模型 API(如 DeepSeek、OpenAI 兼容端点)以及本地脚本化重放模型(ReplayLlm)。

支持的接入方式

Tether 目前支持以下几类模型提供方:

  1. DeepSeek 官方 API:原生支持 DeepSeek Chat 与 DeepSeek Reasoner(带思考流提取)。
  2. OpenAI 兼容网关:接入任何提供标准 /v1/chat/completions SSE 流式接口的第三方网关或自建服务。
  3. ReplayLlm(无密钥重放):使用预录的 JSON 文件模拟模型响应,用于单元测试、CI 管道与无网络环境下的功能验收。

快速配置 API 密钥

在运行真实模型前,最直接的方式是设置环境变量:

# Linux / macOS
export DEEPSEEK_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxx"
# 可选:自定义 API 基础路径(默认为 https://api.deepseek.com/anthropic)
export DEEPSEEK_BASE_URL="https://api.deepseek.com/anthropic"

# Windows PowerShell
$env:DEEPSEEK_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxx"

Tether 启动时会自动读取当前工作目录或根目录下的 .env 文件:

# .env
DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
DEEPSEEK_BASE_URL=https://api.deepseek.com/anthropic

在 YAML 组合中配置模型

Tether 的插件树完全由 YAML 组合驱动。发行组合里模型提供方集中在 llm-multi 行 (src/Tether.Bundles/bundles/base.yaml 的 llm-pi-ai 行):它的 providers 是一张 route → profile 表,每个 profile 声明 kind、baseURL、凭据键(apiKeyEnv 或 credentialId)等;defaultRoute 指定缺省路由。Web UI 的「模型提供商」设置页写的就是 同一张表——保存进当前 profile 的 cordis.patch.yml,或直接给宿主加 --patch:

# ~/.tether/profiles/<profile>/cordis.patch.yml 或任意 --patch 文件
patches:
  - id: llm-pi-ai
    name: llm-multi
    config:
      filesIndexPath: ${TETHER_HOME:-.tether}/llm-deepseek/files-v1.json
      defaultRoute: ${TETHER_DEFAULT_ROUTE:-deepseek}
      providers:
        deepseek:
          kind: deepseek
          apiKeyEnv: DEEPSEEK_API_KEY

注意:补丁的 config 是整体替换,不是逐键深合并(见组合加载与补丁); 上面的 filesIndexPath/defaultRoute 保留了发行组合的写法,patch 文件里同样支持 ${ENV:-default} 环境变量展开。未写出的键回到 LlmMultiConfig 的类默认值,不会继承原行。

kind: deepseek 走 Anthropic Messages 协议的官方端点(缺省 https://api.deepseek.com/anthropic,可用 baseURL 覆盖),目录里公布 DeepSeek-V4 系列 模型;缺省 route 与缺省模型也可分别用环境变量 TETHER_DEFAULT_ROUTE / TETHER_DEFAULT_MODEL 覆盖。

开启深度思考

深度思考不再是另一个模型,而是同一模型上的推理强度:内建目录里的 DeepSeek-V4 系列都公布 off、low、high、max 四档,由发起请求的一侧选择。

档位随每次请求给出——Web UI 的推理菜单、CLI 或直接调用方选哪一档就发哪一档,不在插件配置里固定。 未选择档位时请求不带 thinking 与 output_config.effort,交由服务端决定;目录里公布的默认档位只用于 预选界面里的选项,不会被悄悄写进请求。off 序列化为 thinking: { type: disabled } 且此时不发送 output_config,以兼容拒绝未知取值的网关;模型未公布的档位在联网前就失败。

Tether 会自动解析流式响应中的 thinking 块:流式期间逐段写成 type 为 reasoning-delta 的 assistant/chunk,流结束后作为 reasoning 内容(TextReasoningContent)并入同一条 assistant/message,与正文文本分开存放。


接入 OpenAI 兼容自定义网关

对于公司内部中转网关、Ollama、vLLM 或 LocalAI,在同一张 providers 表里新增一个 kind: openai-compatible 的 route。config 是整体替换,所以多个 route 要写在同一个 patch 的同一张表里:

patches:
  - id: llm-pi-ai
    name: llm-multi
    config:
      filesIndexPath: ${TETHER_HOME:-.tether}/llm-deepseek/files-v1.json
      defaultRoute: ${TETHER_DEFAULT_ROUTE:-deepseek}
      providers:
        deepseek:
          kind: deepseek
          apiKeyEnv: DEEPSEEK_API_KEY
        gateway:
          kind: openai-compatible
          baseURL: https://gateway.internal.example.com/v1
          apiKeyEnv: INTERNAL_GATEWAY_KEY

openai-compatible 是 catalog-free 的 kind:models 键可选,请求时给出的任意模型名都会透传 到端点(未声明 models 时不会在本地按目录拒绝)。要让 Agent 默认走这个网关,把 defaultRoute 改成 gateway(或启动时设 TETHER_DEFAULT_ROUTE=gateway),并把模型名告诉 发行组合的 default-model 行——它读 ${TETHER_DEFAULT_ROUTE:-deepseek} 与 ${TETHER_DEFAULT_MODEL:-deepseek-flash},所以 TETHER_DEFAULT_ROUTE=gateway TETHER_DEFAULT_MODEL=<模型名> 两个环境变量即可完成整条默认链路的切换。

api 字段与可注册协议

每个 profile 可显式声明 api(协议名)覆盖 kind 推导出的默认协议: deepseek/anthropic 默认 anthropic-messages,openai/openai-compatible 默认 openai-completions。api 可取任意已注册协议名——协议由 ILlmWireRegistry 服务承载:内置 openai-completions 与 anthropic-messages 两个协议,树外插件可 注册自己的 ILlmWire(如 openai-responses)。路由声明了未安装协议的,目录面 (LlmProviderInfo/LlmRouteInfo)会以 protocolAvailable: false 与 protocolUnavailableReason 标注,调用时以 UNSUPPORTED 稳定失败;安装对应 wire 后即恢复可用。

标准 OpenAI 走官方 adapter(#470)

kind: openai 且未设置 baseUrl、自定义 headers 或 compat 的 profile 由官方 Microsoft.Extensions.AI.OpenAI adapter 服务(默认路径,准入谓词 MeaiOpenAiBridge.CanServe)。一旦写了显式 baseUrl、自定义请求头或 compat 开关,该 route 自动回落到通用 ChatCompletionsWire ——兼容网关、自托管中转与带 compat 覆盖的 profile 都不受影响,无需改配置。adapter 路径上 SDK 自带重试已关闭,一次调用恰好一次网络尝试,可见重试仍由 ProviderRetryPlugin 承担。

凭据隔离原则

永远不要把明文 API Key 硬编码在 YAML 文件中。配置中只写 apiKeyEnv(环境变量名)或凭据引用键,Tether 在启动加载时由 Tether.Credentials 插件安全解析。


使用 ReplayLlm 进行免密钥开发

在没有 API Key 或编写自动化测试时,可以使用 ReplayLlm。它通过读取预设的响应脚本返回内容或工具调用指令,完全不消耗网络与 Token:

dotnet run --project apps/Tether.Cli -- \
  --replies apps/Tether.Cli/replies/text.json \
  --send "总结当前代码库"

预录响应文件是一个 JSON 数组,每项对应一次模型响应(按顺序消费):

[
  {
    "kind": "text",
    "text": "这是一个由 ReplayLlm 模拟返回的代码库总结文本。"
  }
]

每项的 kind 缺省为 text,也可以是 tool(模拟工具调用)或 fail(模拟请求失败)。 tool 项使用 callId/name/arguments 字段(arguments 可为 JSON 对象或原始字符串,name 必须是当前组合里真实注册的工具名);fail 项必须带 code 与 message;text 项可用 chunks 指定分段流式输出、delayMs 控制间隔、reasoningChunks 模拟思考流。

如果需要模拟模型调用工具并处理返回值的多轮交互,可以配置 tool 与 text 依次排列的脚本 (CLI 组合自带 echo 工具,参数 text):

[
  {
    "kind": "tool",
    "callId": "call_01",
    "name": "echo",
    "arguments": {"text": "ping"}
  },
  {
    "kind": "text",
    "text": "已读取 echo 工具的返回结果。"
  }
]

同一文件里混排 provider+model 与不带路由的项,可以把脚本按 route 分流到不同模型身份; usage 字段(inputTokens/outputTokens/totalTokens/cacheReadTokens)用于在回放里携带 token 计量。


配置后自动显示用量

启用 Usage 可选功能包后的 Web 组合会把已配置密钥的模型路由自动派生成 Usage 面板里的账号,密钥仍只存在 Tether.Credentials 一份,不会复制到 usage 记录:

路由 baseUrl自动关联到
api.deepseek.comDeepSeek 余额
api.kimi.com/coding…Kimi Code 额度
open.bigmodel.cn/api/coding… 或 /api/anthropic…BigModel Coding Plan
api.z.ai/api/coding… 或 /api/anthropic…z.ai Coding Plan
opencode.ai/zen/go…OpenCode Go

自建网关(如 sub2api)没有固定域名:在 Usage 面板”未识别的路由”里一键标记为对应平台即可。派生账号的别名就是路由的 displayName,改名去 设置 → 模型提供商(点击该提供商行,修改「显示名称」,内置提供商同样可改);它只能暂停,不能删除;密钥缺失时卡片显示”凭据失效”并提示在模型设置填写。Claude、Codex 等使用 OAuth token 的平台仍需在面板里手动添加账号。


常见排错

  • MISSING_API_KEY 异常:检查 DEEPSEEK_API_KEY 环境变量是否已导出,或检查 .env 文件是否位于当前执行目录下。
  • 401 Unauthorized:密钥失效或被提供方撤销,请核对密钥字符串。
  • 404 Not Found:自定义网关的 baseUrl 路径有误。通常 OpenAI 兼容接口需要以 /v1 结尾。
  • 思考内容未展示:确认所选模型公布了推理档位且本次请求选的不是 off,并确认会话日志里出现了 type 为 reasoning-delta 的 assistant/chunk(没有说明服务端未返回思考内容)。

下一步

在 GitHub 上编辑此页