Skip to content

博客

基于GOST反向代理实现大模型API的智能路由

AI越来越火,相应的大模型也越来越多,对应的大模型供应商也是百花齐放,但现实是Token仍旧是有限资源,单一供应商的限制触手可及。于是多供应商的组合使用,根据模型名动态分配,越来越成为更具性价比的方案。

协议转换与模型路由

好在目前主流的模型API种类不多,最常使用的几种:

  • OpenAI Chat - /v1/chat/completions 许多大模型供应商默认提供的API接口格式。
  • OpenAI Responses - /v1/responses 目前Codex使用的API。
  • Anthropic Messages - /v1/messages 目前Claude Code使用的API。

于是对接不同的供应商模型就转变成了让Claude Code, Codex等使用大模型API的客户端对接以上三种供应商兼容的API。

协议转换

不同的供应商开放的API种类不一,例如DeepSeek官方目前只提供了OpenAI Chat和Anthropic Messages兼容的AI,但没有提供OpenAI Responses API,因此无法直接接入Codex使用。OpenCode-Go中的DeepSeek目前却只提供了OpenAI Chat接口,Claude Code和Codex都无法直接使用。这种客户端使用的接口与模型供应商提供的接口不一致性,是模型路由的最大障碍,也是大模型API路由重点要解决的问题。

如果先不考虑以上接口转换的复杂性,单纯看API层面,一次模型接口的调用就是一次标准的HTTP请求。从这个角度考虑,大模型API路由就变成了纯HTTP协议的转换,也正是反向代理的功能。

这里的HTTP协议转换分为三层:

请求主机和URI的转换

客户端一般默认使用标准URI,例如Claude Code使用/v1/messages,Codex使用/v1/responses。但是模型供应商可能使用的是不同的URI,例如DeepSeek使用/anthropic/v1/messsages,OpenCode-Go使用/zen/go/v1/chat/completions。反向代理在接收到客户端的请求后需要根据具体的模型供应商提供的API端点,修改请求的Host和URI。

请求头的修改

模型API的调用需要提供apikey认证信息,在组合使用不同供应商时,apikey就不能由客户端来指定了。由于认证信息是放在HTTP请求头中的Authorization字段,因此反向代理需要根据不同供应商分别将对应的apikey设置到请求头中。

请求/响应体的转换

这一步正是上面提到的最复杂也最重要的部分,不同协议之间的接口转换。例如将Anthrpoic Messages的请求转成OpenAI Chat请求,再将OpenAI Chat的响应转成Anthropic Messages的响应。

以上三层的转换对于客户端是完全透明无感知的,客户端只需将其模型URL指向反向代理服务即可。

模型路由

Claude Code和Codex等客户端一般都提供了模型切换功能,例如Claude Code目前提供了Fable,Opus,Sonnet,Haiku四种模型,Codex也提供了gpt-5.5,gpt-5.4,gpt-5.3,gpt-5.2四种模型。一般的做法是在客户端直接设置模型映射,但为了让使用上更灵活和更方便,模型的选择可以由反向代理动态选择。

例如根据请求体中的model字段值可以设置对应的模型映射与供应商路由,claude-fable映射到OpenCode-Go的glm-5.2,claude-opus映射到DeepsSeek的deepseek-v4-pro等。如果需要调整映射,只需修改反向代理配置,同样对于客户端是无感知的。

GOST反向代理

与市面上已经存在的大模型专用网关不同,GOST反向代理本身仅是一个通用的端口转发服务,不与任何特定的具体业务(LLM路由)耦合,其通过以下提供的功能来解决上面的问题。

修改Host与URI

hops:
- name: hop-0
  nodes:
  - name: opencode-go-deepseek
    addr: opencode.ai:443
    http:
      host: opencode.ai
      rewriteURL:
      - match: /v1/messages
        replacement: /zen/v1/chat/completions
      - match: /v1/responses
        replacement: /zen/v1/chat/completions

通过http.host将请求的Host替换为opencode.ai

通过http.rewriteURL指定URI重写规则。

自定义请求头

hops:
- name: hop-0
  nodes:
  - name: opencode-go-deepseek
    addr: opencode.ai:443
    http:
      requestHeader:
        Authorization: "Bearer your-opencode-api-key"

通过http.requestHeader注入对应供应商提供的apikey。

修改请求/响应体

GOST提供了Rewriter插件功能,反向代理通过使用插件来提供对请求/响应的重写功能。在这里是将协议转换部分完全独立出去交由插件实现。

hops:
- name: hop-0
  nodes:
  - name: opencode-go-deepseek
    addr: opencode.ai:443
    http:
      rewriteRequestBody:
      - rewriter: openai-converter
      rewriteResponseBody:
      - rewriter: openai-converter

rewriters:
- name: openai-converter
  plugin:
    type: http
    addr: http://localhost:8000/rewrite

通过http.rewriteRequestBody来指定请求体重写的规则,可以使用rewriter插件。

通过http.rewriteResponseBody来指定响应体重写的规则,可以使用rewriter插件。

GOST已经提供了一个大模型API转换的Rewriter插件服务llm-api-converter,可以直接使用。

# claude-fable* -> glm-5.2(OpenAI Chat)
# claude-opus* -> deepseek-v4-pro (OpenAI Chat)
# catch-all fallback (*) -> deepseek-v4-flash (OpenAI Chat)
./llm-api-converter \
  --addr :8000 \
  --model deepseek-v4-flash \
  --model-map "claude-fable=glm-5.2:openai,claude-opus=deepseek-v4-pro:openai,*=deepseek-v4-flash:openai"

请求体匹配规则

GOST通过在节点上提供匹配规则来选择对应的节点实现供应商选择

hops:
- name: hop-0
  nodes:
  - name: opencode-go-deepseek
    addr: opencode.ai:443
    matcher:
      rule: '(BodyRegexp(`"model"\s*:\s*"claude-opus[^"]*"`) || BodyRegexp(`"model"\s*:\s*"gpt-5.4[^"]*"`))'
      bodySize: 65536

通过matcher.rule中使用BodyRegexp来匹配HTTP请求体的模型名,对于以上配置规则匹配model字段以claude-opusgpt-5.4为前缀的请求。

GOST的反向代理服务通过以上功能便可以灵活的对接任何协议兼容的大模型供应商,同时提供给Claude Code,Codex等客户端使用。

示例

以OpenCode为例,以下是一个完整的对接OpenCode-Go和OpenCode-Zen方案。

对于claude-fablegpt-5.5模型前缀,路由到OpenCode-Go并使用glm-5.2模型。

对于claude-opusgpt-5.4模型前缀,路由到OpenCode-Go并使用deepseek-v4-pro模型。

对于claude-sonnetgpt-5.3模型前缀,路由到OpenCode-Go并使用deepseek-v4-flash模型。

其他模型,路由到OpenCode-Zen并使用deepseek-v4-flash-free模型。

# gost.yaml
services:
  - name: llm-proxy
    addr: :8787
    handler:
      type: tcp
      metadata:
        sniffing: true
    listener:
      type: tcp
    forwarder:
      hop: hop-0

hops:
  - name: hop-0
    nodes:
      - name: opencode-go-glm-5.2
        addr: opencode.ai:443
        matcher:
          rule: 'Method(`POST`) && Header(`Content-Type`, `application/json`) && (BodyRegexp(`"model"\s*:\s*"claude-fable[^"]*"`) || BodyRegexp(`"model"\s*:\s*"gpt-5.5[^"]*"`))'
          bodySize: 65536
        tls:
          secure: true
          serverName: opencode.ai
        http:
          host: opencode.ai
          rewriteURL:
            - match: /v1/messages   # Anthropic Messages API
              replacement: /zen/go/v1/chat/completions
            - match: /v1/responses # OpenAI Responses API
              replacement: /zen/go/v1/chat/completions
          requestHeader:
            Authorization: "Bearer your-opencode-apikey"
          rewriteRequestBody:
            - rewriter: openai-converter
          rewriteResponseBody:
            - rewriter: openai-converter

      - name: opencode-go-deepseek-v4-pro
        addr: opencode.ai:443
        matcher:
          rule: 'Method(`POST`) && Header(`Content-Type`, `application/json`) && (BodyRegexp(`"model"\s*:\s*"claude-opus[^"]*"`) || BodyRegexp(`"model"\s*:\s*"gpt-5.4[^"]*"`))'
          bodySize: 65536
        tls:
          secure: true
          serverName: opencode.ai
        http:
          host: opencode.ai
          rewriteURL:
            - match: /v1/messages
              replacement: /zen/go/v1/chat/completions
            - match: /v1/responses
              replacement: /zen/go/v1/chat/completions
          requestHeader:
            Authorization: "Bearer your-opencode-apikey"
          rewriteRequestBody:
            - rewriter: openai-converter
          rewriteResponseBody:
            - rewriter: openai-converter

      - name: opencode-go-deepseek-v4-flash
        addr: opencode.ai:443
        matcher:
          rule: 'Method(`POST`) && Header(`Content-Type`, `application/json`) && (BodyRegexp(`"model"\s*:\s*"claude-sonnet[^"]*"`) || BodyRegexp(`"model"\s*:\s*"gpt-5.3[^"]*"`))'
          bodySize: 65536
        tls:
          secure: true
          serverName: opencode.ai
        http:
          host: opencode.ai
          rewriteURL:
            - match: /v1/messages
              replacement: /zen/go/v1/chat/completions
            - match: /v1/responses
              replacement: /zen/go/v1/chat/completions
          requestHeader:
            Authorization: "Bearer your-opencode-apikey"
          rewriteRequestBody:
            - rewriter: openai-converter
          rewriteResponseBody:
            - rewriter: openai-converter

      - name: opencode-zen-deepseek-v4-flash-free
        addr: opencode.ai:443
        matcher:
          rule: 'Method(`HEAD`) || Method(`POST`) && Header(`Content-Type`, `application/json`)'
        tls:
          secure: true
          serverName: opencode.ai
        http:
          host: opencode.ai
          rewriteURL:
            - match: /v1/messages
              replacement: /zen/v1/chat/completions
            - match: /v1/responses
              replacement: /zen/v1/chat/completions
            - match: /v1/models
              replacement: /zen/v1/models
            - match: /v1
              replacement: /zen/v1
            - match: /
              replacement: /zen
          requestHeader:
            Authorization: "Bearer your-opencode-apikey"
          rewriteRequestBody:
            - rewriter: openai-converter
          rewriteResponseBody:
            - rewriter: openai-converter

rewriters:
  - name: openai-converter
    plugin:
      type: http
      addr: http://localhost:8000/rewrite

GOST反向代理服务

./gost -C gost.yaml

Rewriter插件服务,负责模型映射与协议转换

./llm-api-converter \
  --addr :8000 \
  --model-map "gpt-5.5=glm-5.2:openai,claude-fable=glm-5.2:openai,gpt-5.4=deepseek-v4-pro:openai,claude-opus=deepseek-v4-pro:openai,gpt-5.3=deepseek-v4-flash:openai,claude-sonnet=deepseek-v4-flash:openai,*=deepseek-v4-flash-free:openai"

数据流

Anthropic Messages 客户端(Claude Code)
POST :8787/v1/messages (model: claude-opus-4-8)
  → Sniffer 嗅探 HTTP,读取 Body 前缀
  → BodyRegexp 匹配 "claude-opus" → 选中 opencode-go-deepseek-v4-pro 节点
  → rewriteURL: /v1/messages → /zen/go/v1/chat/completions
  → rewriteRequestBody: openai-converter 转换协议 + model 替换为 deepseek-v4-pro
  → 转发至 https://opencode.ai/zen/go/v1/chat/completions
OpenAI Responses 客户端(Codex)
POST :8787/v1/responses (model: gpt-5.5)
  → Sniffer 嗅探 HTTP,读取 Body 前缀
  → BodyRegexp 匹配 "gpt-5.5" → 选中 opencode-go-glm-5.2 节点
  → rewriteURL: /v1/responses → /zen/go/v1/chat/completions
  → rewriteRequestBody: openai-converter 转换协议 + model 替换为 glm-5.2
  → 转发至 https://opencode.ai/zen/go/v1/chat/completions
兜底(其他请求)
POST :8787/v1/messages (model: claude-haiku-5)
  → Sniffer 嗅探 HTTP,读取 Body 前缀
  → 命中 兜底规则 → 选中 opencode-zen-deepseek-v4-flash-free 节点
  → rewriteURL: /v1/messages → /zen/v1/chat/completions
  → rewriteRequestBody: openai-converter 转换协议 + model 替换为 deepseek-v4-flash-free
  → 转发至 https://opencode.ai/zen/v1/chat/completions

Docker Compose

使用 Docker Compose 部署 GOST 与 llm-api-converter 重写插件:

# docker-compose.yml
services:
  gost:
    image: gogost/gost
    ports:
      - "8787:8787"
    volumes:
      - ./gost.yaml:/etc/gost/gost.yaml
    depends_on:
      - llm-converter

  llm-converter:
    image: ginuerzh/llm-api-converter
    command:
      - --addr=:8000
      - --model-map=gpt-5.5=glm-5.2:openai,claude-fable=glm-5.2:openai,gpt-5.4=deepseek-v4-pro:openai,claude-opus=deepseek-v4-pro:openai,gpt-5.3=deepseek-v4-flash:openai,claude-sonnet=deepseek-v4-flash:openai,*=deepseek-v4-flash-free:openai
    ports:
      - "8000:8000"

注意:Docker Compose 中服务间通过容器名通信,需将 gost.yaml 中的 rewriter 地址改为 http://llm-converter:8000/rewrite

启动:

docker compose up -d

GOST中的日志

程序运行的日志无论是对开发人员还是使用人员都是一个很重要且很有价值的信息。对于开发人员可以帮助其快速定位问题,对于使用人员一方面在遇到问题时可以将日志提供给开发人员方便分析和定位问题,另一方面通过日志可以对应用的使用情况进行统计和分析。日志也是可观测性概念的组成部分,日志让我们多了一个观测和监控程序运行状态和行为的手段。

用户级别的流量统计和动态限流方案

GOST中的观测器组件可以用来对服务的连接和流量进行统计,当服务配置使用了观测器,则会周期性以事件(Event)的方式上报此服务的接收(inputBytes)和发送数据(outputBytes)总字节数。而限制器组件则可以用来限制服务的连接和流量。

有些时候可能需要对流量进行更加精细化管理。例如一个支持认证的代理服务,需要按用户进行流量统计或限速,更进一步可能还需要根据用户的实时流量来做动态限流。由于不同的使用场景可能会有比较复杂的处理逻辑,为了获得更高的灵活性和更强的扩展性,GOST本身并没有提供用户级别的限流功能,而是通过插件的方式开放给使用者来实现自己的逻辑。

利用反向代理隧道实现k8s中服务的本地调试

当前的容器,Kubernetes等云原生技术使我们的服务部署和管理更加灵活和便利,然而任何技术有利就会有弊,当一个应用运行在Kubernetes集群中时,如果需要对其进行调试则会很麻烦。

目前比较主流的解决方案是使用Telepresence等工具,将服务的流量拦截并转发到本地服务,从而实现本地环境下的开发和调试。下面我们将利用反向代理隧道来实现类似的功能。

GOST.PLUS-公共反向代理服务

反向代理隧道是GOST中新增的一个较大功能,同时也是一个很重要的功能,借助于反向代理和内网穿透,可以很方便的将内网Web服务暴露到公网,随时随地都能访问。

为了能够对此功能进行更全面的测试,同时也为了能够给需要临时暴露内网服务的用户提供一种快捷的方式,特公开推出GOST.PLUS公共反向代理测试服务。此服务面向所有用户开放,无需注册。

串口重定向

串口目前在个人电脑主机上基本不存在了,但在一些小型设备特别是工业设备和工控机上还是能够看到这些接口。由于串口通信的方式和我们平时所使用的TCP/IP有很大差异,因此在调试和分析基于串口的通讯协议时所用的方法也会有所不同。

GOST在3.0.0-rc8之后的版本中增加了串口重定向或者也可以称做串口端口转发功能,可以将本地串口数据转发给TCP服务,或者将TCP服务的数据转发给本地串口,也可以将本地串口转发到远程主机的串口。利用串口转发可以实现两个功能:串口远程通讯和串口通讯数据监控。

反向代理隧道实战

上一篇博文中,对反向代理和内网穿透做了基本的介绍。本篇将通过具体应用案例更加直观的展示反向代理隧道的使用。

反向代理隧道是将反向代理和内网穿透两个功能相结合一种技术手段,这两个概念之间其实没有必然的联系,反向代理可以不使用内网穿透,内网穿透也并不一定是为了实现反向代理,只不过很多情况下我们需要这两个功能组合在一起使用。例如一般的家庭网络或公司网络可能没有公网IP,因此无法通过公网直接访问,这个时候就需要用到内网穿透,通过一台具有公网IP的机器来间接的访问内网的服务。

反向代理与内网穿透

反向代理也是代理服务的一种,我们通常使用的代理服务,例如HTTP/SOCKS5代理,其代理的目标是客户端,代理服务代替客户端与所访问的服务器建立连接,而反向代理服务代理的目标是服务器,因此像HTTP/SOCKS5这种代理服务也被称作正向代理。二者的主要却别在于正向代理中客户端是知道代理服务的存在,而反向代理中客户端(甚至是服务端)是不知道代理服务的存在,在客户端看来代理服务就是我真正要访问的服务。

从某种角度来看,反向代理与端口转发是一样的,二者都是在两个端口之间建立映射关系,将数据从一个端口转发到另外一个端口,只不过反向代理会利用转发的数据进行更精确的控制,而端口转发一般不会依赖于数据的内容,仅仅是纯粹的端到端的转发行为。GOST中的反向代理就是基于端口转发来实现的。

基于TUN设备的VPN组网

GOST最初是在v2.9版本中引入对TUN(和TAP)设备的支持,在v3版本(beta.4)中又将实现方式由songgao/water库(TAP未变化)改为了wireguard-go,并且增加了心跳和认证机制。

GOST中TUN设备的设计思想是简单轻量,因此没有添加过于复杂的配置,对数据也没有做过多的处理。只要能够满足一些特定的使用场景就达到目的了,如果需要更加复杂的应用完全可以通过wireguard来实现。

TUN设备可以有很多的用处,比较多的可能是用来构建VPN,这里就以讲一下基于GOST的TUN设备VPN组网方案。