一个基于 Go 标准库实现的轻量级网关服务,用于将 NoteGPT 的聊天接口封装为 OpenAI 兼容 API。
服务接收标准 OpenAI Chat Completions 请求,将消息历史整理为单条上游提示词,请求 NoteGPT 的流式聊天接口,再将返回结果转换为 OpenAI 风格的 JSON 或 SSE 流式响应。对于上游原生不支持的函数调用,本项目在网关层提供了兼容型 tools / tool_choice shim,可对外返回标准 tool_calls。默认场景面向带本地 proxy-pool 的沙盒或网关部署环境。
- 提供标准化网关入口:
GET /healthzGET /v1/modelsPOST /v1/chat/completions
- 同时支持流式与非流式聊天补全
- 支持 OpenAI
tools、tool_choice、parallel_tool_calls的兼容型函数调用 - 默认从本地
proxy-pool动态拉取可用 HTTP 代理 - 上游出现访客登录或额度类错误时自动切换出口并重试
- 每次上游请求自动生成新的访客标识
- 仅依赖 Go 标准库,部署简单,运行足迹小
- 对外仅暴露规范模型名,不泄露内部上游模型值
- 工具调用场景下自动将模型输出映射为标准
message.tool_calls
- 为 OpenAI SDK、OpenWebUI、SillyTavern、LangChain 等客户端提供兼容接入层
- 将现有依赖 OpenAI Chat Completions 的应用快速接入 NoteGPT
- 在多代理出口环境下构建轻量级模型网关
- 为测试、联调或沙盒环境提供统一模型接口
| 路径 | 方法 | 说明 |
|---|---|---|
/healthz |
GET |
健康检查 |
/v1/models |
GET |
返回公开模型列表 |
/v1/chat/completions |
POST |
OpenAI 兼容聊天接口,支持 stream=true、tools、tool_choice |
请求处理链路如下:
- 客户端发送
POST /v1/chat/completions - 网关解析模型名并转换为内部上游模型值
- 从
proxy-pool选择一个可用代理出口 - 将 OpenAI
messages压平成上游可接受的单条提示词 - 请求 NoteGPT 的
/api/v2/chat/stream - 将上游事件流改写为 OpenAI JSON 或 SSE 输出
- 当请求携带
tools时,在网关层执行函数调用提示词编排与tool_calls响应映射
当前 /v1/models 对外公开的模型名如下:
basicdeepseek-r1deepseek-v3deepseek-v3.2gemini-2.5-flashgemini-3-flashgemini-3.1-flash-litegpt-4.1-minigpt-4o-minigpt-5-mini
说明:
/v1/models只显示对外规范模型名。- 服务内部仍保留对历史别名和上游原始模型值的兼容输入能力,避免破坏已有调用。
- 未命中的模型名会继续透传到上游,是否可用取决于 NoteGPT 当前匿名访客策略。
cd /root/notegpt-openai-proxy
go run .默认监听地址为 :8099。
服务通过环境变量完成配置。
| 变量名 | 默认值 | 说明 |
|---|---|---|
LISTEN_ADDR |
:8099 |
HTTP 监听地址 |
API_KEY |
空 | 网关 Bearer Token;为空时不鉴权 |
PROXY_POOL_URL |
http://127.0.0.1:8090/api/proxies |
本地 proxy-pool API 地址 |
FIXED_PROXY_URL |
空 | 固定使用单个出口代理,设置后跳过代理池 |
UPSTREAM_URL |
https://notegpt.io/api/v2/chat/stream |
NoteGPT 上游流式接口 |
DEFAULT_MODEL |
gemini-3.1-flash-lite-preview |
请求未显式指定模型时的默认上游模型 |
RETRY_COUNT |
3 |
最多跨代理重试次数 |
PROXY_COOLDOWN |
10m |
代理被判定失败后的冷却时间 |
NOTEGPT_LANGUAGE |
auto |
上游请求中的 language 字段 |
NOTEGPT_TONE |
default |
上游请求中的 tone 字段 |
NOTEGPT_LENGTH |
moderate |
上游默认输出长度 |
NOTEGPT_CHAT_MODE |
standard |
上游聊天模式 |
补充说明:
API_KEY为空时,网关不校验客户端身份。FIXED_PROXY_URL设置后,服务不会再访问proxy-pool。PROXY_POOL_URL为空时,服务会直接访问上游。
开发运行:
API_KEY=sk-demo go run .编译二进制:
go build -o notegpt-openai-proxy .生产环境示例:
LISTEN_ADDR=:8099 \
API_KEY=sk-demo \
PROXY_POOL_URL=http://127.0.0.1:8090/api/proxies \
./notegpt-openai-proxycurl -s http://127.0.0.1:8099/v1/models \
-H 'Authorization: Bearer sk-demo'curl -s http://127.0.0.1:8099/v1/chat/completions \
-H 'Authorization: Bearer sk-demo' \
-H 'Content-Type: application/json' \
--data '{
"model": "basic",
"stream": false,
"messages": [
{"role": "user", "content": "用一句话解释反向代理。"}
]
}'curl -N http://127.0.0.1:8099/v1/chat/completions \
-H 'Authorization: Bearer sk-demo' \
-H 'Content-Type: application/json' \
--data '{
"model": "deepseek-v3.2",
"stream": true,
"messages": [
{"role": "user", "content": "请只回答三个英文单词。"}
]
}'curl -s http://127.0.0.1:8099/v1/chat/completions \
-H 'Authorization: Bearer sk-demo' \
-H 'Content-Type: application/json' \
--data '{
"model": "deepseek-v3.2",
"stream": false,
"messages": [
{"role": "user", "content": "北京现在天气怎么样?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的实时天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}
}
],
"tool_choice": {
"type": "function",
"function": {"name": "get_weather"}
}
}'返回示例:
{
"choices": [
{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_xxx",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"北京\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}工具结果回传示例:
curl -s http://127.0.0.1:8099/v1/chat/completions \
-H 'Authorization: Bearer sk-demo' \
-H 'Content-Type: application/json' \
--data '{
"model": "deepseek-v3.2",
"messages": [
{"role": "user", "content": "北京现在天气怎么样?"},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_weather_1",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"北京\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_weather_1",
"content": "{\"city\":\"北京\",\"condition\":\"晴\",\"temperature_c\":24}"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的实时天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}
}
],
"tool_choice": "auto"
}'尽管 /v1/models 仅返回规范模型名,服务仍兼容以下历史输入类型:
- 旧版自定义别名
- 内部上游原始模型值
- 已存在客户端缓存中的旧模型名
这意味着你可以逐步迁移客户端到新的公开模型名,而不需要一次性切断旧调用。
本项目支持 OpenAI Chat Completions 风格的以下工具调用字段:
toolstool_choiceparallel_tool_callsassistant.tool_callstool角色消息与tool_call_id
实现方式说明:
- NoteGPT 上游并不直接返回 OpenAI 原生
tool_calls - 网关会将工具定义注入提示词,并要求模型按约定格式输出
- 网关随后将该输出转换为标准 OpenAI
message.tool_calls或流式delta.tool_calls - 客户端执行工具后,可将
tool结果按标准消息继续回传,网关会在下一轮继续编排上下文
当前限制:
- 仅支持
type=function - 工具调用的
stream=true为兼容型流式输出:网关会先收完整个上游响应,再输出 OpenAI SSE 事件 - 工具参数要求为合法 JSON;如果模型未按协议返回,网关会返回错误而不是伪造工具结果
parallel_tool_calls=false时,网关会将工具调用限制为单个函数请求tool_choice="none"时,网关会退回普通聊天模式,不启用工具 shim
已验证行为:
- 强制
tool_choice的首轮工具请求 assistant.tool_calls+tool结果回传后的二轮最终回答- 非流式
message.tool_calls输出 - 流式
delta.tool_calls输出
当上游返回已知的访客登录错误或额度错误时,服务会将当前代理标记为暂时不可用,并尝试切换到新的出口代理后重新请求,直到达到 RETRY_COUNT 上限。
如果所有代理重试都失败,网关将向客户端返回 OpenAI 风格的错误结构。
usage为估算值,因为上游不提供 OpenAI 风格的 token 计量- 响应头附带
X-Upstream-Proxy,便于定位实际使用的出口代理 - 响应头附带
X-Conversation-Id,便于关联上游会话 - 若某个代理连续触发登录或额度类错误,会进入冷却期并被暂时跳过
messages会先被压平成单条提示词后再提交给上游- 流式场景会将上游 SSE 事件改写为 OpenAI Chat Completions chunk
- 非流式场景会完整收集上游文本后再返回 OpenAI 风格 JSON
- 工具调用场景会在网关层注入工具协议提示词,并将模型输出映射为标准
tool_calls