Skip to main content

QMT - Qwen-MT CLI

命令行翻译工具,默认使用百炼上的通用大模型 qwen3.8-max,同时支持通义多模态翻译 (AnyTrans) 与 DashScope Qwen-MT 系列。通用大模型没有服务端翻译协议,术语干预、翻译记忆、领域提示改由提示词在客户端重建,详见 通用大模型后端;要走 AnyTrans 的服务端术语干预与 BatchTranslate 摊销,显式加 -m anytrans。提供单句翻译、交互式 REPL、CSV/Excel 批量翻译、半价批量推理,以及术语表、翻译记忆、领域提示、向量语义匹配等专业翻译辅助功能。

安装

需要 Python >= 3.10。

pip install qwen-mt-cli

或从源码安装:

git clone https://github.com/leoleils/qwen-mt-cli.git
cd qwen-mt-cli
pip install -e .

安装后即可使用 qmt 命令。

配置

DashScope(默认模型所需 / 向量语义匹配 / qwen-mt-*)

设置 DashScope API Key(申请地址):

export DASHSCOPE_API_KEY="sk-xxxxxxxx"

默认的 qwen3.8-max、其余通用大模型后端(-m qwen3.8-flash、-m llm:<名>,含 --batch-api)、qwen-mt-* 模型,以及向量语义匹配,走的都是同一个百炼账号与同一把 Key。只有 -m anytrans 不用它。

端点默认打北京百炼。 要换区域(新加坡 / 美国 / 香港)、换业务空间专属的 MaaS 网关、或换任意 OpenAI 兼容的第三方网关,用端点档案:它把 base URL 与该区域的 API Key 绑成一对存起来,一条命令切换。QMT_BASE_URL 仍然可用,是 CI 与临时覆盖的逃生口。

AnyTrans(-m anytrans 时需要)

设置阿里云 AccessKey 和业务空间 ID:

export ALIBABA_CLOUD_ACCESS_KEY_ID="your-ak-id"
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-ak-secret"
export ANYTRANS_WORKSPACE_ID="llm-xxxxxxxx"

或通过 qmt config set 持久化保存:

qmt config set --access-key-id <AK> --access-key-secret <SK> --workspace-id <WID>

项目默认值(语向 / 场景 / 术语检索模式)

四个键可持久化到 .qmt/config,省去每条命令重复指定:

qmt config set --source-lang Chinese --target-lang Korean --scene mt-turbo --rag-mode cloud
qmt config show     # 逐项标注每个值的来源 (CLI / env / 项目 / 全局 / 默认)
qmt config clear --rag

优先级:命令行 > 环境变量(QMT_SOURCE_LANG / QMT_TARGET_LANG / QMT_SCENE / QMT_RAG_MODE)> 项目 .qmt/config > 全局 ~/.qmt/config,逐键覆盖。显式 -t English 永远赢过持久化的 target_lang。

副作用:这四个键对所有后端生效,不只 AnyTrans。持久化 target_lang = Korean 之后,qmt -m qwen-mt-plus "hello" 也会翻成韩语。

rag_mode 的完整说明见 术语 RAG 模式。

端点档案(多区域切换)

百炼的 API Key 是分区域的:一把 Key 只在签发它的那个区域生效,配上别的区域的 base URL 直接 401(官方说明)。所以端点档案把 base_url 与该端点的 api_key 绑成一对存取,切换时一起换——只换 URL 不换 Key 正是这个 401 的成因,而它的报错完全看不出因果。

# 建档案:--base-url 收完整 URL,也收下面四个预置区域名
qmt endpoint add sg --base-url singapore --api-key sk-xxxxxxxx
qmt endpoint add gw --base-url https://my-gateway.example.com/v1 --api-key sk-yyyyyyyy

qmt endpoint use sg          # 设为生效(写进 .qmt/config,之后每条命令都用它)
qmt endpoint list            # 档案 / 层级 / 生效项 / 本次运行实际会用的 URL 与 Key 来源
qmt endpoint remove sg
qmt --endpoint sg "你好" -t English   # 只覆盖这一次运行,不改配置

--global 在 add / use / remove 上都可用,写到 ~/.qmt/config。档案名限 1-32 个 A-Za-z0-9_- 字符(不能带点,点是配置分层的分隔符),URL 必须以 http:// / https:// 开头。预置区域名只在 --base-url 处展开,--endpoint 只认已存档案名或完整 URL。

预置名 base URL
beijing(默认) https://dashscope.aliyuncs.com/compatible-mode/v1
singapore https://dashscope-intl.aliyuncs.com/compatible-mode/v1
us https://dashscope-us.aliyuncs.com/compatible-mode/v1
hongkong https://cn-hongkong.dashscope.aliyuncs.com/compatible-mode/v1

业务空间专属的 MaaS 端点(形如 https://ws-xxxx.<region>.maas.aliyuncs.com/compatible-mode/v1)没有预置——它需要控制台才知道的 WorkspaceId,照上面的 gw 例子原样填即可。

跟随档案的流量:通用大模型后端(含 --batch-api 的文件上传、提交、轮询、下载)、qwen-mt-*、以及本地 RAG 的 qwen3.7-text-embedding-flash。-m anytrans 不跟随:它用账号级 AK/SK 签名,endpoint/region 钉在 cn-beijing。

优先级:--endpoint > QMT_BASE_URL > 项目 .qmt/config 的生效档案 > 全局 ~/.qmt/config 的生效档案 > 默认(北京)。

两条与上面项目默认值不同的分层规则,都是为了防止半覆盖拼出一个必然 401 的组合:

  • 档案整体覆盖,不逐键合并。 项目级的同名档案整条遮蔽全局级的。逐键合并会把项目的 URL 拼到全局的 Key 上,而这个错配发生在配置文件内部,没有任何一层能看见它。
  • QMT_BASE_URL 整体胜出,连带丢弃档案的 Key。 它是 CI 与临时覆盖的逃生口,行为与引入档案之前一致;但 shell 里 export 的北京 URL 配上档案里存的新加坡 Key 同样必然 401,所以这里打一条醒目警告并点名被覆盖的档案,Key 回落到 --api-key / DASHSCOPE_API_KEY / 配置里的 api_key。刻意不提供 QMT_ENDPOINT(档案名)环境变量:它带不了 Key,等于把刚消掉的半覆盖请回来。

档案没存 Key 时走上面那条回落链并提示一次——档案可以只存 URL。

翻译缓存不按端点隔离。 同一个模型换个区域还是同一个模型,cache.db 共用,跨区域重跑照样命中。

向量索引会记下建库时的端点。 换区域后本地检索仍然可用(索引本身不依赖端点),qmt vectordb status 会指出不一致并建议 rebuild,翻译时只警告一次、不失败。嵌入模型变更则相反,是硬失败。注意 qwen3.7-text-embedding-flash 是分区域上架的,新加坡端点没有这个模型——切到那边嵌入调用会直接 404,需要先把模型换回该区域可用的。

401 / InvalidApiKey 的报错会多一行诊断,点名当前端点、档案名与 Key 的来源。qmt config show 的 API Endpoint 段是同一份信息的常驻版本,换端点后先跑它确认。REPL 里用 /endpoint,见交互式模式。

快速开始

# 翻译文本(默认 qwen3.8-max,通用大模型后端)
qmt "你好世界" -t English

# 切换到 AnyTrans(服务端术语干预、专家角色、BatchTranslate 摊销)
qmt "你好世界" -t English -m anytrans

# 指定专家角色(仅 -m anytrans 生效,不带 -m anytrans 会被静默忽略)
qmt "你好世界" -t English -m anytrans --agent game

# 切换到 DashScope 翻译模型
qmt "Hello World" -t Chinese -m qwen-mt-plus

# 换一个通用大模型
qmt "Hello World" -t Chinese -m qwen3.8-max

# 百炼上的任意模型
qmt "Hello World" -t Chinese -m llm:qwen-plus

# 翻译文件
qmt -f article.txt -t English

# 管道输入
echo "你好" | qmt -t English

# 流式输出(默认模型即支持;qwen-mt-flash/lite 与所有通用大模型也支持)
qmt "你好世界" -t English --stream

# 交互式模式
qmt -i -t English

# 列出所有模型及其能力矩阵
qmt models

可用模型

qmt models 打印的就是这张表,且直接读注册表,不会与代码漂移——以它为准。

模型 后端 特点 流式 批量推理 语言数
anytrans AnyTrans 通义多模态翻译,服务端术语干预/专家角色/BatchTranslate - - 92
qwen-mt-plus DashScope 翻译 最高翻译质量,适用于专业领域 - - 92
qwen-mt-flash DashScope 翻译 通用首选,效果/速度/成本平衡 支持 - 92
qwen-mt-lite DashScope 翻译 最快响应速度,适用于实时场景 支持 - 31
qwen3.8-max 通用大模型 通义千问 3.8 旗舰 (默认) 支持 支持 -
qwen3.7-plus 通用大模型 通义千问 3.7 Plus,质量/成本平衡 支持 支持 -
qwen3.8-flash 通用大模型 通义千问 3.8 Flash,响应最快 支持 - -
deepseek-v4-pro-0813 通用大模型 DeepSeek V4 Pro (第三方,百炼托管) 支持 - -
kimi-k3 通用大模型 Kimi K3 (第三方,百炼托管) 支持 - -

通用大模型的语言覆盖由模型本身决定,不是翻译协议规定的清单,所以不列数字。

百炼上的其他模型用 -m llm:<模型名>(如 -m llm:qwen-plus)。注册表里没有的名字,能力位按最保守方向推断:假定支持流式(判错最坏是端点返一个明确错误),假定支持批量推理(判错最坏是任务在 validating 阶段被拒,此时不计费)。

批量推理 为 - 的模型带 --batch-api 时会提示一次并回退到实时并发路径——不是报错,因为百炼本来就会在 validating 阶段拒绝它们;但回退后按实时全价计费,这一点会明确说出口。

llm: 只接受模型名,不接受保留名:-m llm:anytrans 与 -m llm:qwen-mt-plus 会在发请求前被拒绝,因为这些模型的翻译能力来自服务端私有协议,提示词替代不了。

选型实测

2026-09,中→韩,1000 行 MMO 修仙文案,1135 条术语,同一份领域提示词,四个全新无缓存目录。裁判 qwen3.7-plus——不是四个选手中的任何一个,否则自我偏好会污染结果;抽 200 行,每行跑两次不同的标签洗牌(四份译文随机分到甲乙丙丁)消除位置偏见。

模型 术语 语义 语域 格式 综合 平均名次
qwen3.8-max 4.88 4.84 4.72 4.98 4.85 1.90
llm:deepseek-v4.1-flash 4.76 4.72 4.61 4.89 4.75 2.22
qwen3.8-flash 4.77 4.79 4.12 4.96 4.66 2.48
qwen-mt-plus 4.62 4.75 2.99 4.91 4.32 3.40

两两胜出:max 胜 mt 85.9%、胜 flash 66.3%、胜 deepseek 58.0%;deepseek 胜 flash 58.0%。

语域是唯一拉开差距的维度——但这只是中→韩的结论。 mt 2.99 对 max 4.72,其余三维四个模型全在 0.3 分以内。历次对比(max vs flash、max vs deepseek)都是同一个结论。纯规则指标独立印证了这件事:领域提示词要求 UI 文案简洁无敬语,而 mt 有 62.5% 的行用了 합니다 体(max 11.4%、deepseek 14.1%、flash 38.0%),长度比中位数 2.17 对其余的 1.50–1.67——mt 把 UI 短语写成了完整敬语句。

别把这条搬去别的语向。 同一个 mt、同一份领域提示词,换到中→日就基本合规了,于是语域不再是区分维度,max 改成四个维度全部第一地赢——见日语复核。「选后端先看敬语率」是中→韩这一轮的经验,不是通用启发式:敬语率衡量的是模型对你这个语向的语域要求的服从度,换语向必须重测。

术语的字面命中率不能单独拿来排名。 mt 走服务端受约束解码,字面命中却是四个里最低的:

模型 字面命中 裁判术语分 残留中文
qwen3.8-max 95.3% 4.88 0
qwen3.8-flash 94.4% 4.77 0
llm:deepseek-v4.1-flash 91.5% 4.76 2 行
qwen-mt-plus 91.4% 4.62 3 行

字面命中算的是"译词是否作为子串出现",它把两类完全不同的东西混在一起。mt 漏的 33 个术语里 14 个 max 也漏——那是四个模型共同的坑,开启→오픈、装备→장비、高级→고급 这类词性与词边界误判(装备 作动词该译 장착,最高级 里含 高级)。mt 独有的 44 次里,23 次是同一个条目 已达上限→상한 달성:mt 译成完整句 상한에 도달했습니다,max 译成名词短语 상한 달성,两个语义都对,但只有后者字面命中,而且后者才符合领域提示词的"UI 简洁"。真错也有——该槽位未激活 被 mt 译成"未被解除的槽位"。mt 那 3 行残留中文是把汉字原样留在了韩文里(귀蟾왕、도화槽)。

批量吞吐不是模型速度。 单条延迟 mt 反而最快:

模型 单条延迟 批量有效 倍率
qwen-mt-plus 1.50s 2.57s/行 0.6×
qwen3.8-max 1.88s 0.26s/行 7.2×
qwen3.8-flash 2.03s 0.50s/行 4.1×
llm:deepseek-v4.1-flash 9.29s 1.11s/行 8.4×

四个都是并发 5、批 10。其余三个拿到了 4–8 倍并行加速,mt 只有 0.6 倍——比串行还慢。本轮日志里没有可见的限流记录,所以不能坐实原因;机制上的嫌疑是全局退避:一个车道吃到 429 会暂停所有车道。结论是别按 行/分 给模型排速度,那个数字混进了退避等待。deepseek 的 9.29s 是模型自身的(思考型,还出现过一次 28.69s),不适合实时场景。

llm:deepseek-v4.1-flash 必须配 --no-thinking,否则 1.9% 的行会直接失败——见「思考模式」。

日语复核

2026-09,中→日,500 行,7265 条术语,同一份领域提示词(多了一节段位名冲突说明),四个全新无缓存目录。裁判、抽样、标签洗牌口径与中→韩那轮一致。

两轮不能直接比数字:语料零重合,术语表 7265 条对 1135 条,行数 500 对 1000。能跨轮比的只有结论的结构。

模型 术语 语义 语域 格式 综合 平均名次
qwen3.8-max 4.89 4.82 4.81 4.89 4.85 2.19
llm:deepseek-v4.1-flash 4.81 4.79 4.72 4.80 4.78 2.40
qwen3.8-flash 4.83 4.66 4.81 4.73 4.76 2.42
qwen-mt-plus 4.64 4.64 4.29 4.74 4.58 3.00

两两胜出:max 胜 mt 69.6%、胜 flash 56.0%、胜 deepseek 55.8%;flash 对 deepseek 50.8%,是平局。耗时 mt 31m22s、flash 6m26s、deepseek 4m07s、max 2m04s——大模型反而最快这个反直觉结果与中→韩一致,同一个警告适用。

max 仍然第一,但赢法完全不同。 中→韩是一维拉爆(语域差 1.73,其余三维 ≤0.3),中→日是四个维度全部第一的广谱领先,语域只差 0.52。

mt 的敬语率在日语里是假信号。 它 56 行用了 です・ます体(11.2%),按原文长度拆开后是:411 行短 UI 文案里 2 行(0.5%),89 行长句里 54 行(60.7%)。领域提示词只约束「UI 与系统提示文案」,长句用敬体在日语里是对的。统计敬语率必须按文案类型分层,否则会把合规的模型判成最差。同一件事的另一面:flash 在中→韩的短板(语域 4.12,합니다 体 38%)在日语里不存在(UI 敬语 0/411),于是它和 deepseek 打平了。

mt 在日语输在结构性的地方,改提示词治不了。 段位名 秩序白银V 该译 秩序シルバーV(前缀条目 + 金属条目 + 罗马数字),max/flash/deepseek 15/15 全对,mt 只有 7/15。不是没收到提示词——domains 字段确实带上了整份领域提示词(840 字符,没触 3000 上限)——而是结构化 terminologies 走服务端受限解码,硬约束压过自由文本指令。另有 15 行把 ASCII V 改写成罗马数字码位 Ⅴ,21 行把中文人名转写成片假名(浦立群 → プーリー群),而仙侠题材的日文惯例是直接用汉字。

日语特有的保真缺陷是简体字形漏转,不是残留中文。 日语目标语本身就写汉字,中→韩那轮的「残留中文」指标在这里失效(carries_source_script 刻意排除日语是同一个理由);「译文与原文逐字相同」也不能当未翻译——500 行里有 37 行四个模型全部原样输出,全是 夏侯惇、九天玄女、敖丙 这类专名,原样是正确行为。真正能规则化检查的是字形:乐山白 该写 楽山白、洪泰华 该写 洪泰華。flash 一家 10 行,其余三家各 1 行;裁判给 flash 的语义分 4.66 是三个通用模型里最低的,与此吻合。

字面命中率这轮会惩罚遵守提示词的行为。 术语表里 黄金→金 与 荣耀黄金→ゴールド 并存,max 正确遵守「长条目优先」写出 栄光ゴールドV,反被记一次未命中。

AnyTrans 专家角色

使用 --agent 指定翻译专家(仅 -m anytrans 生效):

game medical legal tech finance news academy novel ebook music design web3 github twitter reddit ao3 chess ecom vocab wordalign summarize simplify mixlang dongbei bai2wen wen2bai sublearn

AnyTrans 模型场景

使用 --scene 选择模型档位(默认 mt-turbo):

  • mt-plus — 专业版,更高质量
  • mt-turbo — 轻量版,更快响应

通用大模型后端

AnyTrans 与 Qwen-MT 的翻译能力来自服务端私有协议:Qwen-MT 读 translation_options,AnyTrans 读 ext.terminologies / ext.examples / ext.domain_hint。百炼上的通用大模型不认识这些字段,传了会被忽略。

这个后端把同样的三种干预在客户端重建为提示词,其中术语一分为二——一个源词有多个已批准译词时,它需要的是与术语表其余部分相反的指令:

干预 翻译特化模型 通用大模型
术语 terms / ext.terminologies,服务端受约束解码级硬干预 system prompt 的 ## Glossary 段,要求逐字采用
一词多译的术语 从硬约束通道整条撤出,折进 domains / ext.domain_hint 的自由文本 ## Terms with context-dependent translations 段,让模型按上下文从候选里选一个
翻译记忆 tm_list / ext.examples,few-shot 参考 system prompt 的 ## Approved reference translations 段,并明确"这是风格示例、不是待译文本"
领域提示 domains / ext.domain_hint system prompt 一行,用户文本逐字插入(中文写的领域提示不会被再翻译一道)
专家角色 agent / ext.agent 映射成角色短语替换首句的领域定语

上层完全不变:单句、REPL、CSV/Excel 批量、向量语义匹配、--learn 回写、缓存、断点恢复都照常工作。

骨架英文、用户内容原样。 角色定义与输出纪律用英文写(指令遵从更稳),domain / 术语 / 记忆里你的文本一律逐字插入。

上下文裁剪。 先按 4000 字符预算裁(术语优先吃预算、记忆填剩余,与其他后端同一策略),再叠一道条数闸门:术语 ≤30 条、一词多译的冲突术语 ≤8 条、记忆 ≤6 条。40 条示例在 AnyTrans 那边是按请求发一次被整批摊薄,在这里是每行重发——既烧 token,也让模型更可能去续写示例而不是守规则,所以闸门比字符预算收得更紧。冲突术语的 8 条是独立闸门,不继承术语的 30:一行冲突最多渲染 k 个候选 × 120 字符,30 行就是一堵歧义墙,而这一节最不能成为的恰恰是歧义墙。

输出清洗只剥不可能是合法译文的东西。 外层代码围栏、包裹整句的引号、开头的 Translation: 一类前缀标签会被剥掉;但源文自己以引号开头时引号保留,正文中间的围栏和 Translation: 不动,尾部的 \n\n注: 一律不动——游戏文本里这些都可能是真实台词,剥掉就是数据损坏,还会被 --learn 写进 memory.csv 变成永久污染。流式输出走同样的清洗。可疑但没被修改的内容用 -v 提示,交给你判断。

思考模式

qwen3.8 / 3.7 系列默认开启思考且思考 token 计费。翻译用不上思维链,所以注册表标记为"默认开"的模型会被主动关掉(发 enable_thinking=false),不关就是白烧钱 + 变慢。

实测代价(真实调用,total_tokens)。同一行 CSV、12 条术语 + domain 注入:

模型 不发 flag 关掉后 其中思考 token
qwen3.8-flash 1019 531 443
deepseek-v4-pro-0813 1024 526 418
kimi-k3 1255 558 623

约 2 倍。文本越短比例越夸张——裸发一句 10 字中文时是 115→40、166→21、350→41,思考占比随源文长度摊薄。第三方模型(DeepSeek / Kimi)也认这个参数,说明它是百炼网关层的开关而非模型自带。

qmt "你好" -t English -m qwen3.8-max                    # 自动关闭思考
qmt "你好" -t English -m qwen3.8-max --thinking         # 强制开启
qmt "你好" -t English -m qwen3.8-max --thinking --thinking-budget 1024

注册表里没有的模型(含 -m llm:<自由名>)不发这个参数:给不认识它的模型发 enable_thinking 会直接 400,而判错的代价(多花思考 token)远小于命令失败。要强制就用 --thinking / --no-thinking,报 400 就说明该模型不认。

判错的代价不止是多花钱。思考预算和答案共用 max_tokens,所以一个没被注册表标出来的思考模型可能把额度全花在思考上、答案被截断,qmt 直接报"输出被 max_tokens 截断"而不是交回半句话。实测 -m llm:deepseek-v4.1-flash 跑 1000 行有 19 行(1.9%)这样失败,加 --no-thinking 后 19 行全部正常、零吐回。用 llm: 接第三方思考模型时先拿几十行试跑,见到这个报错就加 --no-thinking。

思考内容永远不进输出——只读 content,不读 reasoning_content。批量推理的结果里根本没有 reasoning_content,所以 --batch-api 配 --thinking 只会烧钱、看不到思考过程。

已知限制

1. 术语干预靠提示词,不靠受约束解码。 translation_options.terms(qwen-mt)与 ext.terminologies(AnyTrans)是受约束解码级别的硬干预,服务端保证逐字采用;通用大模型后端只是把术语表渲染进 system prompt,那是"请求"而不是约束。glossary 非空且选了通用大模型时会向 stderr 提示一次(管道输出保持干净)。

机制上的差别是真的,但它在 1000 行 / 1135 条术语的量级上没有转化成可测的遵从度优势。四方实测里走硬约束的 qwen-mt-plus 字面命中 91.4%,是四个里最低的;走提示词的 qwen3.8-max 是 95.3%,裁判的术语分两者也只差 0.26(4.62 对 4.88)。详见「选型实测」,包括为什么字面命中率本身不能单独当结论——它把词性误判、词边界误判和语域差异混在一个数字里。

早期只测过 12 条武侠术语表(五个预设全部 11/11 命中),那个量级看不出衰减,别拿它外推。

想量化自己的术语表上的差距,跑这两条对比:

qmt "而这套生物传感器运用了石墨烯" -t English -m qwen-mt-plus
qmt "而这套生物传感器运用了石墨烯" -t English -m qwen3.8-max

2. 占位符可能损坏。 通用模型会把 {0} 规整成 { 0 }、把 %s 前后的词一起译掉。system prompt 有保护规则,但只能缓解不能保证。游戏 CSV 里这是致命的,跑完请抽查带占位符的行。

同一类形态也出现在分隔符上。实测 21 行带 | 的文案,| 的个数一次都没丢,但其中 2 行被加了空格:格挡|15% → Block | 15%、神兵暴击|983.46万 → Divine Weapon Crit | 9.8346M(同批韩文 10 行全干净)。客户端若按 | 切分又不 trim,字段就会带前后空格。输出清洗刻意不自动删这些空格:分隔符两侧的空格在某些文案里是合法的,按猜测改译文比留着它更危险。代价是没有任何自动检测——-v 的可疑提示只认尾部的解释块,认不出分隔符空格,所以这一类只能靠人工抽查。

3. token 成本与 AnyTrans 是两套算法。

  • 实时逐行:AnyTrans 的 BatchTranslate 把 ext 按请求发一次、被整批 N 行摊薄(约 8 倍降幅,见 批量为什么省钱);通用大模型后端每行重发完整 system prompt。10 条术语 + 6 条记忆 + 规则骨架 ≈ 800–1500 input token/行,1 万行 CSV 就是额外 800 万–1500 万 input token。建议从 -C 3 起步。
  • --batch-api:单价是实时的 50%,但每行仍带完整 system prompt,且批量推理不支持上下文缓存。所以"1 万行 × 1200 token × 半价"仍可能贵于 AnyTrans BatchTranslate 摊薄后的全价。

粗略盈亏平衡(用下面那张表的数字):设通用大模型单行 system prompt ≈ 1200 token、源文本 ≈ 30 token,则 --batch-api 是 1230 token/行 × 半价,折合 615 token/行的全价开销;AnyTrans --batch-size 10 摊薄后是 130 token/行全价。差约 4.7 倍。

在术语/记忆较重的场景下,AnyTrans 批量仍然明显更便宜;通用大模型后端的价值在于模型选择面,不在于成本。要压这 615,只能减少注入的术语与记忆条数。

4. 改提示词骨架后请 qmt cache clear。 缓存键不含提示词版本号,旧译文会继续命中。

5. 关掉思考可能让模型把原文原样吐回。 实测 qwen3.8-flash 在收到 enable_thinking=false 时会把短中文串一字不改地交回来(20/20 确定性复现,不关思考时 0/20)——它把"不要思考"理解成了"不要处理"。两道防线:

  • 译文仍带源文字、且本次确实是 qmt 关掉了思考时,自动开着思考重试一次,第二次还不行就交回第一次的结果(不报错,因为那可能只是这一行的问题)。
  • 缓存拒绝落盘空译文和与原文相同的译文。少了这一道,一次模型抽风会被冻结成永久命中:之后每次都报缓存命中并交回没翻译的原文。

已知缺口:重试的触发条件是"qmt 自己关过思考"。用 -m llm:<自由名> 接的未注册模型什么都不发(见「思考模式」),它要是自己吐回原文,重试不会触发,那一行会进输出文件——缓存仍然拒绝落盘,所以不会污染后续运行。四方实测里 deepseek 有 1 行是这样。

目标语本身就写汉字时(中→日、auto)不做这个检测,否则满是汉字的正确日文译文会被判成未翻译。误报的代价是多调一次,漏报的代价是一行没翻译还被缓存冻住,所以这个检测刻意只覆盖"CJK 源 → 非 CJK 目标"。

批量翻译

支持 CSV (.csv/.tsv) 和 Excel (.xlsx/.xls) 文件的批量翻译。翻译文件第一列内容,结果追加为新列。

# CSV 批量翻译(默认 5 路并发)
qmt -B input.csv -t English

# Excel 批量翻译(所有 sheet)
qmt -B input.xlsx -t Korean

# 指定并发数(1-20)
qmt -B input.csv -t English -C 10

# 指定输出文件
qmt -B input.csv -O output.csv -t English

# 首行也翻译(无表头模式)
qmt -B input.csv -t English --no-header

# 断点恢复(中断后从上次位置继续)
qmt -B input.csv -t English --resume

# 调整每批携带条数(默认 10,范围 1-50,仅 anytrans)
qmt -B input.csv -t English --batch-size 20

# 退化回逐条翻译(逃生口)
qmt -B input.csv -t English --batch-size 1

默认行为:

  • 批量请求:仅 -m anytrans 走 BatchTranslate,相邻行合并成一个请求(--batch-size 控制条数)。默认的 qwen3.8-max 与其他后端逐行请求,--batch-size 对它们无效,显式传了会 warn 一次
  • 并发翻译:默认 5 路并发调用翻译 API,可通过 -C 调整(1-20)。-C 始终是并发请求数:走 BatchTranslate 时实际在飞行文本数 ≈ C × batch-size,逐行后端就等于 C
  • 自适应限流:遇到 API 429 自动降低并发度,恢复后自动回升
  • 翻译缓存:相同文本+参数命中缓存时直接返回,不重复调用 API(缓存存储在 .qmt/cache.db)
  • 分批写入:翻译完一批立即落盘(默认约 100 行一次),崩溃时未落盘的行由 --resume 重译
  • 首行视为表头,不翻译(--no-header 可改变此行为)
  • 输出文件自动命名为 原文件名_translated.ext
  • Excel 文件翻译所有工作表
  • 触发 API 限流时自动指数退避重试(2s -> 4s -> 8s -> 16s -> 32s,最多 5 次)
  • 支持 --resume 断点恢复,中断后可继续翻译

批量为什么省钱(token 摊销)

AnyTrans 按 token 计费,而术语与翻译记忆(ext)是按请求发送的。逐条翻译时每行都重发一遍同样的上下文;批量后这份上下文被整批 N 行摊薄。

以典型游戏文本为例(ext ≈ 2000 字符 ≈ 1000 token,单行 ≈ 30 token):

模式 输入 token / 行
逐条 --batch-size 1 1000 + 30 = 1030
批量 --batch-size 10 1000/10 + 30 = 130

约 8 倍降幅。摊销随条数单调增强,但官方未公布单请求条数上限,且一条坏数据的爆炸半径随批增大,所以默认保守取 10。跑通后可用 --batch-size 20~30 继续压低——单批源文本总字符另有 20000 的自限保护。实际开销在翻译摘要的 Token 消耗 行输出。

批内上下文共享

同一批所有行共用一份术语与翻译记忆:按批做一次块级语义匹配(批内各行向量取最大相关分聚合、取 Top-N、按库顺序输出),而不是逐行各配一份。

  • 收益:索引同步从每行一次降到每批一次(25K 术语 × 1000 行的量级下差异显著);块内重复短串("确定"/"取消"/"返回")从各自 miss 变成同一缓存 key、第二次起直接命中
  • 代价:单行术语召回精度下降。这是刻意取舍——相邻行通常来自同一屏、同一语境,用词高度重叠
  • 需要逐行精度时用 --batch-size 1

滚动翻译记忆仍跨批、跨并发 lane 共享(一批完成后整批写入,下一批取最近 5 条),但不参与缓存 key,否则命中率会随运行顺序漂移。

云端模式下「付一次、处处用」的性质保持不变:整个文件的行在 warm 阶段一次性打包成 TermQuery 单元(每单元 ≤800 字符且 ≤10 行,总数超过 200 次调用则直接降级),之后每个批次的召回都是纯字典查表、零网络 I/O。所以批循环里不可能因为检索而触发限流。

--agent 与批量翻译

--agent(27 个专家角色)只有 TextTranslate 支持,BatchTranslate 的 ext 没有该字段。两者共用时批量被整轮禁用,降级为逐条 TextTranslate 并提示一次——宁可慢,也不静默丢弃你显式指定的专家角色。

要批量加速就去掉 --agent;要专家角色就接受逐条速度。

批量推理(--batch-api,半价)

这与上面的 BatchTranslate 是两个东西,只是中文都叫"批量"。 BatchTranslate 是一次请求带 N 行、秒级返回、只有 AnyTrans 有;批量推理是把整个文件拼成 JSONL 上传,任务在服务端排队,等待以小时计,单价是实时的 50%。仅通用大模型后端可用。

qmt -B input.csv -t English -m qwen3.8-max --batch-api -v

约束(都是平台限制,不是可调项):

  • 仅 CSV/TSV。 Excel 会直接报错而不是静默回退——你点名要半价,悄悄按全价跑实时路径就是撒谎。请先导出为 CSV。
  • 仅通用大模型后端。 -m anytrans / -m qwen-mt-* 带 --batch-api 会报错:这些模型的批量能力来自服务端私有协议,批量推理无法表达。
  • 注册表标 批量推理 = ✗ 的模型会提示一次并回退到实时并发路径,按全价计费。
  • 支持面按端点变,注册表预测不了。 实测某个国际业务空间端点上,五个预设全部被拒(not supported by the Batch API),连百炼文档明确列过的 qwen3.5-flash 也拒,而 qwen-plus / qwen-max 正常完成。注册表那一位是照文档查证的白名单,所以刻意不据此改成 ✗——判错的代价不对称:多声明一次只换来一个几十秒内 failed、不计费、且报错里写明了出路的任务;少声明一次则是在最多 5 万行上静默付全价。遇到这个报错就照它说的做:去掉 --batch-api 走实时全价,或换成该端点接受批量的模型。
  • 单个输入文件 ≤ 50000 条请求且 ≤ 500MB;超出自动拆成多个任务。
  • 一个文件只能是同一个模型 + 同一种思考模式。
  • completion_window 固定 24h,但实际等待可能更久;超时就变 expired。
  • 结果文件 30 天后删除。
  • 批量推理不支持上下文缓存,所以每行仍带完整 system prompt。成本对比见 已知限制 第 3 条。

命中缓存的行不会进 JSONL——半价的请求也是请求。

中断与重连。 提交成功后 batch_id 立刻写入 .qmt/batch_jobs.json,然后才开始轮询。所以 Ctrl-C 之后重跑同一条命令会继续等待已提交的任务,不会重新提交:

检测到未完成的批量任务 batch_xxx,继续等待结果 (未重新提交)

匹配条件是 (输入文件, 输出文件, 模型, 思考模式, 待译行号, 端点) 全部一致;改了输入或切了端点档案就不重连、提交新任务。端点必须参与匹配:batch_id 是该账号该区域的资源,北京提交的任务在新加坡端点上重连,换来的是数小时等待之后一个莫名的 404。想放弃一个在跑的任务:删掉 .qmt/batch_jobs.json(任务本身仍会在服务端跑完并计费,只是本地不再等它)。

部分失败是一等输出。 completed 的任务仍可能带一个 error 文件——那些行写 [ERROR: ...],其余行照常,退出码非零。failed / expired / cancelled 的任务若已有输出文件,已完成的行会先写盘再报错,因为那些行你已经付过钱了。

批量翻译故障排查

现象 原因与处理
某行输出 [ERROR: ...] 批量失败后已自动回退逐条 TextTranslate 再试一次,仍失败才标记。--resume 只重译这些行
速度没变快、仍逐条 -m 不是 anytrans;或带了 --agent;或 --batch-size 1
首批之后全部降级逐条 业务空间未开通 BatchTranslate(403/404)。运行级降级已避免每批白付一次失败请求,需去控制台确认权限
频繁 429 -C 是并发请求数,批量不改变它,但单请求更重。先降 --batch-size,再降 -C
在飞行行数被自动压低 C × batch-size 超过 100 行软上限时会收窄 lane 数并提示一次
想量化摊销收益 同一份文件分别用 --batch-size 1 与默认值跑,对比 Token 消耗
--batch-api 报 Excel 不支持 刻意不静默回退。导出为 CSV/TSV 再跑
--batch-api 报模型不支持 -m 是 anytrans / qwen-mt-*。批量推理只服务通用大模型后端。这是提交前的本地拦截
--batch-api 任务提交成功却 failed,报 not supported by the Batch API 端点不收这个模型,是服务端拒绝,本地白名单预测不了。报错里已写明出路:去掉 --batch-api 走实时全价,或换 -m llm:<该端点支持批量的模型>。不计费。任务文件保留并标记 terminal,重跑会提交新任务而不是重新挂上去
--batch-api 提示"已回退到实时并发翻译" 该模型不在百炼批量支持列表(qmt models 的批量推理列为 ✗)。回退后是全价,不是半价
--batch-api 一直显示"排队中" 正常。轮询退避 10s→60s,进度条读服务端的 request_counts,提交后长时间 0% 是预期行为
--batch-api 重跑却又提交了新任务 pending_row_indices 变了(输入文件被改过)、端点变了(切了档案),或 .qmt/batch_jobs.json 被删了
任务终止于 expired 超过 completion_window。已完成部分若已产出会写盘,job 条目保留在 .qmt/batch_jobs.json 供排查

进度观察与 Hook 唤醒

批量翻译跑起来之后,终端里那条进度条只有盯着它的人能看见。如果命令是后台跑的、或由外部 Agent 调起的,进度就是个黑盒。两件事解决它:运行状态落盘成 JSON 供随时查询,以及终态时敲一下你指定的命令把 Agent 唤醒。

查进度:qmt status

qmt status                  # 人类可读面板
qmt status --json           # 机器可读快照(走 stdout,可直接管道进 jq)
qmt status --all            # 列出近期运行
qmt status --run <run_id>   # 查指定那次运行
qmt status --wait           # 阻塞到最近一次运行结束再输出
qmt status --wait --timeout 600   # 最多等 600s

--wait 是给收不到 Hook 的 Agent 的兜底:后台跑批之后直接 qmt status --wait --json,一次调用拿到结果,不用自己写轮询循环。

状态文件落在 .qmt/runs/<run_id>.json,同时维护一份 .qmt/runs/latest.json 指向最近一次运行。写入节流到约每 0.5s 一次,阶段切换与终态立即落盘。

快照字段:

字段 含义
status running / completed / failed / interrupted
phase reading / embedding / translating / writing / collecting / queued
mode realtime / batch_api
total / completed 可翻译行数 / 已推进行数
succeeded / failed / skipped 成功 / 失败 / resume 已跳过
rows_per_minute / eta_seconds 速率与预计剩余;运行头 2s 内为 null(样本太短,算了会误导)
sheet Excel 当前在翻的工作表
error 失败原因
run_id / pid / updated_at_epoch 运行标识与存活信号

phase 不是装饰:25k 行文件的向量预热能静默几十秒,没有它你只会看到进度条卡在 0% 而无从判断是在预热还是挂了。--batch-api 排队同理,会显示 queued。

两个容易踩的坑:

  • completed 可能领先 succeeded + failed。completed 跟的是进度条(缓存命中、resume 跳过、空行、失败行都算推进),而 succeeded/failed 只在每个 chunk 边界对账。判断"跑完了没"要看 status,不要看这两个数相加。
  • stale。进程被 SIGKILL 时来不及写终态,qmt status 会把它标成 stale(updated_at 超过 30s 没动)。--wait 遇到这种状态以退出码 3 结束,不会无限挂住。无运行记录时退出码是 1。

Hook:终态唤醒

批量翻译进入终态时执行一条你指定的 shell 命令。Agent 因此不必轮询守候——配一次,跑完自动被叫醒。

三层配置,优先级依次升高:

# 1. 配置文件(持久,项目级覆盖全局)
qmt config set --hook 'echo "$QMT_EVENT" >> /tmp/qmt-done.log'
qmt config show        # 看当前生效值与来源层
qmt config clear --hook

# 2. 环境变量(临时)
export QMT_HOOK='curl -X POST https://example.com/wake'

# 3. CLI 参数(单次运行覆盖)
qmt -B input.csv -t Korean --hook './notify.sh'

三个终态事件共用同一条命令,靠 $QMT_EVENT 区分:batch_complete / batch_fail / batch_interrupted。要按事件分流就在命令里判:

qmt config set --hook '[ "$QMT_EVENT" = batch_complete ] || exit 0; ./review.sh'

载荷有两份,命令按需取用:

  • stdin:完整 JSON,{"event": "...", "state": {...全部快照字段...}}
  • 环境变量:关键值摊平成 QMT_*,让一行 shell 就能用、不必依赖 jq
环境变量 内容
QMT_EVENT 事件名
QMT_STATUS completed / failed / interrupted
QMT_TOTAL / QMT_COMPLETED / QMT_SUCCEEDED / QMT_FAILED / QMT_SKIPPED 计数
QMT_INPUT / QMT_OUTPUT 输入与输出文件绝对路径
QMT_STATE_FILE 状态文件路径(想读全量细节就 cat 它)
QMT_ELAPSED_SECONDS / QMT_RUN_ID / QMT_MODEL / QMT_TARGET_LANG 耗时与标识
QMT_ERROR 失败原因;成功时为空串(不是字面量 None)

唤醒配方,按你的 Agent 运行时挑一条:

# 写哨兵文件,Agent 侧轮询这个文件即可
qmt config set --hook 'echo "$QMT_STATUS $QMT_OUTPUT" > /tmp/qmt-wake'

# 打 webhook
qmt config set --hook 'curl -sf -X POST -H "Content-Type: application/json" -d @- https://example.com/hook'

# 桌面通知(macOS)
qmt config set --hook 'osascript -e "display notification \"$QMT_SUCCEEDED/$QMT_TOTAL 完成\" with title \"qmt 翻译\""'

几条约定:

  • 顺序是先落终态状态、再触发 Hook。Hook 的第一个动作通常就是读状态文件,那时它已经是终态了,不会读到 running。
  • Hook 失败不影响翻译。非零退出、超时(30s)、命令不存在都只打一条警告,翻译结果照常落盘。
  • 失败与中断也会触发。这正是最该叫醒 Agent 的时刻——以前这两种情况一路抛到顶层,没有任何出口。
  • Hook 命令只能来自 CLI 参数 / 环境变量 / 配置文件这三个你亲手填的来源,绝不从翻译内容或输入文件派生。

术语管理

固定术语确保特定词汇翻译一致。保存在项目目录 .qmt/terms.csv,无表头,每行 源词,译词[,备注]。第三列可选,两列老行和三列新行可以共存于同一个文件,升级不需要迁移。

# 添加术语
qmt terms add "React" "React"
qmt terms add "组件" "コンポーネント"

# 同一个源词有多个已批准译词时,用 -n 写清各自的使用语境(见「一词多译」)
qmt terms add "丹药" "단약" -n "物品栏用词"
qmt terms add "丹药" "영약" -n "修炼语境"

# 查看术语列表
qmt terms list

# 删除术语
qmt terms remove "React"

# 清空所有术语
qmt terms clear

# 从 CSV/TSV 文件批量导入
qmt terms import terms.csv

# 翻译时使用内联术语(不保存)
qmt "React组件开发" -t Japanese --terms "React:React,组件:コンポーネント"

# 对账上传到 AnyTrans 云端干预词库(详见「术语 RAG 模式」)
qmt terms push --dry-run
qmt terms push
qmt terms pull

terms import 不同步向量索引:批量导入的术语在 qmt vectordb rebuild 之前对本地语义匹配不可见。云端模式无此问题,push 后即刻可召回。

一词多译

同一个源词允许有多个已批准译词。这时该源词整条撤出硬约束通道:AnyTrans 的 terminologies 与 Qwen-MT 的 terms 都只有 src/tgt 两个字段,两条 src 相同、tgt 互斥的条目是受约束解码级的矛盾指令,模型必须违反其中一条。撤出后所有候选译词连同备注折进自由文本(AnyTrans 的 domain_hint、Qwen-MT 的 domains、通用大模型提示词里独立的一节),由模型按当前上下文选一个。

备注就是给模型的选择依据,也只在这种情况下生效。单译词术语写备注不会有任何效果——硬约束通道没有备注字段,把它渲染出来等于将一个确定的术语降级成软提示。qmt terms list 用 ! 标出会走软裁决的源词。

两条边界:

  • 备注是本地独有的。TermEdit 只有 src/tgt,terms push 推不上去;terms pull --export 写的是服务端快照,也只有两列。
  • 内联 --terms "源:译" 表达不了备注。

缓存:改了冲突源词的备注会让相关行的键移动一次(定向 miss,重跑即可),其余备注编辑都不动键——细则见「缓存」。

复合词与子词译法不一致

一词多译是同一个源词有多个译词。还有另一类歧义,源词互不相同,所以 split_conflicts 检测不到,任何代码路径都拦不住——只能写进领域提示词。

典型形态是复合条目的译词只覆盖了原文的一部分,而它内部的子词另有自己的条目:

秩序白银 → シルバー     秩序 → 秩序
荣耀黄金 → ゴールド     荣耀 → 栄光
永恒钻石 → ダイヤ       永恒 → 永遠

段位名实际是「前缀 + 金属 + 罗马数字」三段,秩序白银 → シルバー 给的只是金属段。模型看到长条目字面命中就照抄,译出 シルバーV——它没有违规,它在严格执行术语表。

处理办法是在项目级 .qmt/domain.md 里把这类条目点名说清楚:列出冲突的条目、说明长条目只覆盖哪一段、给出拼装后的正确译法。抽象规则(「术语要完整译出」)不管用,必须落到具体条目。

实测(qwen3.8-max,中→日,15 行段位名):领域提示词点名说明后 4 次独立运行全部 15/15;对照(同一术语表、无这一节)平均 7.5/15 且波动很大(6–10)。500 行全量上召回后遵守率 91.0% → 93.7%,召回率不变(领域提示词不参与检索)。顺带把罗马数字被转成全角(V → Ⅴ)的 8 行压到 1 行。

翻译记忆

提供参考翻译对,帮助模型保持翻译风格一致。保存在 .qmt/memory.csv。

# 添加翻译记忆
qmt memory add "你好世界" "Hello World"

# 查看记忆列表
qmt memory list

# 删除记忆
qmt memory remove "你好世界"

# 清空所有记忆
qmt memory clear

# 从文件批量导入
qmt memory import memory.csv

记忆对所有后端生效,注入方式不同:默认的通用大模型后端把它渲染进 system prompt(见 通用大模型后端),AnyTrans 映射为 ext.examples few-shot 示例,qwen-mt-* 映射为 tm_list。云端 RAG 模式下 AnyTrans 改为把记忆折叠进 ext.domain_hint(见下节)。

本地模式下术语与记忆共享 4000 字符的请求预算,超出时按「术语优先」顺序截断——术语是硬约束必须保留,记忆只是软参考。云端模式的记忆走 domain_hint(另有预算),不再与术语争抢这 4000 字符。

术语 RAG 模式

术语与翻译记忆的召回方式有两种模式,由 --rag 选择:

模式 术语来源 记忆来源 需要 DashScope Key
local 本地 terms.csv + zvec HNSW 向量索引 本地 memory.csv + 向量索引 是(embedding)
cloud AnyTrans 服务端干预词库(TermQuery 召回) 折叠进 ext.domain_hint,词法匹配 否(仅指召回;模型本身仍可能要,只有 -m anytrans 不要)

默认跟随后端:-m anytrans 走 cloud,其余后端(含默认的 qwen3.8-max)走 local。--rag、QMT_RAG_MODE、qmt config set --rag-mode 可强制任一种。

解析出的模式是 cloud 不等于真的走云端——还有一道「必须先 push」的闸门,见下。

--rag cloud -m qwen-mt-plus 是合法组合——术语查 AnyTrans 词库、翻译走 DashScope。但记忆的 domain_hint 折叠只发生在 AnyTrans 的线上序列化层,所以这种组合下记忆仍按 DashScope 的方式注入。

云端模式的前提:必须先 push

.qmt/ 在 .gitignore 里,记录 termId 的边车 .qmt/terms.remote.json 不随仓库走。因此有一道硬闸门:解析出的云端作用域在边车里没有条目时,直接留在 local,不发任何探测请求。

实际含义:只有跑过 qmt terms push 的那台机器才真正进入云端模式。同事克隆仓库后行为与升级前完全一致,不会每次翻译白花一个 TermQuery 往返加一条警告。接手一个已有云端词库的项目,先 qmt terms pull 把 termId 拉回本地。

作用域由四个字段界定,四者共同决定查的是哪个词库:workspace_id / source_lang / target_lang / scene。改动其中任一个都会指向另一个(可能是空的)词库。

上手

# 1. AnyTrans 凭证与业务空间(已有则跳过)
qmt config set --access-key-id <AK> --access-key-secret <SK> --workspace-id <WID>

# 2. 固定语向与场景。auto 不是语言代码,不能作为词库的键
qmt config set --rag-mode cloud --source-lang Chinese --target-lang Korean --scene mt-turbo

# 3. 预览差异,再对账上传
qmt terms push --dry-run
qmt terms push

# 4. 验证
qmt config show          # 看「RAG / 术语检索」段是否显示「云端可用」
qmt "攻击力提升" -v       # -v 会打印「术语检索: 云端词库 (scene=mt-turbo)」

-v 只确认走了云端通路,不列出召回到的具体术语。要看服务端到底持有什么,用 qmt terms pull --export 导出词库对照。

术语对账(push / pull)

terms.csv 是真源,云端是派生镜像。push 拿本地 CSV 与边车对账,产出 ADD / MODIFY / DELETE 三类变更,分批(默认 100 条一次 TermEdit)上传,每批确认后立即落盘 termId——Ctrl-C 不会丢掉已确认的部分。

qmt terms push --dry-run        # 只打印增/改/删计数,不发任何请求
qmt terms push -v               # 逐条列出变更的术语
qmt terms push --prune          # 同时删除云端有、本地 CSV 没有的术语
qmt terms push --chunk-size 50  # 调整单次 TermEdit 携带条数
qmt terms pull                  # 拉云端状态到 .qmt/terms.remote.json
qmt terms pull --export         # 另外导出云端词库为 terms.cloud.csv

三条安全约束:

  • pull 从不写 terms.csv。默认只更新边车,--export 也只生成新文件,绝不覆盖真源。
  • 删除必须显式 --prune。清空本地 CSV 后直接 push 产生零条删除,防止顺手清空云端词库。
  • 本地同一源词有多个译词时 push 折叠为末行胜出,warn 一次并列出这些源词——云端词库一个源词只允许一个译词。只有 push 折叠:翻译时所有候选仍一起交给模型裁决,见「一词多译」。

源语言为 auto(默认值)时 push/pull 硬失败并点名修复命令:往 auto 这个键上写词库是不可恢复的垃圾数据。翻译时遇到 auto 则只是降级为 local——写坏数据不可逆,召回次优可逆。

云端模式的翻译记忆

AnyTrans 没有翻译记忆接口,所以云端模式把筛选出的记忆对折叠进 ext.domain_hint(引导语用英文,记忆对本身保持原语言),这是两个端点唯一共享的自由文本字段。一词多译的冲突块也用这个字段,但两个模式都用;记忆折叠仍然只在云端发生,本地模式有原生的 ext.examples。

云端模式没有 embedding 可用,记忆筛选是零 API 调用的词法匹配:子串包含(记忆源文出现在行文本里)+ CJK 二元组 Jaccard 重叠。确定性、可缓存,同一输入永远得到同一选择。

预算:冲突块和记忆块各有 1200 字符的独立上限,冲突块先扣费;整个 domain_hint 上限 3000 字符。触顶时先砍折叠块,绝不砍你的 domain.md 正文——正文每条请求都一样长,砍了会表现成「提示词随机失效」。一块放不下也不会把另一块带走。

降级链

云端模式任何一环失败都降级为本地向量检索,各只提示一次,绝不中断翻译:

触发条件 结果
AnyTrans 凭证缺失 降级 local,提示一次
作用域不完整(缺 workspace_id / source_lang / target_lang) 降级 local
边车无此作用域条目 静默留在 local(不发请求,避免新克隆机器每条命令都警告)
TermQuery 调用数超过 200 上限 降级 local,不静默发数百个付费调用
TermQuery 出错 / 超时 / 部分失败 丢弃整轮召回并降级 local
云端词库空但本地 CSV 非空 降级 local(镜像是空的,不是没有术语)

批量运行中降级是永久的:词库不可达、为空、或太大无法查询,在整个运行期间都不会变好,按块重试只是每次都白付一个往返去重新得到同一个答案。交互式模式下每轮重建,一次网络抖动不会拖垮整个会话。

排查入口:config show

qmt config show 的 RAG / 术语检索 段是唯一的诊断入口——降级本身是静默的(除了那一行提示),这里才能看出为什么:

RAG / 术语检索:
  rag_mode    = cloud
  生效模式    = cloud
  source_lang  = Chinese  来源: project
  target_lang  = Korean  来源: project
  scene        = mt-turbo  来源: default
  workspace_id = llm-xxxxxxxx
  状态: 云端可用,边车已记录 25452 条 termId

作用域不完整时会点名具体卡在哪个字段并给出确切的修复命令:

  状态: 云端 scope 不完整,翻译时将降级为本地向量检索
    缺 source_lang: -s/--source 指定源语言,或 qmt config set --source-lang Chinese
                    ("auto" 不是语言代码,不能作为词库的键)
    缺 workspace_id: --workspace-id 指定业务空间,或 qmt config set --workspace-id <ID>

缓存

rag_mode 参与翻译缓存键。服务端可能自行施加已注册的干预术语,所以同文本、同术语、同记忆的 cloud 请求与 local 请求是不同的 API 载荷,不能共用缓存条目。该字段仅在取值 cloud 时拼入键,因此升级不会让已有的 cache.db 失效。

术语哈希同样只把真的会上线的东西算进去:备注仅在它所属的源词确实有多个已批准译词时才拼入键。于是无备注的 terms.csv 与升级前逐字节同哈希;把备注写在单译词术语上也不动键——那种备注根本不渲染,载荷逐字节相同;而改了冲突源词的备注会移动一次键,让「冲突已存在 → 补备注 → 重跑批量」这个主用法不会命中旧缓存、备注永远不被行使。

qmt cache stats    # 条目数 / 上限 / 存储路径
qmt cache clear    # 清空(会先确认)

缓存键覆盖文本、语向、模型、领域、术语与记忆的内容哈希、rag_mode——但不含提示词骨架的版本。所以改了通用大模型后端的提示词(或升级到改了提示词的版本)之后,旧译文仍会命中,需要 qmt cache clear。

端点也不在键里,这是刻意的:同一个模型换个区域还是同一个模型,跨区域重跑命中缓存是省钱,不是错配。别来"修"它。

向量语义匹配

本节描述的机制属于 local 模式。云端模式的术语召回走 AnyTrans TermQuery、记忆召回走词法匹配,全程不调用 DashScope embedding。但本地索引仍然是云端降级时的兜底目标,所以 --rag local 覆盖和降级链都还需要它。

当术语或翻译记忆条目较多时(默认超过 20 条),QMT 自动启用向量语义匹配,从大量条目中智能筛选与当前翻译内容最相关的 Top-K 条(默认 10 条)传给模型,而不是全量传入。

底层使用 zvec 向量数据库(HNSW 索引 + 余弦距离)存储嵌入向量,通过 DashScope qwen3.7-text-embedding-flash 模型生成向量嵌入进行语义检索。向量索引以二进制格式存储在 .qmt/terms.zvec/ 和 .qmt/memory.zvec/,首次翻译时自动构建,后续增量同步。

# 默认行为:术语/记忆超过 20 条时自动启用
qmt "进入副本后需要先组队" -t Korean -v

# 控制返回条数(默认 10)
qmt "进入副本" -t Korean --top-k 5

# 调整启用阈值(默认 20,设高则不启用)
qmt "进入副本" -t Korean --threshold 50

# 批量翻译时自动预嵌入所有源文本,逐行筛选最相关术语/记忆
qmt -B input.xlsx -t Korean -v

--learn 自动学习

翻译结果可自动写回翻译记忆库,并即时同步向量索引:

# 单条翻译 + 自动学习
qmt "锻造装备需要金币" -t Korean --learn

# 批量翻译 + 批量学习(所有成功翻译自动写入记忆)
qmt -B input.csv -t Korean --learn

# 交互模式中切换学习
qmt -i -t Korean
# 输入 /learn 开启自动学习

向量索引管理

当术语库条目较多时(如 25000+),首次翻译需要嵌入所有术语会非常耗时。推荐提前构建向量索引:

# 查看当前索引状态
qmt vectordb status

# 从 CSV 重建全部向量索引(术语 + 翻译记忆)
qmt vectordb rebuild

# 清除所有向量索引(不影响 CSV 数据)
qmt vectordb clear

rebuild 会调用 DashScope embedding API 逐批嵌入所有术语/记忆,构建完成后后续翻译即可使用向量语义匹配。如果未预先构建索引且术语超过 500 条,翻译时会自动降级为截取前 N 条。

status 首行会报当前 RAG 模式。云端模式下 rebuild / clear / query 仍然可用——--rag local 是合法的临时覆盖,它需要一个能用的索引,所以这里只报告模式、不禁用命令。

每个索引还会记下落盘时用的端点,与当前生效端点不一致时 status 多打一行:

    端点: https://dashscope.aliyuncs.com/compatible-mode/v1 (当前: https://dashscope-intl.aliyuncs.com/compatible-mode/v1) ⚠ 不一致,建议 rebuild

这是警告,不是失败。 索引本身不依赖端点,照样能查,真实风险只是命中率悄悄不对——为这个把翻译卡住不划算。翻译时同样只警告一次。但 qwen3.7-text-embedding-flash 是分区域上架的,新加坡端点没有它,切过去嵌入调用会直接 404,那是 API 层的失败、不走这条警告。对比之下嵌入模型变更是硬失败(报"嵌入模型已变更,请先运行: qmt vectordb rebuild"),因为不同模型的向量彼此无意义。manifest 早于端点档案功能时没有这一项,视为"历史/未知",不告警。

领域提示

领域提示告诉模型翻译的专业领域和风格要求,支持两级存储:

  • 项目级 — 保存在当前目录 .qmt/domain.md,优先级最高
  • 全局级 — 保存在 ~/.qmt/domain.md,作为默认值
# 设置项目级领域
qmt domain set "technology"

# 设置全局级领域
qmt domain set "medical translation, formal tone" --global

# 查看当前配置
qmt domain show

# 清除项目级领域
qmt domain clear

# 清除全局级领域
qmt domain clear --global

优先级:命令行 -d 参数 > 项目级 > 全局级。

翻译时自动加载已保存的领域提示,无需每次手动指定:

# 先设置领域
qmt domain set "game localization, martial arts world view"

# 后续翻译自动生效
qmt "仙缘副本" -t Korean
qmt -B game_strings.xlsx -t Korean

分工。 qmt 内置的 system prompt 是语言无关的,只放通用纪律:术语逐字采用、记忆作参考、只输出译文。任何跟具体语言或具体项目绑定的规则都写在 domain.md 里——敬语体、数字与标点习惯、占位符处理方式,以及术语表条目互相矛盾时怎么裁决(见「复合词与子词译法不一致」)。用户文本逐字插入,不会被再翻译一道。

交互式模式

qmt -i -t English

交互模式下可用命令:

命令 说明
/help 显示帮助
/target <lang> 切换目标语言
/source <lang> 切换源语言
/model <name> 切换模型,并重建客户端 (anytrans/plus/flash/lite/qwen3.8-max/llm:<名>)
/endpoint <name> 切换端点档案,base URL 与该区域的 API Key 一起换;无参数列出当前端点与已存档案
/stream 切换流式输出
/domain <text> 设置领域提示
/domain save [global] 保存当前领域到项目/全局
/domain clear 清除当前领域
/agent <name> AnyTrans 专家角色 (game/medical/tech/...,clear 清除)
/scene <name> AnyTrans 模型场景 (mt-plus/mt-turbo)
/rag <mode> 术语检索模式 (local/cloud),切换后立即重建检索上下文
/topk <n> 设置语义匹配 Top-K
/learn 切换翻译记忆自动学习
/info 显示当前设置
exit / quit 退出

/target /source /scene /model /rag /topk /endpoint 都会改变术语与记忆的召回条件,因此每条命令执行后立即重建检索上下文——会话余下部分不会继续查询过期的语向或作用域。云端召回出错时同样按轮重建,一次网络抖动不会让整个会话都退化成无术语翻译。

/model 还会重建翻译客户端并关闭旧的。它此前只换掉模型名变量,请求仍旧走原来那个 client,新名字被静默忽略;三个后端之后这不只是"用错模型",而是"用错凭证体系"(DashScope API Key vs AnyTrans AK/SK)。所以 /model anytrans 与 /model qwen3.8-max 之间的往返切换是真的换了后端。

/endpoint 换的不只是 URL,Key 跟着一起换——这是端点档案存在的理由。会话内的翻译客户端、本地 RAG 的 embedding 调用、以及 --learn 的记忆回写全部改用新区域的 Key;只搬 URL 会得到一个用 A 区 Key 打 B 区 URL 的会话,而它的表现是几轮之后一个看不出因果的 401。切换失败(档案名不存在、Key 短了)时打一行原因并留在原端点,会话不受影响。/info 里能看到当前端点与掩码后的 Key。-m anytrans 下切换照样有效(本地 RAG 的 embedding 确实跟随),但 AnyTrans 自身仍钉在 cn-beijing。

完整参数一览

qmt [TEXT] [OPTIONS]

参数:
  TEXT                    待翻译文本

选项:
  -t, --target TEXT       目标语言 (默认 Chinese)
  -s, --source TEXT       源语言 (默认 auto)
  -m, --model TEXT        模型: anytrans / qwen-mt-plus|flash|lite / qwen3.8-max
                          / qwen3.7-plus / qwen3.8-flash / deepseek-v4-pro-0813
                          / kimi-k3 / llm:<任意百炼模型名>
                          (默认 qwen3.8-max;完整能力表见 qmt models)
  -f, --file PATH         翻译文件内容
  -d, --domain TEXT       领域提示
  -i, --interactive       交互式模式
  --stream                流式输出 (qwen-mt-flash/lite 与通用大模型支持)
  --terms TEXT            内联术语 (源:译,源:译)
  --terms-file PATH       术语文件 (CSV/TSV)
  --memory-file PATH      记忆文件 (CSV/TSV)
  -B, --batch PATH        批量翻译文件 (CSV/Excel)
  -O, --output PATH       批量翻译输出文件
  -C, --concurrency INT   批量翻译并发请求数 (默认 5, 范围 1-20)
  --batch-size INT        每批携带文本条数 (默认 10, 范围 1-50, 仅 anytrans)
  --batch-api             百炼批量推理: 半价, 但等待以小时计 (仅通用大模型 + CSV/TSV)
  --hook TEXT             批量终态时执行的 shell 命令 (唤醒外部 Agent,见「进度观察与 Hook 唤醒」)
  --no-header             CSV/Excel 无表头模式
  --resume                断点恢复
  --top-k INT             语义匹配返回条数 (默认 10)
  --threshold INT         语义匹配启用阈值 (默认 20)
  --learn                 翻译结果自动写入翻译记忆
  --agent TEXT            AnyTrans 专家角色 (仅 -m anytrans 生效),与 -B 共用时禁用批量
  --scene TEXT            AnyTrans 模型场景 (mt-plus/mt-turbo,仅 -m anytrans 生效)
  --thinking / --no-thinking
                          思考模式 (仅通用大模型)。不传时按注册表决定
  --thinking-budget INT   思考 token 预算 (1-32768, 仅在思考开启时发送)
  --rag [local|cloud]     术语检索模式 (默认 -m anytrans 走 cloud、其余后端走 local)
  --workspace-id TEXT     AnyTrans 业务空间 ID
  --api-key TEXT          DashScope API Key
  --endpoint TEXT         本次请求的端点: 已存档案名 (qmt endpoint list) 或完整 URL。
                          档案自带该区域的 Key,URL 与 Key 一起切;优先级高于
                          QMT_BASE_URL 与持久化的生效档案。-m anytrans 不受影响
  -v, --verbose           显示详细信息
  --no-store              不加载本地术语/记忆库
  -h, --help              显示帮助
  -V, --version           显示版本

项目结构

src/qmt/
  cli.py              # CLI 入口与命令定义
  model_registry.py   # 模型名解析与后端路由 (llm: 前缀在此剥掉)
  backends.py         # 按后端构建客户端的工厂 + 各后端的 warn-once 提示
  anytrans_client.py  # AnyTrans 翻译客户端 (ROA 签名,推荐)
  client.py           # Qwen-MT API 客户端 (同步,OpenAI 兼容模式)
  llm_prompt.py       # 通用大模型提示词适配层 (纯函数,零 I/O)
  llm_client.py       # 通用大模型客户端 (同步,OpenAI SDK)
  async_client.py     # 异步客户端 (批量翻译用,含通用大模型后端)
  batch.py            # CSV/Excel 批量翻译 (并发 + 自适应限流)
  batch_inference.py  # 百炼批量推理 (--batch-api,上传 JSONL + 轮询)
  run_config.py       # RunConfig: 一次运行解析好的配置,在 CLI 边界构建一次后下发
  endpoint.py         # 端点档案: base_url 与该区域 API Key 的解析/校验/发布 (含模块级 holder)
  cache.py            # 翻译结果缓存 (SQLite)
  interactive.py      # 交互式 REPL
  matcher.py          # 向量语义匹配编排层
  rag.py              # RAG 作用域与 ContextProvider 抽象 (local/cloud 模式选择)
  cloud_terms.py      # AnyTrans 云端干预词库客户端 (TermQuery / TermEdit)
  term_sync.py        # terms.csv 与云端词库的对账 (push/pull,无 SDK 依赖)
  embedding.py        # DashScope qwen3.7-text-embedding-flash 嵌入
  vectorstore.py      # 向量索引存储 (zvec HNSW)
  store.py            # 本地持久化存储 (术语/记忆/领域/凭证,带文件锁)
  filelock.py         # 跨进程文件锁 (fcntl)
  models.py           # 数据模型 + 一词多译的冲突裁决 (拆分/渲染/切片)
  parsers.py          # 输入解析
  formatters.py       # 终端输出格式化
  constants.py        # 常量定义
  exceptions.py       # 自定义异常
  notify.py           # 邮件通知
  runstate.py         # 批量运行状态落盘 (.qmt/runs/,供 qmt status 读取)
  hooks.py            # 终态 Hook: 执行用户配置的 shell 命令唤醒外部 Agent

开发

# 安装开发依赖
pip install -e ".[dev]"

# 代码检查(门禁;规则集在 pyproject.toml 里钉死)
ruff check src/qmt/ tests/

# 运行测试
pytest

不要对整棵树跑 ruff format:这棵树从来没被 format 过,一次会重写 53 个文件里的 36 个,真实改动就埋进无关 diff 里了。只手工格式化自己碰过的行,向周围风格看齐。

License

Apache-2.0

Metadata

Release files for qwen-mt-cli 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for qwen-mt-cli 1.0.0
File Size Uploaded
qwen_mt_cli-1.0.0.tar.gz 395.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for qwen-mt-cli 1.0.0
File Interpreter ABI Platform
qwen_mt_cli-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 611.2 kB

Release files / qwen_mt_cli-1.0.0.tar.gz

Download URL qwen_mt_cli-1.0.0.tar.gz
Size 395.7 kB
Tags Source
SHA-256 checksum
How to use checksums
3dc2b1d29d7306cf0a2c8fcdd9c7e4acfacd30b6d3491c6f766d94fb9de6cdfa
BLAKE2b-256 checksum
How to use checksums
4fef82e6fa5feb89107af1d4822d99a26831bb22dc61c27d97d00996766dc889
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.3

Release files / qwen_mt_cli-1.0.0-py3-none-any.whl

Download URL qwen_mt_cli-1.0.0-py3-none-any.whl
Size 215.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1d847d391336f11b2fbc4e751ac6a8d7746a442cb1d765214184c69f1cc367d6
BLAKE2b-256 checksum
How to use checksums
a98eb34e543d76f6eef054934b1ec67864595db1643a3acfff0d9f3ef410cb3a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.3

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

0.5.10

2 release files

0.5.9

2 release files

0.5.8

2 release files

0.5.7

2 release files

0.5.6

2 release files

0.5.5

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.9

2 release files

0.3.8

2 release files

0.3.7

2 release files

0.3.6

2 release files

0.3.5

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page