Skip to main content
Glama

paeg-lang-style

Python License: MIT Tests PRs Welcome

Chinese | English


What This Is

paeg-lang-style is a Chinese language standards module — three-layer architecture (user requirements):

Layer

Capability

Core Files

Grammar rule constraints (most important)

Lexical/syntactic/punctuation rules as system prompts, directing the LLM to use complete words, complete syntax, and sufficient adverbials — works for anyone

rule_registry.py + prompts/builder.py

Forbidden-word fallback

Dynamically maintained forbidden-word lexicon (AI-speak/empty buzzwords/fake empathy/cheap encouragement/internet slang), a safety net when the LLM disobeys

forbidden.py

Rewriting script

LLM output post-processing: detect rule hits → feedback with rule IDs → multi-round Self-Refine rewriting

refiner.py + gate.py

Originated from the PAEG educational agent (v0.12-v0.71 iterations), refactored into a zero-host-dependency standalone plugin — any Python project can integrate it.

Related MCP server: sensitive-lexicon-mcp

Core Features

  • Extensible rule set: rules externalized to data/rules.json; appending hot-loads them (RuleRegistry)

  • General principles direct the LLM: lexical completeness/syntactic completeness/sufficient-adverbial principles let the LLM generalize rather than memorize word-by-word (avoiding the narrowness of "only optimizing 倦 into 疲倦")

  • Deterministic fallback: enumeration-layer rules ("我在这里听着你。"→"我就在这里听你说说。") guard against LLM disobedience

  • Rule-ID feedback loop: rewrite feedback carries "violates #rule-lx-001", forming a rule-generation-feedback loop

  • Dynamic forbidden-word lexicon: runtime add/remove + external JSON hot-loading

  • Injection-based design: chat_fn is force-injected — plug in your own LLM call, zero host coupling

  • Three profile tiers: general / teaching / confessional — assemble system prompts by scenario

  • AI-taste detection: 5-dimensional signals — sentence-length variance/transition-word density/three-part structure/dashes/paragraph symmetry

  • 8+ grammar rules: GB/T 15834 punctuation standards + six categories of faulty sentences + teaching language standards

  • 75 tests all green + 20 behavioral consistency checks (string-equal vs. the original PAEG implementation)

Installation

# 方式 1:pip 安装(推荐)
pip install -e /path/to/paeg-lang-style-plugin

# 方式 2:直接引用(零安装)
# 把 src/ 加入 sys.path 即可
import sys
sys.path.insert(0, "/path/to/paeg-lang-style-plugin/src")

Requires Python 3.9+. Zero third-party runtime dependencies.

Integrating as an MCP Server (install and use directly, just like MCP)

Accessibility standard (§3.109): after pip install in any project, declare it in the MCP client config to integrate — zero code bridge, zero host dependency.

# 方式 1:console_scripts 入口(pip install 后)
paeg-lang-style-mcp

# 方式 2:python -m 入口(源码运行)
python -m paeg_lang_style.mcp_server

MCP client config declaration (e.g. config/mcp_servers.json):

{
  "mcpServers": {
    "paeg-lang-style": {
      "command": "python",
      "args": ["-m", "paeg_lang_style.mcp_server"],
      "cwd": "D:/wbo-workspace/paeg_project/paeg-lang-style-plugin"
    }
  }
}

Exposed MCP tools (7):

Tool Name

Function

read/write

normalize_text

Text language-standards gate (L0 rules + L2 rewriting)

read

language_policy_check

AI-taste detection + forbidden-word hit report

read

forbidden_words

Dynamic forbidden-word lexicon management (add/remove/query)

write

check_grammar

Grammar check (8 rule categories)

read

check_ai_taste

AI-taste 5-dimensional signals

read

build_style_prompt

System prompt assembly (works for anyone)

read

list_rules

Extensible rule set listing

read

Quick Start

Three steps: install → assemble the prompt → process the output.

from paeg_lang_style import RuleRegistry, make_refiner, gate_content

# Step 1: 语法规则拼进你的系统提示词(谁用都拼)
system_prompt = "你是教育智能体,负责讲解数学概念。"
system_prompt += RuleRegistry().build_prompt("teaching")   # 通则层指挥 LLM

# Step 2: 注入你的 LLM 调用(改写脚本)
def my_llm(system, user, max_tokens=800, **kw):
    return call_my_llm_api(system, user, max_tokens=max_tokens)

refiner = make_refiner(chat_fn=my_llm)

# Step 3: LLM 输出后处理(L0 规则 + L2 重写)
raw_output = "总的来说,让我们一起赋能这个时代!"
clean = gate_content(raw_output, refiner=refiner)
# → "我们把这个时代里的每一个孩子,把他们的潜能一步步唤起。"

Table of Contents

Core Concepts

Three-layer architecture + rule-set data model — extensibility runs through all three places: rule definition/detection/prompt generation:

graph LR
    A[LLM 生成文本] --> B[gate_content 守门]
    B --> C{L0 规则检测}
    C -->|命中列举层| D[确定性替换<br/>听着你→听你说说]
    C -->|通则触发| E[L2 refiner.refine<br/>chat_fn 注入 LLM]
    E --> F[反馈带规则 ID<br/>违反 #rule-lx-001]
    F --> G[多轮 Self-Refine]
    D --> H[输出]
    G --> H
    H --> I[收口: 规则再跑一遍]

Rule data model (Rule — extensible via external JSON):

{
  "id": "rule-lx-general-001",
  "type": "general",
  "category": "lexical",
  "pattern": "(倦|乏|沉|累|苦|慌|虚|弱|低|烦|闷|困|急|乱)",
  "replacement": null,
  "message": "存在单字状态词——应扩展为完整双字词形",
  "prompt_block": "### 词法完整通则(指挥 LLM 泛化)...",
  "severity": "high",
  "enabled": true,
  "source": "builtin",
  "profile_tags": ["general", "teaching", "confessional"]
}
  • type: "general" (general-principle layer): prompt_block is assembled into the system prompt, directing the LLM to generalize — "any single-character adjective expressing state/feeling must be expanded to its full two-character form (倦/乏/沉/累/苦/慌/虚/弱/低/烦/闷/困/急/乱)". The LLM generalizes to unlisted words on its own, rather than only fixing "倦→疲倦".

  • type: "explicit" (enumeration layer): pattern + replacement deterministic fallback — the last line of defense when the LLM disobeys.

External Project Integration Guide

User requirement: any project/agent that wants to use our grammar rules module — how?

Scenario A: Only want "grammar rule constraints" (system prompt)

from paeg_lang_style import RuleRegistry

# 语法规则拼进自己的系统提示词(谁用都拼)
system = "你是我的客服机器人。"
system += RuleRegistry().build_prompt("general")   # 或 "teaching" / "confessional"

The rule fragments returned by build_prompt(profile) (lexical-completeness principle/syntactic-completeness principle/sufficient-adverbial principle/punctuation standards) can be concatenated directly into your LLM system prompt. This is the essence of "directing the LLM to use complete words" — no dependency on our rewriter.

Scenario B: Want to use the "rewriting script" to process LLM output

from paeg_lang_style import make_refiner, gate_content

# 注入你自己的 LLM 调用包装(chat_fn 强制注入,零宿主耦合)
def my_chat(system, user, max_tokens=800, **kw):
    return call_your_llm(system, user, max_tokens=max_tokens)

refiner = make_refiner(chat_fn=my_chat)
clean = gate_content(your_llm_output, refiner=refiner)   # L0 规则 + L2 重写
# 纯规则(不调 LLM):
clean = gate_content(your_llm_output)                    # 病句/违禁词确定性修正

Scenario C: Want to use the "forbidden-word lexicon"

from paeg_lang_style import ForbiddenWords

fb = ForbiddenWords()                     # 内置违禁词(AI 腔/空洞大词/伪共情/网络用语)
fb.load_json("my_words.json")             # 合并你自己的词库(动态扩充)
fb.add("你们公司的禁词")                  # 运行时新增
hits = fb.detect(text)                    # → ["禁词1", "禁词2"]

Scenario D: Want to extend the rules (extensibility)

Edit data/rules.json and append a rule — it hot-loads:

{
  "rules": [
    {
      "id": "rule-my-001",
      "type": "explicit",
      "category": "lexical",
      "pattern": "你们行业的黑话",
      "replacement": "规范说法",
      "message": "这是行业黑话,应改规范",
      "severity": "medium",
      "enabled": true,
      "source": "user",
      "profile_tags": ["general"]
    }
  ]
}
reg = RuleRegistry()
reg.load("data/rules.json")    # 合并(追加即生效)
reg.watch("data/rules.json")   # mtime 变更自动热重载
reg.check_reload()             # 每次调用前检查

Scenario E: Full integration (including the rule-ID feedback loop)

from paeg_lang_style import RuleRegistry, make_refiner, gate_content, get_style_prompt

# 1. 系统提示词(规则 + 风格)
system = get_style_prompt("all") + "\n" + RuleRegistry().build_prompt("general")

# 2. 改写器(规则检测 → 反馈带 ID → 重写)
refiner = make_refiner(chat_fn=my_chat)

# 3. 守门(L0 规则 + L2 重写 + 收口)
final = gate_content(raw, refiner=refiner)

Extensibility

Extension Point

Method

Mechanism

Grammar rules

Edit data/rules.json to append a Rule

RuleRegistry.load() merges + watch() hot-reload + PAEG_RULES_PATH env var overrides the path

Forbidden words

ForbiddenWords.load_json("custom.json") / add() / remove()

Runtime dynamic maintenance

Corpus

Replace data/weil_corpus.json with a neutral corpus

corpus_path parameter at construction

Profiles

Add profile_tags when adding rules

build_prompt(profile) filters by scenario

LLM backend

Inject any chat_fn

Force-injected, zero host coupling

Rule-ID contract

Rule id is stable, referenced in feedback

Rule-generation-feedback loop, convenient for telemetry

Maintainability

  • Zero host dependency: imports no host project modules, independently testable

  • Legacy API compatibility: rules.py/rules_enhanced.py kept as thin wrappers, backward compatible

  • 75 tests: full coverage of rule-set loading/hot-reload/detection/assembly/user extension/corruption tolerance

  • Behavioral consistency: 20 samples string-equal vs. the original PAEG implementation (zero drift)

  • Corruption tolerance: keeps the previous rule set when JSON is corrupted — never "runs empty"

  • Anti-bloat: token_budget controls system prompt length (default 800)

  • Clear separation of modes: general-principle layer (directs the LLM) vs. enumeration layer (deterministic fallback) with distinct responsibilities

Built-in Grammar Rules

ID

Type

Category

Rule

Trigger Pattern

Correction

rule-lx-general-001

general

lexical

Lexical completeness

Single-character state words (倦/乏/沉/累/苦/慌/虚/弱/低/烦/闷/困/急/乱)

Expand to full two-character form

rule-sx-general-001

general

syntactic

Syntactic completeness

Subject-verb-object/verb-object/preposition/compound sentences

All components present

rule-sx-general-002

general

syntactic

Sufficient adverbials

Short sentences starting with a verb/lone single verbs

Add time/place/manner/condition/object/purpose adverbials

rule-pn-general-001

general

punctuation

Punctuation standards (GB/T 15834)

Enumeration comma vs. comma/colon after 说

Punctuation standards

rule-lx-001

enumeration

lexical

倦→疲倦

觉得倦了|感到倦|已倦

Deterministic replacement

rule-lx-002

enumeration

lexical

乏→疲乏

的乏($|[,。;])

Deterministic replacement

rule-lx-003

enumeration

lexical

道出→说出来

道出

Deterministic replacement

rule-lx-004

enumeration

lexical

探知→探索并了解

探知

Deterministic replacement

rule-sx-001~004

enumeration

syntactic

"听着你" dangling

(我在这里|在这里|我)?听着你 + sentence end

听你说说

rule-sx-005

enumeration

syntactic

Dangling object

与你探讨$ etc.

Add object

rule-sx-006

enumeration

syntactic

Verb-object collocation

带着(重量|分量)

有很重的分量

rule-sx-007

enumeration

syntactic

Translationese redundancy

进行(一个)?(分析|讨论|思考)

Say the verb directly

rule-pn-001

enumeration

punctuation

Comma after 说

说:"

Use a comma instead

Sufficient-adverbial principle details (rule-sx-general-002, user-added):

Every action/judgment should be clearly qualified with sufficient adverbials — time, place, manner, condition, object, purpose. "复习单词。" → "你可以在每天睡前用十分钟复习单词。" "使用这个软件。" → "你可以在每天固定的时间使用这个软件。"

Forbidden-Word Lexicon

Category

Examples

AI-speak clichés

总的来说 / 综上所述 / 值得注意的是 / 让我们一起

Empty buzzwords

赋能 / 点亮 / 激活 / 重塑 / 升级 / 全方位

Fake-empathy verbs

接住(情绪)/ 托住 / 兜住 / 我懂你 / 心疼你

Cheap encouragement

加油 / 你真棒 / 你一定可以

Low-grade internet slang

yyds / 绝绝子 / 栓Q / 破防 / 内卷 / 躺平 / 宝子

Empty praise adjectives

深刻 / 全面 / 系统 / 本质

How to extend: ForbiddenWords().load_json("path.json") — JSON structure {"extra_forbidden": [...], "ai_tells_extra": [...]}.

API Reference

RuleRegistry

Method

Signature

Description

all()

() -> list[Rule]

All rules

by_id(id)

(str) -> Rule|None

Look up a rule by ID

add_rule(rule)

(dict) -> bool

Add at runtime (same ID overrides built-in)

remove_rule(id)

(str) -> bool

Remove at runtime

load(path)

(str|None) -> int

Merge external JSON (hot-load)

watch(path)

(str|None) -> None

mtime watch

check_reload()

() -> bool

Check and reload

detect(text, profile)

(str, str|None) -> list[Rule]

Detect hit rules

apply_explicit(text, profile)

(str, str|None) -> str

Deterministic replacement

build_prompt(profile, token_budget)

(str, int) -> str

Assemble system prompt

LanguageRefiner / make_refiner

Method

Signature

Description

make_refiner(chat_fn, llm, corpus_path)

(*, chat_fn, ...) -> LanguageRefiner

Factory (chat_fn required)

refine(text, context, max_rounds)

(str, str, int) -> str

Multi-round Self-Refine rewriting

check_grammar(text)

(str) -> list

Grammar check

detect_ai_tells(text)

(str) -> list

Forbidden-word hits

detect_ai_taste_signals(text)

(str) -> AITasteSignals

AI-taste signals

gate_content / gate_short

Function

Signature

Description

gate_content(text, context, apply_l2, refiner, polish_fn)

(str, str, bool, refiner|None, fn|None) -> str

L0 rules + L2 rewriting

gate_short(text, context, refiner, polish_fn)

(str, str, ...) -> str

Short-text fast path (L0 only)

ForbiddenWords

Method

Signature

Description

add(word) / remove(word)

(str) -> bool

Runtime add/remove

load_json(path)

(str|None) -> int

Merge external lexicon

detect(text)

(str) -> list

List of hit words

detect_count(text)

(str) -> int

Total hit count

get_style_prompt

Parameter

Description

"all"

Full language-style prompt

"weil" / "lexicon" / "syntax" / "forbidden"

Segments

["weil", "syntax"]

Multi-segment concatenation

Configuration Reference

Environment Variables

Variable

Default

Description

PAEG_RULES_PATH

src/paeg_lang_style/data/rules.json

Rule-set path override

data/ Files

File

Purpose

Extensible

data/rules.json

Grammar rule set

Yes, append to hot-load

data/forbidden_words.json

Forbidden-word lexicon

Yes, dynamic maintenance

data/weil_corpus.json

Corpus few-shot

Yes, replaceable

Architecture Design

宿主系统(任何 Python 项目 / 智能体)
  system_prompt += RuleRegistry().build_prompt()    <- 语法规则拼系统提示词(谁用都拼)
  gate_content(output, refiner=make_refiner(chat))  <- 输出后处理
        |
        | 零宿主依赖(不 import 宿主任何模块)
        v
paeg_lang_style(独立插件)
  +------------------+  +-----------------+  +-----------------+
  | rule_registry    |  | forbidden.py    |  | ai_taste.py     |
  | 可扩充规则集      |  | 动态违禁词库     |  | AI 味检测        |
  +--------+---------+  +--------+--------+  +--------+--------+
           |                    |                     |
           v                    v                     v
  +-----------------------------------------------------------+
  | refiner.py(改写脚本:chat_fn 注入 + 规则 ID 闭环)           |
  | gate.py(守门入口:L0+L2 编排)                              |
  +-----------------------------------------------------------+
  data/(rules.json / forbidden_words.json / 语料)

Integration with the PAEG Main Project

The PAEG educational agent integrates through the single adapter layer infra/lang_plugin_bridge.py (R18/R20 zero-breakage iron law):

from infra.lang_plugin_bridge import gate_content, get_style_prompt, make_refiner
# 插件挂载 → 走插件;插件未挂载 → 静默回退 PAEG 原实现(旧文件永不删除)

See docs/integration_paeg.md for details.

Testing

python -m pytest tests/ -q
# 75 项:规则集加载/热重载/检测/拼装/用户扩充/损坏容错 + 通则 + 充分状语 + 行为一致性

Contribution Guide

Contributions welcome! Please read CONTRIBUTING.md (to be created) to learn about:

  • Add new grammar rules: edit data/rules.json to append a Rule (with tests)

  • Add forbidden words: ForbiddenWords.load_json or submit a PR directly to the built-in lexicon

  • Code style: follow the existing module structure + comment conventions

Changelog

See CHANGELOG.md for details.

Acknowledgements

  • PAEG Education Agent (v0.12-v0.71 iterations) — this plugin is extracted from its language specification module

  • LanguageTool — declarative rule engine paradigm (dev.languagetool.org)

  • textstat — readability measurement paradigm (github.com/textstat/textstat)

  • GB/T 15834-2011《Usage of Punctuation Marks》 — national standard for punctuation rules

  • Agent Skills Standard — progressive disclosure paradigm (agentskills.io)

License

MIT © 2026 PAEG Team — see the LICENSE file for details.

Related MCP Connectors

Related MCP Servers