--- license: apache-2.0 library_name: transformers pipeline_tag: text-classification tags: [multilingual, en, zh, ruhui, jev, laya, system-one, calibrated-decisions, rlcd, classification, routing, scoring, guardrails, moderation, reinforcement-learning, commercial-use] --- [![PyPI](https://img.shields.io/pypi/v/ruhui.svg)](https://pypi.org/project/ruhui/) [![License](https://img.shields.io/badge/License-Apache%202.0-green.svg)](https://opensource.org/licenses/Apache-2.0) [![Hugging Face](https://img.shields.io/badge/%F0%9F%A4%97%20Model-anyforge%2Fruhui-blue)](https://huggingface.co/anyforge/ruhui) [![ModelScope](https://img.shields.io/badge/ModelScope-anyforge%2Fruhui-624aff.svg)](https://modelscope.cn/models/anyforge/ruhui) # Ruhui · 如晦 **A non-autoregressive System 1 decision engine for Chinese & multilingual text, with calibrated probabilities.** Named after Du Ruhui (杜如晦, courtesy name Keming 克明) of the legendary *Fang Mou Du Duan* (房谋杜断) pair — Fang Xuanling was the strategist, Du Ruhui the decisive judge. *Ruhui* inherits the "decisive" half: it makes a fast System 1 decision, generates no text, has nothing to parse, and therefore cannot hallucinate. --- ## What it is Ruhui answers **typed questions** — `choice`, `score`, `noul` (yes/no) — over any state (text, email, ticket, or JSON) in a **single forward pass**, returning a **calibrated probability** for every option. No text generation, no parsing, no hallucination. Two backends share the same interface: | Backend | Architecture | Size | Latency | Strength | |---|---|---|---|---| | **bert** | bidirectional encoder (mmBERT-base) + decision head | 322M | ~33 ms | fast, CPU-friendly, Chinese/English | | **llm** | Causal LM (Qwen3.5) + LoRA + PointerHead | 0.8B+ | hundreds of ms | stronger generalization | --- | Resource | Link | Notes | |---|---|---| | 📦 **PyPI** | [![PyPI](https://img.shields.io/pypi/v/ruhui.svg)](https://pypi.org/project/ruhui/) | `pip install ruhui -U` | | 🐙 **GitHub** | [![GitHub](https://img.shields.io/badge/GitHub-anyforge%2Fruhui-181717.svg?logo=github)](https://github.com/anyforge/ruhui) | Source + bilingual README + skill | | 🧩 **ModelScope** | [![ModelScope](https://img.shields.io/badge/ModelScope-anyforge%2Fruhui-624aff.svg)](https://modelscope.cn/models/anyforge/ruhui) | Model repo (bert + 0.8B) | | 🤗 **Hugging Face** | [![HF](https://img.shields.io/badge/%F0%9F%A4%97%20Model-anyforge%2Fruhui-blue)](https://huggingface.co/anyforge/ruhui) | Model repo (bert + 0.8B) | | 🛠️ **OpenClaw Skill** | [![OpenClaw](https://img.shields.io/badge/OpenClaw-Skill-orange.svg)](https://clawhub.ai/anyforge/skills/ruhui) | Agent skill (ClawHub) | | 🛠️ **ModelScope Skill** | [![ModelScope Skill](https://img.shields.io/badge/ModelScope-Skill-624aff.svg)](https://www.modelscope.cn/skills/anyforge/ruhui) | Agent skill (ModelScope) | --- ## Installation ```bash pip install ruhui -U ``` Python 3.10+. Core deps: `torch`, `transformers`, `safetensors`, `huggingface_hub`, `numpy`. The LLM backend additionally needs `peft`. --- ## Quick Start ### llm backend (larger model, stronger generalization) ```python from ruhui.llm import LLMAgent # a merged (self-contained) model — no base_dir needed agent = LLMAgent(checkpoint_dir="/path/to/anyforge/ruhui/0.8B") # choice task result = agent.predict( {"message": "我被重复扣款了,请退款"}, {"intent": {"type": "choice", "instructions": "客户想做什么?", "criteria": {"refund": "退款", "billing": "账单"}}}, ) print(result["answers"]) # score task result = agent.predict( {"message": "我被重复扣款了,客服三天没回复,今天必须解决,不然就取消订阅!"}, { "frustration": { "type": "score", "instructions": "客户有多生气?", "criteria": ["平静", "有点不满", "明显恼火", "非常愤怒,威胁投诉"], }, "urgency": { "type": "score", "instructions": "这件事有多紧急?", "criteria": ["不急", "需尽快处理", "紧急且阻塞"], }, }, ) print(result["answers"]) # noul task result = agent.predict( {"message": "不然我就取消订阅,去用你们竞争对手的产品"}, { "churn_risk": { "type": "noul", "instructions": "客户是否威胁要离开或取消?", }, "refund_requested": { "type": "noul", "instructions": "客户是否明确要求退款?", }, }, ) print(result["answers"]) ``` ### bert backend (the original, unchanged) ```python import ruhui agent = ruhui.load("/path/to/anyforge/ruhui") # hub, or a local directory result = agent.predict( {"message": "我被重复扣款了,请退款"}, { "intent": {"type": "choice", "instructions": "客户想做什么?", "criteria": {"refund": "退款", "technical": "技术问题", "billing": "账单咨询"}}, "churn_risk": {"type": "noul", "instructions": "客户是否威胁要离开?"}, }, ) print(result["answers"]) ``` details repo: [anyforge/ruhui](https://github.com/anyforge/ruhui)。 --- ## How the two backends work ### bert backend — encoder + decision head A bidirectional encoder reads the whole input, then a 2-layer decision head scores each option at its own `[MASK]` slot, all in parallel. Probabilities come from a softmax over those option slots, trained with **RLCD** (reinforcement learning from strictly-proper-scoring-rule rewards) so the reported confidence is statistically meaningful. ``` [CLS] question + [MASK] opt0 [MASK] opt1 ... [SEP] state [SEP] → bidirectional encoder → gather the [MASK] slot vectors → parallel scorer → softmax → calibrated probabilities ``` ### llm backend — Causal LM + PointerHead (KEV-style) A frozen causal LM runs **prefill-only** (never generates tokens). Each question becomes a branch sharing one state prefix, isolated by a block-causal mask. A pointer head then reads the `` position and "points" at the option boundary tokens — the attention scores become the option probabilities. ``` [state] [q: instr opt A opt B ] → Causal LM (prefill only) → PointerHead: q(decide) · k(option) → logits → softmax ``` The LoRA adapter is folded into the base weights at inference (or merged permanently with `merge_model.py`). --- ## Decision primitives | Primitive | Output | |---|---| | `choice` | top label + full probability distribution + confidence | | `score` | expected level on an ordinal rubric | | `noul` | calibrated P(true) | Confidence is normalized entropy (`1 − H(p)/log K`), so it is safe to gate on: ```python if conf >= 0.85: route_automatically(dept) # high confidence else: escalate_to_human(dept) # low confidence ``` --- ## Fine-tuning ### llm backend (KEV-style) ```bash # 1. convert soft labels to KEV-format training data python scripts/convert_to_kev.py --soft_dir --out datas/train.jsonl # 2. fine-tune from an existing checkpoint (delta mode) python scripts/finetune.py \ --data datas/train.jsonl \ --base Qwen/Qwen3.5-0.8B-Base \ --init_from anyforge/ruhui/0.8B \ --out runs/ruhui-0.8b \ --epochs 2 --device cuda # 3. resume if interrupted python scripts/finetune.py ... --resume # 4. merge LoRA into the base weights (bf16 halves the size) python scripts/merge_model.py \ --checkpoint runs/ruhui-0.8b \ --base Qwen/Qwen3.5-0.8B-Base \ --out runs/ruhui-0.8b-merged \ --dtype bf16 ``` ### bert backend (Laya-style) ```bash python scripts/train.py \ --model_dir \ --train_items \ --output_dir \ --epochs 4 ``` --- ## Repository layout ``` ruhuipro/ ruhui/ bert/ # encoder backend (agent / router / common / presets / ...) llm/ # LLM backend (model / api / checkpoint / train / data / agent) scripts/ convert_to_kev.py # soft labels → KEV format finetune.py # LLM fine-tune (--init_from / --resume) merge_model.py # LoRA merge + dtype control train.py # bert fine-tune tests/ ``` --- ## Model repositories - Hugging Face: `anyforge/ruhui` (root = bert model; `0.8B/` = LLM 0.8B merged model) - ModelScope: `anyforge/ruhui` (same layout) --- ## Acknowledgments - **Laya** ([NandhaKishorM/laya](https://github.com/NandhaKishorM/laya), Apache 2.0) — the non-autoregressive System 1 decision paradigm and RLCD training that the bert backend is forked from. - **KEV** ([jaredpalmer/kev](https://github.com/jaredpalmer/kev), Apache 2.0) — the Causal LM + LoRA + PointerHead architecture that the llm backend is built on. ## License Apache 2.0. Developed by AnyForge.