上下文边界上的静默截断:探出每个后端从哪一段开始不再读你的长提示词
介绍一种「草堆找针」式的探测方法:通过在被测提示词的不同深度埋入可验证的标记,定位每个后端模型从哪个位置开始静默丢弃内容,从而得到可用的有效上下文窗口。文中给出可复用的脚本思路、判断标准、跨模型对比方式,以及在聚合站多后端场景下的注意事项。
为什么标称上下文不等于可用上下文
各家模型都会公布一个上下文上限,但那个数字通常指的是「模型架构能接受的 token 数」。在真实的服务链路里,实际能读到的内容往往更短:网关可能按自己的规则截断,推理框架可能为输出预留空间,长提示词的中间部分可能被压缩或丢弃,超长请求也可能被悄悄裁剪而不报错。
真正要命的不是报错,而是静默截断。请求成功返回 200,模型也给了看似合理的回答,只是它从某一段开始根本没看到你的内容。如果你的应用依赖长文档问答、代码库检索、多轮对话历史,这种静默失败会表现为「偶尔答不准」,很难归因。
在聚合站上这个问题更值得一测:同一个 API key 后面挂着多个后端,每个后端的截断策略未必一致。标称 128k 的模型,实际有效长度可能远小于此,而且不同模型之间并不相同。
草堆找针:探测的基本思路
思路很朴素:构造一个足够长的提示词,把「针」埋在不同深度,看模型还能不能把针拔出来。
针必须满足两个条件:
- 模型无法从上下文之外猜出它,比如一段随机字符串、一个随机数、一个无意义代号。
- 答案可以被程序自动判定,不需要人工阅读。
草堆则是大量低信息量的填充文本,用来把提示词撑到目标长度。填充内容要避免本身携带答案,也不应触发模型拒答。
流程大致是:
- 选定一个目标深度(例如从提示词开头算起的某个比例位置)。
- 在该位置插入针,其余位置填草。
- 在提示词末尾(或开头)提出一个固定问题:请复述针的内容。
- 判定回答是否命中针。
- 改变深度,重复。
命中的最深位置,就是这条链路上该模型的有效上下文下界;第一次稳定失败的深度,就是它开始不再读取的位置附近。
用二分而不是线性扫描
逐 token 往上加太慢。更实际的做法是先用几个粗粒度深度(例如提示词长度的几个分位点)定位失败区间,再在区间内二分。
判断标准要留余量:「命中」不等于每次都命中。采样本身有随机性,同一条请求重复几次可能结果不同。建议每个深度重复多次,把「全部命中」视为通过,「全部失败」视为失败,中间的模糊地带继续细分。这样得到的是一个带误差带的边界,而不是一个精确到 token 的数字——这本来也更符合现实。
一个可复用的脚本骨架
下面的伪代码只表达结构,具体 SDK、填充文本、重复次数按你的环境替换。
import random, string
def make_needle():
# 随机、不可猜、易于精确匹配
return "NEEDLE-" + "".join(random.choices(string.ascii_uppercase + string.digits, k=16))
def build_prompt(filler, needle, depth_ratio, question):
cut = int(len(filler) * depth_ratio)
body = filler[:cut] + "\n" + needle + "\n" + filler[cut:]
return body + "\n\n" + question
def probe(client, model, filler, question, depth_ratio, trials=5):
hits = 0
for _ in range(trials):
needle = make_needle()
prompt = build_prompt(filler, needle, depth_ratio, question)
resp = client.chat(model=model, prompt=prompt)
if needle in resp.text:
hits += 1
return hits / trials
def find_edge(client, model, filler, question, lo=0.0, hi=1.0, eps=0.05):
while hi - lo > eps:
mid = (lo + hi) / 2
if probe(client, model, filler, question, mid) == 1.0:
lo = mid
else:
hi = mid
return lo, hi
关键点:每次探测都重新生成针,避免模型从历史或缓存里「记住」上一次的答案。判定用精确子串匹配,不做模糊匹配。
让测量结果可比较
只有把变量控制住,跨模型的数字才有意义。
- 填充文本固定。同一批测试用同样的草堆,否则 token 密度不同,字符长度不可比。
- 用 token 而不是字符做刻度。不同模型的分词器不同,按字符比较会系统性偏差。可以先用分词工具估算,或直接报告字符数并注明这一点。
- 固定问题位置。把提问放在末尾是最常见的用法;如果你要测「中间内容是否被丢弃」,也可以把问题放开头对比。
- 固定采样参数。温度、最大输出长度都会影响可用的输入预算。
- 记录时间。后端可能更新,今天的边界明天不一定成立。
聚合站场景下的额外注意点
同一聚合站上的不同模型走的是不同后端,探测结果应当逐模型记录,不要用一个数字代表整站。此外:
- 同一模型名可能路由到不同上游,重复探测时结果出现离散的两簇,说明背后不只一个实现。
- 请求失败与静默截断要分开统计。报错说明链路拒绝了,静默成功但答不出说明被裁剪,两者的处理方式不同。
- 计费按输入 token 走时,探测本身会消耗额度。用短草堆先摸范围,再对可疑区间细测。
- 探到的边界应视为「安全使用上限」的下界,实际业务里再留一段余量给系统提示词、工具定义和输出。
把探测结果落进工程实践
测出边界之后,做法就明确了:在客户端按模型维护一张有效上下文表,超长输入先做分块或摘要,而不是指望服务端帮你处理。对关键任务,可以在长输入里放一个轻量的自检标记,运行时抽检模型是否真的读到了中段内容。
静默截断是长上下文应用里最隐蔽的一类故障。花一个下午写个探测器,把每个后端的真实边界量出来,比在线上靠猜要划算得多。