大模型不按指定语言回答怎么办:一次线上事故的修法(脚本检查 → 带规则重问一次 → 翻译兜底,附 Python 代码)
说明:文中的服务是作者自己做的(LinkDigest,把小红书、抖音链接读成文字的接口)。数字来自 2026-11-06 当晚的线上日志。
背景
我们有一个接口:一组小红书笔记读完之后,用一个便宜的 flash 模型写一份「对比报告」(共同主题、开头钩子、读者顾虑、空白点……),报告语言可以指定,比如 translate_to: "ja"。系统提示里写得很清楚:Reply with JSON, in Japanese.
事故
当晚给日本的客户做 8 个城市的公开报告。前 3 批里有 2 批,模型写出来的 summary 和每条要点全是中文。 原因不难猜:输入的笔记是中文,便宜模型被素材的语言「带跑了」,提示词里那句 in Japanese 被无视。
第一反应通常是改提示词:加粗、写三遍、放到最前面。我们的规矩是:一个线上模型两次违反同一条指令,就不再换措辞,改成三步:代码检查 → 一次重问 → 确定性的兜底。
一、代码检查:看文字本身是什么语言
不需要语言识别模型,看「字形」就够了:
- 日文:必须出现假名(平假名、片假名)。中日文共用汉字,只看汉字分不出来;
- 韩文:看谚文;
- 中文:有汉字,而且几乎没有假名;
- 英文等:拉丁字母明显多于汉字。
import re
_CJK = re.compile(r"[㐀-鿿]")
_LATIN = re.compile(r"[A-Za-z]")
_KANA = re.compile(r"[-ゟ゠-ヿ]")
_HANGUL = re.compile(r"[가-힣]")
def written_in(language: str, texts: list[str]) -> bool:
text = " ".join(t for t in texts if t)
if not text.strip():
return True # 没有可判断的内容,不拦
code = language.split("-")[0].lower()
cjk, kana = len(_CJK.findall(text)), len(_KANA.findall(text))
if code == "ja":
return kana >= 3
if code == "ko":
return len(_HANGUL.findall(text)) >= 3
if code == "zh":
return cjk >= 1 and kana < 3
return len(_LATIN.findall(text)) >= 4 * cjk
只检查模型自己写的「散文」(summary、每条要点的 text、无法判断的说明),不检查引文:引文按设计要保留笔记原文的中文,它们是证据。
二、带规则重问一次
检查不过,就把规则写得更直白,再问一次(只问一次):
LANGUAGE RULE: the notes are in Chinese; the reader is not. Every `summary`, `text` and `missing`
string MUST be written in Japanese — describe and summarise in Japanese, never copy the notes'
sentences. Only each example's `quote` keeps the note's original words.
三、翻译兜底:只翻散文,证据由代码拷回
重问还是中文,就走一次「翻译」调用:把 JSON 交给模型,只翻 summary / text / missing。返回之后,由代码把 note 编号、count、quote 从原始结果拷回去——不相信模型会「原样保留」。
def translate_prose(data: dict, language: str) -> dict:
raw = call_model(json.dumps(data, ensure_ascii=False),
system=TRANSLATE_PROMPT.format(language=language),
max_tokens=12000)
out = json.loads(raw)
for name in SECTIONS:
src = data.get(name) or []
for i, point in enumerate(out.get(name) or []):
if i < len(src):
point["examples"] = src[i].get("examples") or [] # 证据原样拷回
point["count"] = src[i].get("count", point.get("count"))
return out
四、第二个坑:重问本身跑飞了
修完上线,大阪那一批(5 篇笔记)的重问请求一路写到了 48,000 token 的输出上限,约 7 分钟后报「输出被截断」,整批的对比报告丢了——连第一次已经生成好的中文版也一起没了。
修法两条:
- 重问和翻译这两步单独设
max_tokens=12000。一份对比报告几千 token 就够,模型跑飞时一分钟内失败,而不是等到 48k; - 每一步都允许失败:失败就保留上一步的结果;如果最后仍然不是目标语言,把结果的
language标成它实际的语言(标题也随之用中文),而不是谎称这是一份日文报告。
data = parse(call_model(content, system=system))
if translate_to and not written_in(lang, prose(data)):
try:
again = parse(call_model(content, system=system + RULE, max_tokens=12000))
if written_in(lang, prose(again)) or len(prose(again)) >= len(prose(data)):
data = again
except Exception:
pass # 第一份结果保留
if not written_in(lang, prose(data)):
try:
data = translate_prose(data, lang)
except Exception:
pass
if not written_in(lang, prose(data)):
lang = language_of(notes) # 如实标注实际语言
五、结果
修好后把 8 个城市重新跑了一遍:7 份报告的散文都是日文(每份 139–317 个假名),引文保持中文原文、逐条和笔记核对;东京药妆那一份因为搜索只返回 1 条笔记,被我们自己拿掉了。测试里用假模型覆盖三条路径:一次就对(不重问)、重问后对、重问和翻译都失败时保留第一份并如实标注语言。
六、可以带走的几条
- 便宜模型 + 外语素材,「用 X 语言回答」经常失效。别指望提示词,做代码检查;
- 判断中文还是日文看假名,不要看汉字;
- 兜底翻译时,证据字段由代码拷回,不让模型碰;
- 每个重试步骤都要有自己的 token 上限,失败时保留已有结果;
- 结果的语言如实标注,宁可是一份标明「中文」的报告,也不要一份挂着「日文」标题的中文报告。
更多推荐

所有评论(0)