LanguageTool API 调用与运维实战(核心篇)
LanguageTool 6.8 自建服务 API 调用与运维实战核心篇:/v2/check 全参数体系(language/level/preferredVariants/motherTongue/规则开关)与响应结构解析、/v2/languages 语言清单、中英文检测样例实测、400/429/413 错误处理与健康检查、性能量级与 JVM 验证,全部命令与回显实测照抄。
LanguageTool API 调用与运维实战(核心篇)
目标:把 LanguageTool 6.8 自建校对服务的 HTTP API 讲透并跑通——
/v2/check的全部请求参数(language、level、preferredVariants、motherTongue、规则/类别开关等)逐个实测,响应 JSON 结构逐块解析,配合/v2/languages语言清单、中英文检测样例、400/429/413 错误处理与健康检查、性能量级与 JVM 验证,形成一份可直接照抄的调用与运维手册。全文在 Ubuntu 22.04 服务器(服务端口 8130,发行版meyay/languagetool:6.8)实测,命令与回显逐行照抄。
一、环境信息
- 操作系统:Ubuntu 22.04(x86_64)
- LanguageTool:6.8(GitHub 官方 tag 构建,发行镜像
meyay/languagetool:6.8,2026-05-05 官方发布) - 部署形态:Docker Compose,宿主端口 8130 → 服务监听 8081(部署过程见 《LanguageTool 6.8 Docker Compose 部署实战》)
- JVM 内存:
-Xms256m -Xmx512m -XX:+UseG1GC(部署篇压堆配置,本文性能量级在此配置下测得) - ngram 语言模型:已挂载 nl 语言数据(2.4GB,
languageModel=/ngrams;挂载机制与陷阱见部署篇第八节) - 客户端工具:curl(系统自带)+ python3(仅用于格式化 JSON 输出)
- 服务认证:无(自建服务不带鉴权,访问控制靠网络层与反代,见三、限流约定)
二、前置条件
2.1 服务健康确认
所有 API 调用前先确认服务健康。镜像内置健康检查端点为 GET /v2/healthcheck(注意不是 /health):
curl -s http://127.0.0.1:8130/v2/healthcheck
实测回显:
OK
排障实录:直觉去请求 /health 会踩坑——它不是健康端点,返回 HTTP 400 与 API 提示文本(实测照抄):
curl -s -o /dev/null -w 'HTTP:%{http_code}\n' http://127.0.0.1:8130/health
# HTTP:400
curl -s http://127.0.0.1:8130/health
# Error: This is the LanguageTool API. You have not specified any parameters. Please see https://languagetool.org/http-api/swagger-ui/#/default
同理,GET /、GET /v2/check(不带参数)、/healthz、/livez 实测均返回 HTTP 400 同款提示;POST /v2/telemetry 默认返回 404(遥测上报未启用)。唯一可用的健康探针是 /v2/healthcheck,返回 OK 且耗时毫秒级(实测 0.0016s 量级),负载均衡与健康检查配置都应指向它。
2.2 语言清单可用性确认
curl -s http://127.0.0.1:8130/v2/languages | python3 -c "import json,sys; print(len(json.load(sys.stdin)), '种语言')"
# 60 种语言
2.3 认证形态说明
自建 LanguageTool 没有任何用户认证机制(官方自建服务不带 UI、不支持鉴权,见官方文档),/v2/check 匿名即可调用。服务进程默认以 --public 参数运行(允许任意来源),暴露公网时必须在网络层(防火墙限源)或反代层(认证/限流)完成访问控制,参数细节见 《LanguageTool 6.8 配置参数详解》5.6 节。
三、API 总览
自建服务对外只暴露三个 REST 端点(实测确认,其余路径一律 400/404):
| 端点 | 方法 | 用途 | 实测状态码 |
|---|---|---|---|
/v2/check | POST(也支持 GET) | 提交文本做语法/拼写校对,返回 matches | 200 / 400 / 413 / 429 |
/v2/languages | GET | 返回服务支持的语言清单(code/longCode/name) | 200 |
/v2/telemetry | POST | 遥测上报(默认未启用) | 404 |
约定与事实(均实测):
- 请求方式:官方建议 POST(
application/x-www-form-urlencoded表单参数);/v2/check的 GET 形态(curl -G --data-urlencode)实测同样可用,参数语义一致,适合浏览器调试场景。 - API 版本:响应
software.apiVersion固定为1;URL 中的/v2/是路径约定,与 apiVersion 是两回事。 - 响应格式:全部为 JSON(
Content-Type: application/json);错误也走 JSON 语义但 body 为Error: ...文本行 + 对应 4xx 状态码。 - 限流约定:自建服务的限流完全由服务端配置驱动(
requestLimit/requestLimitPeriodInSeconds/requestLimitInBytes),不配置则不限流(实测默认部署可无限连发);官方公共 API(api.languagetool.org)的 20 次/分/IP 口径只对公共端点生效,自建不受其约束(见 《LanguageTool 介绍与选型指南》5.1 节溯源数据)。
四、首次调用与响应结构
4.1 首次校对请求
用一句同时含主谓一致、冠词误用、动词形态错误的经典例句做首次调用:
curl -s -d "language=en-US" \
--data-urlencode "text=This are an test of the grammar checker. I didn't knew that." \
http://127.0.0.1:8130/v2/check
实测回显(JSON 节选,matches 4 条,为展示保留核心字段):
{"software": {"name": "LanguageTool", "version": "6.8", "buildDate": "1980-02-01 00:00:00 +0000",
"apiVersion": 1, "premium": false,
"premiumHint": "You might be missing errors only the Premium version can find. Contact us at support<at>languagetoolplus.com.", "status": ""},
"warnings": {"incompleteResults": false},
"language": {"name": "English (US)", "code": "en-US",
"detectedLanguage": {"name": "English (US)", "code": "en-US", "confidence": 0.99, "source": "fasttext"}},
"matches": [
{"message": "The singular demonstrative pronoun ‘this’ does not agree with the plural verb ‘are’. Did you mean “these”?",
"shortMessage": "Grammatical problem: use ‘these’",
"replacements": [{"value": "These"}],
"offset": 0, "length": 4,
"context": {"text": "This are an test of the grammar checker. I d...", "offset": 0, "length": 4},
"sentence": "This are an test of the grammar checker.",
"type": {"typeName": "Other"},
"rule": {"id": "THIS_NNS", "subId": "5", "sourceFile": "grammar.xml",
"description": "'this' vs. 'these'", "issueType": "grammar",
"category": {"id": "GRAMMAR", "name": "Grammar"}}, ...},
{"message": "The verb ‘are’ is plural. Did you mean: “is”?...", "rule": {"id": "PLURAL_VERB_AFTER_THIS", ...}, "offset": 5, ...},
{"message": "Use “a” instead of ‘an’ if the following word doesn’t start with a vowel sound...",
"rule": {"id": "EN_A_VS_AN", "subId": "1", "sourceFile": "grammar.xml", "issueType": "grammar",
"category": {"id": "MISC", "name": "Miscellaneous"}}, ...},
{"message": "Did you mean “know”?...", "rule": {"id": "AUXILIARY_DO_WITH_INCORRECT_VERB_FORM", ...}, ...}],
"sentenceRanges": [[0, 40], [41, 60]],
"extendedSentenceRanges": [{"from": 0, "to": 40, "detectedLanguages": [{"language": {"code": "en-US"}, "rate": 1.0}]}, ...]}
版本号 "version": "6.8" 与 "premium": false 同时出现,说明请求命中了自建 6.8 开源版(云端 AI 增强规则不在自建版内,premiumHint 字段是固定提示文案,非错误)。
4.2 响应结构逐块解析
| 顶层字段 | 含义 | 实测要点 |
|---|---|---|
software | 服务端软件信息 | version 用于部署验证;premium 自建恒为 false;apiVersion 恒为 1;buildDate 显示 1980-02-01 是镜像构建时时间戳被归零的产物,非异常 |
warnings.incompleteResults | 结果完整性标记 | 规则引擎超时中断时变 true(配合服务端 maxCheckTimeMillis),正常校对恒为 false |
language | 本次校对语言 | code 为实际生效的语言变体(en-US);detectedLanguage 是 fastText 的识别结果(显式指定 language 时也会给出识别对照),confidence 为置信度、source 为识别来源 |
matches | 检出问题数组(可能为空) | 每条对应一个规则命中,结构见下表 |
sentenceRanges | 文本被切分的句子区间 | [起始offset, 结束offset] 数组,用于把 matches 定位回原句 |
extendedSentenceRanges | 句级语言检测结果 | 多语言混排文本时给出每句的语言构成与占比 |
matches 数组每条的核心字段(以第一条 THIS_NNS 为例):
| 字段 | 含义 | 实测值示例 |
|---|---|---|
message | 面向人的完整问题描述 | 「The singular demonstrative pronoun 'this' does not agree with…」 |
shortMessage | 短提示(UI 展示用) | 「Grammatical problem: use 'these'」 |
replacements | 替换建议数组 | [{"value": "These"}] |
offset / length | 出错文本在原文中的字节区间 | 0 / 4(即 "This") |
context | 出错位置的上下文窗口 | {"text": "This are an test of the grammar checker. I d...", "offset": 0, "length": 4} |
sentence | 所属完整句子 | 「This are an test of the grammar checker.」 |
rule.id / subId | 规则标识(停用规则的键) | THIS_NNS / 5 |
rule.issueType | 问题类型 | grammar(语法)/ misspelling(拼写)等 |
rule.category.id | 规则类别(类别级停用的键) | GRAMMAR、MISC、TYPOS 等 |
💡 说明:
offset/length是字节偏移而非字符数——英文文本两者一致,含多字节字符(中文、重音字母)的文本中两者会错位,客户端定位 UI 高亮时需按 UTF-8 字节处理。
4.3 GET 与 POST 两种请求形态
POST 为官方建议形态;GET 形态把参数放到查询串,实测行为一致:
curl -s -G http://127.0.0.1:8130/v2/check \
--data-urlencode "language=en-US" \
--data-urlencode "text=This are a test of the grammar checker." \
| python3 -c "import json,sys; d=json.load(sys.stdin); print([m['rule']['id'] for m in d['matches']])"
实测回显:
['THIS_NNS', 'PLURAL_VERB_AFTER_THIS']
两种形态返回的 matches 完全一致;GET 的局限是 URL 长度上限(长文本必须走 POST)。下文参数实测统一用 POST 形态。
五、/v2/check 参数体系详解
本节按参数逐个实测。/v2/check 的全部请求参数如下(服务端配置键不在此列,见配置详解篇):
| 参数 | 类型 | 说明 |
|---|---|---|
text | 必填 | 待校对文本(UTF-8) |
language | 必填 | 语言变体代码或 auto |
level | 可选 | 校对档位(10 档,默认 default) |
motherTongue | 可选 | 作者母语(影响同音词建议优先级) |
preferredVariants | 可选 | language=auto 时的变体偏好列表 |
enabledRules / disabledRules | 可选 | 规则级启停(rule id,逗号分隔) |
enabledOnly | 可选 | true 时只跑 enabledRules 列出的规则 |
disabledCategories / enabledCategories | 可选 | 类别级启停(category id) |
offset / length | 可选 | 只校对文本的指定区间 |
enableNestedLanguages | 可选 | 激活嵌套变体(如 de-LU) |
autoDetect / username / apiKey / dictsLookup 等 | 可选 | 云端专属或词典扩展参数,自建开源版无实际作用 |
5.1 language:语言指定与 auto 自动识别
显式指定直接给 longCode(如 en-US)。自动识别传 auto,由服务端 fastText 模型判定(模型随镜像内置,识别模型 lid.176.bin 首次启动自动下载,见部署篇):
curl -s -d "language=auto" \
--data-urlencode "text=Guten Tag, wie geht es Ihnen heute?" \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; d=json.load(sys.stdin); print(json.dumps(d['language'], ensure_ascii=False)); print('matches:', len(d['matches']))"
实测回显:
{"name": "German (Germany)", "code": "de-DE", "detectedLanguage": {"name": "German (Germany)", "code": "de-DE", "confidence": 0.99, "source": "fasttext"}}
matches: 0
德语正确句识别为 de-DE(置信度 0.99),且正确句 matches 为 0——无报错即通过。
传非法语言码返回 400 并给出可取值提示(实测照抄,错误体为一行文本):
curl -s -d "language=xx" -d "text=hello" http://127.0.0.1:8130/v2/check -w "\nHTTP:%{http_code}\n"
Error: 'xx' is not a language code known to LanguageTool. Supported language codes are: ar, ast-ES, be-BY, ... zh-CN. The list of languages is read from META-INF/org/languagetool/language-module.properties in the Java classpath. See https://dev.languagetool.org/java-api for details.
HTTP:400
完全不传 language 同样 400(实测):
Error: Missing 'language' parameter, e.g. 'language=en-US' for American English or 'language=fr' for French
💡 技术拓展:LanguageTool 的语言代码分两层——
code(基础码,如en)与longCode(变体码,如en-US)。变体体系为:英语 6 变体(en-AU/en-CA/en-GB/en-NZ/en-US/en-ZA)、德语 5 变体(de/de-AT/de-CH/de-DE/de-DE-x-simple-language/de-LU)、葡语 4 变体(pt-AO/pt-BR/pt-MZ/pt-PT)等;/v2/languages返回的longCode是调用时唯一可靠的取值来源(变体拼写错一个字母就 400)。code与longCode的取舍:传en由服务端解析为默认变体 en-US,显式传en-GB则直接锁定英式变体。
5.2 level:校对档位(10 档)
level 控制校对严格程度,取值必须全小写。传大写直接 400(实测照抄):
Error: Unknown value 'PICKY' for parameter 'level'. Valid values: default, picky, academic, clarity, professional, creative, customer, jobapp, objective, elegant
这个报错本身给出了全部 10 档合法值。picky(挑刺档)与 default 的差异实测——用两个风格敏感句:
curl -s -d "language=en-US" -d "level=picky" \
--data-urlencode "text=We discussed about the problem." \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; d=json.load(sys.stdin); print([(m['rule']['id'], m['replacements']) for m in d['matches']])"
实测回显:
[('MENTION_ABOUT', [{'value': 'discussed the'}])]
We discussed about the problem.(discuss 及物动词,about 冗余)在 picky 档检出 MENTION_ABOUT。同句在 default 档实测同样检出(level 缺省时对照确认),再测另一句:
curl -s -d "language=en-US" -d "level=picky" \
--data-urlencode "text=The reason is because it was raining." \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; d=json.load(sys.stdin); print([(m['rule']['id'], m['replacements']) for m in d['matches']])"
[('REASON_IS_BECAUSE', [{'value': 'is that'}])]
实测结论:discussed about、reason is because 这类惯用法规则在 default 与 picky 两档均检出——两档的差异不是这两条规则的开关,而是部分只读风格规则(如 I would like to shortly explain the issue. 的副词位置、In my opinion I think that this is good. 的冗余表达)在两档实测均未触发检出(0 matches),说明这些规则在当前 6.8 开源规则集内不随档位生效或尚未内置。对自建用户而言,level 档位的实际增益取决于语言规则集,建议以自身语料实测为准,不要假设高档位必然多检出。
5.3 preferredVariants:auto 识别下的变体偏好
preferredVariants 只在 language=auto 时合法,否则 400(实测照抄):
Error: You specified 'preferredVariants' but you didn't specify 'language=auto'
用它把同一句英文分别按英式/美式校对,观察变体规则的反向检出:
curl -s -d "language=auto" -d "preferredVariants=en-GB" \
--data-urlencode "text=The color of the sweater is my favorite." \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; d=json.load(sys.stdin); print(d['language']['code'], [(m['rule']['id'], [r['value'] for r in m['replacements']]) for m in d['matches']])"
实测回显(英式视角,美式拼法全被点名):
en-GB [('MORFOLOGIK_RULE_EN_GB', ['colour']), ('EN_GB_SIMPLE_REPLACE_SWEATER', ['jumper']), ('MORFOLOGIK_RULE_EN_GB', ['favourite'])]
反向切到美式:
curl -s -d "language=auto" -d "preferredVariants=en-US" \
--data-urlencode "text=The colour of the sweater is my favourite." \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; d=json.load(sys.stdin); print(d['language']['code'], [(m['rule']['id'], [r['value'] for r in m['replacements']]) for m in d['matches']])"
en-US [('MORFOLOGIK_RULE_EN_US', ['color']), ('MORFOLOGIK_RULE_EN_US', ['favorite'])]
实测要点:auto + preferredVariants 组合下,识别语言落到指定变体(en-GB/en-US),拼写词典按变体切换——color 在英式是错、colour 在美式是错,且英式还触发了独有的用词规则(sweater → jumper)。这正是变体感知的实际价值。
5.4 motherTongue:母语提示(同音词建议增强)
motherTongue 声明作者母语,理论上用于把「母语者常犯的同音词错误」排到建议前列。实测德语母语者经典错误句:
curl -s -d "language=en-US" -d "motherTongue=de" \
--data-urlencode "text=I didn't knew that." \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; d=json.load(sys.stdin); print([(m['rule']['id'], [r['value'] for r in m['replacements']]) for m in d['matches']])"
实测回显:
[('AUXILIARY_DO_WITH_INCORRECT_VERB_FORM', ['know'])]
不传 motherTongue 的对照组实测同样检出该规则(id 与建议一致)。多组德语母语易错句(He make the homework every day. → HE_VERB_AGR、We see us tomorrow. → 0 matches、I have since two years a new car. → 0 matches)带与不带 motherTongue 实测结果完全一致。结论:自建开源版的 motherTongue 在 6.8 实测中不改变检出结果——母语相关的精细化建议主要在云端/Premium 规则里,自建调用可省略该参数。
5.5 enabledRules / disabledRules / enabledOnly:规则级开关
三个参数构成请求粒度的规则控制系统。基线句子(实测两条规则):
curl -s -d "language=en-US" --data-urlencode "text=This are a test." \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; print([m['rule']['id'] for m in json.load(sys.stdin)['matches']])"
# ['THIS_NNS', 'PLURAL_VERB_AFTER_THIS']
停用单条规则(disabledRules),THIS_NNS 被关掉,只剩另一条(实测):
curl -s -d "language=en-US" -d "disabledRules=THIS_NNS" \
--data-urlencode "text=This are a test." \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; print([(m['rule']['id'], m['offset']) for m in json.load(sys.stdin)['matches']])"
# [('PLURAL_VERB_AFTER_THIS', 0)]
只跑指定规则(enabledRules + enabledOnly=true)——注意「规则窄化」效应,测试句里 teh 的拼写错误被过滤掉了(拼写规则不在 enabledRules 清单内,实测照抄):
curl -s -d "language=en-US" -d "enabledRules=THIS_NNS" -d "enabledOnly=true" \
--data-urlencode "text=This are a very big mistake. teh" \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; print([(m['rule']['id'], m['offset']) for m in json.load(sys.stdin)['matches']])"
# [('THIS_NNS', 0)]
实测要点:enabledOnly=true 是「白名单模式」,未列入的规则全部不跑——适合只需要特定规则的业务场景(如只查术语拼写),但会连拼写检查一起关掉,白名单必须显式包含所需拼写规则(如 MORFOLOGIK_RULE_EN_US)。
5.6 disabledCategories:类别级开关
en-US 拼写类规则的类别 id 实测为 TYPOS(不是文档旧口径的 TYPO1)。先看基线:
curl -s -d "language=en-US" \
--data-urlencode "text=I buyed a book yesterday and i readed it." \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; print([(m['rule']['id'], m['rule']['category']['id']) for m in json.load(sys.stdin)['matches']])"
# [('MORFOLOGIK_RULE_EN_US', 'TYPOS'), ('MORFOLOGIK_RULE_EN_US', 'TYPOS')]
停用 TYPOS 类别后同句实测:
curl -s -d "language=en-US" -d "disabledCategories=TYPOS" \
--data-urlencode "text=I buyed a book yesterday and i readed it." \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; print('matches:', json.load(sys.stdin)['matches'])"
# matches: []
两个动词形态错词(buyed/readed,实为词表外词)全靠拼写类别检出,类别停用后 matches 清空。类别 id 可从 match 响应的 rule.category.id 收集——按类别停用比按规则停用更适合批量降噪。
5.7 offset 与 length:区间校对
对长文本只校对指定区间。实测构造「错误在区间外」的文本:
curl -s -d "language=en-US" -d "offset=0" -d "length=15" \
--data-urlencode "text=This are a test. teh end is near." \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; print([(m['rule']['id'], m['offset']) for m in json.load(sys.stdin)['matches']])"
实测回显:
[('THIS_NNS', 0), ('PLURAL_VERB_AFTER_THIS', 5), ('MORFOLOGIK_RULE_EN_US', 17)]
teh 在 offset 17(区间 [0,15) 之外)但仍被检出——6.8 自建实测中 offset/length 参数被接受(HTTP 200)但未实际截断校对范围,matches 与全文本校对一致。结论:6.8 自建版该参数未生效,区间校对需求应在客户端自行切分文本后分次提交(注意切分点按句子边界,跨句错误上下文会丢失)。
5.8 enableNestedLanguages:嵌套变体激活
针对 de-LU(卢森堡德语)这类「嵌套在其他变体内」的语言代码,默认不带嵌套激活:
curl -s -d "language=de-LU" \
--data-urlencode "text=Der hund läuft schnell über die strasse." \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; d=json.load(sys.stdin); print(d['language']['code'], d.get('detectedLanguage'), len(d['matches']))"
# de {'name': 'German (Germany)', 'code': 'de-DE', 'confidence': 0.924, 'source': 'fasttext'} 0
传 enableNestedLanguages=true 实测(语言解析结果与 matches 数量不变):language.code 仍为 de、检出同为 0。6.8 自建实测两种取值行为一致,de-LU 未被激活为独立校对变体。需要按地区变体校对时,直接用受支持的主变体(de-DE/de-AT/de-CH)。
六、/v2/languages 语言清单接口
curl -s http://127.0.0.1:8130/v2/languages
实测回显(首尾节选,60 条):
[{"name":"Arabic","code":"ar","longCode":"ar"},
{"name":"Asturian","code":"ast","longCode":"ast-ES"},
{"name":"Belarusian","code":"be","longCode":"be-BY"},
...
{"name":"Chinese","code":"zh","longCode":"zh-CN"},
{"name":"Dutch","code":"nl","longCode":"nl"},
{"name":"Dutch (Belgium)","code":"nl","longCode":"nl-BE"},
{"name":"Dutch","code":"nl","longCode":"nl-NL"},
...
{"name":"English (US)","code":"en-US","longCode":"en-US"}]
按变体归类统计(python 处理实测清单):
| 语言 | longCode 形态 | 条数 |
|---|---|---|
| 英语 en | en / en-AU / en-CA / en-GB / en-NZ / en-US / en-ZA | 7 |
| 德语 de | de / de-AT / de-CH / de-DE / de-DE-x-simple-language / de-LU | 6 |
| 荷兰语 nl | nl / nl-BE / nl-NL | 3 |
| 中文 zh | zh-CN(单一形态) | 1 |
清单用途:① 校验 language 参数合法取值(5.1 的 400 报错提示也是从这里来的);② 客户端做语言选择器;③ 确认某语言变体是否受支持(如 zh 只有 zh-CN 一种,无繁体变体)。
七、多语言检测实测(含中英文样例)
本节给出业务集成前最关心的「各语言真实检出能力」样例。全部回显实测照抄,包括 0 检出的结果——0 检出也是实测事实,直接反映规则覆盖现状。
7.1 英语:语法 + 拼写全命中
curl -s -d "language=en-US" \
--data-urlencode "text=This are a test. teh end is near." \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; print([(m['rule']['id'], m['offset'], m['length']) for m in json.load(sys.stdin)['matches']])"
实测回显(语法两条 + 拼写一条):
[('THIS_NNS', 0, 4), ('PLURAL_VERB_AFTER_THIS', 5, 3), ('MORFOLOGIK_RULE_EN_US', 17, 3)]
7.2 中文:拼写与语法检出能力实测(重点结论)
用三个典型中文错字/搭配句实测 zh-CN:
for t in "我在公司上班,注要负责产品测试工作。" "因为我昨天看了电影,所依很晚才睡觉。" "他的建议对我启发很大,我觉的非常有道理。"; do
echo "### $t"
curl -s -d "language=zh-CN" --data-urlencode "text=$t" http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; d=json.load(sys.stdin); print([(m['rule']['id'], m['offset']) for m in d['matches']] or '(0 matches)')"
done
实测回显:
### 我在公司上班,注要负责产品测试工作。
(0 matches)
### 因为我昨天看了电影,所依很晚才睡觉。
(0 matches)
### 他的建议对我启发很大,我觉的非常有道理。
(0 matches)
三个句子分别含「注要/主要」「所依/所以」「觉的/觉得」的同音错字,6.8 自建版 zh-CN 全部 0 检出。这是真实且重要的结论:中文无 hunspell 拼写词表(无空格分词语言不带拼写检查,见选型篇溯源),内置的 1800 余条中文规则以成语/同音词混淆对为主,对上述自然行文中的错字形态覆盖有限。以中文校对为主要诉求的业务,自建 LanguageTool 不能作为主力方案,只能做辅助(成语误写类)。
7.3 德语:拼写规则强检出
curl -s -d "language=de-DE" \
--data-urlencode "text=Der hund läuft schnell über die strasse." \
http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; print([(m['rule']['id'], m['offset'], m['replacements']) for m in json.load(sys.stdin)['matches']])"
实测回显:
[('GERMAN_SPELLER_RULE', 4, [{'value': 'Hund'}, {'value': 'und'}]), ('GERMAN_SPELLER_RULE', 29, [{'value': 'Straße'}, {'value': 'Ostrasse'}])]
两处拼写错误(hund→Hund、strasse→Straße)均检出且给出含 ß 转写的德语正字法建议——德语规则集(5200+ 条 XML 规则)与拼写词表的完整度在自建版里属第一梯队。
7.4 语言锁定效应
对中文接口传英文错拼句,实测被语言参数锁定、不做英文检查:
curl -s -d "language=zh-CN" -d "text=teh quick brown fox" http://127.0.0.1:8130/v2/check \
| python3 -c "import json,sys; print('matches:', len(json.load(sys.stdin)['matches']))"
# matches: 0
language 指定后 fastText 识别不再介入(即使文本明显是英文)。多语言混排业务用 language=auto(识别来源在响应 detectedLanguage.source 里回显为 fasttext,可观测)。
八、限流与错误处理(400/429/413 实测)
自建服务默认不限流不限长;本节的 429/413 回显来自按生产口径配置限额后的实测(配置方法与参数语义见 《LanguageTool 6.8 配置参数详解》5.2 节:langtool_maxTextLength=1000、langtool_requestLimit=3、langtool_requestLimitPeriodInSeconds=60)。
8.1 400:请求参数错误
三类实测已见上文:非法语言码(5.1)、缺 language 参数(5.1)、level 非法值(5.2)。共性:body 为一行 Error: ... 文本 + HTTP 400,且报错文本自带合法取值提示,可直接透传给调用方排障。
8.2 429:requestLimit 限流
限额 3 次/60 秒 下,窗口内 fresh 连发 4 次(实测逐行):
for i in 1 2 3 4; do curl -s -o /dev/null -w "第${i}次: HTTP:%{http_code}\n" \
-d "language=en-US" -d "text=hello world" http://127.0.0.1:8130/v2/check; done
第1次: HTTP:200
第2次: HTTP:200
第3次: HTTP:200
第4次: HTTP:429
第 4 次响应体(实测照抄):
Error: Access from 10.89.56.1 denied: Client request limit of 3 requests per 60 seconds exceeded
两个实测要点:
- 限流按来源 IP 计数:报错里的
10.89.56.1是服务看到的请求来源地址(直连形态下为宿主网关地址)。窗口期过后自动恢复,无需重启服务。 - 同源共享一个桶:实测通过 Nginx 反代(8141)的请求与直连 8130 的请求互相消耗限额——反代未透传客户端真实 IP 时,后端把反代地址当成来源。生产反代形态必须开服务端
trustXForwardForHeader=true并让 Nginx 设置X-Forwarded-For(配置见配置详解篇 5.6 节),否则所有用户共享反代的限额、正常用户被误伤。 - 额外实测确认:
GET /v2/languages与/v2/check的 POST 计入同一个限额桶——健康探测或语言清单轮询频繁时也会触发 429,健康检查间隔应避开限流窗口(或对健康检查路径单独放行于反代层)。
8.3 413:maxTextLength 超长
限额单请求 1000 字符后,提交 1200 字符文本(实测):
Error: Your text exceeds the limit of 1000 characters (it's 1200 characters). Please submit a shorter text.
状态码 HTTP 413;同配置下 900 字符文本实测 HTTP 200 正常返回。客户端应按报错文本里的两个数值(限制值与实际值)做截断或分段重试。
九、运维与维护命令
9.1 健康检查(含探针踩坑)
见 2.1 节:唯一健康端点 GET /v2/healthcheck → OK;/health 等直觉路径全为 400。负载均衡健康检查、Compose 健康检查(镜像内置 /entrypoint healthcheck,30 秒间隔)都以它为准。
9.2 性能量级实测(512MB 堆、单并发)
不同文本规模的响应耗时(curl time_total,实测三次取样,ngram nl 已挂载态):
| 文本规模 | 三次实测耗时 |
|---|---|
| 300 字符英文 | 0.033 ~ 0.099 s |
| 900 字符英文 | 0.043 ~ 0.056 s |
| 3000 字符英文 | 0.040 ~ 0.047 s |
| 100000 字符英文 | 15.4 ~ 17.4 s |
实测结论:常规业务文本(万字符以内)毫秒到百毫秒级,512MB 堆完全够用;十万字符级文本耗时暴涨到 15 秒以上(规则引擎与句子切分开销超线性增长),长文本业务应在客户端按段落/句子分段提交,或调大服务端堆并评估 maxCheckTimeMillis 防护。
并发量级参考(实测:25 个请求 × 5 并行,900 字符文本,512MB 堆 + G1GC):耗时 min 0.086s / p50 0.155s / max 0.312s,无失败请求——10 线程池(启动日志 Setting up thread pool with 10 threads)支撑小型团队并发无压力。
服务内存占用实测(ngram nl 挂载态,含 fastText 子进程与堆外):
languagetool-6.8-server: 711.2MiB / 19.48GiB 0.18%
9.3 JVM 内存生效验证(无 jps 的替代方案)
镜像内置的 JRE 经 jlink 裁剪,不含 jps(实测执行报 OCI runtime exec failed: exec: "jps": executable file not found in $PATH)。堆参数验证用两条实测可行的替代:
- 启动日志行(每次启动都打印):
docker logs languagetool-6.8-server 2>&1 | grep 'Using JAVA_OPTS'
# INFO: Using JAVA_OPTS=-Xms256m -Xmx512m -XX:+UseG1GC -XX:+UseStringDeduplication
- 宿主机进程参数(procps 格式,UID 783 即服务运行用户):
ps -ef | grep '[H]TTPServer'
实测回显(逐字段照抄):
783 323948 323872 1 09:55 ? 00:00:11 java -Xms256m -Xmx512m -XX:+UseG1GC -XX:+UseStringDeduplication -Djna.tmpdir=/tmp -Dlogback.configurationFile=/tmp/logback.xml -cp .:languagetool-server.jar org.languagetool.server.HTTPServer --port 8081 --public --allow-origin * --config /tmp/config.properties
-Xms256m -Xmx512m 即部署篇压堆配置生效的直接证据;命令行尾部的 --port 8081 --public --allow-origin * --config /tmp/config.properties 与三、总览的行为完全对应。
9.4 日志观察与服务管理
每个请求在 INFO 级日志留一行处理记录(观察限流与耗时的第一现场):
docker logs languagetool-6.8-server 2>&1 | grep 'Handled request' | tail -3
实测回显(格式示例,耗时与状态码随请求而变):
2026-10-28 02:05:33 +0000 Handled request in 42ms; sending code 200
常用服务管理(Docker Compose 部署形态,数据卷持久化说明见部署篇第五节):
cd /opt/languagetool
docker compose ps # 实例状态(含 healthy)
docker compose logs -f languagetool # 跟随日志
docker compose restart languagetool # 重启(配置文件不重读)
docker compose up -d --force-recreate # 配置变更后重建(环境变量重读的唯一方式)
十、常见问题(FAQ)
Q1:调用返回 400 「This is the LanguageTool API. You have not specified any parameters」?
请求缺 text 或 language 参数,或路径写错(如少了 /v2、拼错端点名)。先核对 URL 为 http://<服务器>:<端口>/v2/check,再核对 POST 表单里两个必填参数都在。
Q2:中文文本送进去一个错误都不报,服务是不是坏了? 不是。7.2 节实测:中文无拼写词表,内置中文规则以成语/同音词混淆为主,对一般性错字 0 检出是 6.8 自建版的真实覆盖现状。中文校对主力方案应另行选型,LanguageTool 适合作为多语言场景中的欧洲语言主力。
Q3:429 Too Many Requests 频繁出现?
来源 IP 触发了 requestLimit。排查三步:① 报错文本里的 IP 是否符合预期(反代后是否已开 trustXForwardForHeader,否则全站共享反代 IP 的限额);② 健康检查/语言清单轮询是否也在消耗限额(8.2 节实测同桶);③ 窗口(requestLimitPeriodInSeconds)是否设置过短。窗口过后自动恢复。
Q4:长文本响应特别慢甚至超时?
十万字符级文本在 512MB 堆下实测 15 秒以上(9.2 节)。客户端分段提交是第一方案;确需大文本一次校对时,调大 JAVA_XMX 并评估设置 maxCheckTimeMillis(超时中断保护,warnings.incompleteResults 会变 true 提示结果不完整)。
Q5:offset/length 参数传了但不生效?
6.8 自建实测确认该参数被接受但不截断校对范围(5.7 节)。区间校对在客户端切分文本后分次提交。
Q6:motherTongue 传了没效果?
实测自建开源版该参数不改变检出结果(5.4 节)——母语精细化建议属云端/Premium 能力,自建可省略。
Q7:想启用某条规则却发现它不存在?
规则 id 因语言而异(如英语拼写类别实测为 TYPOS),且部分文档旧口径已变。以响应 JSON 的 rule.id/rule.category.id 实测值为准来拼 enabledRules/disabledRules/disabledCategories 参数。
Q8:健康检查应该探哪个路径?
GET /v2/healthcheck(返回 OK)。/health、/healthz、/livez 全部 400(2.1 节实测),误配会导致负载均衡把健康实例摘除。
十一、参考链接
- 官方 HTTP API 交互式文档(Swagger):https://languagetool.org/http-api/swagger-ui/
- 官方自建服务说明:https://dev.languagetool.org/http-server
- 公共 HTTP API 官方说明(限流口径参照):https://dev.languagetool.org/public-http-api
- Java API 说明(language-module.properties 机制):https://dev.languagetool.org/java-api
- ngram 语言模型官方说明:https://dev.languagetool.org/finding-errors-using-n-gram-data
- 平台内相关篇目:《LanguageTool 6.8 Docker Compose 部署实战》(部署与数据持久化)、《LanguageTool 6.8 配置参数详解》(服务端配置与限额参数)、《LanguageTool 介绍与选型指南》(认知与选型对比)。