logo
0
1
WeChat Login

FakeModel

基于 Gin 的大模型提示词注入代理

适用于企业场景:在客户端请求转发给上游大模型前,统一注入预设的系统提示词(安全规范、角色设定、业务上下文等),客户端无需改动。

特性

  • 透明代理:客户端只需把 base_url 指向本服务,其余不变。
  • 多端点兼容:
    • OpenAI /v1/chat/completions(chat)
    • OpenAI /v1/responses(responses)
    • Anthropic /v1/messages(anthropic)
  • 多模型多提示词:一个配置文件里为不同 model + 端点配置不同注入规则。
  • 不写死 system:role 可配置(system / user / developer),mode 可选 merge / insert,position 可选 prepend / append,兼容各家协议差异。
  • 保留 cache_control:Anthropic 的 prompt caching 标记在合并时原样保留。
  • 完整支持流式:请求阶段注入,响应阶段字节级透传,SSE 逐 chunk flush。
  • 灵活配置:config.yml + 环境变量(${VAR} 插值 + FM_ 前缀覆盖)+ CLI flag,优先级清晰。
  • 单次请求覆盖:通过 X-FakeModel-* Header 临时指定 profile / 禁用注入。
  • 严格白名单:未命中任何 profile 的请求默认拒绝(400 + OpenAI 风格错误体);配置兜底 profile 后未知模型才能放行。

快速开始

# 1. 复制配置
cp config.example.yml config.yml
# 编辑 config.yml,填入上游地址;密钥用环境变量提供

# 2. 设置密钥
export OPENAI_API_KEY=sk-xxx

# 3. 启动
go run ./cmd/fakemodel --config config.yml

客户端把 base_urlhttps://api.openai.com 改为 http://localhost:8080 即可,系统提示词会自动注入。

配置

完整字段见 config.example.yml。核心结构:

server: { addr, debug, auth_token }
upstream: { base_url, api_key, timeout }   # 全局默认上游
profiles:                                    # 多套注入规则
  - name
    match: { models, endpoint, model_map }    # model + 端点命中即用;model_map 做模型名映射
    inject: { prompt | prompt_file, position, order, role, mode, separator }
    upstream: { base_url, api_key }          # 可选:此 profile 专用上游

注入参数语义

字段取值含义
modemerge(默认)/ insertmerge 并入已有 system;insert 插入独立消息项
positionprepend(默认)/ append相对已有 system 内容的位置(merge 模式下生效)
orderfirst(默认)/ last在消息序列里的位置(insert 模式下生效)
rolesystem / user / developer注入为哪个角色,不写死
separator任意字符串(默认 \n\n)合并时连接新旧内容的分隔符

各端点的 system 存放位置

端点system 位置合并要点
chatmessages[]role==systemcontent 支持 "abc"[{type,text}] 双形态
responses顶层 instructions + input[] 中 system 项优先合并 instructions
anthropic顶层 system(字符串或数组)保留 cache_control 标记

配置来源与优先级

从高到低:

  1. CLI flag:--addr--debug--config
  2. 环境变量(前缀 FM_):
    • FM_CONFIGFM_ADDRFM_DEBUGFM_AUTH_TOKEN
    • FM_UPSTREAM_BASE_URLFM_UPSTREAM_API_KEY
  3. config.yml(支持 ${VAR} 插值)
  4. 代码默认值

单次请求覆盖(Header)

调试或特殊场景下,客户端可在单次请求里用 Header 临时改变行为。这些 Header 在转发给上游前会被剥离,不泄漏:

Header作用
X-FakeModel-Profile: <name>强制使用指定 profile,绕过 model 路由
X-FakeModel-Model: <name>强制覆盖转发给上游的模型名(优先级高于 model_map)
X-FakeModel-Disable-Inject: true本次请求跳过注入,纯透传
X-FakeModel-Debug: true调试标记

鉴权

server.auth_token 保护代理本身的访问,客户端需带 Authorization: Bearer <token>。支持单值或列表两种写法,任一命中即放行:

server:
  auth_token: "single"            # 单值

  # 或列表(多客户端各自一个 token)
  auth_token: ["client-a-token", "client-b-token"]

留空(或列表为空)则不校验。环境变量 FM_AUTH_TOKEN 仅支持单值;多值请在 config.yml 里写成列表。

路由与白名单

进入的请求按 model + endpoint 匹配 profile,匹配规则:

  • 命中更具体的 profile(指定了 models + endpoint)→ 使用该 profile 的注入规则与上游

  • 未命中任何 profile默认拒绝(返回 400,OpenAI 风格错误体):

    {"error": {"message": "模型 \"xxx\" 未在配置中允许...", "type": "model_not_allowed", "code": "model_not_allowed"}}
    
  • 配置了兜底 profile(如 match: { endpoint: all })→ 未知模型走兜底放行,转发到全局上游

也就是说:默认是严格白名单,只有显式配了兜底 profile 才会放行任意模型。这样避免「配了全局 upstream 就能转发任意模型」的安全口子。要开启宽松放行,在 config.yml 末尾加:

profiles:
  - name: "default"
    match:
      endpoint: "all"
    inject:
      prompt: ""
      mode: merge

模型映射(model_map)

对外暴露统一的模型名,转发上游时替换为真实模型名。常见场景:多模型统一入口、隐藏内部代号、A/B 切换上游。

profiles:
  - name: "glm-proxy"
    match:
      models: ["glm-5.1", "glm-5.2"]   # 客户端用这两个名字请求
      endpoint: "chat"
      model_map:                         # 对外名 → 上游真实名
        glm-5.1: "deepseek-v4-flash"
        glm-5.2: "deepseek-v4-flash"
    inject:
      prompt: "..."
      role: developer

客户端发 {"model":"glm-5.1", ...},转发到上游时 body 被改写为 {"model":"deepseek-v4-flash", ...},对外名不暴露给厂商。

模型名解析优先级(从高到低):

  1. X-FakeModel-Model Header(强制覆盖,调试/特殊场景)
  2. match.model_map[请求model](配置的映射)
  3. 请求体原 model(原样透传)

未在 model_map 中列出的模型名原样转发(但仍需命中 models 或兜底 profile 才会放行)。

响应改写(身份伪装完整闭环)

仅改请求方向的 model 是不够的 —— 上游响应里的 model 字段会暴露真实模型(如客户端请求 glm-5.2,上游响应里却是 "model":"qwen3.6-35b-a3b")。FakeModel 会自动把响应中的身份字段改回客户端请求的对外名:

  • 流式 SSE:逐帧解析 data: {...},改写 model 字段后重新写出,保持 token 级延迟
  • 非流式 JSON:缓冲完整响应后改写 model
  • 额外清理:system_fingerprint(OpenAI 模型版本指纹)一并清除,避免身份泄漏
  • 安全回退:改写失败时保留原始字节,宁可泄漏身份也不破坏响应可用性

启用 X-FakeModel-Disable-Inject: true 时,响应也不改写(纯透传)。

Docker

镜像基于 distroless 多阶段构建,体积约 17MB,无 shell。

# 构建
docker build -t fakemodel .

# 运行(挂载配置 + 注入密钥)
docker run --rm -p 8080:8080 \
  -v $PWD/config.yml:/etc/fakemodel/config.yml:ro \
  -e OPENAI_API_KEY=sk-xxx \
  fakemodel

镜像内置 config.example.yml 作为默认配置(路径 /etc/fakemodel/config.yml),挂载自己的 config.yml 即可覆盖。配置也可全部走环境变量(FM_*)。

开发

go test ./...            # 全部测试
go test ./internal/injector/... -v   # 单包测试
go build ./...           # 编译
gofmt -w . && go vet ./...           # 风格检查

目录结构

cmd/fakemodel/         程序入口
internal/
  config/              配置加载(yml + env + ${VAR} 插值)
  injector/            注入器(接口 + chat/responses/anthropic 三个实现)
  model/               按 model + endpoint 路由 profile
  proxy/               反向代理 + SSE 流式透传
  server/              Gin 路由、中间件、组装

设计要点

  • 注入与转发解耦:注入只发生在请求阶段,响应(含 SSE)字节级透传,二者互不影响。
  • 不写死 system:role / mode / position 均可配置,兼容不同厂商的协议差异(如 OpenAI 新引入的 developer 角色)。
  • 保留厂商特性:Anthropic 的 cache_control、多模态的 image_url 等字段在合并时原样保留。

About

No description, topics, or website provided.
Language
Go94.9%
Shell3.2%
Dockerfile1.9%