自定义代码渠道

在管理端贴一个普通 spec 类,就能接入任何形态的上游:兼容接口、OAuth 设备码、账密登录、非标 SSE、动态接入地址。只写你要改变的部分,其余沿用平台的配置驱动实现。

问题改中转源码 / 放弃接入代码渠道
上游接口不标准等平台发版支持贴一个 spec 类当场接入,保存即热更新
登录方式特殊自己维护一套登录脚本声明字段 + 授权钩子,轮询/刷新由框架调度
SSE 格式非标整段重写聊天逻辑只写 parse_chunk 一帧解析
只想加一个头/一个字段大动干戈headers / payload 单钩子搞定
usage 统计缺失自己算 token框架按实际内容自动估算,不用管

先记住 7 条规则

  1. class MyChannel:不要继承 BaseProvider / CustomProvider
  2. 方法推荐写 @staticmethod,第一个参数 p 是渠道实例。
  3. 渠道地址在管理端「渠道配置」里填(必填);spec 用 p.base_url 读,不要写死域名。不打外网的 spec 写 REQUIRES_BASE_URL = False 可豁免。
  4. usage 不用你算:上游没回 token 统计时框架自动估算;只有拿到真实 usage 才需要覆盖。
  5. 不要自己 import aiohttp:用 p.send_sse_request(...) / p._make_session(),出站代理、连接复用、请求留痕才生效。
  6. 不要写 PROVIDER_NAME,加载器会用渠道 id 覆盖。贴入的代码以服务进程权限执行,等同部署一个 Python 文件,只允许可信管理员编辑。
  7. OAuth / 多字段账号在 account_schema()["fields"] 声明表单字段;运行时字段写 ACCOUNT_FIELDS。两者自动挂成 p.access_token 这类实例属性。

场景索引:你属于哪种情况?

你的情况你写什么
上游是 OpenAI / Anthropic / Responses / Gemini 兼容接口零钩子(渠道配置即可);源码至少留一个最小 init_auth
只多一个签名头 / 租户头headers
请求体只多一个字段payload
聊天 URL 非标准路径build_url
接入地址登录后动态下发base_url(覆盖全部出站路径)
SSE 字段不是 choices[].deltaparse_chunk
OAuth 设备码登录account_schema + begin_device_flow + poll_device_flow
OAuth 回调登录account_schema + build_auth_start + handle_auth_callback
token 需要定时刷新refresh_auth + SCHEDULED_REFRESH = True
运行时刷新 token 要落库await p.persist_account_fields({...})
账密登录换 ticket / Cookieinit_auth + 需要的聊天/模型钩子
只想拒绝某些模型或消息check_message
上游不认 function callingp.build_tools_prompt + p.parse_tool_calls(解析框架已内置)

钩子速查

认证、账号和授权

钩子签名用途
init_auth(p, is_check=False) -> bool首次使用、失效自愈、体检
check_auth / is_init / health_check(p)检查登录态 / 凭据就绪 / 健康检查
account_schema() -> dict声明前端账号字段和授权入口
begin_device_flow / poll_device_flow(p[, poll_params]) -> dict设备码授权启动与单步轮询
build_auth_start / handle_auth_callback(name, …, cfg)OAuth 跳转与回调
refresh_auth(p, account, cfg) -> dict刷新 token,返回窄写字段

聊天、模型和请求

钩子签名用途
fetch_models(p) -> list[dict]自定义模型列表
stream_chat(p, model_id, messages, **kwargs)自定义流式聊天,yield 统一帧
non_stream_chat(…) -> dict自定义非流式响应
headers / payload(p, …) -> dict请求头 / 请求体后处理
build_url / base_url(p[, kwargs]) -> str覆盖聊天 URL / 全部出站地址
parse_chunk(p, event_str) -> dict覆盖一帧 SSE 解析

回调、配额和生命周期

钩子用途
on_response_headers / update_quota响应头回调 / 从响应头更新配额
check_message / record_message请求前放行判定 / 自定义记账
on_channel_attached / close配置注入后初始化 / 释放资源
clear_conversations清理上游会话

类级开关

开关默认作用
SUPPORTS_TOKEN_AUTO_REFRESHFalse凭据过期后能否自愈;True 时前端标「已过期(等待自动刷新)」
SCHEDULED_REFRESHFalse纳入每日 10:00 主动刷新(需同时写 refresh_auth
SUPPORTS_MULTI_MESSAGESFalse是否把多轮 messages 整批发给上游
TOOLS_AS_PROMPT / TOOLS_PROMPT_FORMATFalse / "xml"上游不认 function calling 时工具说明进 system;形态 xml/json/hermes
REQUIRES_BASE_URLTrue渠道地址是否必填;不打外网的 spec 写 False 豁免
ACCOUNT_FIELDS()账号字段名清单,挂实例属性
APPLY_CLIENT_PRESETTrue是否套客户端伪装头;上游校验自家 CLI 头时写 False 硬关

p 上最常用的东西

属性 / 方法含义
p.base_url管理端「渠道配置」里填写的渠道地址;不要在 spec 里写死
p.api_key / p.username / p.password渠道 / 账号配置派生的凭据
p.proxy / p.timeout_seconds统一出口代理配置 / 请求超时
p._channel渠道领域对象:base_url、protocol、chat_protocols、billing_mode
p._make_session() / p.send_sse_request(...)遵循渠道代理的 HTTP session / SSE 请求(带请求留痕)
p.build_openai_response(id, model, content, …)构造统一 OpenAI 响应;id 传 generate_completion_id()(已注入)
p._raise_upstream_error(error)复用框架错误归一,不要自己复制一份
p.build_tools_prompt / p.prepare_provider_messages / p.parse_tool_calls文本工具三件套:说明 prompt / 拼 system / 文本→工具调用解析
await p.persist_account_fields({...})运行时刷新出的字段窄写回本账号并同步池内实例

流式统一帧

yield {}  # 可选但推荐:先通知主链路上游已接通
yield {"content": "正文增量", "thinking": "", "tool_calls": []}
yield {"content": "", "thinking": "思考增量", "tool_calls": []}
yield {"content": "", "thinking": "", "tool_calls": [{
    "index": 0, "id": "call_1", "type": "function",
    "function": {"name": "search", "arguments": {"q": "天气"}}}]}
# 也可带 usage / finish_reason / done;上游返回真实 usage 时才需要覆盖

可用样例

样例演示内容用到的钩子
最小可跑:EchoChannel原样回吐用户最后一条消息,验证「贴代码→出渠道」链路init_auth / fetch_models / stream_chat
OAuth 设备码授权渠道声明前端字段 + 授权按钮 + 轮询 + 定时刷新令牌;聊天走渠道配置,只用 headers 塞 Authorizationaccount_schema / begin_device_flow / poll_device_flow / refresh_auth / init_auth / headers
OpenAI 兼容反代上游已是标准 /v1/chat/completions;聊天一行不写,只用 init_auth + headers 塞访问密码init_auth / headers / payload
账密登录抓取型(EaiChat 真实实现)账号密码换 ticket 再换 Cookie、非标 SSE;登录态存 Redis、失效自愈、thinking/tool_calls 解析、usage 估算init_auth / check_auth / fetch_models / stream_chat / non_stream_chat / clear_conversations

样例源文件随仓库发布(docs/providers/samples/),可直接复制到「源码」Tab 后按需修改。

常见错误

错误处理
渠道地址留空就保存保存被拒绝;填地址,或写 REQUIRES_BASE_URL = False 声明不打外网
继承 BaseProvider 报错删掉继承,写普通类;框架能力通过 p.xxx 使用
写了 stream_chat 没有 yield流式无内容;至少 yield 统一帧
account_schema 字段名和 account_data 对不上两边 key 必须一致,否则授权后不回填
base_url 写在代码里管理端填的地址不生效;用 p.base_url
自己 import aiohttp 发请求绕开出站代理、连接复用和请求留痕;用 p._make_session()
每个钩子都重写越容易漏协议、工具调用、日志和重试;优先只写一个最小钩子
保存即热更新保存 code 后,后端校验语法、扫描唯一 spec 类并热更新适配器,进程内立即生效无需重启。语法错、无 spec、钩子拼错、误继承基类、渠道地址缺失都会返回 HTTP 400,不落库。代码在服务进程权限下执行,只应由可信管理员编辑;它不是沙箱。

← LLM Server 能力详解 · 渠道配置操作 →