LanguageTool API 调用与运维实战(核心篇)

LanguageTool 6.8 自建服务 API 调用与运维实战核心篇:/v2/check 全参数体系(language/level/preferredVariants/motherTongue/规则开关)与响应结构解析、/v2/languages 语言清单、中英文检测样例实测、400/429/413 错误处理与健康检查、性能量级与 JVM 验证,全部命令与回显实测照抄。

LanguageToolLanguageTool2026/10/28

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/checkPOST(也支持 GET)提交文本做语法/拼写校对,返回 matches200 / 400 / 413 / 429
/v2/languagesGET返回服务支持的语言清单(code/longCode/name)200
/v2/telemetryPOST遥测上报(默认未启用)404

约定与事实(均实测):

  1. 请求方式:官方建议 POST(application/x-www-form-urlencoded 表单参数);/v2/check 的 GET 形态(curl -G --data-urlencode)实测同样可用,参数语义一致,适合浏览器调试场景。
  2. API 版本:响应 software.apiVersion 固定为 1;URL 中的 /v2/ 是路径约定,与 apiVersion 是两回事。
  3. 响应格式:全部为 JSON(Content-Type: application/json);错误也走 JSON 语义但 body 为 Error: ... 文本行 + 对应 4xx 状态码。
  4. 限流约定:自建服务的限流完全由服务端配置驱动(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 形态条数
英语 enen / en-AU / en-CA / en-GB / en-NZ / en-US / en-ZA7
德语 dede / de-AT / de-CH / de-DE / de-DE-x-simple-language / de-LU6
荷兰语 nlnl / nl-BE / nl-NL3
中文 zhzh-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

两个实测要点:

  1. 限流按来源 IP 计数:报错里的 10.89.56.1 是服务看到的请求来源地址(直连形态下为宿主网关地址)。窗口期过后自动恢复,无需重启服务。
  2. 同源共享一个桶:实测通过 Nginx 反代(8141)的请求与直连 8130 的请求互相消耗限额——反代未透传客户端真实 IP 时,后端把反代地址当成来源。生产反代形态必须开服务端 trustXForwardForHeader=true 并让 Nginx 设置 X-Forwarded-For(配置见配置详解篇 5.6 节),否则所有用户共享反代的限额、正常用户被误伤。
  3. 额外实测确认: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)。堆参数验证用两条实测可行的替代:

  1. 启动日志行(每次启动都打印):
docker logs languagetool-6.8-server 2>&1 | grep 'Using JAVA_OPTS'
# INFO: Using JAVA_OPTS=-Xms256m -Xmx512m -XX:+UseG1GC -XX:+UseStringDeduplication
  1. 宿主机进程参数(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 节实测),误配会导致负载均衡把健康实例摘除。

十一、参考链接

  1. 官方 HTTP API 交互式文档(Swagger):https://languagetool.org/http-api/swagger-ui/
  2. 官方自建服务说明:https://dev.languagetool.org/http-server
  3. 公共 HTTP API 官方说明(限流口径参照):https://dev.languagetool.org/public-http-api
  4. Java API 说明(language-module.properties 机制):https://dev.languagetool.org/java-api
  5. ngram 语言模型官方说明:https://dev.languagetool.org/finding-errors-using-n-gram-data
  6. 平台内相关篇目:《LanguageTool 6.8 Docker Compose 部署实战》(部署与数据持久化)、《LanguageTool 6.8 配置参数详解》(服务端配置与限额参数)、《LanguageTool 介绍与选型指南》(认知与选型对比)。