> ## Documentation Index
> Fetch the complete documentation index at: https://docs.playapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 上下文管理

> 在显式历史、状态引用、压缩和应用裁剪之间选择合适方案。

## 先选择状态归属

| 方案 | 适用条件 | 需要保留什么 |
| - | - | - |
| Chat 显式历史 | 普通无状态对话 | 有序消息、完整工具调用与结果 |
| Responses 显式回放 | 支持输入项的原生链路 | 全部需要续用的输出项，包括密文、ID、`phase` 和元数据 |
| `previous_response_id` | 上游保存状态且路由可续用 | 前一响应 ID；不能假定它包含下一轮应继续生效的 instructions |
| 手动 `/v1/responses/compact` | 渠道支持压缩端点 | 完整压缩窗口与原始历史备份 |
| 应用侧裁剪 | 业务能接受舍弃较旧内容 | 长期约束、最新问题与完整轮次边界 |

缓存只复用输入计算，不增加模型上下文窗口；Responses 状态 ID 也不能绕过模型容量限制。为输出及模型所需推理预留空间，工具定义、媒体和协议开销同样需要计入。字符数、UTF-8 字节数和消息条数都不等于 token 数。本项目的计数接口边界见[覆盖范围](/zh/guides/api-coverage)，不要假设存在可用的通用 token 计数 API。

## 自动截断和上下文编辑的边界

原生 Responses DTO 接收 `truncation` 与 `context_management`；上游是否支持以及具体语义由模型和渠道决定。当前 Responses → Chat 转换会拒绝 `context_management`、`previous_response_id` 等状态字段，且没有复制 `truncation` 的映射，不能依赖转换后自动裁剪。

Claude 的 `context_management` 是协议专用对象；Claude → Responses 转换不会照搬它。OpenRouter 的 `transforms: ["middle-out"]` 也不是本站已验证的通用接口能力。需要压缩时先使用已有的[显式压缩示例](/zh/guides/compaction)，不要把不同协议的同名字段当作等价实现。

## 可运行：完整文本轮次的本地窗口

本例适合没有工具、媒体、推理状态的纯文本 Chat 历史。它保留 `system` 与最新 `question`，按轮数和序列化消息的 UTF-8 字节上限，移除最旧的完整 user / assistant 对。它不估算 token，不承诺一定适配模型窗口，不调用摘要模型。

创建 `history.json`：

```json theme={null}
{
  "system": "Help plan travel. Always preserve the no-driving constraint.",
  "turns": [
    {"user": "Suggest a destination.", "assistant": "Kyoto has convenient public transport."},
    {"user": "Suggest an area.", "assistant": "Consider eastern Kyoto for your first day."}
  ],
  "question": "Suggest a rainy-day activity."
}
```

沿用 [REST 运行时](/zh/guides/http-runtime)，保存 `examples/context-window.mjs` 与 `http-client.mjs`：

```sh theme={null}
export API_ROOT_URL='https://gateway.example.com'
export API_KEY='YOUR_API_KEY'
export API_MODEL='YOUR_CHAT_MODEL_ID'
export API_HISTORY_FILE='./history.json'
export API_CONTEXT_KEEP_TURNS='1'
export API_CONTEXT_MAX_BYTES='4096'
node examples/context-window.mjs
```

```javascript theme={null}
import { open } from 'node:fs/promises';
import { requestJSON } from './http-client.mjs';

try {
  const { API_MODEL, API_HISTORY_FILE } = process.env;
  const keep = Number(process.env.API_CONTEXT_KEEP_TURNS || '4');
  const budget = Number(process.env.API_CONTEXT_MAX_BYTES || '4096');
  if (!API_MODEL?.trim() || !API_HISTORY_FILE || !Number.isInteger(keep) || keep < 0 || keep > 20
    || !Number.isInteger(budget) || budget < 128 || budget > 65_536) throw new Error('Invalid configuration.');
  const file = await open(API_HISTORY_FILE, 'r');
  let input;
  try {
    if (!(await file.stat()).isFile()) throw new Error('Expected a regular file.');
    const buffer = Buffer.alloc(131_073);
    let bytesRead = 0;
    while (bytesRead < buffer.length) {
      const part = await file.read(buffer, bytesRead, buffer.length - bytesRead, bytesRead);
      if (!part.bytesRead) break;
      bytesRead += part.bytesRead;
    }
    if (bytesRead > 131_072) throw new Error('History exceeds example file limit.');
    input = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(buffer.subarray(0, bytesRead)));
  } finally { await file.close(); }
  if (!input || Object.keys(input).sort().join(',') !== 'question,system,turns'
    || typeof input.system !== 'string' || !input.system.trim()
    || typeof input.question !== 'string' || !input.question.trim() || !Array.isArray(input.turns)
    || input.turns.some((turn) => !turn || Object.keys(turn).sort().join(',') !== 'assistant,user'
      || typeof turn.user !== 'string' || !turn.user.trim()
      || typeof turn.assistant !== 'string' || !turn.assistant.trim())) {
    throw new Error('Expected text-only complete turns.');
  }
  const turns = keep === 0 ? [] : input.turns.slice(-keep);
  let messages;
  while (true) {
    messages = [{ role: 'system', content: input.system },
      ...turns.flatMap((turn) => [{ role: 'user', content: turn.user }, { role: 'assistant', content: turn.assistant }]),
      { role: 'user', content: input.question }];
    if (Buffer.byteLength(JSON.stringify(messages), 'utf8') <= budget) break;
    if (!turns.length) throw new Error('Required instructions and question exceed local byte budget.');
    turns.shift();
  }
  const result = await requestJSON('/v1/chat/completions', { model: API_MODEL, messages });
  const choice = result.choices?.[0];
  const message = choice?.message;
  if (!Array.isArray(result.choices) || result.choices.length !== 1 || choice?.index !== 0
    || choice.finish_reason !== 'stop' || message?.role !== 'assistant'
    || message.refusal != null || message.tool_calls != null || message.function_call != null
    || typeof message.content !== 'string' || !message.content.trim()) throw new Error('Incomplete text response.');
  console.log(JSON.stringify({ answer: message.content, retained_turns: turns.length,
    omitted_turns: input.turns.length - turns.length, messages_bytes: Buffer.byteLength(JSON.stringify(messages), 'utf8') }));
} catch {
  console.error('Context example failed. Check the text-only history, local budget, and request logs. Original file is unchanged.');
  process.exitCode = 1;
}
```

输入文件最多 128 KiB；`KEEP_TURNS` 范围 0–20，`MAX_BYTES` 范围 128–65536。这些是示例的本地上限，不是服务端或模型限制。未知输入字段、非完整文本轮次、无效 UTF-8 / JSON 会在请求前失败；必需的 system 与最新问题超出预算时也直接失败，不截断字符串。

成功输出包含答案、保留 / 省略轮数及发送消息字节数。原始文件始终保留，失败不自动重试；省略历史会改变语义，应先把不能丢失的业务约束保存在受信任的长期状态中，而不是依赖旧对话仍在窗口内。

## 工具、摘要与推理状态

工具调用与其结果必须成组保留，不得只删除其中一条。推理密文、原生签名与压缩项是协议状态，不能用本文的文本裁剪脚本处理。通过[推理模型示例](/zh/guides/reasoning-models)验证原样回放，或使用[压缩示例](/zh/guides/compaction)保留完整返回窗口。

如果应用自行生成摘要，保留事实、用户约束、未完成任务和引用来源，并让业务确认允许丢失的细节；摘要不是无损压缩。不要把用户内容或工具输出自动提升成 system 指令。压缩失败应保留原始历史，确认后再更新会话状态；超限错误也不应触发不受控的重复生成。

依据：`30a6d88a4` 的 Responses / Claude DTO、`relaykit/relayconvert/internal/oai_responses/to_oai_chat_req.go` 与 `relaykit/relayconvert/internal/claude_messages/to_oai_responses_req.go`。参考 [OpenRouter 消息变换专题](https://openrouter.ai/docs/guides/features/message-transforms)和 [OpenAI 会话状态说明](https://developers.openai.com/api/docs/guides/conversation-state)。

[多轮对话](/zh/guides/conversations) · [缓存](/zh/guides/prompt-caching) · [Responses API](/zh/api-reference/responses)

[Read in English](/en/guides/context-management)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.