AI Studio 的 Build 应用经官网宿主页调用 MakerSuite 代理 RPC 访问 Gemini API,免费账户在这部分调用与 Playground 上计入同一模型的同一份每日请求次数,例如 gemini-3.8-flash 为每日 100 次。服务把 Build 作为与 Playground 对等的生成通道,生成请求按账户与通道的组合调度。本文定义代理 RPC 的 wire、请求头与 WAA、模型目录与资格、调度与冷却、Gemini API JSON 的逐字段映射、响应解码、错误与额度处理,通道的配置与显示,以及经 -build 模型调用的写代码助手通道。Playground 的 GenerateContent 见 协议规范。
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 代理生成,build作为旧通道保留;逗号分隔,去除空白并转小写;至少一个,不能重复,其他值在配置校验时报错 - 列表顺序即同一账户内的通道顺序
- 配置来源为
.env、管理页面设置中的“上游通道”(至少保留一个,保存为playground,build顺序)或PUT /api/config的upstream_channels;保存值在下一次启动生成服务时生效
BUILD_NATIVE_NONSTREAM=true 为默认值:Gemini、Chat、Responses、Anthropic、Interactions、图片和语音端点的非流式请求在每个账户上优先使用 Build 通道,执行 ProxyUnaryCall 与 :generateContent。Build 通道未启用、该账户的 Build 目录不支持模型、该账户的 Build 额度冷却或 Build 已对该账户停用生成时,由同一账户的其余可用通道执行;文件引用与专用能力按其 Playground 路由执行。关闭该选项后按原通道顺序调度,选中 Build 的非流式请求仍执行单次调用。
Playground GenerateContent 的响应为 repeated 流帧,完整收集后转换为公开非流式响应。日志的“上游调用”记录实际通道、native / stream 模式和 RPC;启用 Build 通道时,非流式请求使用流式传输以 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 |
两个 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。
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,SpiderMonkey 后端经账户固定出口的 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。流式解码器在响应列表中每出现一个完整元素时立即解码。
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
Build 计数经 ProxyUnaryCall 调用 /v1beta/models/<模型>:countTokens,请求体为 {"generateContentRequest":{model,contents,systemInstruction?,tools?,generationConfig?{mediaResolution}}},响应读取 totalTokens、systemInstructionsTokens、contentTokens、toolTokens 与 promptTokensDetails。
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、恢复令牌。
账户同步模型目录时,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 |
思考能力 |
modelStage |
模型阶段 |
Build 目录保存在账户内存中,不写入 runtime-state.json。读取失败时该账户本轮目录同步记为失败,进入待重试集合,按 30 秒周期重试。
账户的 Build 通道可以承担模型 M 的请求,需要同时满足:
- 请求是普通生成:方法为
generateContent,没有 capability 限定、资源绑定、专用 scope 或 Playground 固定要求 UPSTREAM_CHANNELS启用build- 账户的 Build 目录包含 M 且 methods 含
generateContent - M 出现在任一账户的 Playground 目录时:M 不是 Interactions 或转录模型,且账户权益满足 M 的 AccessModes
- M 只在 Build 目录中时:M 不是只能配合 Computer Use 工具的模型
以下请求只使用 Playground:Playground 目录中的 Live 与 Robotics 模型、Veo、转录、Interactions 模型、带 Drive 文件引用的生成、带多说话人 mode 的 TTS。Playground 目录中模型的 CountTokens 使用 Playground,各 Part 的 mediaResolution 不同时经 Build 计数;Build 未启用时与生成一样经 Playground 发送,各 Part 按模型默认分辨率计数。
带 File Search 工具的生成只经 Build 通道,没有可用的 Build 账户或 Build 拒绝请求时返回对应错误。
MODEL_CHANNELS 限定的模型只经所列通道调用。限定为 build 的模型收到带文件引用或只有 Playground 支持的参数的 generateContent 请求时,在选择账户前返回 400,错误写明该模型限定的通道。
Playground 目录没有的 embedding(embedContent)、Live(bidiGenerateContent)、实时音乐(bidiGenerateMusic)模型及这类模型的计数(countTokens)只经 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 列出至少一个启用账户可调用它的通道,启用账户的权益都不满足 AccessModes 时不含 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,消息给出最早恢复时间,Retry-After为距最早恢复的秒数 - 生成成功在该通道的 scope(
<模型>或build:<模型>)上写入verified
| 输入 | Build | Playground |
|---|---|---|
| 内联附件 | 直接写入 inlineData |
上传到本次生成账户的 Drive,以 file ID 引用;同一账户相同内容复用已上传的文件 |
| YouTube 链接 | fileData |
external media Part |
| Drive 文件引用 | 不接受 | 使用文件所属账户 |
带 Drive 文件引用的生成固定走 Playground。文件所属账户没有可调度的 Playground 候选,或其冷却在 1 分钟后才恢复时,文件临时复制到其他账户,由该账户的 Playground 生成,请求结束后回收副本。
Build 请求与 Playground 共用同一预处理:工具可用性校验、模型媒体默认值、TTS 台词处理、参数校验与目录默认值。停止序列不发送,由本地匹配截断(见本节“停止序列、usage 与终态”)。请求体顶层字段:
| 字段 | 取值 |
|---|---|
contents |
规范 contents |
systemInstruction |
每段系统指令一个 text Part |
tools |
工具声明 |
toolConfig |
内置工具(Google 工具、MCP、File Search)与函数同时声明,或请求 includeServerSideToolInvocations 时为 {"includeServerSideToolInvocations":true},图片模型不发送;带检索位置或语言时加 retrievalConfig {latLng?, languageCode?} |
generationConfig |
生成参数 |
safetySettings |
安全设置 |
serviceTier |
SERVICE_TIER_<请求值大写> |
store、labels |
请求值 |
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"}
]
}user 与 tool 角色写为 user,assistant 写为 model,没有 part 的 content 省略。YOUTUBE_LINKS=true 时 user 文本中的 YouTube 链接先转为 external media part。
| 规范 Part | Gemini API Part |
|---|---|
| text | text;带说话人或风格时附 speechMetadata {speaker, style};思考文本带 thought: true |
| inline data | inlineData {mimeType, data, displayName?},data 为标准 Base64 |
| external media | fileData {mimeType, fileUri, displayName?} |
| Drive file | 返回参数错误 |
| function call | functionCall {name, args, id?} |
| function result | functionResponse {name, response, id?} |
| executable code | executableCode {id?, language, code} |
| code execution result | codeExecutionResult {id?, outcome, output?} |
| 服务端工具调用与结果 | toolCall {id, toolType?, toolName?, args?}、toolResponse {id, toolType?, response?} |
| 只有签名 | text: "" |
- thought signature 写入 Part 的
thoughtSignature;函数调用没有签名时写skip_thought_signature_validator - function result 缺少名称时,按 call ID 关联当前轮尚未返回结果的调用,未匹配且只剩一个调用时使用其名称
- 函数参数与结果是 JSON 对象时原样写入,其他 JSON 值封装为
{"result":<VALUE>} - 代码执行失败时
output为错误输出 - Part 的分辨率写为
mediaResolution {level}
| 工具 | Gemini API |
|---|---|
| 函数声明 | {"functionDeclarations":[{"name","description?","parametersJsonSchema?","responseJsonSchema?"}]},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":{"enableWidget"?:true}} |
| Computer Use | {"computerUse":{environment?,excludedPredefinedFunctions?,disabledSafetyPolicies?,enablePromptInjectionDetection?}} |
| MCP | {"mcpServers":[{name,streamableHttpTransport{url,headers?,timeout?,sseReadTimeout?,terminateOnClose?}}]} |
| File Search | {"fileSearch":<请求值>} |
工具选择为默认或 auto 时发送工具声明,none 时省略 tools,其他取值返回参数错误。
| 字段 | 取值 |
|---|---|
maxOutputTokens |
请求值或目录默认值 |
temperature、topP、topK、seed |
请求值或目录默认值 |
responseMimeType |
请求值 |
responseSchema |
protobuf JSON 形式的 Schema |
responseFormat |
约束为原样 JSON Schema 时为 {"text":{"mimeType":"APPLICATION_JSON","schema":<Schema>}},此时不发送 responseMimeType |
responseModalities |
大写模态名 |
mediaResolution |
请求级分辨率 |
imageConfig |
{aspectRatio?, imageSize?} |
speechConfig |
单声音为 voiceConfig.prebuiltVoiceConfig.voiceName 或 voiceConfig.voice;多说话人为 multiSpeakerVoiceConfig.speakerVoiceConfigs[{speaker, voiceConfig}];语言为 languageCode |
audioTranscriptionConfig |
请求的逐词时间与说话人设置 |
enableEnhancedCivicAnswers |
请求为真时发送 |
thinkingConfig |
includeThoughts(请求要求输出思考时为 true)、thinkingBudget、thinkingLevel |
maxOutputTokens按模型上限校验;带语音配置且未显式设置时不发送responseSchema复用 Playground 的 Schema 校验与规范化:嵌套type使用大写枚举,字符串const转为enum,null 联合转为nullableresponseModalities为图片模型补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 的固定槽、可设置分辨率图片模型的默认工具槽。
每段 GenerateContentResponse 解码为规范事件:
| 字段 | 事件 |
|---|---|
text(thought 为假) |
text |
text(thought: true) |
reasoning |
inlineData(thought 为假) |
media,Base64 解码为字节 |
fileData |
media,外部 URI |
executableCode |
executable code |
codeExecutionResult |
code execution result;OUTCOME_OK 时 output 为输出,否则为错误 |
functionCall |
tool call,args 缺失时为 {} |
toolCall、toolResponse |
服务端工具调用与结果 |
text Part 的 audioTranscription |
正文的转录元数据(text、speakerLabel、words) |
只有 thoughtSignature 的 Part |
thought signature |
citationMetadata.citationSources |
citation(uri、title、license、startIndex、endIndex) |
groundingMetadata |
grounding |
urlContextMetadata.urlMetadata |
grounding 的 URL 读取结果(retrievedUrl、urlRetrievalStatus) |
usageMetadata |
usage |
modelVersion |
事件的 provider model |
responseId |
该帧全部事件的上游 responseId |
turnToken |
该帧全部事件的轮次令牌 |
debugInfo |
该帧全部事件的调试信息 |
候选的 detailedFinishReason |
finish 的细分结束原因 |
候选与 promptFeedback 的 safetyRatings |
finish 与输入拒绝的安全评级 |
finishReason |
usage 与 finish |
- Part 的
thoughtSignature附在该 Part 产生的事件上 - 带
thought: true的inlineData是图片模型思考过程中的草图,作为带 thought 标记的媒体事件 groundingMetadata读取webSearchQueries、imageSearchQueries、searchEntryPoint {renderedContent, sdkBlob}、groundingChunks的web(uri、title、domain、siteName、searchResultMapping)、image(sourceUri、imageUri、title、domain)、retrievedContext、maps(uri、title、text、placeId、placeAnswerSources)、chunk 的origin与referenceId、groundingSupports(segment 的partIndex、startIndex、endIndex、text,groundingChunkIndices,confidenceScores,renderedParts,origin)与googleMapsWidgetContextTokenusageMetadata的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与读取中断相同,报告为上游流中断与已接收帧数
Build 与 Playground 的事件流经过同一处理:
- 停止序列不发送给上游;本地在 text 事件中匹配,命中时截断正文并关闭上游流,终态为
stop_sequence - 带停止序列的请求并行调用同账户的 Playground
CountTokens,命中时用计数的输入总数加已输出内容构造 usage;计数失败时省略 usage - 上游没有返回 usage 时在本地统计输入、工具声明、思考与输出
- usage 在 finish 之前发出
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:<模型>,全局限额写入账户*;列出的额度项上限全部为limit: 0时冷却类型为“无配额” - HTTP 400、Code 3 发生在可经 Playground 完成的请求上且 Playground 通道启用时,请求改经 Playground 重试且不再发往 Build:该账户的 Playground 可用时由同一账户执行,冷却时换其他账户,需要 Build 的结构化输出改用 Playground 载体;只启用 Build 时返回 Build 的错误。Playground 返回首个事件后对被拒账户按
build:<模型>递增冷却 1、5、15、30、60 分钟,该范围成功一次后从 1 分钟开始;Playground 同样拒绝时返回 Playground 的错误;带 File Search 的请求不改经 Playground - Build 对账户返回
PROXY_GENERATION_DISABLED(HTTP 400、Code 3)时,错误原样返回客户端,不改经 Playground,也不写入冷却 - 流尾错误按换算后的 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
| 路径 | 职责 |
|---|---|
internal/config/config.go |
UPSTREAM_CHANNELS 解析与校验 |
internal/app/channels.go |
配置到调度池通道的转换 |
internal/aistudio/channel.go |
通道定义、冷却 scope、通道资格、组合排序与公开目录 channels;-build 模型只用 assistant 通道 |
internal/aistudio/accounts.go |
候选分类、租约通道、全部冷却错误 |
internal/aistudio/build.go |
请求编码、代理 wire、binding、响应与流尾解码、Build 目录 |
internal/aistudio/generate.go |
通道分派、共用预处理、停止序列与 usage |
internal/aistudio/service.go |
受保护发送、Build 与写代码助手的 binding 和 proof field |
internal/aistudio/upload.go |
Build 通道与写代码助手通道保留内联附件 |
internal/aistudio/assistant.go |
写代码助手的目录条目派生、附件改写、CodeAssistantOffline 编码、RPC 顺序、临时应用清理与任务流解码 |
internal/aistudio/transport_http.go |
写代码助手附件上传 |
internal/aistudio/models.go |
能力码 48 与 -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 时,按以下顺序落点:
- 在
internal/aistudio/build.go定义 Gemini API 路径、HTTP 方法、JSON 请求体与ProxyStreamedCall或ProxyUnaryCall外层;binding 始终取最终路径和最终 JSON 文本,避免编码后再改正文 - 在
internal/aistudio/channel.go声明候选资格与 Playground 固定条件;文件归属、专用 scope、模型方法和账户权益必须在取得租约前确定 - 在
internal/aistudio/generate.go复用规范请求预处理,并为 Build 编码器只映射 Gemini API 公开字段;Playground 私有槽位留在 Playground 编码器 - 在
DecodeBuildStream或DecodeBuildUnary把完整 Gemini API 响应转成规范事件;流式外层按完整 ProxyResponse 元素切分,不能按网络 chunk 猜测 JSON 边界 - 把错误映射到统一的 HTTP 状态、重试阶段与通道冷却 scope;首个语义事件之后的错误只能作为流内终态
- 将模型
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 和唯一终态。
Build 页面的对话面板由写代码助手回复。助手带有服务端的系统提示词,用量计入 Build 对话的额度:免费账户为免费请求,Google AI 订阅账户为订阅额度,与 Playground、Build 应用代理分别计算。服务把它作为第三个通道 assistant,经模型名后缀 -build 调用。
本节的 RPC 都位于 MakerSuite 服务,地址为 https://alkalimakersuite-pa.clients6.google.com/$rpc/google.internal.alkali.applications.makersuite.v1.MakerSuiteService/<RPC>,方法为 POST,正文为 application/json+protobuf 稀疏数组:protobuf field N 对应 JSON 索引 N-1,中间未设置的 field 写 null。
ListModels 行的主能力码(field 65)含 48 的模型,即 Build 设置面板 “Select model to use in Chat” 下拉框列出的模型。目录解析完成后,每个这样的模型派生一个条目,按原顺序追加在该账户目录末尾;目录已有同名 ID 时不派生。
| 字段 | 派生条目 |
|---|---|
| ID | <基础模型 ID>-build |
| 名称 | <基础模型名称> (Build) |
| methods | 基础模型 methods 中的 generateContent 与 countTokens |
| capabilities | assistant_route、function_declarations、google_search,加基础模型具备的 chat_model、thinking、thinking_level |
| 能力选项 | 空 |
| 默认参数 | 输出上限与可选 thinking level 取基础模型,思考开启,默认 level 为 high(3),基础模型不支持 high 时取最接近的可用 level;目录的默认档位为该 level |
| 其余字段 | 取基础模型,含描述、token 上限与 AccessModes |
ListModels: gemini-3.8-flash(主能力码含 48)
目录追加: gemini-3.8-flash-build "Gemini 3.8 Flash (Build)" methods [countTokens, generateContent]
-build模型的候选通道只有assistant;该通道始终启用,UPSTREAM_CHANNELS与BUILD_NATIVE_NONSTREAM只作用于其他模型- 账户的目录含该
-build条目、且账户权益满足基础模型的 AccessModes 时,账户可以承担请求;候选顺序、热 Worker 优先与round-robin、fill-first策略与其他模型相同 - 冷却 scope 为
-build模型 ID 本身,例如gemini-3.8-flash-build,与基础模型的 Playground<模型>、Buildbuild:<模型>分开记录;额度错误、换号与排队按“调度与冷却”与“错误、额度与能力边界”处理 - 模型
channels、请求channel与冷却channel为assistant,管理页面显示为 “Build 对话” - 模型名可以带思考后缀,后缀写在
-build之后,例如gemini-3.8-flash-build(low) - 计数请求把模型换成基础模型 ID,调用 Playground
CountTokens
| 规范请求 | CodeAssistantOffline |
|---|---|
| contents | field 1 |
| system | field 9 自定义指令 |
| 函数声明 | field 9 中系统提示之前的函数调用说明 |
| 思考强度或思考预算 | field 19 的 thinking level |
- 上游请求只携带对话轮、系统提示、函数调用说明与思考强度;生成参数、结构化输出与安全设置使用助手的服务端设置
- 内联附件按官网对话面板上传,对话轮中以 Part field 18
[mime, 上传路径, 文件名, 字节数]引用,文件名为attachment-<序号>,序号按附件在请求中的顺序从 1 开始;上传见“附件上传” CodeAssistantOffline没有客户端函数声明字段,对话轮中的函数调用与结果 Part 不进入模型输入。请求声明函数且 tool choice 不是none时,函数声明以 JSON 写入下面的函数调用说明,放在 field 9 系统提示之前(中间空一行)- 对话轮中的函数调用与结果 Part 转录为代码块文本:同一轮内相邻的调用合为一个
function_calls块,相邻的结果合为一个function_results块,元素分别为{"name","arguments"}与{"name","response"};转录在上传附件与计算 WAA binding 之前完成
You can call the following client functions. They run on the user's machine, not in this workspace, and are the only way to read or change the user's files, run the user's commands or reach the user's services.
<函数声明 JSON>
To call functions, reply with only one fenced block and no other text:
```function_calls
[{"name": "<function name>", "arguments": {<arguments>}}]
```
Function results arrive in a later user turn as a fenced function_results block. Answer normally when no function is needed.
- Google Search 工具不写入请求:写代码助手按需调用内置的
search_web,搜索步骤按“任务流”输出为 grounding;其他 Google 工具在发送前丢弃 - 停止序列在本地匹配截断,见“停止序列、usage 与终态”
- thinking level 为 high
3、medium2、low1、minimal4;未指定时为 high。reasoning_effort、thinkingLevel与 Anthropicoutput_config.effort的xhigh、max按 high,none按 minimal;思考预算<=0、<=1024、<=8192与更大值分别取 minimal、low、medium、high。模型不支持所得 level 时取强度顺序 minimal、low、medium、high 中最接近的可用 level
/_/upload(每个内联附件一次) → 上传路径
CreateApplet → 应用 ID
CodeAssistantOffline(WAA proof) → 任务 ID
StreamCodeAssistantOfflineGeneration → 步骤快照流,读到上游关闭
流关闭后在后台:
CancelCodeAssistantOfflineGeneration (已取得任务 ID 且流没有正常解码完成时)
DeleteApplet
- 请求头的集合、顺序与取值和 Playground 文本请求的
GenerateContent相同;权益头X-AIStudio-G1-Tier只随CodeAssistantOffline发送(Pro 为TIER1、Ultra 为TIER2、Plus 为TIER0,Free 不带) CodeAssistantOffline由账户 WAA Worker 发送;其余四个 RPC 与附件上传经账户的 MakerSuite Go HTTP transport,使用同一账户的 Cookie、UA 与出口- 每个请求新建一个应用,请求结束后删除。上游把同一应用上以前的对话带入后续生成,应用只承载一次请求
- 非流式请求同样读取任务流,收齐后输出;日志的“上游调用”记录
stream模式、RPCCodeAssistantOffline与原因 “Build 写代码助手使用服务端流,收集完成后返回”
附件经 AI Studio 的可续传上传接口写入,两次请求都是 POST,带账户 Cookie、UA、Accept-Language、Origin 与 Referer 为 https://aistudio.google.com、Sec-Fetch-Site: same-origin,不带 Authorization 与 X-Goog-Api-Key。X-Goog-Hash 为附件字节 SHA-256 的标准 Base64,写作 sha256=<BASE64>。
| 步骤 | 地址 | 请求头 | 正文 | 响应 |
|---|---|---|---|---|
| start | https://aistudio.google.com/_/upload |
X-Goog-Upload-Protocol: resumable、X-Goog-Upload-Command: start、X-Goog-Upload-Header-Content-Length、X-Goog-Upload-Header-Content-Type、X-Goog-Hash |
空 | 200,X-Goog-Upload-URL 为上传地址 |
| upload | X-Goog-Upload-URL |
Content-Type、X-Goog-Hash、X-Goog-Upload-Command: upload, finalize、X-Goog-Upload-Offset: 0 |
附件字节 | 200,X-Goog-Upload-Status: final,正文为上传路径 |
/blobstore/prod/makersuite/uploads/<SHA256_HEX>
非 200 响应按“错误、额度与能力边界”换算。上游在任务流的用户轮回显中把上传路径改写为存储内部路径。
[null,["Untitled","Generated by Gemini.",null,null,"default"],null,null,1]| field | 内容 |
|---|---|
| 2 | 应用元数据 [标题, 描述, 首条提示, null, 模板] |
| 5 | 1 |
官网页面在元数据 field 3 写入用户的第一条提示,在 field 6 写入页面 API key,上游据首条提示生成应用的标题与描述;服务发送的元数据 field 3 为 null,正文只到 field 5。响应:
["<APPLET_ID>",["<APPLET_ID>",["1792242943",522276000],["1792242943",522276000],["Untitled","Generated by Gemini.",null,null,"default",null,null,1,[1],null,[1]],1,[1,1,1]]]服务读取索引 0 的应用 ID;缺失或为空时返回协议错误 “缺少应用 ID”。
[[[[[null,"What is 6*7?"]],"user"]],"<WAA_PROOF>",null,null,null,1,null,"models/gemini-3.8-flash","<SYSTEM>",null,null,"<APPLET_ID>",null,[2,null,2],null,1,null,null,[1,null,null,3],null,null,null,null,null,null,null,null,null,"<UUID>"]| field | 内容 |
|---|---|
| 1 | 对话轮,编码与 Playground GenerateContent 的 contents 相同:[[Part...], role],role 为 user 或 model |
| 2 | WAA proof |
| 6 | 1 |
| 8 | models/<基础模型 ID> |
| 9 | 系统提示;system 为空时为 null |
| 12 | 应用 ID |
| 14 | [2,null,2] |
| 16 | 1 |
| 19 | 思考配置 [1,null,null,<thinking level>] |
| 29 | 每个请求新生成的大写 UUID v4 |
WAA binding 与 Playground GenerateContent 相同:contents 各 Part 依次取文本、内联数据的标准 Base64 或 Drive file ID,其余 Part 取空字符串,以单个空格连接;proof 写入 field 2。Camoufox 后端的页面输入框写入 contents 各 Part 的文本,以空格连接。
响应:
["<GENERATION_ID>","<APPLET_ID>"]服务读取索引 0 的任务 ID;缺失或为空时返回协议错误 “缺少任务 ID”。
["<GENERATION_ID>",null,null,"<APPLET_ID>"]响应为流式正文,格式见下一小节。
["<GENERATION_ID>","<APPLET_ID>"]
["<APPLET_ID>"]第一行是 CancelCodeAssistantOfflineGeneration 的正文,第二行是 DeleteApplet 的正文;DeleteApplet 成功响应为 [],之后该应用返回 404。
- 清理在任务流正文关闭时触发,包括正常读完、客户端断开与停止序列截断;
CreateApplet之后任一步失败时同样触发 - 每个请求的清理只执行一次,在后台以独立的 1 分钟期限依次执行 Cancel 与 Delete
- 任一步失败时以 WARN 记录 “Build 写代码助手清理失败”,字段为
account、method、applet与error
响应根数组的 field 1 是消息列表,之后可带 google.rpc 状态 [code, message, details?]。下例为一次完整的纯问答,消息 1 是用户轮回显,消息 2 是完成的模型轮:
[[
[null,null,[[[[[null,"What is the capital of France?"]],"user"]],null,null,null,null,null,["1792220560",486323906],["1792220562",547770000],null,null,["<APPLET_ID>",["1792220558",913217000]],0,1,"models/gemini-4-argon",null,null,"<GENERATION_ID>"]],"<APPLET_ID>"],
[null,null,[[[[[null,"Paris"]],"model"]],null,null,null,null,null,["1792220560",486643491],["1792220571",188407749],null,null,["<APPLET_ID>",["1792220571",308506000]],1,1,"models/gemini-4-argon",null,["19942","45"],"<GENERATION_ID>"]],"<APPLET_ID>"]
]]消息为 [null, null, [步骤...], 应用 ID]。每条消息带若干步骤的当前快照,同一条消息可以同时含用户轮回显与模型轮。步骤字段:
| field | 内容 |
|---|---|
| 1 | 对话轮列表 [[[Part...], role]] |
| 3 | 应用文件修改 |
| 7、8 | 时间戳 ["<秒>", <纳秒>] |
| 10 | 工具动作,例如编译应用;其中 field 19 为 search_web 搜索 [查询, 域名, 摘要, [[标题, 地址]...]] |
| 11 | [应用 ID, 时间戳] |
| 12 | 步骤序号,用户轮为 0 |
| 6 | 失败原因 [消息, 详情] |
| 13 | 状态:1 完成,2 进行中,4 失败 |
| 14 | models/<模型 ID> |
| 16 | 用量 [未命中缓存的输入, 输出, 缓存命中],数值为十进制字符串,缺项为 0 |
| 17 | 任务 ID |
Part 的 field 2 为文本,field 13 为 1 或 true 时是思考。只修改应用文件或执行工具的步骤没有模型轮。
解码:
- 消息列表中每出现一个完整元素立即解码;消息 field 3 为
null时跳过 - 每个步骤读取序号与状态,二者缺失或类型不符时返回带路径的协议错误;field 6 非空时返回上游错误 “生成步骤失败: <消息>”;状态为
1且带用量的步骤按序号记录用量,同一序号以最后一次为准 - 跳过 role 不是
model的对话轮。模型轮的 Part 以(步骤序号,Part 序号)定位,同一步骤内多个模型轮的 Part 序号连续编号 - Part 文本是该位置的完整快照:以上一次快照开头时输出新增部分,与上一次相同时不输出;不以上一次快照开头时返回协议错误 “步骤正文不是上一次快照的延续”
- 思考 Part 的增量输出为 reasoning,其余为 text;text 来自与上一段 text 不同的步骤、且是该 Part 的第一段增量时,前面加
\n\n - 请求声明了函数时,text 中以
```function_calls开头、以换行加```结束的代码块内容为 JSON 数组时,每个元素输出为一个函数调用,调用 ID 为新的 UUID,arguments为null或缺失时为{};内容不是调用数组的代码块按原文输出为 text。可能是代码块开头的结尾文本暂缓输出,直到能判定 - 状态为
1的步骤的 field 10 带search_web时,查询记入webSearchQueries,每个[标题, 地址]记为一个网页来源块;同一步骤只记录一次 - 状态为
1的步骤含模型轮时,回复记为已完成 - 消息列表结束后读取流尾状态,code 0 为成功,其他 code 按“错误、额度与能力边界”的表换算 HTTP 状态
- 流关闭时回复已完成:依次输出暂缓的文本、记录的搜索 grounding、用量之和,再以
stop结束;回复没有完成时返回协议错误 “流结束前没有完成的模型回复”
用量之和:输入为各步骤未命中缓存的输入与缓存命中之和,缓存为缓存命中之和,输出为输出之和,总数为输入加输出。
最后一个步骤完成后,上游约 1 到 14 秒后关闭流,finish 在流关闭后输出。
| 情况 | 处理 |
|---|---|
附件上传、CreateApplet、CodeAssistantOffline 或任务流返回非 200 |
按“错误、额度与能力边界”解码与换算,进入换号与冷却流程,冷却 scope 为 -build 模型 ID |
| 流尾状态非 0 | 同上;首个语义事件之后以流内 error 结束 |
| 步骤 field 6 非空 | HTTP 502,消息为 “生成步骤失败: <上游消息>”,按 5xx 处理 |
| 应用 ID、任务 ID 缺失或流结构不符 | 协议错误,HTTP 502 |
| 流关闭时没有完成的模型回复 | 协议错误,HTTP 502 |