基于 Gin 的大模型提示词注入代理。
适用于企业场景:在客户端请求转发给上游大模型前,统一注入预设的系统提示词(安全规范、角色设定、业务上下文等),客户端无需改动。
base_url 指向本服务,其余不变。/v1/chat/completions(chat)/v1/responses(responses)/v1/messages(anthropic)model + 端点配置不同注入规则。role 可配置(system / user / developer),mode 可选 merge / insert,position 可选 prepend / append,兼容各家协议差异。config.yml + 环境变量(${VAR} 插值 + FM_ 前缀覆盖)+ CLI flag,优先级清晰。X-FakeModel-* Header 临时指定 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_url 从 https://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 专用上游
| 字段 | 取值 | 含义 |
|---|---|---|
mode | merge(默认)/ insert | merge 并入已有 system;insert 插入独立消息项 |
position | prepend(默认)/ append | 相对已有 system 内容的位置(merge 模式下生效) |
order | first(默认)/ last | 在消息序列里的位置(insert 模式下生效) |
role | system / user / developer | 注入为哪个角色,不写死 |
separator | 任意字符串(默认 \n\n) | 合并时连接新旧内容的分隔符 |
| 端点 | system 位置 | 合并要点 |
|---|---|---|
| chat | messages[] 中 role==system 项 | content 支持 "abc" 与 [{type,text}] 双形态 |
| responses | 顶层 instructions + input[] 中 system 项 | 优先合并 instructions |
| anthropic | 顶层 system(字符串或数组) | 保留 cache_control 标记 |
从高到低:
--addr、--debug、--configFM_):
FM_CONFIG、FM_ADDR、FM_DEBUG、FM_AUTH_TOKENFM_UPSTREAM_BASE_URL、FM_UPSTREAM_API_KEY${VAR} 插值)调试或特殊场景下,客户端可在单次请求里用 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
对外暴露统一的模型名,转发上游时替换为真实模型名。常见场景:多模型统一入口、隐藏内部代号、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", ...},对外名不暴露给厂商。
模型名解析优先级(从高到低):
X-FakeModel-Model Header(强制覆盖,调试/特殊场景)match.model_map[请求model](配置的映射)model(原样透传)未在 model_map 中列出的模型名原样转发(但仍需命中 models 或兜底 profile 才会放行)。
仅改请求方向的 model 是不够的 —— 上游响应里的 model 字段会暴露真实模型(如客户端请求 glm-5.2,上游响应里却是 "model":"qwen3.6-35b-a3b")。FakeModel 会自动把响应中的身份字段改回客户端请求的对外名:
data: {...},改写 model 字段后重新写出,保持 token 级延迟modelsystem_fingerprint(OpenAI 模型版本指纹)一并清除,避免身份泄漏启用 X-FakeModel-Disable-Inject: true 时,响应也不改写(纯透传)。
镜像基于 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 路由、中间件、组装
role / mode / position 均可配置,兼容不同厂商的协议差异(如 OpenAI 新引入的 developer 角色)。cache_control、多模态的 image_url 等字段在合并时原样保留。