Appearance
15|继续工作:没做完怎么办
上一篇的停止条件覆盖了正常完成和异常停止。但有一种情况它没覆盖:模型没出错,也没回答完,只是被截断了。
报告写了一半,输出长度到了上限,模型停下。这时候不是错误(上一篇讲过,输出 token 耗尽是 incomplete 状态不是异常),也不是正常完成(任务没做完)。本篇讲继续工作:被截断时怎么让模型把剩下的写完,又不陷入无限继续。
先分清三种 token
推理模型的 token 不是单一概念,预算至少分三块。混在一起算会漏掉最大的一项。

一次请求的用量分输入 token、输出 token、推理 token。输入 token 是请求体本身(系统指令、历史、工具定义)。输出 token 是模型可见的回答。推理 token 是模型内部思考消耗,藏在 response.usage.output_tokens_details.reasoning_tokens 里,和输出 token 一样按输出价计费,但不出现在可见回答里。
python
used_input = response.usage.input_tokens
used_output = response.usage.output_tokens
used_reasoning = (
response.usage.output_tokens_details.reasoning_tokens
if response.usage.output_tokens_details else 0
)
total_used = used_input + used_output + used_reasoningreasoning.effort 越高推理 token 越大,账单和延迟同步上升。漏算推理 token 是成本预估偏低的常见原因——表面看输出只有几百 token,实际加上推理可能翻几倍。
这三块共享一个上界:context window。窗口是输入+输出+推理的总容量上限,不是三者各有各的额度。推理占多了,留给输入历史和输出回答的空间就少。
怎么判断被截断了
模型停下来的原因不全是"写完了"。Responses API 里,被截断的停止体现在响应状态上:
python
def is_truncated(response) -> bool:
return (
response.status == "incomplete"
and response.incomplete_details
and response.incomplete_details.reason == "max_output_tokens"
)response.status == "incomplete" 加上 reason == "max_output_tokens",明确表示被输出上限截断了。这是触发继续工作的信号。
要和"主动完成"区分开:response.status == "completed" 是模型自己觉得写完了,强行继续可能画蛇添足。两种处理完全不同,先查 status 再决定动不动。
继续不是追加一句 user 消息
这是继续工作最容易写错的地方。

朴素的做法是追加一句"请继续"作为 user 消息:
python
# 错误做法
state.messages.append({"role": "user", "content": "请继续"})
response = client.responses.create(model=settings.model, input=state.messages)这对推理模型有问题。模型在上一轮产生了内部推理状态,朴素追加会丢失这条推理链——模型从零重新思考,可能重复已输出内容,甚至换个方向重新写。
正确做法是用 previous_response_id 续接,服务端托管了上一轮的状态,包括推理链:
python
CONTINUE_PROMPT = "上文因长度限制中断,请继续完成剩余内容。保持相同的格式和结构。"
def inject_continue(response, client, settings):
follow_up = client.responses.create(
model=settings.model,
instructions=settings.instructions, # 续接时必须重发
input=CONTINUE_PROMPT,
previous_response_id=response.id, # 服务端回放上一轮,含推理状态
max_output_tokens=settings.max_output_tokens,
)
return follow_up无状态场景(store=false 或要自己控上下文)改用手工回放——把上一轮的 response.output items 放进 input,再追加继续提示。OpenAI 提供 encrypted reasoning items,在无状态下把推理状态加密回传,避免推理链断裂。
核心是让模型看到"自己之前写到哪了、想了什么",而不是从空白开始。继续提示也要明确两点:为什么继续("上文因长度限制中断")、怎么继续("保持相同格式"),防止模型换格式或重写全文。
防止无限循环
继续机制最大的风险是无限循环:模型每次继续写一点点,系统每次都触发继续,永远停不下来。
python
class ContinueTracker:
def __init__(self, max_continues=3):
self.max_continues = max_continues
self.continue_count = 0
self.last_output_length = 0
self.stagnant_count = 0
def should_continue(self, current_output):
self.continue_count += 1
if self.continue_count > self.max_continues:
return False # 次数上限
if len(current_output) - self.last_output_length < 50:
self.stagnant_count += 1
if self.stagnant_count >= 2:
return False # 增量停滞
else:
self.stagnant_count = 0
self.last_output_length = len(current_output)
return True三种情况停止继续:次数上限(连续继续超过 max_continues 次)、增量停滞(连续两次继续增量不足 50 字符,说明模型无话可写)、预算耗尽。
和错误恢复的区别
继续工作和上一篇的错误恢复看起来都是"让模型再试一次",但本质不同。
错误恢复处理的是异常(BadRequestError 等),目标是消除错误让请求成功。继续工作处理的是响应状态(incomplete),目标是让模型输出更多内容。错误恢复是故障修复,继续工作是正向推进。两层机制可以共存:先错误恢复,恢复成功后如果被截断,再触发继续工作。
继续机制的成本通常比一次性给足输出空间更高——每次继续是一次新 API 调用,固定开销付多次。最优策略是在预算允许范围内给 max_output_tokens 足够空间,尽量减少触发继续。生成固定格式短报告时关掉继续机制,生成长篇排查报告时开启并设 max_continues=2。
继续之后
到这里循环与控制设计的核心都讲到了:两层循环、错误处理、停止条件、继续工作。但还有两个执行细节要补:调用前的消息预处理,和调用后的工具并发执行。
下一篇讲消息预处理管线。循环每轮调 API 前,消息要先经过从轻到重的处理——这和第 10 篇的压缩策略呼应,但角度不同,是调用前的固定管线。