File size: 8,908 Bytes
fc99637
 
66a2eed
 
9e82344
fc99637
9a8ff52
f379495
 
 
 
 
9a8ff52
 
 
 
f379495
 
 
 
 
 
 
9a8ff52
f379495
9a8ff52
f379495
 
 
 
9a8ff52
 
 
8cc31a5
 
 
 
 
 
 
 
 
 
f379495
9a8ff52
f379495
62e4304
f379495
 
 
9a8ff52
 
 
f379495
 
7e73aa5
f379495
 
7e73aa5
9a8ff52
7e73aa5
 
9a8ff52
603a6a3
 
f379495
28d2945
7e73aa5
 
f379495
 
603a6a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f379495
9a8ff52
7e73aa5
9a8ff52
 
7e73aa5
f379495
7e73aa5
f379495
 
 
7e73aa5
 
 
 
 
f379495
 
9a8ff52
 
7e73aa5
 
9a8ff52
 
f379495
9a8ff52
f379495
9a8ff52
f379495
 
 
 
 
 
 
9a8ff52
 
f379495
9a8ff52
f379495
9a8ff52
f379495
 
 
 
 
9a8ff52
f379495
9a8ff52
f379495
 
 
9a8ff52
f379495
 
 
 
 
 
 
 
 
 
 
 
 
9a8ff52
 
 
 
f379495
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9a8ff52
 
f379495
 
 
 
 
 
9a8ff52
f379495
 
 
9a8ff52
 
f379495
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9a8ff52
f379495
 
9a8ff52
 
 
f379495
 
 
 
 
9a8ff52
 
f379495
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
---
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 `<decide>` position and "points" at the option boundary tokens — the attention scores become the option probabilities.

```
[state] [q: instr <opt>opt A</opt> <opt>opt B</opt> <decide>]
   → 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 <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 <base_model_dir> \
  --train_items <train_items.pt> \
  --output_dir <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.