大模型推出思考能力后,推理过程不再是一个黑盒。Gemini API在流式接口streamGenerateContent中提供了专门的机制,把模型的思考过程文本和最终回答分开返回。这套机制依赖includeThoughts参数和part.thought标志,理解清楚响应的分片结构是正确提取思考文本的关键。本文从响应结构入手,结合Python SDK和原生HTTP两种调用方式,完整演示思考过程文本的提取方法。

一、流式响应结构:思考文本到底藏在哪一层
streamGenerateContent返回的不是一次性JSON,而是一连串分片(chunk)。每个分片的基本结构是GenerateContentResponse,里面最核心的字段是candidates数组。每个candidate包含一个content对象,content下又有一个parts数组。模型的输出就是被切成一个个part依次推送给客户端的。
普通文本分片和思考分片的区别就在part对象上。思考文本所在的part会携带一个布尔字段thought,值为true;而最终回答的part没有这个字段,或者值为false。也就是说,判断某个分片是不是思考内容,只需要检查part.thought即可,不需要靠猜测文本内容来区分。
需要注意一点:如果你没有在请求里开启includeThoughts,流式响应中就只会出现最终回答的分片,思考部分被模型内部消化掉了,客户端完全看不到。这是很多开发者反馈“提取不到thought”的最常见原因,不是代码写错了,而是参数没开。
二、Python SDK实现:分段收集思考与回答
用google-genrativeai的Python SDK调用时,先构建请求开启思考输出,然后遍历流式响应逐个检查part。推荐把思考文本和回答文本分别收集到两个缓冲区,流结束后再拼接,这样逻辑清晰且方便后续落盘或展示。
from google import genai
client = genai.Client(api_key="YOUR_API_KEY")
response_stream = client.models.generate_content_stream(
model="gemini-2.5-flash",
contents="一个水池有进水管和出水管,进水管5小时注满,出水管6小时放空,两管同开几小时注满?请详细推理。",
config=genai.types.GenerateContentConfig(
thinking_config=genai.types.ThinkingConfig(
include_thoughts=True # 开启思考过程输出
)
),
)
thought_text = []
answer_text = []
for chunk in response_stream:
for part in chunk.candidates[0].content.parts:
# thought为True表示当前分片是思考过程
if getattr(part, "thought", False):
thought_text.append(part.text)
else:
answer_text.append(part.text)
print("=== 思考过程 ===")
print("".join(thought_text))
print("=== 最终回答 ===")
print("".join(answer_text))</code>代码里有几个细节值得说明。第一,getattr(part, "thought", False)这种写法比直接访问part.thought更稳妥,因为旧版本SDK的part对象可能没有这个属性,直接访问会抛AttributeError。第二,思考分片的粒度比普通分片更细,一次长推理可能产生几十个思考分片,逐个append再拼接是最可靠的方式。第三,如果模型把思考内容组织成摘要形式返回,part中还可能出现thoughtSignature字段,它主要用于后续多轮请求的签名校验,提取文本时可以忽略。
三、原生HTTP调用:cURL配合alt=sse解析事件流
不走SDK而直接请求REST接口时,推荐在URL中加上alt=sse参数,这样返回的是标准的Server-Sent Events格式,每个data:行对应一个分片,解析起来比纯JSON数组方便得多。
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent?alt=sse" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: YOUR_API_KEY" \
-d '{
"contents": [{"role": "user", "parts": [{"text": "证明根号2是无理数"}]}],
"generationConfig": {
"thinkingConfig": {
"includeThoughts": true
}
}
}'返回的SSE流中,每个事件都是一个JSON对象。判断思考内容的逻辑和SDK版本完全一致,看part里有没有"thought": true。下面是一个典型分片的样例结构:
{
"candidates": [
{
"content": {
"parts": [
{
"text": "假设根号2是有理数...",
"thought": true
}
],
"role": "model"
},
"index": 0
}
],
"usageMetadata": {
"candidatesTokenCount": 42,
"thoughtsTokenCount": 128
}
}注意usageMetadata里的thoughtsTokenCount字段,它单独统计思考部分消耗的token数量。做成本核算或者用量监控时,务必把这个字段也算进去,因为思考token是实际计费的,只统计正文token会导致账单对不上。
四、常见问题与避坑要点
第一个坑是模型不支持思考输出。includeThoughts目前只在具备推理能力的模型上生效,比如gemini-2.5系列。如果给不支持的模型传了这个参数,有的版本会直接报400错误,有的会静默忽略。上线前先用目标模型跑一遍测试确认行为。
第二个坑是前端渲染时的顺序错乱。思考分片和回答分片在时间上是先思考后回答,但如果你的展示层把两个缓冲区并行渲染,用户可能同时看到两段内容在增长。更好的做法是把思考区域折叠,流式期间只更新折叠面板内部,回答区域在第一个非thought分片到达后再开始渲染。
第三个坑是和函数调用混用。当流中出现functionCall类型的part时,part.text可能为空字符串,直接拼接没问题,但如果你对text做了非空断言就会中断流处理。提取逻辑里对空文本保持容忍,能显著提升代码健壮性。
最后提醒一点,思考文本是模型推理路径的展示,不保证逻辑上毫无跳步,也不应该把它当作可复现的中间结果做程序化依赖。它的价值更多在于调试提示词、观察模型理解偏差,以及给用户展示推理透明度。把定位摆正,这套机制才能在你的应用里发挥真正的作用。
Gemini APIstreamGenerateContent思考过程提取修改时间:2026-09-03 19:30:59