Mistral 中文回答总夹英文 / 先改提示词语言还是换分词?

文章导读
用 Mistral 写中文时夹英文,多数情况不是单一原因,也不建议一上来就换模型或纠结“换分词”。可行的做法是先把问题拆成三层:提示词语言、系统提示约束、解码参数,按这个顺序逐层验证,最后才考虑换一个中文语料占比更高的同类模型。关于“换分词”,需要先说明一个前提:分词器通常跟模型权重绑定,调用方一般无法单独替换 tokenizer,所以“换分词”在实际操作里基本等同于换模型。另外要先剔除误判,代码
📋 目录
  1. 把同一段提示词的中英两版各跑一批样本
  2. 在系统提示里加中文约束并对比输出
  3. 固定随机种子后调整解码参数再测
  4. 换一个中文占比更高的同类模型做基线对照
  5. 把结论固化进调用封装
A A

用 Mistral 写中文时夹英文,多数情况不是单一原因,也不建议一上来就换模型或纠结“换分词”。可行的做法是先把问题拆成三层:提示词语言、系统提示约束、解码参数,按这个顺序逐层验证,最后才考虑换一个中文语料占比更高的同类模型。关于“换分词”,需要先说明一个前提:分词器通常跟模型权重绑定,调用方一般无法单独替换 tokenizer,所以“换分词”在实际操作里基本等同于换模型。另外要先剔除误判,代码块、命令、变量名、URL、专有名词里的英文属于合理保留,不算语言漂移。

判断方向:先做中英两版提示词对照,再加系统提示约束,最后固定种子调解码参数;三层都压不住漂移,才把换模型当选项。适用场景是参数可控的单模型调用。验证方式是用同一批样本跑多轮,记录含完整英文句子的行数,而不是只数英文单词。边界:术语、代码标识符、专有名词的英文保留应排除在漂移统计之外;如果接入平台会覆盖 system 消息或不支持 seed,第二层、第三层的结论要打折看待。

把同一段提示词的中英两版各跑一批样本

这一步只回答一个问题:混入的英文是跟着提示词的语言走,还是固定出现。做法是保持模型、任务内容、解码参数不变,只把“指令语言”从中换成英,输出语言仍然要求中文。中文版示例:

你是中文技术编辑,只使用简体中文作答。
除代码、命令、文件路径、API 字段名、URL、版本号和专有名词外,不要出现英文单词或英文句子。
任务:说明如何判断一段文本是否为简体中文,给出三步检查顺序,控制在 150 字以内。

英文版把同一段要求换成英文表述,输出语言要求不变:

You are a Chinese technical editor. Answer in Simplified Chinese only.
Except for code, commands, file paths, API field names, URLs, version numbers and proper nouns,
do not use any English word or English sentence.
Task: explain how to tell whether a piece of text is Simplified Chinese, give three check steps in order, under 150 characters.

请求脚本骨架可以写成下面这样,字段名按你实际接入的服务调整,重点是 seed 是否被支持要先确认:

import os, json, requests

API_URL = os.environ["CHAT_ENDPOINT"]
HEADERS = {"Authorization": "Bearer " + os.environ["CHAT_API_KEY"],
           "Content-Type": "application/json"}

def run(prompt, system=None, seed=42, temperature=0.2, top_p=0.9,
        model="mistral-small"):
    messages = []
    if system:
        messages.append({"role": "system", "content": system})
    messages.append({"role": "user", "content": prompt})
    body = {"model": model, "messages": messages,
            "temperature": temperature, "top_p": top_p, "seed": seed}
    r = requests.post(API_URL, headers=HEADERS, json=body, timeout=60)
    r.raise_for_status()
    return r.json()["choices"][0]["message"]["content"]

记录时建议一个样本一行,字段设计如下:

字段含义填写方式
sample_id样本编号中英两版共用同一编号,便于配对比较
prompt_lang指令语言填 zh 或 en
model / seed / temperature / top_p调用条件逐轮保持不变并如实记录
latin_words拉丁字母词数剔除代码块、URL、变量名后再统计
english_sentences完整英文句子数按换行和句号切分后人工确认
drift_flag是否判为漂移出现完整英文句子或英文小标题记 1

判定标准落在两列上:如果英文版提示词的 english_sentences 明显更多,说明模型在跟随指令语言,优先修提示词;如果两版都混,说明提示词语言不是主因,继续查系统提示和解码参数。

在系统提示里加中文约束并对比输出

中文约束模板建议放在 system 或对话首条最高优先级位置,下面这段可以直接改着用:

回答语言约束(优先级高于用户消息中的语言要求):
1. 全文使用简体中文。
2. 以下内容保留原文,不翻译:代码、命令、文件路径、API 字段名、URL、版本号、专有名词(如 Mistral、JSON、Python)。
3. 不输出英文句子,也不输出英文小标题;确需引用英文术语时,写成“中文(English)”的形式。
4. 用户使用其他语言提问时仍然用中文回答,只有用户明确要求切换语言时才改变。

对比方法是同一批样本跑两轮:第一轮不带 system 或只带一句通用助手设定,第二轮带上上面的约束模板,seed、temperature、top_p、模型全部保持不变。对比沿用上一节的字段,重点看 english_sentences 和 drift_flag,而不是凭阅读感觉判断。

Mistral 中文回答总夹英文 / 先改提示词语言还是换分词?

显式约束仍然失效时,按顺序检查这几处:system 是否被平台截断或覆盖;接口是否把 system 合并进了第一条 user 消息;上下文是否超长导致约束被挤掉。确认无误后,可以把同一段约束压缩成一句“再次强调:全文简体中文”,追加在 user 消息末尾再试一次,然后再进入解码参数这一层。

固定随机种子后调整解码参数再测

采样设置本身不决定语言,但温度偏高、候选范围偏宽时,中文语境里蹦出英文片段的机会通常更大。验证方式是固定种子,一次只改一个参数,其余保持一致:

轮次temperaturetop_ptop_k目的
A0.20.9默认基线,与上一节第二轮一致
B0.0 到 0.11.0默认接近确定性解码,看漂移是否减少
C0.20.6默认收窄核采样范围
D0.20.920限制每步候选词数

固定种子的做法是请求里带同一个 seed,其他参数不动,重复调用同一输入。需要注意,多数服务只保证同环境、同版本下大致稳定,换版本、换部署或服务端负载变化都可能让结果不一致,所以最好同一配置重复跑三到五次,看漂移出现的稳定性,而不是跑一次就下结论。如果接入的服务不支持 seed 参数,就退化成多次重复取观察,并在记录里注明结果不可复现。

换一个中文占比更高的同类模型做基线对照

如果前两层已经排干净,混用仍然明显,就换一个中文语料占比更高的同类模型做基线对照。要求是同一批测试样本、同一段提示词、同一组解码参数,只换模型名。对照维度包括:

  • english_sentences:完整英文句子的数量
  • drift_flag:漂移样本在同一批样本中的分布
  • 术语保留是否合理:该保留原文的英文有没有被硬翻成中文
  • 中文表达是否自然:有无明显翻译腔或生硬直译
  • 输出长度与结构化程度:列表、小标题是否符合原要求
  • 上下文长度上限与响应时间:作为选型时的附加约束

结论落到模型选择上:换模型后混用明显减少且提示词没变,说明原来的混用主要来自模型自身的中文偏好;换模型后仍然混用,说明问题在提示词写法、解码设置或平台封装,换模型解决不了。选型时不要只看语言表现,还要确认函数调用、长上下文、结构化输出这些能力是否满足你的场景。

把结论固化进调用封装

把验证结果写进一个统一封装,让默认调用就不容易混用。提示词模板、参数默认值和输出语言检查放在一起:

import re
import requests

DEFAULT_SYSTEM = """回答语言约束(优先级高于用户消息中的语言要求):
1. 全文使用简体中文。
2. 代码、命令、文件路径、API 字段名、URL、版本号、专有名词保留原文。
3. 不输出英文句子或英文小标题;引用英文术语写成“中文(English)”。
4. 用户用其他语言提问时仍然用中文回答,除非用户明确要求切换。"""

DEFAULTS = {
    "model": "mistral-small",
    "temperature": 0.2,
    "top_p": 0.8,
    "seed": 42,
}

CODE_BLOCK = re.compile(r"```.*?```", re.S)
ENGLISH_RUN = re.compile(r"(?:[A-Za-z][A-Za-z'\-]*[\s,]+){4,}")

def call_chat(user_text, system=DEFAULT_SYSTEM, **overrides):
    params = dict(DEFAULTS)
    params.update(overrides)
    body = {
        "model": params["model"],
        "messages": [
            {"role": "system", "content": system},
            {"role": "user", "content": user_text},
        ],
        "temperature": params["temperature"],
        "top_p": params["top_p"],
        "seed": params["seed"],
    }
    resp = requests.post(API_URL, headers=HEADERS, json=body, timeout=60)
    resp.raise_for_status()
    return resp.json()["choices"][0]["message"]["content"]

def language_ok(text, allow=("JSON", "API", "URL", "Python", "Mistral")):
    clean = CODE_BLOCK.sub(" ", text)
    for seg in ENGLISH_RUN.findall(clean):
        seg = seg.strip()
        if seg and not any(a in seg for a in allow):
            return False, seg
    return True, None

def chat_checked(user_text, retries=1, **overrides):
    text = call_chat(user_text, **overrides)
    ok, seg = language_ok(text)
    while not ok and retries > 0:
        retry_input = user_text + "\n\n再次强调:全文使用简体中文作答。"
        text = call_chat(retry_input, **overrides)
        ok, seg = language_ok(text)
        retries -= 1
    return text, ok, seg

后处理只做检查和重试,不要直接把英文段落自动机翻回中文:代码、命令、字段名一旦被翻译就不可用。命中英文句子时优先重试一次并追加中文约束,重试后仍命中的样本落库标记,人工校对或走换模型分支。参数默认值也不要写死在一个文件里,不同任务的合适温度差别很大,建议把 DEFAULTS 放进配置,按任务覆盖,并在每次改动后用手上这批样本重新跑一遍对照表确认。