跳转到正文
ThinkWatch
开始输入以搜索文档。
ThinkWatch Core · 配置手册

配置手册#

ThinkWatch Core 只读一个文件:config.yaml。本文逐项说明其中每个字段的作用、默认值、可选值,以及改动如何进入正在运行的进程。在服务器上运行 core、由桌面应用远程管理,见在服务器上运行 core。

本文的字段表由代码生成,与代码不一致时测试失败。表中列出的字段,就是程序实际读取的字段。

文件位置#

平台默认位置
macOS、Linux~/.thinkwatch/config.yaml
Windows%APPDATA%\ThinkWatch\config.yaml

THINKWATCH_HOME 替换整个目录;--config <路径> 为单条命令指定文件。core 的其余数据也在这个目录里:请求数据库(data.db)、配置历史(history/)、下载的价目表(model_prices.json),以及本地控制通道的 socket 文件(twcore.sock;Windows 上是回环端口,记录在 control.port 中)。目录只有所有者可访问(0700),配置文件权限为 0600:其中以明文保存密钥。

没有配置文件时,twcore serve 会写入一份初始配置;twcore init 也可以按需生成。两者生成的内容如下:

version: 1
listen:
  control:
    key: 6629…753d       # 自动生成
clients:
  - name: default
    key: tw-…            # 自动生成

这已是一份完整、合法的配置。其中还没有上游,因此控制面照常运行,请求会得到「尚未配置上游」的错误。加上一个上游即可转发:

providers:
  - name: anthropic
    base_url: https://api.anthropic.com
    key: ${ANTHROPIC_API_KEY}

读取规则#

  • **本文没有的字段名一律是错误。**把 port 写成 prot 时,不会被悄悄忽略、让网关在默认端口上起来;整份配置被拒绝,错误信息指出那个字段。
  • **默认值不写进文件。**没写的字段取下表中的默认值。应用和命令行只在取值不同于默认值时才写入字段,因此文件里出现的都是有人做出的选择。
  • ${VAR} 读取环境变量,适用于标注「可写 ${VAR}」的字段:上游密钥、请求头的值、代理密码。取值来自 core 进程的环境,在发送请求时读取;变量未设置时,该上游的请求失败,错误信息指出变量名。在 systemd 下,这个环境就是 unit 的 EnvironmentFile。
  • **名字即引用。**规则、策略组、密钥按名字引用上游、策略组、路由和价目表。指向不存在的名字,在加载时就报错,而不是成为一条永不命中的规则。在应用里改名时,所有引用在同一次写入中一起修改。以 __ 开头的名字保留给内置项。
  • version 是格式版本,目前为 1。版本更高的文件出自更新的 twcore,整份拒绝。

改动如何生效#

修改配置有三种途径:桌面应用、twcore config … 命令、用编辑器直接修改文件。三者走同一条路径。core 监视配置文件,保存后一秒内重新加载,无需重启。

新版本要通过以下每一关才会换入:

  1. 能按 YAML 解析。
  2. 符合结构:字段名已知,类型正确。
  3. 自洽:名字不重复、引用都能找到、正则能编译、CIDR 写法正确。
  4. 能据此建立运行时对象。

任何一关失败,原有配置继续服务,错误信息说明失败在哪一关、哪个位置。写错一个字不会让网关停下。在保存出合法版本之前,桌面应用会一直显示这次拒绝。

listen.gateway 的改动同样即时生效:core 打开新的监听;打不开时(例如端口被占用)保留原监听并报告原因。保留期限的改动在下一次每小时的清理时生效。

两方同时修改时(应用和手工编辑),后写入的一方因版本不一致被拒绝,不会覆盖先写入的内容。

历史与回滚#

每个生效过的版本都保存在配置文件旁边的 history/ 目录中,并记录来源(应用、命令行、外部编辑、回滚、凭据轮换)。保留最近 50 个版本。

twcore check                      # 只校验配置,不启动任何服务
twcore config show                # 打印当前配置及其版本
twcore config history             # 列出历史版本,最新的在前
twcore config rollback 3f9a2c     # 回滚到某个版本(写前几位即可)
twcore config set /listen/gateway/port 8790 --int

这些命令直接操作文件,因此 core 没有运行时也能使用,而那往往正是最需要回滚的时候。正在运行的 core 会像对待其他保存一样接收这些改动。

twcore config set <路径> <值> 修改文件中已经写出的一个值。路径中的列表项按其 name 定位(/providers/anthropic/base_url);数字表示下标(/routes/0/rules/1/to)。值默认按字符串写入,--int、--bool、--null 另作指定。写入前先校验结果。要添加文件中还没有的字段,请直接编辑文件。

网关写回的凭据#

有一种写入不是由人发起的。上游的 OAuth token 端点换发新的 refresh token 后,旧的随即作废,因此网关会把新 token(以及 access token 和过期时间)写回 providers[].oauth。只改这几个值,注释和排版保持原样。

字段参考#

每张表列出一节的全部字段。「默认值」一栏为「—」表示不写就没有这个字段,其含义见说明。

顶层#

字段类型默认值说明
version整数必填文件格式的版本,目前只有 1。更大的数字说明文件出自更新的 twcore,整份拒绝,不按一知半解的方式读。
listen对象,见 listen—网关和控制通道在哪里监听。
clients对象列表,见 clients[][]网关密钥。至少要有一把;twcore init 和首次 twcore serve 会写入一把名为 default 的。
providers对象列表,见 providers[][]上游。一个都没有也是合法配置:控制面照常运行,请求得到「尚未配置上游」的错误。
proxies对象列表,见 proxies[][]出站代理。在这里声明一次,由 providers[].proxy 按名字引用。
pricing对象,见 pricing—默认价目表是否定期刷新,以及自定义价目表。
client_probes对象,见 client_probes—客户端自行发出的辅助请求(连通性检查、预热、起标题)如何处理。
security对象,见 security—五项防护。出厂时都处在 observe 或 off,不改变、不拦截任何请求。
retention对象,见 retention—请求日志保留多久。
failover对象,见 failover—上游失败后停用多久,以及流式回答的开头最多等多久。
groups对象列表,见 groups[][]策略组:多个上游合用一个名字,并规定如何在其中选择。
routes对象列表,见 routes[][]路由。一条都不写时,请求按上游的声明顺序故障转移。
default_route字符串—未指定路由的密钥走哪条路由。不写:名为 default 的路由;没有这条路由时走内置的故障转移。
default_key字符串—没有专用密钥的客户端使用哪一把。不写:名为 default 的那把,没有则取第一把。这把密钥不能停用。

listen#

core 在哪里接受连接。连接分两种:客户端发送请求的 AI 网关,以及桌面应用和 twcore 命令使用的控制通道。

字段类型默认值说明
gateway对象,见 listen.gateway—AI 网关,即客户端发送请求的地址。
control对象,见 listen.control—控制通道,即桌面应用和 twcore 命令连接 core 的途径。其中有控制密钥,因此每份配置都有这一节。

listen.gateway#

字段类型默认值说明
bindloopback | all | 网卡名 | IP 地址loopbackloopback 只有本机;all 所有网卡;网卡名(en0、eth0)每隔几秒重新解析,地址变了也能跟上,网卡暂时不在时先只监听 127.0.0.1,出现后再补上;写死的 IP 地址在地址变化后失效。绑定单张网卡时同时监听 127.0.0.1。
port整数8788网关的 TCP 端口。
allow_from字符串列表[10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7]本机以外允许连接的来源,写 CIDR 网段或单个地址。本机始终放行。[] 表示只有本机;放行所有来源要明确写 0.0.0.0/0。

bind 超出本机范围时,由 allow_from 决定允许谁连接。网关不提供 TLS:只在可信的网络中开放,或放在隧道、VPN 之后。

listen:
  gateway:
    bind: all
    port: 8788
    allow_from: [192.168.1.0/24]

listen.control#

控制通道供桌面应用,以及同一台机器上的 twcore config、twcore control-key 与 core 通信。本地通道是数据目录中的 socket 文件(Windows 上是回环端口);除非启用 remote,控制通道不开任何网络端口。

每条控制连接,无论本地还是远程,都以一次握手开始,证明双方都持有 key(Noise_NNpsk0_25519_ChaChaPoly_BLAKE2s:密钥作为预共享密钥,每条连接协商新的会话密钥,通信内容加密)。不使用证书。不持有密钥的一方无法完成第一条握手消息。

字段类型默认值说明
key字符串自动生成控制密钥:64 个十六进制字符(32 字节)。所有控制连接,无论本地还是远程,都要证明持有这把密钥。缺失时 twcore serve 在开始监听前写入一把;格式不对的配置整份拒绝。用 twcore control-key 查看,twcore control-key --rotate 更换。
remote对象,见 listen.control.remote—供另一台机器上的桌面应用连接的网络端口。它是本地通道之外额外开的,不取代本地通道。

key 缺失时,由 twcore serve 在控制通道开始监听之前写入;twcore init 生成的配置也带有它。凡是显示配置或写入配置历史的地方,这个字段一律打码;把打码值原样存回时保留原值。密钥缺失或格式不对(不是 64 个十六进制字符)时整份配置无效,因此无法把它换成一把容易猜到的短密钥。

twcore control-key            # 显示密钥,用于粘贴到桌面应用
twcore control-key --rotate   # 更换密钥;已连接的应用需要重新连接

两条命令都在运行 core 的机器上执行。

listen.control.remote#

供另一台机器上的桌面应用连接的网络端口。它在本地通道之外额外开启,因此这里写错(端口被占用、allow_from 把自己挡在外面)也不会把本机锁在门外:twcore config 和本机应用照常可用。

字段类型默认值说明
enabled布尔false是否监听远程端口。不写或 false:不为控制面开任何网络端口。twcore remote enable / twcore remote disable 切换它;运行中的 core 在一秒内跟上。
bindloopback | all | 网卡名 | IP 地址all监听哪张网卡,写法同 listen.gateway.bind。
port整数必填TCP 端口。没有固定默认值:twcore init 和 twcore remote enable 写出这一节时随机写入 20000 到 32000 之间的一个端口(不会和网关相同)。不能是 0,也不能和网关端口相同。
allow_from字符串列表[10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7]允许连接的来源,写法同 listen.gateway.allow_from,但本机不会自动放行(本机有本地通道)。其他来源的连接在握手之前关闭,不回任何字节;收窄名单时,已经连着、不再放行的连接也随即断开。同一来源一分钟内握手失败 5 次,之后一分钟不理它。
listen:
  control:
    key: 9f2c…e41a          # 64 个十六进制字符
    remote:
      enabled: true
      bind: all
      port: 23483            # 随机生成,在生成这一节时写入
      allow_from: [192.168.1.0/24]

经远程端口的连接无论应用如何请求,都不能做三件事:关闭 core(它由 systemd 管理)、修改 listen.control(它进来的那扇门)、生成诊断包(诊断包会写在服务器上)。握手限时 5 秒;同一来源一分钟内握手失败 5 次,封禁一分钟。

clients#

网关密钥,即 Claude Code、Codex 等客户端向网关发送的密钥。密钥即身份:并发上限、模型范围、路由都按密钥设置。

字段类型默认值说明
name字符串必填密钥的名字,不能重复。路由规则用 when.client 匹配它。
key字符串必填客户端发送的密钥(放在 x-api-key 或 Authorization: Bearer 中)。生成的密钥以 tw- 开头,以免被误认作上游的密钥。不能重复。
max_concurrent整数—用这把密钥同时进行的请求数上限,超出的排队等待。不写:不限。0 会被拒绝。
allow字符串列表—这把密钥可用的模型,写模型 ID 或通配(claude-*)。不写:全部模型。[]:一个都不给。
route字符串—这把密钥的请求走哪条路由。不写:default_route。
client字符串—这把密钥是为哪个客户端生成的(claude-code、codex 等),由桌面应用接管客户端时写入。一个客户端最多一把。
disabled布尔false拒绝使用这把密钥的所有请求,密钥本身保留。
clients:
  - name: default
    key: tw-a3f9c8d1e5b2h7k4m6n8p2q4
  - name: build-server
    key: tw-q8r2s4t6u8v2w4x6y8z2a4b6
    max_concurrent: 4
    allow: [claude-sonnet-*]
    route: cheap

providers#

上游,即请求被转发到的接口。

字段类型默认值说明
name字符串必填上游的名字,不能重复,也不能和策略组同名。以 __ 开头的名字保留给内置项。
base_url字符串必填接口地址,http:// 或 https://,按服务商文档写到版本段为止(https://api.anthropic.com、https://api.openai.com/v1)。Bedrock 写所在区域的推理地址:https://bedrock-runtime.<区域>.amazonaws.com。
key字符串,可写 ${VAR}—API 密钥,放进协议规定的请求头:x-api-key(Anthropic)、Authorization: Bearer(OpenAI,以及 Bedrock API Key)、x-goog-api-key(Gemini)。上游不需要密钥、或凭据写在 headers 里时不写。不能和 oauth、aws 同时写。
headers请求头名 → 值的映射{}额外的请求头,按书写顺序发送;值可以用 ${VAR},配置了 oauth 时可以用 {{access_token}}。最多 32 个。HTTP 或网关管理的请求头(host、content-length、connection 等)不能设置。
oauth对象,见 providers[].oauth—OAuth 凭据:用 refresh token 换取 access token。与 key 二选一。
aws对象,见 providers[].aws—Bedrock 上游的 AWS 访问密钥,写在这里或者从 AWS 的 profile 读:每个请求用它们签名(SigV4)。与 key(Bedrock API Key)二选一。
protocolanthropic | openai-chat | openai-responses | gemini | chatgpt | bedrock—上游的接口格式。不写:官方地址按 base_url 识别(Bedrock 的推理地址是 bedrock),其余按 anthropic 处理。
forward_client_identity布尔false同时发送客户端自己的身份:它的 User-Agent、x-app 和 originator 等身份请求头,以及请求体中的身份字段(如 metadata.user_id)。发送的都是客户端的原值,不做伪造。关闭时请求使用 ThinkWatch 的 User-Agent,不带客户端身份。用于只接受特定客户端的上游(Kimi For Coding、百炼 Coding Plan、只允许官方客户端的中转站)。chatgpt 不可用。
proxy字符串directdirect;system,即 core 进程环境变量 HTTPS_PROXY、HTTP_PROXY、ALL_PROXY 中的代理;或 proxies 中某一项的名字。
on_proxy_failfail | directfail代理不可用时:请求失败(fail),或改为直连(direct)。
models字符串列表[]上游不支持 /v1/models 时,按这份清单认定它提供的模型。
models_only字符串列表—只使用这家的这些模型,写 ID 或通配。范围外的模型不出现在模型列表里,也不会路由到这家。不写:全部。写空列表会被拒绝,暂停使用请用 disabled。
billingper-token | freeper-tokenper-token:费用为用量乘以所选价目表中的单价,订阅账号同样如此。free:费用记为 0。
pricing字符串—pricing.sheets 中某张价目表的名字。不写:默认价目表。
disabled布尔false不参与路由,模型也不出现在模型列表里;配置原样保留。

凭据有四种写法:key,放进协议规定的请求头;oauth,用 refresh token 换取 token;aws,Bedrock 上游签名请求用的访问密钥;headers,用于上游自有的鉴权方式。headers 可以和其余几种同时使用,但不能再设置已经承载凭据的那个请求头。

providers:
  - name: anthropic
    base_url: https://api.anthropic.com
    key: ${ANTHROPIC_API_KEY}

  - name: relay
    base_url: https://relay.example.com/v1
    protocol: openai-chat
    headers:
      X-Relay-Token: ${RELAY_TOKEN}
    proxy: office
    models_only: [gpt-4.1*, o3]
    pricing: relay-discount

  - name: local
    base_url: http://127.0.0.1:11434/v1
    protocol: openai-chat
    billing: free

每个请求只带请求本身和上游需要的请求头,客户端的其他信息一律不发:凭据和 headers 中写的请求头、ThinkWatch 自己的 User-Agent,以及客户端请求中该上游协议使用的请求头(Anthropic 为 anthropic-*,OpenAI 为 Idempotency-Key 和 X-Client-Request-Id,Gemini 没有)。客户端自动填写的身份字段(如 Claude Code 的 metadata.user_id)从请求体中去掉。只接受特定客户端的上游,打开 forward_client_identity。

ChatGPT 账号上游(protocol: chatgpt)只接受桌面应用登录得到的凭据,不能手写。不支持 Claude 和 Google 的订阅登录,请使用 API 密钥。

providers[].oauth#

字段类型默认值说明
access字符串—当前的 access token。每次刷新后由网关写回;不写则在第一次使用时换取。
expires_at字符串—access 的过期时间,RFC 3339(UTC),随 token 一起写回。不写:一直用到上游返回 401。
refresh字符串必填Refresh token。token 端点换发新的之后旧的即作废,因此网关会把新的写回本文件。
endpoint字符串必填token 端点的地址。
client_id字符串—OAuth 客户端 ID,端点需要时填写。
client_secret字符串—OAuth 客户端密钥,端点需要时填写。
refresh_before时长(30s、5m、1h)—提前多久刷新。不写或写法无法识别:5m。

access token 默认放进协议的鉴权请求头。要放在别处,在 headers 中写出那个请求头,用 {{access_token}} 标出 token 的位置:

    oauth:
      refresh: ${VENDOR_REFRESH_TOKEN}
      endpoint: https://auth.example.com/oauth/token
      client_id: my-client
    headers:
      X-Access: Token {{access_token}}

providers[].aws#

字段类型默认值说明
access_key_id字符串,可写 ${VAR}—访问密钥 ID。与 secret_access_key 一起写;也可以改写 profile。
secret_access_key字符串,可写 ${VAR}—私有访问密钥。
session_token字符串,可写 ${VAR}—临时凭证(如 STS 签发的)的会话令牌。过期之后请求会被拒绝,直到换上新的。
profile字符串—从 AWS 凭证文件读访问密钥时用的 profile,代替把密钥写在这里:~/.aws/credentials 和 ~/.aws/config,或 AWS_SHARED_CREDENTIALS_FILE、AWS_CONFIG_FILE 指定的文件,读的是 core 所在机器上的。文件变了会重新读。
region字符串—签名用的区域。不写:取 base_url 里的区域,这时 base_url 必须是标准的推理地址。base_url 是 VPC 端点或代理时必须写。

Bedrock 上游有两种认证方式。Bedrock API Key 写在 key 中,以 Authorization: Bearer 发送。AWS 访问密钥写在 aws 中,或者用 aws.profile 从 AWS 凭证文件里的某个 profile 读取:每个请求在请求体定稿之后用它们签名(SigV4),密钥本身不会发送。写在配置中的密钥可以用 ${VAR} 从环境变量读取。profile 读的是 core 所在机器上的文件,文件变了会重新读,因此把临时密钥刷新进 ~/.aws/credentials 的工具不需要重启 core。获取凭证时不执行任何命令,因此经 IAM Identity Center 登录(aws sso login)、运行 credential_process 或扮演角色的 profile 无法使用,请改为导出它们生成的密钥。

providers:
  - name: bedrock
    base_url: https://bedrock-runtime.us-east-1.amazonaws.com
    key: ${AWS_BEARER_TOKEN_BEDROCK}

  - name: bedrock-keys
    base_url: https://bedrock-runtime.eu-west-1.amazonaws.com
    aws:
      access_key_id: ${AWS_ACCESS_KEY_ID}
      secret_access_key: ${AWS_SECRET_ACCESS_KEY}
      session_token: ${AWS_SESSION_TOKEN}

  - name: bedrock-profile
    base_url: https://bedrock-runtime.us-west-2.amazonaws.com
    aws:
      profile: dev

请求转换为 Converse 格式。模型清单取自所在区域的控制面:可按需调用的基础模型、AWS 预设的推理配置(us.anthropic.claude-…),以及账号自己创建的应用推理配置(按调用时使用的 ARN 列出)。列出清单需要 bedrock:ListFoundationModels 和 bedrock:ListInferenceProfiles 权限;没有这两项权限时请求照常转发,可以在 models 中手动列出模型。使用 VPC 端点或代理时,在 base_url 中写它的地址,在 aws.region 中写区域;模型清单也向该地址获取。

proxies#

出站代理。不同上游需要的代理往往不同,因此没有全局开关:由每个上游用 proxy 选择。

字段类型默认值说明
name字符串必填providers[].proxy 引用的名字。direct 和 system 是内置的。
typesocks5 | socks5h | http | httpssocks5hsocks5h 把域名交给代理解析;socks5 先在本地解析。http 和 https 是 HTTP 代理。
addr字符串必填代理的 host:port。
auth对象,见 proxies[].auth—代理需要时填写用户名和密码。

proxies[].auth#

字段类型默认值说明
user字符串必填用户名。
pass字符串,可写 ${VAR}必填密码。
proxies:
  - name: office
    type: http
    addr: proxy.example.com:3128
    auth:
      user: alice
      pass: ${PROXY_PASSWORD}

on_proxy_fail 默认为 fail:静默改为直连会让请求走一条意料之外的路径,而使用者仍以为请求经过了代理。

pricing#

一次请求的费用为用量乘以模型单价。单价来自默认价目表(LiteLLM 的公开数据集,程序内置一份,每天联网刷新),或来自上游选用的自定义价目表。单价变动只影响此后的请求,不改变已记录请求的费用。

字段类型默认值说明
auto_update布尔true每天联网刷新一次默认价目表,保存为 config.yaml 旁边的 model_prices.json;此前以及离线时使用内置于程序中的价目表。
sheets对象列表,见 pricing.sheets[][]自定义价目表。上游用 providers[].pricing 选用。

pricing.sheets#

字段类型默认值说明
name字符串必填价目表的名字,不能重复。
multiplier数字1作用于默认价目表的全部单价,包括缓存和长上下文单价。
models映射: 模型 ID → pricing.sheets[].models.*{}单独定价的模型。它们取代默认价目表中该模型的单价,不乘倍率。

pricing.sheets[].models#

单价以每百万 token 的美元计,与厂商价格页上的写法一致。每个字段都要写明,计价时不做任何推算。

字段类型默认值说明
input数字必填每百万输入 token 的美元价格。
output数字必填每百万输出 token 的美元价格。
cache_read数字必填每百万缓存读取 token 的美元价格。
cache_write_5m数字必填每百万写入 5 分钟缓存 token 的美元价格。
cache_write_1h数字必填每百万写入 1 小时缓存 token 的美元价格。
input_above_200k数字—单次请求的输入(连同缓存读写)超过 200K token 后的输入单价。与 output_above_200k 同时写或都不写;缓存单价仍按上面写的算。
output_above_200k数字—单次请求的输入(连同缓存读写)超过 200K token 后的输出单价。
pricing:
  sheets:
    - name: relay-discount
      multiplier: 0.8
      models:
        claude-sonnet-4-5-thinking:
          input: 3
          output: 15
          cache_read: 0.3
          cache_write_5m: 3.75
          cache_write_1h: 6

client_probes#

客户端发出的请求中,有一部分并非出自使用者:连通性检查、预热、会话标题、话题检测、建议。每一类都可以在本地应答(intercept,不向上游发送任何内容)、原样放行(passthrough),或交给路由规则(route,由 when.intent 匹配)。默认只拦下拦了也不会少任何东西的那几类。

字段类型默认值说明
health_checkintercept | passthrough | routeintercept连通性检查(max_tokens: 1)。默认在本地应答,不影响任何功能。
warmupintercept | passthrough | routeintercept预热请求。默认在本地应答。
titlingintercept | passthrough | routepassthrough为会话起标题的请求。默认放行:拦下后所有会话都会是同一个标题。
topic_detectintercept | passthrough | routepassthrough话题检测。默认放行。
suggestionintercept | passthrough | routepassthrough建议。默认放行。

security#

五项防护,对所有上游一视同仁。每一项都有 mode:off、observe(检测并记录,不改变任何行为)、enforce(处置)。出厂时除输出长度为 off 外,其余都是 observe。各项在 enforce 下的处置不同,分别见下文。

字段类型默认值说明
redact对象,见 security.redact—出站脱敏:请求发出前,把其中的凭据替换掉。
inspect_tools对象,见 security.inspect_tools—工具调用审查:模型返回的工具调用中出现危险命令时切断响应。
hidden_text对象,见 security.hidden_text—人看不见、模型读得到的隐藏字符,出现时拒绝请求。
content对象,见 security.content—内容过滤:调用方发送的内容中出现指定的词或写法时拒绝请求。
output_limit对象,见 security.output_limit—输出长度:回答超过上限时切断。

security.redact#

请求发出前查找其中的凭据。enforce 下将其替换。

字段类型默认值说明
modeoff | observe | enforceobserveoff 不检测;observe 检测并记录,不改变任何行为;enforce 检测并处置。
enable字符串列表[]打开出厂时关着的内置规则,按 id。
disable字符串列表[]关掉内置规则,按 id。
custom对象列表,见 security.redact.custom[][]自定义规则:正则匹配到的内容按凭据处理。

字段类型默认值说明
name字符串必填日志和应用里显示的名字,也是规则的标识;同一项防护里不能重名。
pattern字符串必填正则表达式。
disabled布尔false停用这条规则,规则本身留在文件里。

内置规则:

id名称出厂
anthropic-api-keyAnthropic API key开
openai-project-keyOpenAI project key开
openai-api-keyOpenAI API key开
github-personal-tokenGitHub personal access token开
github-oauth-tokenGitHub OAuth token开
github-server-tokenGitHub server token开
github-user-tokenGitHub user token开
github-fine-grained-tokenGitHub fine-grained token开
slack-bot-tokenSlack bot token开
slack-user-tokenSlack user token开
slack-app-tokenSlack app token开
aws-access-key-idAWS access key ID开
aws-temporary-key-idAWS temporary access key ID开
google-api-keyGoogle API key开
google-oauth-tokenGoogle OAuth token开
gitlab-tokenGitLab token开
stripe-live-keyStripe live key开
stripe-restricted-keyStripe restricted key开
npm-tokennpm token开
digitalocean-tokenDigitalOcean token开
sendgrid-keySendGrid key开
private-keyPrivate key开
jwtJWT开
conn-string-passwordConnection string password开
internal-ipInternal IP address关
internal-domainInternal domain关

security.inspect_tools#

按规则检查模型返回的工具调用。enforce 下命中处置为 cut 的规则时切断响应,客户端拿不到可执行的完整调用。

字段类型默认值说明
modeoff | observe | enforceobserveoff 不检测;observe 检测并记录,不改变任何行为;enforce 检测并处置。
enable字符串列表[]打开出厂时关着的内置规则,按 id。
disable字符串列表[]关掉内置规则,按 id。
actions映射: 内置规则 id → cut | record{}内置规则在 enforce 下的处置,只写与出厂不同的(rm-rf-root: record)。
custom对象列表,见 security.inspect_tools.custom[][]自定义规则,按工具调用的参数匹配。

字段类型默认值说明
name字符串必填日志和应用里显示的名字,也是规则的标识;同一项防护里不能重名。
pattern字符串必填正则表达式。
actioncut | recordrecordenforce 下切断响应(cut),或只记录(record)。
disabled布尔false停用这条规则,规则本身留在文件里。

内置规则:

id名称enforce 下出厂处置
curl-pipe-shDownload and runcut
base64-decode-execDecode and runcut
exfil-envSend out environment variablescut
exfil-credentialsSend out a credential filecut
exfil-credentials-reversedSend out a credential file (verb first)cut
ssh-key-readRead a private key or cloud credentialcut
write-startup-itemWrite a startup itemcut
crontab-installInstall a scheduled jobcut
rm-rf-rootDelete home or rootrecord
chmod-777World-writable permissionsrecord

security.hidden_text#

调用方发送的内容中(包括工具结果)人看不见、模型读得到的字符。enforce 下拒绝请求。

字段类型默认值说明
modeoff | observe | enforceobserveoff 不检测;observe 检测并记录,不改变任何行为;enforce 检测并处置。
disable字符串列表[]不检查的种类:tag、bidi。
种类说明
tagUnicode 标签字符(U+E0000 至 U+E007F):在任何地方都不可见,模型却能读到,足以藏下一整段指令。
bidi双向控制符:使显示顺序与模型读到的顺序不一致。

security.content#

调用方发送的内容中出现的词或写法。enforce 下命中处置为 block 的规则时拒绝请求。

字段类型默认值说明
modeoff | observe | enforceobserveoff 不检测;observe 检测并记录,不改变任何行为;enforce 检测并处置。
enable字符串列表[]打开出厂时关着的内置规则,按 id。
disable字符串列表[]关掉内置规则,按 id。
actions映射: 内置规则 id → block | record{}内置规则在 enforce 下的处置,只写与出厂不同的。
custom对象列表,见 security.content.custom[][]自定义规则。

字段类型默认值说明
name字符串必填日志和应用里显示的名字,也是规则的标识;同一项防护里不能重名。
pattern字符串必填关键词;match: regex 时为正则表达式。均不区分大小写。
matchcontains | regexcontainscontains:正文包含 pattern。regex:pattern 是正则表达式。
actionblock | recordrecordenforce 下拒绝请求(block),或只记录(record)。
disabled布尔false停用这条规则,规则本身留在文件里。

内置规则:

id名称分组出厂enforce 下出厂处置
ignore-previous-instructionsIgnore previous instructionsinjection开block
ignore-all-previousIgnore all previousinjection开block
disregard-your-instructionsDisregard your instructionsinjection开block
jailbreakJailbreakinjection关block
danDANinjection关block
developer-modeDeveloper modeinjection关block
you-are-nowPersona manipulationpersona关block
new-personaNew personapersona关record
act-asAct aspersona关record
pretend-to-bePretend to bepersona关record
system-promptSystem prompt extractionpersona关record
reveal-your-instructionsReveal instructionspersona关record
what-are-your-rulesWhat are your rulespersona关record
base64-wallBase64 smugglingpersona关record
zh-ignore-previousIgnore previous instructions (Chinese)chinese关block
zh-forget-yourForget your instructions (Chinese)chinese关block
zh-do-not-followDo not follow (Chinese)chinese关block
zh-you-are-nowYou are now (Chinese)chinese关block
zh-role-playRole-play (Chinese)chinese关record
zh-reveal-yourReveal your instructions (Chinese)chinese关record
zh-system-promptSystem prompt (Chinese)chinese关record
zh-jailbreakJailbreak (Chinese)chinese关block

security.output_limit#

字段类型默认值说明
modeoff | observe | enforceoff出厂关闭:没有一个上限适合所有用途。observe 记录超长的回答;enforce 在超过上限处停止输出。
max_chars整数100000上限,按字符(Unicode 标量)计,取值 1 到 1000000。
security:
  redact:
    mode: enforce
    enable: [internal-ip]
    custom:
      - name: employee-id
        pattern: 'EMP-\d{6}'
  inspect_tools:
    mode: enforce
  output_limit:
    mode: enforce
    max_chars: 200000

retention#

设两个期限,是因为两类数据的体积相差三个数量级:一条请求的正文有几十 KB,一条请求记录只有几百字节。字节上限用于应对用量突增。

字段类型默认值说明
body_days整数7请求和响应正文保留的天数。
row_days整数90每条请求记录(时间、模型、用量、费用)保留的天数。
body_max_bytes整数2147483648正文最多占用的字节数,超出时从最早的日期开始删除。默认 2 GiB。

failover#

上游失败后会停用一段时间,接下来的请求直接交给下一个候选。停用多久取决于上游 给出的原因:余额不足要等充值,额度用完要等到上游说的重置时刻,限流通常几秒钟就 过去。只有一个候选的请求不受影响。

流式回答在第一段内容交给客户端之前,上游在流里报的错误和错误状态码一样,会把 请求换到下一个候选。

字段类型默认值说明
failures_to_pause整数3没有说明原因的失败(5xx、连接失败)连续几次后停用这家上游,取值 1 到 100。
pause_secs整数60这类失败第一次停用的秒数。之后每停用一次翻一倍,直到 max_pause_secs;成功一次后回到这个值。
max_pause_secs整数600翻倍后的停用上限,单位秒,不小于 pause_secs。
no_balance_pause_secs整数1800上游报告余额不足时停用的秒数。
quota_pause_secs整数3600上游报告额度用完、但没有给出重置时间时停用的秒数。给出了重置时间的,停用到那一刻。
rate_limit_max_pause_secs整数3600被限流的上游按它给的 Retry-After 停用,最多这么多秒。没有 Retry-After 的按没有说明原因的失败计。
stream_start_wait_secs整数15流式回答在第一段内容到达前最多暂存的秒数。在此之前上游报错,请求换到下一家;超过这个时间,已收到的部分照常交给客户端。取值 1 到 120。

groups#

策略组让多个上游合用一个名字。规则用 to 把请求交给策略组。

字段类型默认值说明
name字符串必填策略组的名字,不能重复,也不能和上游同名。
typefallback | select | load-balance | url-test | cheapestfallbackfallback:按顺序取第一个健康的。select:取 selected 指定的那个。load-balance:新对话轮流。url-test:按实测首字节时间取最快的。cheapest:取输入单价最低的。
providers字符串列表必填成员上游的名字。
selected字符串—select 类型选中的成员。

默认类型为 fallback:单个使用者的机器上没有需要分散的负载。

无论哪种类型,一段对话都留在上次回答它的那一家上游,让上游缓存着的那部分被再次读取,而不是换一家全价重算。同一轮之内(客户端正在回传工具结果)一律不换;跨轮时,上一次回答读或写了至少 1024 个 token 的 prompt cache、且距今不到五分钟,才继续留下。上游因失败进入冷却时,对话随之放开;故障转移之后接下回答的那一家,就是之后留下的那一家。一轮开始时命中的规则也沿用到这一轮结束:按输入大小或图片分流的规则不会让一轮半路换家,除非输入已经超出规则所指模型的上下文窗口。因此 load-balance 轮流的是新对话。

routes#

一条路由是一组自上而下求值的规则。每把密钥使用其 route 指定的路由;没有指定时用 default_route;再没有时用名为 default 的路由;一条路由都没有时,请求按上游的声明顺序故障转移。

字段类型默认值说明
name字符串必填路由的名字,不能重复。default 是密钥默认使用的那条。
rules对象列表,见 routes[].rules[][]自上而下求值;第一条匹配且带有 to 或 deny 的规则决定请求去向。

routes[].rules#

字段类型默认值说明
name字符串必填日志和流量详情中显示的名字。
when对象,见 routes[].rules[].when—条件,须全部满足。不写:匹配所有请求。
to字符串—上游或策略组的名字;__all__ 表示按声明顺序的全部上游。不能与 when.provider_would_be 同时写。
set对象,见 routes[].rules[].set—改写请求参数。从所有匹配的规则累积,不只第一条。
deny字符串—以这句原因拒绝请求。

routes[].rules[].when#

字段类型默认值说明
model字符串—请求的模型,可用通配(claude-opus-*)。
client字符串—请求所用网关密钥的名字,精确匹配。
dialect字符串—客户端使用的接口格式:anthropic、openai-chat、openai-responses、gemini。
input_tokens比较式(>200k、<=4k、==3)—估算的输入 token 数。
max_tokens比较式(>200k、<=4k、==3)—请求中的 max_tokens。未写该参数的请求不匹配。
tool_count比较式(>200k、<=4k、==3)—请求中提供的工具数量。
cache布尔—请求是否使用 prompt cache。
tools布尔—请求是否带工具。
image布尔—请求是否包含图片。
thinking布尔—是否开启扩展思考。
stream布尔—是否流式返回。
intent字符串或字符串列表—客户端的辅助请求:assistant_internal 表示任意一类,也可以写具体的一类(titling)。只有在 client_probes 中设为 route 的类别才会进入路由。
provider_would_be字符串或字符串列表—路由选中的上游。这类规则在路由完成后求值,只能 set 或 deny,不能写 to。

比较式以 >、>=、<、<= 或 == 开头,数字可以带 k 或 m 后缀:">200k"、"<=4k"。不带运算符是错误,不当作相等:单写 "200k" 会被拒绝。

routes[].rules[].set#

字段类型默认值说明
model字符串—换成另一个模型发送。该会话的 prompt cache 随之失效。
max_tokens整数—替换 max_tokens。
thinking布尔—开启或关闭扩展思考。
only_at_session_start布尔false只在会话开始时应用。目前只记录和显示,尚未生效。
groups:
  - name: fast
    type: url-test
    providers: [anthropic, relay]

routes:
  - name: default
    rules:
      - name: 长上下文走官方
        when: { input_tokens: ">200k" }
        to: anthropic
      - name: 标题用便宜模型
        when: { intent: titling }
        set: { model: claude-haiku-4-5 }
      - name: 其余
        to: fast
default_route: default

环境变量#

变量作用
THINKWATCH_HOME数据目录,替代 ~/.thinkwatch(Windows 上为 %APPDATA%\ThinkWatch)。
TWCORE_LOG日志过滤,tracing 语法(info、debug、tw_gateway=debug)。
HTTPS_PROXY、HTTP_PROXY、ALL_PROXY、NO_PROXY供 proxy: system 的上游使用。
其他配置中写 ${NAME} 的位置读取。

本页取自 ThinkWatch Core 仓库 docs/config.zh-CN.md (v0.56.0)。