Stream Content
Gemini 流式内容生成,通过 SSE 实时接收生成内容。
POST /v1beta/models/{model}:streamGenerateContent
本接口与非流式 generateContent 使用相同的 Gemini REST JSON 字段命名:generationConfig、inlineData、mimeType 等均使用 camelCase,不要与 snake_case 字段混用。
请求示例
- Curl
- Python
- TypeScript
- Java
- Go
- PHP
- Ruby
- C#
curl -N "https://gw.opentoken.io/v1beta/models/gemini-3-pro-image:streamGenerateContent?alt=sse" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPEN_TOKEN_KEY" \
-d '{
"contents": [{"parts": [{"text": "写一首关于 AI 的短诗"}]}],
"generationConfig": {"temperature": 0.9}
}'
import json
import os
import requests
response = requests.post(
"https://gw.opentoken.io/v1beta/models/gemini-3-pro-image:streamGenerateContent?alt=sse",
headers={
"Authorization": f"Bearer {os.environ['OPEN_TOKEN_KEY']}",
"Content-Type": "application/json",
},
json={
"contents": [{"parts": [{"text": "写一首关于 AI 的短诗"}]}],
"generationConfig": {"temperature": 0.9},
},
stream=True,
)
response.raise_for_status()
for line in response.iter_lines(decode_unicode=True):
if line and line.startswith("data: "):
print(json.loads(line[6:]), flush=True)
const apiKey = process.env.OPEN_TOKEN_KEY;
if (!apiKey) {
throw new Error("OPEN_TOKEN_KEY is not set");
}
const response = await fetch(
"https://gw.opentoken.io/v1beta/models/gemini-3-pro-image:streamGenerateContent?alt=sse",
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
contents: [{ parts: [{ text: "写一首关于 AI 的短诗" }] }],
generationConfig: { temperature: 0.9 },
}),
},
);
if (!response.ok || !response.body) {
throw new Error(`Request failed: ${response.status} ${response.statusText}`);
}
let buffer = "";
for await (const chunk of response.body.pipeThrough(new TextDecoderStream())) {
buffer += chunk;
const lines = buffer.split(/\r?\n/);
buffer = lines.pop() ?? "";
for (const line of lines) {
if (line.startsWith("data: ")) {
console.log(JSON.parse(line.slice(6)));
}
}
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class StreamContent {
public static void main(String[] args) throws Exception {
String apiKey = System.getenv("OPEN_TOKEN_KEY");
if (apiKey == null || apiKey.isBlank()) {
throw new IllegalStateException("OPEN_TOKEN_KEY is not set");
}
String json = "{\"contents\":[{\"parts\":[{\"text\":\"写一首关于 AI 的短诗\"}]}],"
+ "\"generationConfig\":{\"temperature\":0.9}}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://gw.opentoken.io/v1beta/models/gemini-3-pro-image:streamGenerateContent?alt=sse"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<java.io.InputStream> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofInputStream());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new IllegalStateException("Request failed: " + response.statusCode());
}
try (var reader = new java.io.BufferedReader(new java.io.InputStreamReader(response.body()))) {
reader.lines().filter(line -> line.startsWith("data: ")).forEach(System.out::println);
}
}
}
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
apiKey := os.Getenv("OPEN_TOKEN_KEY")
if apiKey == "" { panic("OPEN_TOKEN_KEY is not set") }
body := []byte(`{"contents":[{"parts":[{"text":"写一首关于 AI 的短诗"}]}],"generationConfig":{"temperature":0.9}}`)
request, err := http.NewRequest(http.MethodPost,
"https://gw.opentoken.io/v1beta/models/gemini-3-pro-image:streamGenerateContent?alt=sse",
bytes.NewReader(body))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer "+apiKey)
request.Header.Set("Content-Type", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
if response.StatusCode < 200 || response.StatusCode >= 300 {
errorBody, _ := io.ReadAll(response.Body)
panic(fmt.Sprintf("request failed: %s: %s", response.Status, errorBody))
}
scanner := bufio.NewScanner(response.Body)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "data: ") { fmt.Println(line) }
}
if err := scanner.Err(); err != nil { panic(err) }
}
<?php
$apiKey = getenv('OPEN_TOKEN_KEY');
if (!$apiKey) {
throw new RuntimeException('OPEN_TOKEN_KEY is not set');
}
$buffer = '';
$ch = curl_init('https://gw.opentoken.io/v1beta/models/gemini-3-pro-image:streamGenerateContent?alt=sse');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => false,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'contents' => [['parts' => [['text' => '写一首关于 AI 的短诗']]]],
'generationConfig' => ['temperature' => 0.9],
], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
CURLOPT_WRITEFUNCTION => function ($curl, $chunk) use (&$buffer) {
$buffer .= $chunk;
while (($position = strpos($buffer, "\n")) !== false) {
$line = rtrim(substr($buffer, 0, $position), "\r");
$buffer = substr($buffer, $position + 1);
if (str_starts_with($line, 'data: ')) {
echo $line, PHP_EOL;
}
}
return strlen($chunk);
},
]);
if (curl_exec($ch) === false) {
throw new RuntimeException(curl_error($ch));
}
curl_close($ch);
require 'json'
require 'net/http'
require 'uri'
api_key = ENV.fetch('OPEN_TOKEN_KEY')
uri = URI('https://gw.opentoken.io/v1beta/models/gemini-3-pro-image:streamGenerateContent?alt=sse')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request['Authorization'] = "Bearer #{api_key}"
request['Content-Type'] = 'application/json'
request.body = {
contents: [{ parts: [{ text: '写一首关于 AI 的短诗' }] }],
generationConfig: { temperature: 0.9 },
}.to_json
buffer = +''
http.request(request) do |response|
raise "Request failed: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
response.read_body do |chunk|
buffer << chunk
while (newline = buffer.index("\n"))
line = buffer.slice!(0, newline + 1).strip
puts line if line.start_with?('data: ')
end
end
end
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var apiKey = Environment.GetEnvironmentVariable("OPEN_TOKEN_KEY");
if (string.IsNullOrWhiteSpace(apiKey))
{
throw new InvalidOperationException("OPEN_TOKEN_KEY is not set");
}
using var client = new HttpClient();
using var request = new HttpRequestMessage(
HttpMethod.Post,
"https://gw.opentoken.io/v1beta/models/gemini-3-pro-image:streamGenerateContent?alt=sse");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
request.Content = new StringContent(JsonSerializer.Serialize(new
{
contents = new[] { new { parts = new[] { new { text = "写一首关于 AI 的短诗" } } } },
generationConfig = new { temperature = 0.9 },
}), Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
while (await reader.ReadLineAsync() is { } line)
{
if (line.StartsWith("data: ")) Console.WriteLine(line);
}
请求参数
| 参数 | 位置 | 类型 | 必填 | 描述 |
|---|---|---|---|---|
| model | 路径 | string | 是 | 用于生成内容的模型名称,对应 URL 中的 {model}。 |
| alt | 查询参数 | string | 否 | 示例使用 sse,使响应按 Server-Sent Events 格式返回。 |
| contents | 请求体 | array | 是 | 当前对话内容。单轮请求通常只包含一条用户内容,多轮请求可包含对话历史。 |
| contents[].role | 请求体 | string | 否 | 内容角色,用户输入通常为 user,模型内容为 model。 |
| contents[].parts | 请求体 | array | 是 | 内容片段数组,可包含 text、inlineData、fileData 等字段。 |
| generationConfig | 请求体 | object | 否 | 生成配置,例如 temperature、topP、topK、maxOutputTokens 等。 |
| safetySettings | 请求体 | array | 否 | 按安全类别设置内容过滤规则。每个安全类别最多设置一次。 |
| tools | 请求体 | array | 否 | 模型可使用的工具列表,例如函数调用或代码执行工具。 |
| toolConfig | 请求体 | object | 否 | 请求中工具的统一配置。 |
| systemInstruction | 请求体 | object | 否 | 系统指令,目前仅支持文本内容。 |
| cachedContent | 请求体 | string | 否 | 用作上下文的缓存内容名称,格式为 cachedContents/{cachedContent}。 |
| serviceTier | 请求体 | string | 否 | 请求使用的服务层级。 |
| store | 请求体 | boolean | 否 | 配置该请求的日志记录行为。 |
流式响应结构
响应由多个 SSE 事件组成。每个事件均以 data: 开头,后面是一个独立的 GenerateContentResponse JSON 对象。例如:
data: {"candidates":[{"content":{"parts":[{"text":"在电路与星光之间,"}],"role":"model"},"index":0}]}
data: {"candidates":[{"content":{"parts":[{"text":"思想悄然生长。"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":8,"candidatesTokenCount":12,"totalTokenCount":20}}
单个事件中的主要字段如下:
| 字段 | 类型 | 描述 |
|---|---|---|
| candidates | array | 模型生成的候选内容。提示被拦截时可能不存在。 |
| candidates[].content | object | 当前事件返回的内容。 |
| candidates[].content.role | string | 内容角色,模型响应通常为 model。 |
| candidates[].content.parts | array | 当前事件的内容片段,可包含 text、inlineData 等字段。 |
| candidates[].finishReason | string | 模型停止生成的原因,通常在候选内容结束时出现,例如 STOP、MAX_TOKENS 或 SAFETY。 |
| candidates[].safetyRatings | array | 候选内容的安全评分。 |
| candidates[].index | integer | 候选内容在候选列表中的索引。 |
| promptFeedback | object | 与提示内容过滤相关的反馈。提示被拦截时可能没有 candidates。 |
| usageMetadata | object | Token 用量信息,通常在流式响应后段或最终事件中返回。 |
| modelVersion | string | 实际用于生成响应的模型版本。 |
| responseId | string | 本次响应的标识符。 |
| modelStatus | object | 当前模型的状态信息。 |
不能假设每个事件都包含 candidates[0].content.parts[0].text,也不能假设 parts 的类型或顺序。客户端应逐个处理 SSE 事件,遍历 candidates[].content.parts[],并分别检查 text、inlineData 等实际存在的字段。