一次 Request 中发生了什么:Input、Context、Inference 与 Response

一次 Request 中发生了什么:Input、Context、Inference 与 Response

上一篇区分了 LLM、模型服务和 Agent 系统。这一篇继续拆解“调用一次模型”:一条用户消息如何变成 Request,模型实际看到了什么,又返回了什么。

先看完整链路:

Input → Request → Context → Inference → Response
概念 本文中的含义
Input 用户、应用或工具提供的信息
Request 应用发给模型服务的一次调用
Context 模型本次推理实际可见的信息
Inference 模型计算并生成内容的过程
Response 模型服务返回的调用结果

界面上是一问一答,系统内部处理的却是五个不同概念。混用它们,很容易误判问题发生在哪一层。

从 Input 到 Request

继续使用这个系列的案例。用户输入:

调研三个主流 Agent Runtime,并给出选型建议。

这句话是 Input,不是完整的 Request。应用还会加入指令、历史消息、工具定义、输出格式、模型参数和追踪信息。

sequenceDiagram
    participant U as 用户
    participant A as 应用或 Runtime
    participant S as 模型服务
    participant M as 模型

    U->>A: 用户 Input
    A->>A: 合并指令、历史、工具与格式
    A->>S: Request
    S->>M: 准备本次 Context
    M->>M: Inference
    M-->>S: 生成内容
    S-->>A: Response
    A-->>U: 展示、校验或继续处理

这些信息有不同来源:用户提出需求,产品团队设定规则,Runtime 提供工具并记录元数据。只有被放进本次调用的内容,模型才有机会看到。

Request:应用发出了什么

Request 是应用提交给模型服务的调用数据。各家厂商的字段名不同,但大多包含以下内容:

内容 用途 通常来自哪里
model 选择模型 应用配置或路由策略
instructions 约束角色、规则和处理方式 产品或开发者
input / messages 提供用户内容与历史交互 用户、会话记录
tools 声明模型可选择的外部能力 Tool Registry
output schema 约束返回结构 下游程序契约
generation config 控制长度、采样或推理预算 应用配置
metadata 关联案例、日志和业务记录 Runtime

下面是 C02 使用的一份 OpenAI Request 样例:

{
  "model": "gpt-5.6",
  "instructions": "你是一名技术研究助手。只提炼研究问题、比较维度和当前限制。本次调用没有搜索工具,不得声称已经完成实时资料调研。",
  "input": [
    {
      "role": "user",
      "content": "调研三个主流 Agent Runtime,并给出选型建议。"
    }
  ],
  "tools": [],
  "max_output_tokens": 2000,
  "metadata": {
    "case": "C02"
  },
  "stream": false
}

tools 在这里是空数组,因为 C02 只观察一次模型调用,不执行 Agent Loop。

Prompt 或 Messages 只是 Request 的一部分。Request 还可以包含图片、文件、工具定义和 JSON Schema,因此不能把两者画等号。

Request 也不是 Task。技术调研是用户要完成的 Task;上面的 JSON 只是为这个 Task 发起的一次调用。

Context:模型实际看到了什么

模型不会原样读取 Request。模型服务会套用消息模板、加入控制信息并完成 Tokenization,得到本次 Inference 使用的 Context。

Context 可能包含:

  • 系统或开发者指令;
  • 用户输入和选中的历史消息;
  • 之前的模型输出、Tool Call 与 Tool Result;
  • 工具描述、文件片段和输出格式说明。

聊天记录只是 Context 的来源之一。应用数据库里即使存着一百份资料,本次 Request 只放入三份,模型也只能根据这三份判断。工具定义虽然不显示成普通聊天消息,同样会占用 Context。

模型一次能处理的 Token 数量有限,这个上限通常称为 Context window。输入过长时,服务可能直接拒绝请求,应用也可能事先截断、摘要或删除部分历史。“模型没理会上轮要求”未必是它突然忘了,那条要求可能根本没有进入本次 Context。

所以排查“模型忘记了要求”时,先检查实际 Request 和 Trace。聊天界面不足以说明模型当时看到了什么。

Inference:模型如何生成内容

模型服务收到 Request 后,会校验参数、准备输入并执行计算。在文本生成场景中,大致过程如下:

Request 到达
  → 输入处理与 Tokenization
  → 模型计算下一个 Token
  → 按解码策略选取并追加 Token
  → 重复生成,直到停止
  → 组装 Response

这段计算过程就是 Inference。应用可以发送和记录 Request,但 Inference 发生在模型服务内部。

在最简单的调用中,一次 Request 可以看作一次逻辑生成。但工程实现未必严格一一对应:SDK 可能重试失败请求,服务可能执行路由或批处理,某些托管工具也会在背后做额外编排。审计时,应分别记录应用发出的 Request、模型服务返回的 Response,以及 Runtime 观察到的 Step。

Response:服务返回了什么

生成完成或中止后,模型服务返回 Response。除了文字,它还可能包含:

  • Response ID、状态和模型信息;
  • 结构化内容或 Tool Call;
  • 拒绝、错误和未完成原因;
  • Token usage 与其他元数据。

普通问答应用可以直接展示其中的文本。Agent Runtime 还要判断它是最终输出还是 Tool Call、结构是否合格、是否需要继续执行。一次调用成功,只代表应用收到了 Response,不代表 Task 已完成。

非流式 Response

Schema 只约束结构

C02 接着用 Schema 约束同一个 Request 的输出。先定义 ResearchSummary

const ResearchSummary = z.object({
  research_question: z.string(),
  comparison_dimensions: z.array(z.string()),
  known_limitations: z.array(z.string()),
});

以下是一次实际返回的 Response:

{
  "id": "resp_035b9f82fe1165ab006a71e04bd8f481998c61b4748a4650d8",
  "status": "completed",
  "model": "gpt-5.6-sol",
  "format": {
    "type": "json_schema",
    "description": null,
    "name": "research_summary",
    "schema": {
      "type": "object",
      "properties": {
        "research_question": {
          "type": "string"
        },
        "comparison_dimensions": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "known_limitations": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "research_question",
        "comparison_dimensions",
        "known_limitations"
      ],
      "additionalProperties": false
    },
    "strict": true
  },
  "output": [
    {
      "id": "rs_035b9f82fe1165ab006a71e04c7f30819987a7336810590d25",
      "type": "reasoning",
      "content": [],
      "encrypted_content": "gAAAAABqceBVuIR0yNr***",
      "summary": []
    },
    {
      "id": "msg_035b9f82fe1165ab006a71e04e182c8199aaf5284204c8543d",
      "type": "message",
      "status": "completed",
      "content": [
        {
          "type": "output_text",
          "annotations": [],
          "logprobs": [],
          // 已截取
          "text": "{\"research_question\":\"对三个主流 Agent Runtime 进行横向比较,评估其在目标业务场景中的适配性,并形成选型依据;需先明确候选范围(例如 LangGraph、Micros...]}",
          "parsed": {
            "research_question": "对三个主流 Agent Runtime 进行横向比较,评估其在目标业务场景中的适配性,并形成选型依据;需先明确候选范围(例如 LangGraph、Microsoft AutoGen、CrewAI,或云厂商托管型 Runtime)及具体使用场景。",
            "comparison_dimensions": [
              "产品定位与核心抽象",
              "Agent 编排能力与多 Agent 协作",
              "状态管理、持久化与断点恢复",
              "工具调用、模型接入与协议兼容性(如 MCP)",
              "工作流控制、人工介入与审批机制",
              "记忆、上下文管理与知识检索集成",
              "可靠性、容错、重试与幂等性",
              "可观测性、调试、评测与追踪能力",
              "部署方式、扩缩容与运行隔离",
              "安全、权限、密钥管理与合规能力",
              "性能、延迟、吞吐量与资源消耗",
              "开发体验、学习成本与生态成熟度",
              "商业授权、平台锁定与总体拥有成本",
              "社区活跃度、版本稳定性与长期维护风险",
              "与现有技术栈和团队能力的匹配程度"
            ],
            "known_limitations": [
              "本次调用没有搜索工具,无法完成实时资料调研或验证各产品的最新版本、功能、定价和活跃度。",
              "“三个主流 Agent Runtime”缺少统一定义;框架型、运行时型和云托管型产品不宜在未分组的情况下直接比较。",
              "尚未提供业务场景、负载规模、部署环境、合规要求、预算及团队技术栈,因此不能给出可靠的最终选型建议。",
              "Agent Runtime 领域变化较快,基于非实时信息形成的结论可能存在时效性偏差。"
            ]
          }
        }
      ],
      "phase": "final_answer",
      "role": "assistant"
    }
  ],
  "usage": {
    "input_tokens": 112,
    "input_tokens_details": {
      "cache_write_tokens": 0,
      "cached_tokens": 0
    },
    "output_tokens": 489,
    "output_tokens_details": {
      "reasoning_tokens": 90
    },
    "total_tokens": 601
  }
}

下游代码可以直接读取字段,不必从自然语言中猜测标题和列表。

Note:Schema 只检查结构。格式正确的 JSON 仍可能包含过时或错误的信息,事实需要另行验证。

Streaming:传输方式变了,调用没有变

Streaming 会把同一个 Response 拆成一串事件发送。每个 delta 都不是新的 Request,也不是新的 Agent Step。

非流式调用等生成结束后一次返回完整结果:

Request ─────────等待─────────> 完整 Response

流式调用则在生成期间持续发送事件或文本增量:

Request ─> response.created
        ─> output_text.delta
        ─> output_text.delta
        ─> ...
        ─> response.completed

这次调用收到的 Events 如下:

流式 Events

聚合后的概要如下:

{
  "mode": "stream",
  "response_id": "resp_05803925ca8f9b36006a71e02fc7d48198a67314caafd8737b",
  "status": "completed",
  "model": "gpt-5.6",
  "elapsed_ms": 27889,
  "event_count": 1184,
  "event_type_counts": {
    "response.created": 1,
    "response.in_progress": 1,
    "response.output_item.added": 2,
    "response.output_item.done": 2,
    "response.content_part.added": 1,
    "response.output_text.delta": 1174,
    "response.output_text.done": 1,
    "response.content_part.done": 1,
    "response.completed": 1
  },
  "first_event": "response.created",
  "last_event": "response.completed",
  "usage": {
    "input_tokens": 60,
    "input_tokens_details": {
      "cache_write_tokens": 0,
      "cached_tokens": 0
    },
    "output_tokens": 1611,
    "output_tokens_details": {
      "reasoning_tokens": 408
    },
    "total_tokens": 1671
  },
  // 已被截取
  "aggregated_text": "以下为**非实时、待验证的初步选型框架**;本次未进行联网检索或最新版本核验。这里暂将 Agent Runtime 定义为:支..."
}

Streaming 的价值是让用户更早看到内容,也让应用提前开始处理。屏幕上的文字逐个出现,并不表示模型被反复调用。

代价是应用要处理流式连接的状态。连接中断后,必须分清收到的是完整结果还是半截文本;如果 SDK 自动重连或重试,还要防止重复写入。只保存界面最终显示的字符串,无法还原完整过程。

C02:把一次调用完整记录下来

C02 不接搜索工具,只验证一次调用的边界:Request 从哪里来、两种传输方式如何返回 Response、结构化输出能否通过校验。

代码已上传至 GitHub 仓库 AgentLoopExperienceSample

运行命令:

npm run c02 2>&1 | tee artifacts/00-terminal-output.txt

非流式与流式输出不必逐字相同。采样和服务执行会带来差异。验证重点是两种方式都能得到一个逻辑 Response,并且流式事件可以聚合、记录和校验。

调节 max_output_tokens 等参数,还可以主动制造几类失败:

失败 可观察现象 责任位置
Context 超限 Request 被拒绝或历史被截断 应用的 Context 策略与服务限制
流连接中断 只收到部分事件,没有完成状态 传输与 Runtime 恢复逻辑
Schema 校验失败 Response 存在,但下游无法消费 输出契约与校验代码

C02 的结构化结果还不是“调研报告”。系统没有读取当前的官方资料,也没有保存证据。这个实验只证明应用能够构造、记录和检查一次模型调用。

小结

应用把 Input 组装成 Request,模型服务据此准备 Context、执行 Inference,再返回 Response。Streaming 只改变 Response 的传输方式,Schema 只约束输出结构。它们都不会自动完成整个 Task。

下一篇再回到 Task:用户交给系统的是一个任务,为什么一次 Request 往往不够?


案例实践清单

  • 保存一份 C02 Request,并标注每个字段的来源。
  • 在不启用工具的前提下分别执行非流式和流式调用。
  • 保存流式事件,并聚合出最终 Response。
  • 使用 research_summary Schema 约束和校验输出。
  • 记录 Response ID、状态、Token usage、耗时与失败原因。
  • 确认 C02 只验证调用边界,不宣称完成调研 Task。

参考资料