Skip to content
预计 1 分钟右侧目录可快速定位

一次模型调用受哪些硬边界约束:Token、Context Window、Sampling 与 Latency ​

前置:01 Foundations:模型建议权与 Runtime 事实提交权

先看一个真正会把 Harness 卡死的问题 ​

假设某模型标称支持 128K context。Harness 当前有 70K 历史、20K 检索结果、15K Tool schema,于是程序判断“总共 105K,还没到 128K,可以发”。请求却可能仍然失败,或者成功后没有足够空间生成答案。

原因是 context window 不是 history 的容量,而是一次 inference 的总工作空间。输入消息、工具定义、检索证据、模型输出,以及某些 Provider 暴露或内部使用的额外 token,都要争夺这块空间。Harness 因此不能把 Provider 上限直接当成 max_input_tokens。

这也是为什么 Model Layer 必须先给 Runtime 一个稳定的预算模型,而不是让 ContextManager 自己猜。

Token 和 Context Window 分别约束什么 ​

Token 是模型实际处理文本/结构化输入时使用的离散单位。字符数、字数和 token 数没有固定一一对应关系,因此生产系统应使用目标模型对应的 tokenizer 或 Provider usage 数据估算,而不是用 len(text) 当真实预算。

Context Window 是单次模型调用能够共同容纳的全部 token 上限。工程上更有用的表达不是:

text
input <= context_window

而是:

text
assembled_input
+ reserved_output
+ safety_margin
<= effective_context_limit

例如:

python
@dataclass(frozen=True)
class ModelBudget:
    context_limit: int
    reserve_output: int
    safety_margin: int

    @property
    def max_input(self) -> int:
        return self.context_limit - self.reserve_output - self.safety_margin

ContextManager 消费的是 max_input,而不是 Provider 宣传页上的最大窗口。这样当模型、输出上限或策略变化时,预算仍然由 Model Adapter 统一解释。

Context Window 大,不等于 Context 越多越好 ​

容量问题和信息质量问题要分开。

即使 100K 输入合法,也可能因为低相关历史、巨大 Tool Result 或几十个无关 Tool schema 把关键约束淹没。“装得下”只证明请求合法,不证明模型容易找到正确证据。 Context selection、progressive disclosure 和 compaction 的 canonical 机制在 Module 04;本文只确定它们必须服从的硬容量边界。

因此 Harness 至少应该记录:

text
input_tokens
reserved_output_tokens
tool_schema_tokens
retrieval_tokens
history_tokens
model_context_limit

这样 context overflow 或成本异常才能定位到具体来源,而不是只看到“模型请求失败”。

Sampling 是概率控制,不是业务正确性开关 ​

Temperature、top-p 等 sampling 参数改变的是下一 token 的采样分布。降低随机性通常有利于格式稳定和重复实验,但不能把概率模型变成确定性函数;即使相同输入和相同参数,Provider 实现、模型版本或服务端变化也可能让输出不同。

这给 Harness 两个直接约束:

  1. Runtime 的状态迁移、权限、重试次数、终止条件不能依赖“模型这次应该和上次说一样”;
  2. 想测试确定性软件行为时,用 Scripted/Fake Model,而不是强行把 temperature 调到某个值后假设输出必然相同。

Sampling 参数应该进入 Run Manifest,因为它是解释某次行为差异的重要运行条件,但它不是 execution truth。

Latency 要拆开看,否则不知道该优化哪一层 ​

用户看到的“模型很慢”可能来自完全不同的位置:

text
request queued
→ network / provider admission
→ time to first token (TTFT)
→ token generation
→ stream transfer
→ client consumption

对于 Agent Harness,还要把 Model latency 与 Tool / DB / Queue latency 分开。一个 Run 30 秒,不意味着模型用了 30 秒。

Model Adapter 至少应向 Trace 暴露:

text
request_started_at
first_token_at
request_finished_at
input_tokens
output_tokens
provider/model

于是可以得到:

text
TTFT = first_token_at - request_started_at
generation_time = request_finished_at - first_token_at

TTFT 高,可能是排队、网络或大输入预处理;generation time 高,可能是输出太长或模型本身生成慢。两种问题的优化方向完全不同。

这些边界怎样进入 Harness ​

Model Layer 不应该只返回一段文本。至少需要把“能力”和“本次调用事实”分开:

python
@dataclass(frozen=True)
class ModelCapabilities:
    context_limit: int
    max_output_tokens: int | None
    supports_tools: bool
    supports_streaming: bool

@dataclass(frozen=True)
class ModelUsage:
    input_tokens: int
    output_tokens: int
    ttft_ms: int | None
    total_ms: int

Capability 用来在请求前做预算和功能选择;Usage 用来在请求后记账、Trace 和 Eval。不要拿上一次 Usage 推断当前模型的硬上限,也不要把 Provider SDK 的具体 response object 直接传进 Runtime。

一个最常见的失败链 ​

错误设计:

text
context_limit = 128K
→ ContextManager 塞入 126K
→ Tool schema 临时增加
→ 还要求模型输出长 JSON
→ 请求在生产流量中偶发 overflow

真正的修复不是“失败后截断最后一条消息”,而是从组装前就建立预算:预留输出和安全余量,给 history/retrieval/tool definitions 分配子预算,并把真实 token usage 反馈到观测系统。

这个模型会在后面的 Context、Tool、Performance 模块继续被使用,但不再重新解释 Token / Context Window 的基础语义。

自测 ​

  1. 一次调用同时出现高 TTFT 和总耗时过长,你会怎样区分 queue wait、prompt processing、generation 与 tool wait,而不是笼统归因于“模型慢”?
  2. Context 已接近窗口上限时,为什么不能只把 max_output_tokens 继续调大?输入预算和输出预留应如何共同约束一次请求?

Sources ​

AI Engineering · Agent · Harness · Backend Systems