Mistral Large 4 上下文总被截断 / 先查模型卡和客户端设置,别急着怪模型

文章导读
Mistral Large 4 在长对话里“忘掉”开头,通常不是模型突然变差,而是三层原因叠在一起:模型声明的上下文窗口本来就有限、客户端或框架在发请求前就把历史截掉了、提示词结构让模型主动放弃前文。判断顺序建议从外到内——先在官方模型卡记下 context length,再用递增长度的输入实测截断点,然后翻客户端里控制 token 上限的设置项,最后看服务端日志有没有截断警告。先把“从第几轮开始
📋 目录
  1. 一 在官方模型卡确认宣称的上下文窗口
  2. 二 用 API 或本地接口发送递增长度的输入
  3. 三 检查客户端或框架的最大 token 设置
  4. 四 查看服务端日志中的截断或错误信息
  5. 五 调整提示词结构或分段处理
A A

Mistral Large 4 在长对话里“忘掉”开头,通常不是模型突然变差,而是三层原因叠在一起:模型声明的上下文窗口本来就有限、客户端或框架在发请求前就把历史截掉了、提示词结构让模型主动放弃前文。判断顺序建议从外到内——先在官方模型卡记下 context length,再用递增长度的输入实测截断点,然后翻客户端里控制 token 上限的设置项,最后看服务端日志有没有截断警告。先把“从第几轮开始丢、丢的是开头还是结尾”复现出来,再谈要不要换模型或改提示词。

Mistral Large 4 丢上下文时,先分别验证三件事:模型窗口、客户端截断、提示词结构。在官方模型卡记录 context length,用递增长度输入找到实际开始丢失开头的位置,再检查客户端里 max_tokens、context window、历史条数上限以及本地推理的 n_ctx 一类参数,同时确认服务端日志有没有截断关键词。只有确认截断发生在服务端,调整提示词或分段处理才有意义;客户端限制没排除之前,换模型通常解决不了问题。

在官方模型卡确认宣称的上下文窗口

这一步的目的只是拿到一个理论参照值,避免把“窗口就那么大”当成“客户端设置错了”。在官方模型卡页面里找 context length 或 max context 字段,这是最直接的来源。如果拿不到模型卡,可以从下面几个位置交叉确认:

  • 模型卡或模型详情页的 context length 字段;
  • API 文档里模型列表条目的上下文上限说明;
  • 本地推理时的模型配置,例如 max_position_embeddings、max_model_len、n_ctx 一类字段的默认值;
  • 服务启动参数里是否有人为调低过窗口。

把找到的数值记在排查笔记里,标注来源(模型卡 / 文档 / 本地配置)。需要注意两点:一是声明的窗口通常是输入加输出共享,实际能放进去的输入比这个数小;二是托管服务和本地部署的窗口可能不一样,以你实际调用的那一侧为准。记录完之后,它的用途是和下一步实测出来的截断点做对比——两个数差距大,问题多半不在模型窗口,而链路中下游。

用 API 或本地接口发送递增长度的输入

这一步用来定位“从多长的输入开始,开头内容收不到回应”。做法是把一个唯一标记放在输入最前面,后面拼逐渐变长的填充文本,最后让模型回答标记词。如果模型答不上来而且请求本身没报错,多半是输入被静默截掉了头部;如果请求直接返回 4xx,那就是服务端硬拒绝,属于窗口上限。下面是一段可替换的通用骨架:

import os, requests

API_URL = os.environ.get("LLM_API_URL", "http://127.0.0.1:8000/v1/chat/completions")
API_KEY = os.environ.get("LLM_API_KEY", "")
MODEL   = "mistral-large"  # 替换成你实际调用的模型名

def build_prompt(filler_units):
    marker = "开头标记词是:蓝色灯塔-7391"
    filler = "这是一段用于填充上下文的普通文本。" * filler_units
    return marker + "\n" + filler + "\n请只回答开头标记词,不要解释。"

def ask(prompt, max_tokens=32):
    payload = {
        "model": MODEL,
        "messages": [{"role": "user", "content": prompt}],
        "max_tokens": max_tokens,
        "temperature": 0,
    }
    headers = {"Authorization": f"Bearer {API_KEY}"} if API_KEY else {}
    r = requests.post(API_URL, json=payload, headers=headers, timeout=120)
    r.raise_for_status()
    return r.json()["choices"][0]["message"]["content"].strip()

for units in [1, 4, 16, 64, 256, 1024, 4096]:
    prompt = build_prompt(units)
    try:
        answer = ask(prompt)
    except Exception as e:
        print(f"units={units} 请求失败: {e}")
        continue
    recalled = "7391" in answer
    print(f"units={units} 召回开头标记={recalled} 回答={answer[:40]}")

两点替换说明:填充单位不是精确 token 数,它只用来做粗粒度递增,想要精确值需要接对应的 tokenizer 统计;API_URL 和模型名换成你自己的接入点。观察结果时,第一次出现 recall=False 且请求成功的长度区间,就是这条链路的实际可用输入量级;再和模型卡里的数值对照,就能判断差距出在哪一层。

检查客户端或框架的最大 token 设置

很多“模型忘了前面”其实发生在客户端组装请求的阶段——历史消息在发出去之前就被裁掉了。不同产品的字段名不一样,但控制点大致是这几类,逐个核对你正在用的那一个:

Mistral Large 4 上下文总被截断 / 先查模型卡和客户端设置,别急着怪模型
  • 输出上限类:max_tokens、max_output_tokens、max_completion_tokens。多数接口里它只管生成长度,不控制输入截断;把它当成总预算会误判。
  • 总预算类:context window、max_context_tokens、context_length。它通常决定整个请求的 token 上限,是最容易被调小的一个。
  • 输入专属类:max_input_tokens、max_prompt_tokens、prompt budget。有的框架会按它硬裁输入首部。
  • 历史长度类:max_history_messages、max_turns、只保留最近 N 条消息、保留条数或字符数上限。这类设置最容易造成“越聊越忘”。
  • 本地推理参数:n_ctx、num_ctx、max_model_len、context_size。跑本地模型时这些字段如果沿用默认值,可能远小于模型卡宣称的窗口。
  • 截断策略开关:truncate、truncation 的取值(head / tail / auto),以及超限时先丢最早还是最新消息。

调整方式通常是改配置或构造参数,然后重启服务或重建会话,不要只刷新页面。一个有用的对照实验:把“保留最早”改成“保留最新”,重跑第二步的脚本——如果这时开头标记词丢了、结尾还在,说明是客户端从头裁,而不是模型窗口不够。改完记得用同一段输入复测,确认有效果再继续。

查看服务端日志中的截断或错误信息

要区分“客户端裁的”还是“服务端裁的”,日志是最直接的证据。常见的截断或超限关键词大致包括:truncat、context length、maximum context length、too many tokens、input too long、prompt is too long、exceed、reduce the length、overflow、dropped、window。HTTP 400 的 invalid_request_error 里也常带 “maximum context length” 这类描述,可以直接搜这一串。

开启详细日志的方式按部署形态不同:本地服务可以设日志级别环境变量(例如 LOG_LEVEL=DEBUG)后重启;对接层或网关可以打开请求日志,记录请求体字节数、耗时和返回状态;客户端侧一般有 debug 或 verbose 开关,打开后能看到实际发出去的 messages 数组长度。验证时把请求体中估算的 token 数和响应里 usage 字段的 prompt_tokens 做对比:两者差得远,说明中间有环节提前裁了内容;usage 里的 prompt_tokens 已经接近窗口上限,那才是真的顶到模型边界。

调整提示词结构或分段处理

确认截断点之后,如果它确实受限于窗口,就只能改输入的组织方式。几种通用做法可以按场景组合:

  • 分段总结:把较早的若干轮对话压缩成几句结论,作为系统提示或首条消息保留,原始消息丢弃。适合话题相对稳定、细节不重要只有结论重要的会话。
  • 滑动窗口:只保留最近 N 轮原文,再在前面挂一条滚动摘要。每次追加新轮次时更新摘要并裁掉最老一轮。
  • 关键信息前置或固定位置:把必须记住的约束、变量、结论写进 system 消息或每轮重新声明,不指望它活过窗口边界。
  • 外部存储加按需取回:历史落库或写文件,需要时检索相关片段再拼进本次请求,而不是全量塞入。

一个可用的滑动窗口骨架大致是这样:维护 summary 和 recent_turns 两个变量,每轮结束后判断 recent_turns 是否超过你设定的条数,超过就把最老一轮并入 summary,然后只把 summary 加最近几轮拼成 messages 发出。配套的提示词可以写成“以下是历史摘要,若信息不在摘要和最近对话中,请直接说明不确定,不要推测”。每改一次都用第二步那个标记词脚本复测一遍:关键信息是否还在、丢的是头部还是尾部。若调整后关键内容仍被裁掉,说明问题还在客户端或服务端配置,应该回到第 3、4 步继续查,而不是继续改提示词。