一、合作说明
AIWriteX 当前已经支持代理模式接入。代理服务器可以为客户端统一下发文本模型与图片模型配置,用户无需手动填写多套模型配置,即可按代理提供的能力直接使用。
推荐的合作切入方式
- 先接入文本模型配置与文本中转。
- 在稳定运行后增加图片模型能力。
- 后续再叠加套餐、后台和统计能力。
当前适合的代理业务模式
- 仅文本代理:接入门槛最低,最适合快速落地。
- 文本 + 图片代理:适合提供更完整的图文生成能力。
本页面面向代理合作方,集中说明当前支持的代理合作能力、适合的合作模式、推荐的落地路径,以及代理服务器与客户端之间的接口字段含义与使用规则。
AIWriteX 当前已经支持代理模式接入。代理服务器可以为客户端统一下发文本模型与图片模型配置,用户无需手动填写多套模型配置,即可按代理提供的能力直接使用。
代理合作方请下载代理版本客户端进行部署与测试。
建议代理服务器提供一个配置接口,例如:POST /v1/client-config。
客户端会以 JSON 方式向代理服务器发送请求。
{
"agency_id": "D002",
"license_id": "D002-XXXX-XXXX",
"machine_id": "4c7d8a2b-xxxx-xxxx",
"client_version": "1.0.0",
"contract_version": 2,
"auth_preference": {
"header": "Authorization",
"scheme": "Bearer"
},
"capabilities": {
"text": true,
"image": true,
"wechat_relay": true
}
}
| 字段 | 含义 | 建议用途 |
|---|---|---|
| agency_id | 代理标识 | 用于识别当前属于哪个代理体系 |
| license_id | 用户授权标识 | 用于查询套餐、状态、额度与权限 |
| machine_id | 设备标识 | 用于风控、设备绑定或审计记录 |
| client_version | 客户端版本号 | 用于版本判断和字段灰度控制 |
| contract_version | 代理配置协议版本 | 当前建议返回 v2 字段,用于统一授权和能力分流 |
| auth_preference | 客户端推荐的授权头格式 | 默认使用 Authorization: Bearer <token> |
| capabilities | 客户端支持的能力范围 | 用于决定返回文本、图片、微信中继中的哪些项 |
代理服务器应返回一个 JSON 对象。
{
"mode": "agency",
"ttl_seconds": 300,
"version": "2026-04-24",
"authorization": {
"header": "Authorization",
"scheme": "Bearer",
"token": "agt_unified_user_token"
},
"locked": {
"text": true,
"image": true,
"wechat_relay": true
},
"text": {
"enabled": true,
"api_base": "https://proxy.example.com/llm/v1",
"models": ["gpt-4.1-mini", "gpt-4.1"],
"model_index": 0,
"max_tokens": 32768
},
"image": {
"enabled": true,
"api_base": "https://proxy.example.com/image/v1",
"models": [
{ "id": "gpt-image-1" },
{ "id": "flux-dev" }
],
"model_index": 0,
"resolution": "1024x1024"
},
"wechat": {
"relay": {
"enabled": true,
"url": "https://relay.example.com",
"authorization": {
"header": "Authorization",
"scheme": "Bearer"
}
}
},
"policy": {
"text": { "enabled": true, "quota": "server_enforced" },
"image": { "enabled": true, "quota": "server_enforced" },
"wechat_relay": { "enabled": true, "audit": true, "rate_limit": "server_enforced" }
}
}
| 字段 | 界面含义 |
|---|---|
| enabled | 是否显示文本模型的代理配置入口 |
| api_base | 文本模型地址 |
| authorization.token | 文本模型统一授权 Token;如未单独返回,则使用顶层 authorization.token |
| models | 文本模型下拉列表 |
| model_index | 默认选中的文本模型 |
| max_tokens | 默认最大输出长度 |
| 字段 | 界面含义 |
|---|---|
| enabled | 是否显示图片模型的代理配置入口 |
| api_base | 图片模型地址 |
| authorization.token | 图片模型统一授权 Token;如未单独返回,则使用顶层 authorization.token |
| models | 图片模型下拉列表 |
| model_index | 默认选中的图片模型 |
| resolution | 默认分辨率 |
| 字段 | 界面含义 |
|---|---|
| wechat.relay.url | 微信 API 专用中继地址,例如 https://relay.example.com |
| wechat.relay.authorization | 微信中继统一授权配置;未写 token 时复用顶层 authorization.token |
文本、图片、微信中继互不依赖。代理服务器返回某个有效能力段时,客户端只覆盖该能力段;没有返回的能力段保持客户端原有本地配置,不会被清空或回退。
客户端只负责携带授权信息和应用有效配置;用户是否可使用 text、image、wechat_relay,应由代理服务端根据授权码、统一 Token、套餐、额度、风控状态进行判断。
代理服务端建议同时提供配置下发、文本模型代理、图片模型代理和微信 API 中继四类接口。三种能力必须独立判断,用户能用哪个能力由服务端按授权、套餐、额度和风控状态决定。
| 接口 | 用途 |
|---|---|
| POST /v1/client-config | AIWriteX 客户端拉取代理配置 |
| /llm/v1/* | 文本模型代理,建议兼容 OpenAI Chat Completions 风格 |
| /image/v1/* | 图片模型代理,建议兼容 OpenAI Images 风格 |
| /wechat-relay/relay/* | 微信 API 专用中继,只转发微信官方 cgi-bin 接口 |
USERS = {
"D002-XXXX-XXXX": {
"enabled": True,
"token": "agt_user_token_xxx",
"capabilities": {
"text": {"enabled": True, "quota": 1000000},
"image": {"enabled": True, "quota": 200},
"wechat_relay": {"enabled": True, "quota": 5000},
},
}
}
import hmac
import os
from typing import Any, Dict, Optional
import requests
from fastapi import FastAPI, Header, HTTPException, Request, Response
from pydantic import BaseModel
app = FastAPI(title="AIWriteX Agency Server")
PUBLIC_BASE_URL = os.environ.get("PUBLIC_BASE_URL", "https://proxy.example.com").rstrip("/")
UPSTREAM_TEXT_BASE = os.environ.get("UPSTREAM_TEXT_BASE", "https://api.openai.com/v1").rstrip("/")
UPSTREAM_TEXT_KEY = os.environ.get("UPSTREAM_TEXT_KEY", "")
UPSTREAM_IMAGE_BASE = os.environ.get("UPSTREAM_IMAGE_BASE", "https://api.openai.com/v1").rstrip("/")
UPSTREAM_IMAGE_KEY = os.environ.get("UPSTREAM_IMAGE_KEY", "")
WECHAT_CGI_BASE_URL = "https://api.weixin.qq.com/cgi-bin"
USERS = {
"D002-XXXX-XXXX": {
"enabled": True,
"token": "agt_user_token_xxx",
"capabilities": {
"text": {"enabled": True},
"image": {"enabled": True},
"wechat_relay": {"enabled": True},
},
}
}
HOP_BY_HOP_HEADERS = {
"connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
"te", "trailers", "transfer-encoding", "upgrade", "host",
"content-length", "content-encoding",
}
class ClientConfigRequest(BaseModel):
agency_id: str = ""
license_id: str = ""
machine_id: str = ""
client_version: str = ""
contract_version: int = 2
auth_preference: Dict[str, Any] = {}
capabilities: Dict[str, bool] = {}
def get_user_by_license(license_id: str) -> Optional[Dict[str, Any]]:
user = USERS.get(license_id)
if not user or not user.get("enabled"):
return None
return user
def get_token_from_authorization(authorization: Optional[str]) -> str:
scheme, _, token = (authorization or "").partition(" ")
return token.strip() if scheme.lower() == "bearer" else ""
def get_user_by_token(token: str) -> Optional[Dict[str, Any]]:
if not token:
return None
for user in USERS.values():
if user.get("enabled") and hmac.compare_digest(user.get("token", ""), token):
return user
return None
def require_capability(authorization: Optional[str], capability: str) -> Dict[str, Any]:
user = get_user_by_token(get_token_from_authorization(authorization))
if not user:
raise HTTPException(status_code=401, detail="授权无效")
cap = user.get("capabilities", {}).get(capability, {})
if not cap.get("enabled"):
raise HTTPException(status_code=403, detail="当前能力未开通或已被禁用")
return user
def forward_headers(request: Request, upstream_token: str = "") -> Dict[str, str]:
headers = {
name: value
for name, value in request.headers.items()
if name.lower() not in HOP_BY_HOP_HEADERS and name.lower() != "authorization"
}
if upstream_token:
headers["Authorization"] = f"Bearer {upstream_token}"
return headers
def response_headers(headers: Dict[str, str]) -> Dict[str, str]:
return {name: value for name, value in headers.items() if name.lower() not in HOP_BY_HOP_HEADERS}
def proxy_request(request: Request, body: bytes, upstream_url: str, upstream_token: str, timeout: int) -> Response:
upstream_response = requests.request(
method=request.method,
url=upstream_url,
params=request.url.query,
data=body,
headers=forward_headers(request, upstream_token),
timeout=timeout,
)
return Response(
content=upstream_response.content,
status_code=upstream_response.status_code,
headers=response_headers(upstream_response.headers),
media_type=upstream_response.headers.get("content-type"),
)
@app.post("/v1/client-config")
def client_config(payload: ClientConfigRequest):
user = get_user_by_license(payload.license_id)
if not user:
return {"mode": "local"}
token = user["token"]
caps = user.get("capabilities", {})
response: Dict[str, Any] = {
"mode": "agency",
"ttl_seconds": 300,
"authorization": {"header": "Authorization", "scheme": "Bearer", "token": token},
"locked": {},
"policy": {},
}
if caps.get("text", {}).get("enabled"):
response["locked"]["text"] = True
response["policy"]["text"] = {"enabled": True, "quota": "server_enforced"}
response["text"] = {
"enabled": True,
"api_base": f"{PUBLIC_BASE_URL}/llm/v1",
"models": ["gpt-4.1-mini"],
"model_index": 0,
"max_tokens": 32768,
}
if caps.get("image", {}).get("enabled"):
response["locked"]["image"] = True
response["policy"]["image"] = {"enabled": True, "quota": "server_enforced"}
response["image"] = {
"enabled": True,
"api_base": f"{PUBLIC_BASE_URL}/image/v1",
"models": [{"id": "gpt-image-1"}],
"model_index": 0,
"resolution": "1024x1024",
}
if caps.get("wechat_relay", {}).get("enabled"):
response["locked"]["wechat_relay"] = True
response["policy"]["wechat_relay"] = {"enabled": True, "audit": True, "rate_limit": "server_enforced"}
response["wechat"] = {
"relay": {
"enabled": True,
"url": f"{PUBLIC_BASE_URL}/wechat-relay",
"authorization": {"header": "Authorization", "scheme": "Bearer"},
}
}
return response if any(key in response for key in ("text", "image", "wechat")) else {"mode": "local"}
@app.api_route("/llm/v1/{path:path}", methods=["GET", "POST"])
async def llm_proxy(path: str, request: Request, authorization: Optional[str] = Header(default=None)):
require_capability(authorization, "text")
body = await request.body()
return proxy_request(request, body, f"{UPSTREAM_TEXT_BASE}/{path}", UPSTREAM_TEXT_KEY, timeout=120)
@app.api_route("/image/v1/{path:path}", methods=["GET", "POST"])
async def image_proxy(path: str, request: Request, authorization: Optional[str] = Header(default=None)):
require_capability(authorization, "image")
body = await request.body()
return proxy_request(request, body, f"{UPSTREAM_IMAGE_BASE}/{path}", UPSTREAM_IMAGE_KEY, timeout=180)
@app.get("/wechat-relay/relay/meta/egress-ip")
def wechat_relay_egress_ip(authorization: Optional[str] = Header(default=None)):
require_capability(authorization, "wechat_relay")
for url in ("https://api.ipify.org", "http://icanhazip.com", "https://ifconfig.me/ip"):
try:
resp = requests.get(url, timeout=5)
resp.raise_for_status()
if resp.text.strip():
return {"status": "success", "ip": resp.text.strip()}
except Exception:
pass
raise HTTPException(status_code=502, detail="获取中继出口 IP 失败")
@app.api_route("/wechat-relay/relay/cgi-bin/{wx_path:path}", methods=["GET", "POST"])
async def wechat_relay(wx_path: str, request: Request, authorization: Optional[str] = Header(default=None)):
require_capability(authorization, "wechat_relay")
if not wx_path or wx_path.startswith("/") or ".." in wx_path.split("/"):
raise HTTPException(status_code=400, detail="微信 API 路径无效")
upstream_response = requests.request(
method=request.method,
url=f"{WECHAT_CGI_BASE_URL}/{wx_path}",
params=request.url.query,
data=await request.body(),
headers=forward_headers(request),
timeout=60,
)
return Response(
content=upstream_response.content,
status_code=upstream_response.status_code,
headers=response_headers(upstream_response.headers),
media_type=upstream_response.headers.get("content-type"),
)
https://api.weixin.qq.com/cgi-bin/,不允许任意目标 URL。/v1/client-config 是否返回 mode: agency。text.enabled、text.api_base、模型列表和顶层 Token。image.enabled、image.api_base、models[].id 和顶层 Token。wechat.relay.url 是否可访问,以及 Token 是否通过 wechat_relay 能力鉴权。客户端会缓存代理服务器返回的配置快照。
缓存到期后,客户端会重新请求代理配置。
只有在某一项已经显示并切换到“自定义”时,界面上才会显示刷新按钮。没有有效代理配置时,不会显示刷新按钮。
如果需要进一步了解代理合作、实施方式、套餐能力或商务合作细节,可通过官网页面中的商务联系方式进一步沟通。