Skip to content

Latest commit

 

History

History
455 lines (340 loc) · 28.8 KB

File metadata and controls

455 lines (340 loc) · 28.8 KB

Build 通道

AI Studio 的 Build 应用经官网宿主页调用 MakerSuite 代理 RPC 访问 Gemini API,这部分调用使用与 Playground 分开计算的额度。服务把 Build 作为与 Playground 对等的生成通道:同一账户同时拥有两份额度,生成请求按账户与通道的组合调度。本文定义代理 RPC 的 wire、请求头与 WAA、模型目录与资格、调度与冷却、Gemini API JSON 的逐字段映射、响应解码、错误与额度处理,以及通道的配置与显示。WAA proof 的生成见 WAA 实现,Playground 的 GenerateContent 见 协议规范。

1. 官网机制与通道配置

Build 应用运行在 *.scf.usercontent.goog 的 blob 沙箱 iframe 中。宿主页先向 iframe 发送 bootstrap 消息与 MessagePort,iframe 内的 shim 接管匹配 https://generativelanguage.googleapis.com/.*、https://ai.studio/.* 与 applet:.* 的 fetch,再通过端口发送 fetch、websocket_open、get_host_url、get_model_quota 等消息。用户选择付费 API key 时,宿主页携带该 key 直连 Gemini API;AIStudio2API 实现无 key 的 MakerSuite 代理分支。

宿主页转发前要求 navigator.userActivation.hasBeenActive 为真,同时满足累计 30 次可信 mousemove、5 次可信 keydown 或处于移动设备中的任一条件。未满足时 iframe 的 fetch 保持等待;这个状态只存在于页面桥接层,代理 RPC 的字段和请求头不携带激活标记。服务由账户 Worker 直接发送代理 RPC,以 RPC wire 为实现边界,无需复制 iframe 的交互门控。

Gemini API 请求 代理 RPC
:streamGenerateContent ProxyStreamedCall
其他方法 ProxyUnaryCall
Files 与缓存上传 ProxyUnaryFileApiCall
Live WebSocket 宿主页 WebChannel 转发

服务接入 ProxyStreamedCall 与 ProxyUnaryCall 上的生成、embedding 和模型目录,以及宿主页 WebChannel 转发的 Live 与实时音乐。ProxyUnaryFileApiCall 的处理见“能力路由与边界”。

配置与显示

UPSTREAM_CHANNELS 列出启用的生成通道:

值 通道
playground 官网 Playground 的 GenerateContent
build Build 应用代理的 Gemini API 调用
  • 默认值为 playground,build;逗号分隔,去除空白并转小写;至少一个,不能重复,其他值在配置校验时报错
  • 列表顺序即同一账户内的通道顺序
  • 配置来源为 .env、管理页面设置中的“上游通道”(至少保留一个,保存为 playground,build 顺序)或 PUT /api/config 的 upstream_channels;保存值在下一次启动生成服务时生效

BUILD_NATIVE_NONSTREAM=true 为默认值:Gemini、Chat、Responses、Anthropic、Interactions、图片和语音端点的非流式请求在每个账户上优先使用 Build 通道,执行 ProxyUnaryCall 与 :generateContent。Build 通道未启用、该账户的 Build 目录不支持模型或该账户的 Build 额度冷却时,由同一账户的其余可用通道执行;文件引用与专用能力按其 Playground 路由执行。关闭该选项后按原通道顺序调度,选中 Build 的非流式请求仍执行单次调用。

Playground GenerateContent 的响应为 repeated 流帧,完整收集后转换为公开非流式响应。日志的“上游调用”记录实际通道、native / stream 模式和 RPC;非流式请求使用流式传输时,以 WARN 记录“回退流式”与原因。Build 明确返回单次方法不支持时,尝试该通道的 ProxyStreamedCall;参数错误按原错误返回。

通道在以下位置显示:

位置 字段
GET /v1/models(OpenAI 格式)、GET /v1beta/models(Gemini 格式)、管理端 GET /api/models 与管理页面模型列表 模型对象的 channels
GET /api/requests 与管理页面请求列表 请求当前尝试的 channel
请求日志 request.channel 与管理页面日志 请求实际使用的通道
GET /api/cooldowns 与管理页面冷却列表 channel 与去掉 build: 前缀的 model_id

2. 代理 RPC 与 WAA

两个 RPC 位于 MakerSuite 服务:

https://alkalimakersuite-pa.clients6.google.com/$rpc/google.internal.alkali.applications.makersuite.v1.MakerSuiteService/ProxyStreamedCall
https://alkalimakersuite-pa.clients6.google.com/$rpc/google.internal.alkali.applications.makersuite.v1.MakerSuiteService/ProxyUnaryCall

请求

protobuf field 内容
1 Gemini API 路径
2 请求体 JSON 字符串;GET 为查询参数的 JSON 对象
3 WAA proof;模型目录请求为 null
4 HTTP 方法,只用于 ProxyUnaryCall
["/v1beta/models/<MODEL_ID>:streamGenerateContent", "<GEMINI_API_JSON>", "<WAA_PROOF>"]
["/v1beta/models/<MODEL_ID>:generateContent", "<GEMINI_API_JSON>", "<WAA_PROOF>", "POST"]

非流式请求以及 AccessModes 非空且 Free 权益不能使用的模型,经 ProxyUnaryCall 与 :generateContent;其余流式请求经 ProxyStreamedCall 与 :streamGenerateContent。

请求头

代理 RPC 的请求头集合、顺序与取值和 Playground 文本请求的 GenerateContent 相同:content-type、x-goog-api-key、x-goog-authuser、x-user-agent、x-aistudio-visit-id、x-goog-ext-519733851-bin、authorization 与账户 Cookie。Playground 图片请求不带 x-goog-ext-519733851-bin,Build 代理请求生成图片时仍携带该头。权益头 X-AIStudio-G1-Tier 只随 ProxyUnaryCall 发送(Pro 为 TIER1、Ultra 为 TIER2、Plus 为 TIER0,Free 不带),ProxyStreamedCall 不带该头。需订阅权益的模型经 ProxyStreamedCall 调用时上游返回 HTTP 403 与 Code 7。

WAA

proof 位于 field 3,由账户的同一个 WAA Worker 生成,与 Playground 共用 VM。binding 为 field 1 与 field 2 以单个空格连接:

/v1beta/models/<MODEL_ID>:streamGenerateContent {"contents":[...],"generationConfig":{...}}

摘要为 binding 的 SHA-256 小写十六进制。受保护请求由 Worker 发送,Camoufox 后端经页面原生 fetch,纯 Go 后端经账户固定出口的 Go HTTP。

响应

ProxyUnaryCall 成功响应为一个 ProxyResponse:

["<GenerateContentResponse JSON>"]

ProxyStreamedCall 响应的 field 1 为 repeated ProxyResponse;流中出错时响应列表之后带 google.rpc 状态 [code, message]:

[[["<GenerateContentResponse JSON>"], ["<GenerateContentResponse JSON>"]]]
[[["<GenerateContentResponse JSON>"]], [8, "<MESSAGE>"]]

ProxyResponse 的索引 0 是 JSON 字符串正文,索引 2 是 Base64 字节正文,两者取其一。每个正文是一段完整的 Gemini API GenerateContentResponse。流式解码器在响应列表中每出现一个完整元素时立即解码。

embedding

POST /v1/embeddings、:embedContent 与 :batchEmbedContents 都经 ProxyUnaryCall 调用上游 :batchEmbedContents;上游的 :embedContent 与 /v1beta/openai/embeddings 返回 404。

["/v1beta/models/<MODEL_ID>:batchEmbedContents", "{\"requests\":[{\"content\":{\"role\":\"user\",\"parts\":[{\"text\":\"hello\"}]},\"taskType\":\"RETRIEVAL_QUERY\",\"outputDimensionality\":256,\"model\":\"models/<MODEL_ID>\"}]}", "<WAA_PROOF>", "POST"]
  • 每条请求都带 model,taskType、title、outputDimensionality 有值时写入
  • 上游单批最多 100 条;更多输入按顺序分批发送,各批取 fresh proof,向量与 tokenCount 按输入顺序合并
  • 响应为 {"embeddings":[{"values":[...]}],"tokenCount":"<N>"};向量数与输入数不一致时返回协议错误;tokenCount 计为请求的输入 token

Live 与实时音乐

Build 应用的 Gemini Live WebSocket 由宿主页经 WebChannel 转发:

https://webchannel-alkalimakersuite-pa.clients6.google.com/v1/proxy:proxyBidiStreamedCall

握手、前向 POST、backchannel、terminate 的 query、form 与帧格式和 Playground BidiGenerateContent 相同,见 协议规范。每条客户端消息为 [路径, Gemini Live JSON, WAA proof]:proof 位于 field 3,binding 为路径与 JSON 文本以空格连接,setup 和之后的每条消息各取一次 fresh proof。

方法 路径
bidiGenerateContent /ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent
bidiGenerateMusic /ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateMusic

Live setup 与 Playground setup 的语义一致:

{"setup":{"model":"models/<MODEL_ID>","generationConfig":{"responseModalities":["AUDIO"],"speechConfig":{"voiceConfig":{"prebuiltVoiceConfig":{"voiceName":"Zephyr"}}},"mediaResolution":"MEDIA_RESOLUTION_MEDIUM"},"tools":[{"functionDeclarations":[...]}],"sessionResumption":{"handle":"<SESSION_TOKEN>"},"contextWindowCompression":{"triggerTokens":"104857","slidingWindow":{"targetTokens":"52428"}},"inputAudioTranscription":{},"outputAudioTranscription":{}}}
  • tools 只在声明函数时出现,函数参数写入 parametersJsonSchema;无 session token 时 sessionResumption 为 {}
  • 输出模态为 audio,不接受 translation 与 transcription
  • 实时音乐 setup 只有 {"setup":{"model":"models/<MODEL_ID>"}},不接受 tools、translation、transcription 与 text 输出
公开客户端帧 Gemini Live JSON
text {"realtimeInput":{"text":"..."}}
audio realtimeInput.audio,mimeType 为 audio/pcm;rate=16000
image realtimeInput.video,mimeType 为 image/jpeg
media_end {"realtimeInput":{"audioStreamEnd":true}}
tool_response toolResponse.functionResponses[{id,name,response}],非对象结果包装为 {"result":...}
music_prompts clientContent.weightedPrompts
music_config musicGenerationConfig
playback playbackControl:PLAY、PAUSE、STOP、RESET_CONTEXT

Live 会话只接受前五种帧,实时音乐会话只接受后三种。

backchannel envelope 的 payload 是 ProxyResponse 列表,每个元素的索引 0 是一条 Gemini Live 服务端 JSON;["noop"]、["close"] 与 {"__sm__":{"status":...}} 和 Playground 相同:

[[3,[["{\"serverContent\":{\"outputTranscription\":{\"text\":\"Hello\"}}}"]]]]
服务端字段 公开事件
setupComplete setup_complete
serverContent.modelTurn.parts[].text text;thought 为真时为 provider,raw 为 {"kind":"reasoning","text":...}
serverContent.modelTurn.parts[].inlineData media,Live 音频为 audio/pcm;rate=24000
serverContent.audioChunks[] media,实时音乐为 audio/l16;rate=48000;channels=2
serverContent.inputTranscription、outputTranscription input_transcription、output_transcription
serverContent.interrupted、generationComplete、turnComplete interrupted、generation_complete、turn_complete
toolCall.functionCalls[] 每个调用一条 tool_call
toolCallCancellation.ids tool_call_cancellation
usageMetadata usage,raw 为 Gemini API JSON
goAway go_away
sessionResumptionUpdate session_resumption
其他 provider
  • native audio 模型在轮次结束后经 sessionResumptionUpdate 下发 newHandle

一条消息内的事件顺序与 Playground 相同:setup、模型内容、转写、打断与完成标志、函数调用、usage、go away、恢复令牌。

3. 模型、资格与调度

Build 目录

账户同步模型目录时,Playground ListModels 成功后,Build 通道启用的账户再经 ProxyUnaryCall 读取 Gemini API 模型列表:

["/v1beta/models", "{\"pageSize\":\"200\"}", null, "GET"]

响应含 nextPageToken 时把它写入查询对象的 pageToken 继续读取,最多 10 页。每个模型条目映射为:

Gemini API 字段 目录字段
name 去掉 models/ 的 ID
displayName、description 名称与描述
inputTokenLimit、outputTokenLimit token 上限
supportedGenerationMethods methods;含 generateContent 时设置聊天能力
thinking 思考能力

Build 目录保存在账户内存中,不写入 runtime-state.json。读取失败时该账户本轮目录同步记为失败,进入待重试集合,按 30 秒周期重试。

通道资格

账户的 Build 通道可以承担模型 M 的请求,需要同时满足:

  1. 请求是普通生成:方法为 generateContent,没有 capability 限定、资源绑定、专用 scope 或 Playground 固定要求
  2. UPSTREAM_CHANNELS 启用 build
  3. 账户的 Build 目录包含 M 且 methods 含 generateContent
  4. M 出现在任一账户的 Playground 目录时:M 不是 Interactions 或转录模型,且账户权益满足 M 的 AccessModes
  5. M 只在 Build 目录中时:M 不是只能配合 Computer Use 工具的模型

以下请求只使用 Playground:CountTokens、Playground 目录中的 Live 与 Robotics 模型、Veo、转录、Interactions 模型、带 Drive 文件引用的生成、带多说话人 mode 的 TTS。

Playground 目录没有的 embedding(embedContent)、Live(bidiGenerateContent)与实时音乐(bidiGenerateMusic)模型只经 Build 通道调用:UPSTREAM_CHANNELS 启用 build,且账户的 Build 目录包含该模型与方法时,账户可以承担请求。aqa 等其他方法不进入 Build 通道。

公开目录

公开目录是已启用通道的并集。Build 独有且至少一个启用账户可经 Build 调用的可生成、embedding、Live 与实时音乐模型加入公开目录;embedding 模型的 methods 为 batchEmbedContents, embedContent,实时模型为 bidiGenerateContent 或 bidiGenerateMusic。Build 未启用时这些模型不在目录中,请求返回模型不存在。模型出现在 Playground 目录时,Build 请求使用 Playground 目录的默认参数与能力;Build 独有模型的默认输出上限为 outputTokenLimit,思考能力取 thinking。每个模型的 channels 列出至少一个启用账户可调用它的通道。

调度与冷却

候选单位为账户与通道的组合,组合按账户 ID 升序、同一账户内按 UPSTREAM_CHANNELS 顺序排列。调度仍先尝试已有热 Worker 的账户,再按需启动待机账户的 Worker,规则见 开发与贡献。

策略 组合推进
round-robin 每个模型记录上次选中的账户与通道,从其后的组合开始
fill-first 始终从首个可用组合开始
  • 并发槽位、Worker 与 WAA 按账户共享,两个通道占用同一组请求槽位
  • 冷却按通道记录:Playground 为 <模型>,Build 为 build:<模型>;Live 媒体输入的 scope 为 bidi-media:<模型>,经 Build 时为 build:bidi-media:<模型>;全局 * 冷却对两个通道都生效
  • 账户只有在其全部支持的通道都冷却时才视为该模型冷却
  • 一个通道返回额度错误后写入该通道冷却;同一账户另一通道可用时,请求在同一账户的另一通道重试,不计为已尝试账户
  • 全部候选组合冷却时,最早恢复时间在 1 分钟内的请求排队等待,更晚的请求返回 HTTP 429 rate_limit_exceeded,消息给出最早恢复时间
  • 生成成功在该通道的 scope(<模型> 或 build:<模型>)上写入 verified

附件与文件引用

输入 Build Playground
内联附件 直接写入 inlineData 上传到本次生成账户的 Drive,以 file ID 引用
YouTube 链接 fileData external media Part
Drive 文件引用 不接受 使用文件所属账户

带 Drive 文件引用的生成固定走 Playground。文件所属账户没有可调度的 Playground 候选,或其冷却在 1 分钟后才恢复时,文件临时复制到其他账户,由该账户的 Playground 生成,请求结束后回收副本。

4. 请求映射

Build 请求与 Playground 共用同一预处理:工具可用性校验、模型媒体默认值、TTS 台词处理、参数校验与目录默认值。停止序列不发送,由本地匹配截断(见本节“停止序列、usage 与终态”)。请求体顶层字段:

字段 取值
contents 规范 contents
systemInstruction system 非空时为 {"parts":[{"text":"<SYSTEM>"}]}
tools 工具声明
toolConfig 同时声明函数与 Google 工具时为 {"includeServerSideToolInvocations":true}
generationConfig 生成参数
safetySettings 安全设置
  • safetySettings 默认为骚扰、仇恨、色情、危险四类 OFF,请求中的类别按名称覆盖或追加;图片路由只发送请求中的类别
{
  "contents": [{"role": "user", "parts": [{"text": "Reply OK"}]}],
  "systemInstruction": {"parts": [{"text": "你是诊断助手"}]},
  "generationConfig": {
    "maxOutputTokens": 512,
    "temperature": 0.2,
    "thinkingConfig": {"includeThoughts": true, "thinkingLevel": "LOW"}
  },
  "safetySettings": [
    {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "OFF"},
    {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "OFF"},
    {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "OFF"},
    {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "OFF"}
  ]
}

contents

user 与 tool 角色写为 user,assistant 写为 model,没有 part 的 content 省略。user 文本中的 YouTube 链接先转为 external media part。

规范 Part Gemini API Part
text text;带说话人或风格时附 speechMetadata {speaker, style};思考文本带 thought: true
inline data inlineData {mimeType, data},data 为标准 Base64
external media fileData {mimeType, fileUri}
Drive file 返回参数错误
function call functionCall {name, args, id?}
function result functionResponse {name, response, id?}
executable code executableCode {language, code}
code execution result codeExecutionResult {outcome, output?}
只有签名 text: ""
  • thought signature 写入 Part 的 thoughtSignature;函数调用没有签名时写 skip_thought_signature_validator
  • function result 缺少名称时,按 call ID 关联当前轮尚未返回结果的调用,未匹配且只剩一个调用时使用其名称
  • 函数参数与结果是 JSON 对象时原样写入,其他 JSON 值封装为 {"result":<VALUE>}
  • 代码执行结果的 outcome 不是 OUTCOME_OK 时 output 写错误文本

tools

工具 Gemini API
函数声明 {"functionDeclarations":[{"name","description?","parametersJsonSchema?"}]},JSON Schema 原样发送
Google Search、Image Search {"googleSearch":{...}};请求 Image Search 时带 searchTypes {imageSearch, webSearch?};时间范围写 timeRangeFilter {startTime, endTime}(RFC 3339 UTC)
Code Execution {"codeExecution":{}}
URL Context {"urlContext":{}}
Google Maps {"googleMaps":{}}

工具选择为默认或 auto 时发送工具声明,none 时省略 tools,其他取值返回参数错误。

generationConfig

字段 取值
maxOutputTokens 请求值或目录默认值
temperature、topP、topK、seed 请求值或目录默认值
responseMimeType 请求值
responseSchema protobuf JSON 形式的 Schema
responseModalities 大写模态名
imageConfig {aspectRatio?, imageSize?}
speechConfig 单声音为 voiceConfig.prebuiltVoiceConfig.voiceName;多说话人为 multiSpeakerVoiceConfig.speakerVoiceConfigs[{speaker, voiceConfig}]
thinkingConfig includeThoughts: true、thinkingBudget、thinkingLevel
  • maxOutputTokens 按模型上限校验;带语音配置且未显式设置时不发送
  • responseSchema 复用 Playground 的 Schema 校验与规范化:嵌套 type 使用大写枚举,字符串 const 转为 enum,null 联合转为 nullable
  • responseModalities 为图片模型补 IMAGE、TEXT,为 TTS 与音乐模型补 AUDIO
  • 可设置分辨率的图片模型的 imageSize 默认 1K
  • thinkingConfig 只在模型支持思考时发送

thinking level 由 Playground 枚举换算:Low=LOW、Medium=MEDIUM、High=HIGH、Minimal=MINIMAL。reasoning_effort、预算与等级之间的换算规则与 Playground 相同。

TTS 模型带 speech_metadata 能力时,说话人: 台词 文本按多说话人配置拆成带 speechMetadata 的分段;其他 TTS 模型把分段的说话人与风格折叠回台词文本。Build 代理请求的 multiSpeakerVoiceConfig 只含 speakerVoiceConfigs,带 mode 的请求走 Playground。

Playground 私有字段在 Build 请求中不发送:账户时区、GenerateContent 与 generation config 的固定槽、可设置分辨率图片模型的默认工具槽。

5. 响应、终止与 usage

每段 GenerateContentResponse 解码为规范事件:

字段 事件
text(thought 为假) text
text(thought: true) reasoning
inlineData(thought 为假) media,Base64 解码为字节
executableCode executable code
codeExecutionResult code execution result;OUTCOME_OK 时 output 为输出,否则为错误
functionCall tool call,args 缺失时为 {}
只有 thoughtSignature 的 Part thought signature
citationMetadata.citationSources citation(uri、title、startIndex、endIndex)
groundingMetadata grounding
usageMetadata usage
finishReason usage 与 finish
  • Part 的 thoughtSignature 附在该 Part 产生的事件上
  • 带 thought: true 的 inlineData 是图片模型思考过程中的草图,不作为输出媒体
  • groundingMetadata 读取 webSearchQueries、searchEntryPoint {renderedContent, sdkBlob}、groundingChunks 的 web、retrievedContext、maps(uri、title、text、placeId)、groundingSupports(segment 的 partIndex、startIndex、endIndex、text,groundingChunkIndices,confidenceScores)与 googleMapsWidgetContextToken
  • usageMetadata 的 totalTokenCount 大于 0 时记录:promptTokenCount 为输入,candidatesTokenCount 为输出,thoughtsTokenCount 为思考,toolUsePromptTokenCount 为工具;缺少 candidatesTokenCount 时输出为总数减去其余三项
  • 第一次出现 finishReason 时先发出最近的 usage,再发出 finish;终止原因为去掉 FINISH_REASON_ 前缀后的小写枚举名,与 Playground 的规范终止原因一致,UNSPECIFIED 为 unspecified
  • 没有 candidate 且带 promptFeedback.blockReason 时返回 prompt feedback 错误,原因为去掉 BLOCK_REASON_ 前缀的小写枚举名
  • candidate 数量不是 1、正文不是 JSON 或 Base64 无效时返回带路径的协议错误;流结束时没有 finishReason 同样为协议错误

停止序列、usage 与终态

Build 与 Playground 的事件流经过同一处理:

  1. 停止序列不发送给上游;本地在 text 事件中匹配,命中时截断正文并关闭上游流,终态为 stop_sequence
  2. 带停止序列的请求并行调用同账户的 Playground CountTokens,命中时用计数的输入总数加已输出内容构造 usage;计数失败时省略 usage
  3. 上游没有返回 usage 时在本地统计输入、工具声明、思考与输出
  4. usage 在 finish 之前发出

6. 错误、额度与能力边界

HTTP 非 200 响应解码为协议错误,保留 HTTP 状态、google.rpc code、消息与 ErrorInfo 元数据。错误正文有两种形状:

[7, "The caller does not have permission"]
[null, [7, "The caller does not have permission"]]

ProxyStreamedCall 流尾状态的 code 为 0 表示成功,其他 code 按下表换算 HTTP 状态:

code HTTP
3、9 400
4 504
5 404
7 403
8 429
13 500
14 503
16 401
其他 502
  • HTTP 429 按元数据或消息识别分钟限额与每日限额:分钟限额冷却到窗口重置,每日限额冷却到下一个额度日;非全局限额写入 build:<模型>,全局限额写入账户 *
  • 流尾错误按换算后的 HTTP 状态处理;首个上游语义事件之前的 401、403、404、429 与 5xx 进入换号、换通道与冷却流程,之后的错误以流内 error 结束
  • HTTP 403 与 Code 7 保留账户与模型资格,请求切换到其他账户
  • 模型不存在返回 404 Code 5;只能配合 Computer Use 的模型在纯文本请求时返回 400 Code 3,这类模型不进入 Build 通道

能力路由与边界

能力 当前处理
Live WebSocket Playground BidiGenerateContent 或 Build WebChannel
Files 与缓存上传(ProxyUnaryFileApiCall) 不使用
embedding 上游 batchEmbedContents
Interactions(/v1beta/interactions 返回 404) Playground CreateInteractionStream
Free 账户的订阅图片模型 按 AccessModes 排除
付费 API key 直连 不使用
  • Live 与实时音乐按模型所在目录选择通道,见“通道资格”;附件处理见“附件与文件引用”;embedding 见“embedding”
  • Free 账户调用订阅图片模型时上游返回 403

7. 实现位置

路径 职责
internal/config/config.go UPSTREAM_CHANNELS 解析与校验
internal/app/channels.go 配置到调度池通道的转换
internal/aistudio/channel.go 通道定义、冷却 scope、通道资格、组合排序与公开目录 channels
internal/aistudio/accounts.go 候选分类、租约通道、全部冷却错误
internal/aistudio/build.go 请求编码、代理 wire、binding、响应与流尾解码、Build 目录
internal/aistudio/generate.go 通道分派、共用预处理、停止序列与 usage
internal/aistudio/service.go 受保护发送与 Build proof field
internal/aistudio/upload.go Build 通道保留内联附件
internal/aistudio/build_embed.go embedding 编码、分批、发送、解码与换号
internal/aistudio/build_bidi.go Build Live 与实时音乐的代理外层、客户端帧编码与 backchannel 解码
internal/aistudio/webchannel.go WebChannel 会话按租约通道选择地址、proof field、帧编码与解码
internal/app/embeddings.go embedding 候选、通道日志与输入 token
internal/api/embeddings.go、internal/api/live.go OpenAI 与 Gemini embedding 端点、实时音乐客户端帧
internal/app/runtime.go 重试、冷却写入、同账户换通道、文件引用复制
internal/app/admin.go、internal/api 冷却、请求、日志与模型目录中的通道字段
web/src 设置页通道选择与各列表的通道显示

协议实现

新增一个 Build 代理方法或把现有公开能力接到 Build 时,按以下顺序落点:

  1. 在 internal/aistudio/build.go 定义 Gemini API 路径、HTTP 方法、JSON 请求体与 ProxyStreamedCall 或 ProxyUnaryCall 外层;binding 始终取最终路径和最终 JSON 文本,避免编码后再改正文
  2. 在 internal/aistudio/channel.go 声明候选资格与 Playground 固定条件;文件归属、专用 scope、模型方法和账户权益必须在取得租约前确定
  3. 在 internal/aistudio/generate.go 复用规范请求预处理,并为 Build 编码器只映射 Gemini API 公开字段;Playground 私有槽位留在 Playground 编码器
  4. 在 DecodeBuildStream 或 DecodeBuildUnary 把完整 Gemini API 响应转成规范事件;流式外层按完整 ProxyResponse 元素切分,不能按网络 chunk 猜测 JSON 边界
  5. 把错误映射到统一的 HTTP 状态、重试阶段与通道冷却 scope;首个语义事件之后的错误只能作为流内终态
  6. 将模型 channels、请求 channel 与冷却 channel 暴露到管理 API 和页面,使实际路由可以直接观察

最小验收矩阵

范围 必须确认的结果
通道选择 playground、build 单独启用和同时启用时,目录与实际路由一致
额度隔离 一个通道进入模型冷却后,同一账户的另一个通道仍可承担请求;全局冷却同时作用于两者
协议 Chat、Responses、Anthropic 与 Gemini 的非流式、流式正文和终态一致
内容 system、多轮、函数、Google 工具、图片输入、TTS 与图片输出按模型能力编码
生命周期 取消、客户端断开、停止序列与流尾错误都释放租约和账户并发槽位
可观测性 模型目录、请求列表、请求日志和冷却列表显示同一个实际通道
embedding 与实时 embedding、Live 与实时音乐经 Build 返回预期向量、音频与终态,请求日志通道为 build

调试代理正文时先保存最终 Gemini API JSON、外层数组和解码后的 google.rpc 状态。HTTP 200 只表示代理 RPC 到达;业务通过条件是收到预期内容、合法 usage 和唯一终态。