日期:2026-10-17
场景:自建 AI 网关接入匿名免费 LLM 端点,记录两类服务的请求构造差异与适配方法。
说明:服务名用代号表示。文中方法用于理解请求构造原理,实际使用请遵守各服务的条款。
目录
两类服务的差异
服务 A:完整请求构造
服务 A:为什么规则总在变
服务 B:匿名直连
网关适配的两种方案
通用原则
1. 两类服务的差异
| 维度 | 服务 A | 服务 B |
|---|---|---|
| 认证 | 固定字面量 + 多会话头 | 无 |
| 会话标识 | 特定格式(含时间戳变换) | 无 |
| 请求体 | 需声明客户端能力 | 无特殊要求 |
| 端点 | 会迁移 | 稳定 |
| 限速 | 未公开 | 约 200 请求/时/IP |
| 适配成本 | 高,需持续跟进 | 低 |
2. 服务 A:完整请求构造
2.1 端点
端点路径会随版本迁移。2026 年 10 月中旬从/zen/v1迁到/inference/v1。旧路径返回 403 或重定向。适配时需要确认当前版本使用的路径。
2.2 请求头
完整头部清单:
| 头部 | 值 | 说明 |
|---|---|---|
Content-Type | application/json | 标准 |
Authorization | Bearer public | 字面量public,不是账号 key |
User-Agent | opencode/latest/2.0.29/cli | 格式会随版本变化,旧的ai-sdk/bun后缀已废 |
x-opencode-client | cli | 客户端类型 |
x-opencode-project | global | 必须是global,40 位 hex 会触发 403 |
x-opencode-session | SESSION | 见 2.3 |
x-session-id | SESSION | 同上 |
x-session-affinity | SESSION | 同上 |
x-opencode-session-id | SESSION | 同上 |
四个会话头必须同值。
不能发的头部:x-opencode-request。这个头部存在即 403。早期版本可能需要它,后来改成禁用。
2.3 会话标识格式
格式:
text
ses_ + 反转时间戳(12位hex) + 随机后缀(14位base62)
反转时间戳的算法:
python
import time ts = int(time.time() * 1000) # 毫秒时间戳 v = ~(ts * 0x1000 + 1) & 0xFFFFFFFFFFFF # 按位取反,掩码 48 位 hex_part = f'{v:012x}' # 12 位十六进制后缀是 14 位随机字符,从0-9A-Za-z中取:
python
import random, string suffix = ''.join(random.choice( string.digits + string.ascii_uppercase + string.ascii_lowercase ) for _ in range(14)) session = f"ses_{hex_part}{suffix}"为什么必须是这个格式:2026 年 10 月 13 日起,服务端校验反转位。纯随机值、递增时间戳、格式正确但反转位不对,全部 403。
完整生成:
python
def gen_session(): import time, random, string ts = int(time.time() * 1000) v = ~(ts * 0x1000 + 1) & 0xFFFFFFFFFFFF hex_part = f'{v:012x}' suffix = ''.join(random.choice( string.digits + string.ascii_uppercase + string.ascii_lowercase ) for _ in range(14)) return f"ses_{hex_part}{suffix}"2.4 请求体
标准的model、messages之外,需要三部分。
流式参数(不可省略):
json
{ "stream": true, "stream_options": {"include_usage": true} }四个工具指纹:请求体的tools数组必须包含四个函数定义。缺任何一个 403。名称固定为bash、glob、grep、read。
完整结构:
json
"tools": [ { "type": "function", "function": { "name": "bash", "description": "x", "parameters": { "type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"] } } }, { "type": "function", "function": { "name": "glob", "description": "x", "parameters": { "type": "object", "properties": {"pattern": {"type": "string"}}, "required": ["pattern"] } } }, { "type": "function", "function": { "name": "grep", "description": "x", "parameters": { "type": "object", "properties": {"pattern": {"type": "string"}}, "required": ["pattern"] } } }, { "type": "function", "function": { "name": "read", "description": "x", "parameters": { "type": "object", "properties": {"file_path": {"type": "string"}}, "required": ["file_path"] } } } ]描述字段用x即可,服务端只校验名称和结构,不校验描述内容。
2.5 完整请求示例
bash
SESSION=$(python3 -c " import time, random, string ts = int(time.time() * 1000) v = ~(ts * 0x1000 + 1) & 0xFFFFFFFFFFFF print(f'ses_{v:012x}' + ''.join(random.choice( string.digits + string.ascii_uppercase + string.ascii_lowercase ) for _ in range(14))) ") curl -sS "<endpoint>/inference/v1/chat/completions" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer public" \ -H "User-Agent: opencode/latest/2.0.29/cli" \ -H "x-opencode-client: cli" \ -H "x-opencode-project: global" \ -H "x-opencode-session: $SESSION" \ -H "x-session-id: $SESSION" \ -H "x-session-affinity: $SESSION" \ -H "x-opencode-session-id: $SESSION" \ -d '{ "model": "mimo-v2.6-flash-free", "messages": [{"role": "user", "content": "Say ok"}], "max_tokens": 30, "stream": true, "stream_options": {"include_usage": true}, "tools": [ ... 四个工具定义 ... ] }'2.6 免费模型
通过<endpoint>/inference/v1/models可以拉取当前免费模型列表。2026 年 10 月的清单(部分):
text
mimo-v2.6-flash-free nemotron-3.5-lightning-free nemotron-3.5-super-vl-free beam-free deepseek-v4.1-flash-free glm-5.3-flash-free jev-1.13-free longcat-2.5-preview-free space-bunny-free big-pickle(deepseek 别名)
清单会变,以实时拉取为准。
3. 服务 A:为什么规则总在变
时间线:
| 时间 | 变更 | 表现 |
|---|---|---|
| 9 月中旬 | 加工具指纹校验 | 不带四个工具 403 |
| 10 月 13 日 | 收紧 session 反转时间戳校验 | 之前只查格式,现在查反转位 |
| 10 月 14 日 | 端点迁移,头部增减 | 旧路径 403,x-opencode-request从必需变禁用 |
这三次变更导致网上静态教程全部失效。第一次变更后还能靠格式蒙混,第二次变更后随机 session 全废,第三次变更后端点全错。
适配这类服务必须有跟进机制。静态文档写完就开始过期。
4. 服务 B:匿名直连
服务 B 的请求构造简单:
bash
curl -sS "<endpoint>/chat/completions" \ -H "Content-Type: application/json" \ -H "User-Agent: <任意>" \ -d '{ "model": "<model>:free", "messages": [{"role": "user", "content": "Say ok"}], "max_tokens": 30, "stream": false }'无需 key、无需会话标识、无需工具指纹。匿名可用,限速约 200 请求/时/IP。
模型清单带:free后缀,覆盖 MiniMax、GLM、open 等系列。
注意:同类的 NVIDIA 免费端点(nemotron 系列)不要碰。官方文档标注警告,实测挂起超时。
5. 网关适配的两种方案
5.1 配置型网关
通过配置文件定义上游,只能设置静态头部。
适合:服务 B 这类无需动态会话的端点。
不适合:服务 A。静态写死 session 会被识别为异常,返回 403 或 429。
5.2 专用适配进程
需要动态生成会话的服务,需要一个中间进程:
text
客户端 → 适配进程 → 目标服务
适配进程职责:
每次请求前生成新的 session(反转时间戳 + 随机后缀)
组装完整头部(四个会话头同值)
补齐请求体的工具指纹
强制
stream: true和stream_options转发并回传
配置型网关做不到这些。要么写适配进程,要么放弃服务 A 用服务 B。
5.3 请求体转换
部分网关会标准化请求体(比如强制stream: false)。服务 A 要求stream: true,需要在适配层覆盖。
6. 通用原则
6.1 请求要与预期客户端一致
服务端校验的本质是判断“请求是否来自它预期的客户端”。适配时注意:
UA 格式与当前版本一致
会话标识的生成规则一致
请求体声明的能力与客户端实际发送的一致
多余的头部可能触发拒绝
6.2 动态生成
会话标识、时间戳字段必须动态生成。静态值会被识别。
6.3 跟进版本
端点、头部、会话格式都会变。需要对照当前版本的服务端实现调整。
6.4 优先选简单的
能匿名直连的服务(服务 B 这类)优先用。适配成本高、需要持续跟进的服务,除非有特别价值,否则不值得投入。
本文记录请求构造的方法与原理。不同服务的规则会随版本变化,具体参数以当前实测为准。使用第三方免费端点时,请遵守各服务的使用条款。