xuanbo commited on
Commit
f7d2e2e
·
verified ·
1 Parent(s): 6f9e97e

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +186 -3
README.md CHANGED
@@ -1,3 +1,186 @@
1
- ---
2
- license: apache-2.0
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ library_name: transformers
4
+ pipeline_tag: text-classification
5
+ language:
6
+ - zh
7
+ - en
8
+ tags:
9
+ - bert
10
+ - text-classification
11
+ - sales
12
+ - intent-classification
13
+ - dialogue
14
+ - evaluation
15
+ ---
16
+
17
+ # SaleIntent-BERT
18
+
19
+ **SaleIntent-BERT** is a fine-tuned BERT classifier that reads a complete sales conversation and predicts **how the customer ended up** — from clear purchase intent down to hostility. It is the outcome-scoring half of the [SalesLLM benchmark](https://github.com/Bairong-Xdynamics/Benchmarking-LLM-Realistic-Selling-Skill).
20
+
21
+ Scoring a sales dialogue with an LLM judge alone conflates two different questions: *did the salesperson run a good process?* and *did the customer actually want to buy at the end?* A model can be articulate, polite, and well-structured while the customer walks away — and an LLM judge, reading the whole transcript, tends to reward the articulate process. SaleIntent-BERT answers the second question independently, by looking only at where the conversation landed.
22
+
23
+ It reaches **93.51% accuracy on Chinese and 92.94% on English**, and pairs with the LLM judge to produce the final SalesLLM Score.
24
+
25
+ | | |
26
+ | :--- | :--- |
27
+ | **Task** | 5-class sequence classification over a full dialogue |
28
+ | **Base model** | BERT (see `config.json` for the exact checkpoint) |
29
+ | **Languages** | Chinese, English |
30
+ | **Input** | Flattened multi-turn dialogue, **last 128 tokens** |
31
+ | **Accuracy** | 93.51% (ZH), 92.94% (EN) |
32
+ | **License** | Apache 2.0 |
33
+
34
+ ---
35
+
36
+ ## Labels
37
+
38
+ The five classes are an **outcome grade**, not a monotonic intent ladder. Each maps to a point score used in the final benchmark metric:
39
+
40
+ | Label | Meaning | Score |
41
+ | :---: | :--- | :---: |
42
+ | **A** | Customer has **clear** purchase intent | 10 |
43
+ | **B** | Customer **possibly** has intent | 8 |
44
+ | **C** | Customer has **no** purchase intent | 6 |
45
+ | **X** | Customer has **weak** intent; dismissive / going through the motions | 4 |
46
+ | **F** | Customer is **abusive or complaining** | 2 |
47
+
48
+ Read the ordering carefully — **C (no intent) scores higher than X (weak, dismissive)**. A clean, honest "no" is a better conversational outcome than one the salesperson dragged into disengaged stonewalling, and an outright hostile ending (F) is worst of all. The scale grades the *state the salesperson left the customer in*, not just how close the sale was.
49
+
50
+ Class indices are `A=0, B=1, C=2, F=3, X=4`. Do not assume index order matches score order — always resolve through `model.config.id2label`.
51
+
52
+ ---
53
+
54
+ ## Input Format
55
+
56
+ The model expects the dialogue **flattened into a single string** with explicit speaker tags, then **tail-truncated to the last 128 tokens**:
57
+
58
+ ```python
59
+ def flatten_dialogue(messages):
60
+ out = ""
61
+ for msg in messages:
62
+ tag = "[ASSISTANT]" if msg["role"] == "assistant" else "[USER]"
63
+ out += tag + msg["content"]
64
+ return out
65
+ ```
66
+
67
+ Two details are load-bearing:
68
+
69
+ - **Speaker tags, no separators.** `[ASSISTANT]`/`[USER]` are concatenated directly against the message text with no spaces or newlines. The model was trained on exactly this string shape.
70
+ - **Tail truncation, not head.** Buying intent is decided at the *end* of a conversation, so the last 128 tokens are kept and everything before is dropped. Standard `truncation=True` keeps the *head* and will silently feed the model the opening pleasantries instead of the outcome — this is the single most common way to get bad predictions from this model.
71
+
72
+ `last_token_num=128` is the benchmark's validated setting. The window is deliberately short: a longer window pulls in mid-conversation negotiation that dilutes the end-state signal.
73
+
74
+ ---
75
+
76
+ ## Usage
77
+
78
+ ### Direct inference
79
+
80
+ ```python
81
+ import torch
82
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer
83
+
84
+ model_id = "MultiSense/SaleIntent_bert"
85
+ tok = AutoTokenizer.from_pretrained(model_id)
86
+ model = AutoModelForSequenceClassification.from_pretrained(model_id).eval()
87
+
88
+ LABEL2SCORE = {"A": 10, "B": 8, "C": 6, "X": 4, "F": 2}
89
+ N, MAX_LEN = 128, 512
90
+
91
+ def flatten_dialogue(messages):
92
+ return "".join(
93
+ ("[ASSISTANT]" if m["role"] == "assistant" else "[USER]") + m["content"]
94
+ for m in messages
95
+ )
96
+
97
+ def encode_tail(text, n=N, max_length=MAX_LEN):
98
+ """Keep the LAST n tokens — intent lives at the end of the dialogue."""
99
+ toks = tok.tokenize(text)[-min(n, max_length - 2):]
100
+ ids = [tok.cls_token_id] + tok.convert_tokens_to_ids(toks) + [tok.sep_token_id]
101
+ mask = [1] * len(ids)
102
+ pad = max_length - len(ids)
103
+ return {
104
+ "input_ids": torch.tensor([ids + [tok.pad_token_id] * pad]),
105
+ "attention_mask": torch.tensor([mask + [0] * pad]),
106
+ }
107
+
108
+ messages = [
109
+ {"role": "user", "content": "你好,我想了解一下你们的降噪耳机。"},
110
+ {"role": "assistant", "content": "好的,这款支持32dB混合降噪,续航38小时,售价1999元。"},
111
+ {"role": "user", "content": "听起来不错,那我下单一个吧。"},
112
+ ]
113
+
114
+ with torch.no_grad():
115
+ logits = model(**encode_tail(flatten_dialogue(messages))).logits
116
+
117
+ label = model.config.id2label[logits.argmax(-1).item()]
118
+ print(label, LABEL2SCORE[label]) # -> A 10
119
+ ```
120
+
121
+ ### As part of the SalesLLM score
122
+
123
+ `comprehensive_score.py` runs this model over a results file and blends it with the LLM judge:
124
+
125
+ ```bash
126
+ python salesllm/comprehensive_score.py \
127
+ --bert_path "MultiSense/SaleIntent_bert" \
128
+ --source_file "./results/zh/<output>.jsonl" \
129
+ --llm_model_name "<judge_model>" \
130
+ --api_key "<key>" --end_point "<base_url>" \
131
+ --last_token_num 128 \
132
+ --proportion 0.6
133
+ ```
134
+
135
+ The final score is a weighted blend of the two signals:
136
+
137
+ ```
138
+ final_score = proportion * LABEL2SCORE[bert_label] + (1 - proportion) * llm_judge_score
139
+ ```
140
+
141
+ The benchmark uses **`proportion = 0.6`** — outcome weighted slightly above process, because outcome is the harder signal to game. Both components are on the same 0–10 scale, so the blend is directly interpretable.
142
+
143
+ Output is written to `<source_file>_scored.json`, with `A/B` (the predicted label), `conversation_quality` (the LLM judge's 0–10), and `final_score` added to each record.
144
+
145
+ ---
146
+
147
+ ## Evaluation
148
+
149
+ | Language | Accuracy |
150
+ | :--- | :---: |
151
+ | Chinese | **93.51%** |
152
+ | English | **92.94%** |
153
+
154
+ The combined pipeline (this classifier at 0.6 + LLM judge at 0.4) achieves a **Pearson correlation of r = 0.98** with human ratings of overall sales performance, which is the result that justifies using the automated score in place of human annotation at benchmark scale.
155
+
156
+ Accuracy is reported over the full 5-class problem. Note that the classes are not balanced in realistic sales data — successful closes are rarer than non-purchases, and `F` (abusive) is rarest of all — so per-class recall on the tail classes will be lower than the aggregate figure suggests. If your use case hinges on detecting `F` or `X` specifically, measure per-class performance on your own data before relying on it.
157
+
158
+ ---
159
+
160
+ ## Limitations and Risks
161
+
162
+ - **Truncation is the main failure mode.** Feed it head-truncated text and predictions degrade badly while still looking confident. Always tail-truncate.
163
+ - **128-token window.** Intent expressed early and never restated near the end will be missed. Conversations that end with an off-topic exchange can also mislead it.
164
+ - **Trained on simulated + real sales dialogue** in Financial Services and Consumer Goods. Other verticals, other conversation formats (email threads, support tickets), and non-sales dialogue are out of distribution.
165
+ - **Format-coupled.** The `[ASSISTANT]`/`[USER]` tagging is part of the learned input representation, not a cosmetic choice. Different tags or added whitespace will shift predictions.
166
+ - **Not a purchase predictor.** It classifies *expressed* intent at the end of a conversation. Stated intent is not a real-world conversion rate, and it should not be used to forecast revenue or to score individual human salespeople for performance management.
167
+ - **Ordinal scores are a benchmark convention.** The 10/8/6/4/2 mapping was chosen for the SalesLLM metric. The intervals are not calibrated probabilities and should not be treated as such.
168
+ - **Inherited bias.** Predictions may vary with dialect, phrasing formality, and translationese in ways that correlate with demographics. Do not use it to gate access, rank customers, or make decisions affecting individuals.
169
+
170
+ ---
171
+
172
+ ## Citation
173
+
174
+ ```bibtex
175
+ @misc{salesllm,
176
+ title = {SalesLLM: Benchmarking LLM Realistic Selling Skill},
177
+ author = {MultiSense},
178
+ year = {2025},
179
+ url = {https://github.com/Bairong-Xdynamics/Benchmarking-LLM-Realistic-Selling-Skill}
180
+ }
181
+ ```
182
+
183
+ ## Related
184
+
185
+ - 📊 [SalesLLM benchmark & code](https://github.com/Bairong-Xdynamics/Benchmarking-LLM-Realistic-Selling-Skill)
186
+ - 🤗 [CustomerLM](https://huggingface.co/MultiSense/CustomerLM) — the user simulator that generates the customer side of the dialogues this model scores