LLM-Cookbook 学习——搭建基于 ChatGPT 的问答系统>评估输入——分类 Classification
LLM-Cookbook 第三章学习笔记:评估输入——分类 Classification
一、本章主要讲什么?
这一章的标题是:
评估输入——分类(Classification)
这里的“分类”并不是让我们自己训练一个传统的机器学习分类模型,而是:
让大语言模型先判断用户的输入属于哪一种类型,然后根据分类结果决定下一步应该执行什么操作。
例如用户向客服机器人发送:
我想注销我的账号。
LLM 不需要马上生成最终客服回答。
我们可以先让模型做一步:
用户输入
↓
LLM 分类
↓
Account Management
↓
Close account
然后系统看到:
primary = Account Management
secondary = Close account
再进入专门负责“注销账户”的处理流程。
因此,这一章真正重要的思想是:
先识别用户意图
↓
对输入进行分类
↓
根据分类结果
选择不同的处理流程
这实际上已经是一个非常简单的 LLM Routing(路由) 思想。
二、为什么需要先对用户输入进行分类?
假设我们正在设计一个在线客服系统。
用户可能提出各种各样的问题:
我为什么被扣了 99 元?
软件打不开怎么办?
我要修改密码。
你们这个电视多少钱?
显然,这几个问题需要完全不同的处理方式。
如果所有问题都塞给一个巨大的 Prompt:
如果用户问付款……
如果用户问密码……
如果用户问软件……
如果用户问产品……
如果用户问退款……
如果用户问账户……
……
系统会变得越来越复杂。
一种更加合理的方法就是:
用户输入
│
↓
分类模型
│
┌─────────────┼─────────────┐
↓ ↓ ↓
账单问题 技术问题 账户问题
│ │ │
↓ ↓ ↓
账单处理Prompt 技术支持Prompt 账户管理Prompt
因此:
分类的作用就是决定下一步把用户请求交给谁处理。
三、本章客服案例的分类体系
教程首先人为规定了一套固定分类体系。
一级分类一共有四类:
| 一级分类 | 英文 | 含义 |
|---|---|---|
| 计费 | Billing | 付款、扣费、账单 |
| 技术支持 | Technical Support | 软件、设备、故障 |
| 账户管理 | Account Management | 密码、账户、个人信息 |
| 一般咨询 | General Inquiry | 产品、价格、人工客服等 |
然后,每个一级分类下面还有更加具体的二级分类。
3.1 Billing——计费
Billing
│
├── Unsubscribe or upgrade
│ 取消订阅或升级
│
├── Add a payment method
│ 添加付款方式
│
├── Explanation for charge
│ 收费解释
│
└── Dispute a charge
费用争议
3.2 Technical Support——技术支持
Technical Support
│
├── General troubleshooting
│ 常规故障排除
│
├── Device compatibility
│ 设备兼容性
│
└── Software updates
软件更新
3.3 Account Management——账户管理
Account Management
│
├── Password reset
│ 重置密码
│
├── Update personal information
│ 更新个人信息
│
├── Close account
│ 关闭账户
│
└── Account security
账户安全
3.4 General Inquiry——一般咨询
General Inquiry
│
├── Product information
│ 产品信息
│
├── Pricing
│ 定价
│
├── Feedback
│ 用户反馈
│
└── Speak to a human
与人工客服对话
所以这里实际上建立了一棵:
一级类别
↓
二级类别
组成的分类树。
四、第一步:定义 delimiter 分隔符
教程首先定义:
delimiter = "####"
这里的 delimiter 中文叫:
分隔符
作用就是:
用一个特殊符号,把 Prompt 中不同性质的内容分隔开。
例如:
用户输入:
####我要关闭我的账户####
这样模型更容易区分:
这是系统指令
和:
这是用户真正输入的数据
五、为什么要使用分隔符?
假设没有分隔符:
请判断下面用户的问题属于什么类别。
我想删除账户和所有个人数据。
模型当然也可能理解。
但当 Prompt 越来越复杂以后:
系统规则
分类列表
输出格式要求
用户输入
示例
背景信息
全部混在一起,就容易产生结构上的混乱。
加入分隔符以后:
系统规则……
用户输入:
####用户真正说的话####
结构就会更加清晰。
因此 delimiter 的作用可以简单理解成:
给 Prompt 不同区域划边界。
常见的分隔方式其实很多:
####
"""
---
<user_input>
...
</user_input>
并不是一定要使用 ####。
这一章使用它主要是为了让模型清楚知道:
#### 中间的内容是用户查询
六、第二步:定义 System Message
本章最核心的一部分代码是:
system_message = f"""
你将获得客户服务查询。
每个客户服务查询都将用{delimiter}字符分隔。
将每个查询分类到一个主要类别和一个次要类别中。
以 JSON 格式提供你的输出,
包含以下键:
primary 和 secondary。
主要类别:
Billing
Technical Support
Account Management
General Inquiry
……
"""
这里实际上是在 System Prompt 中告诉模型四件事情。
第一件事:告诉模型任务是什么
你将获得客户服务查询。
相当于告诉模型:
接下来我给你的东西都是客服问题。
第二件事:告诉模型输入在哪里
每个客户服务查询都将用 #### 字符分隔。
例如:
####我想关闭账户####
模型就知道:
####中间是需要进行分类的用户输入。
第三件事:告诉模型有哪些类别
例如:
主要类别:
Billing
Technical Support
Account Management
General Inquiry
同时继续规定:
Account Management:
Password reset
Update personal information
Close account
Account security
也就是说,我们不是让模型:
“随便想一个类别。”
而是在告诉它:
只能从我规定好的分类体系中选择。
这一点非常重要。
七、为什么要提前规定类别?
假设用户输入:
我的密码忘记了。
如果不规定类别,模型可能返回:
密码问题
也可能返回:
登录问题
还可能返回:
账户问题
每一次名称都可能不同。
程序就很难判断:
if result == ???
到底应该判断什么。
而如果提前规定:
Account Management
└── Password reset
那么模型只需要从已有标签中选择。
因此分类任务中一个非常重要的 Prompt Engineering 思路就是:
尽可能明确地定义允许模型输出的类别集合。
八、第四件事:规定输出格式 JSON
教程中特意告诉模型:
以 JSON 格式提供输出。
包含:
primary
secondary
因此模型应该返回类似:
{
"primary": "Account Management",
"secondary": "Close account"
}
这里为什么一定要让模型输出 JSON?
因为我们最终不是只给“人”看这个结果。
更重要的是:
后面的程序还要读取这个分类结果。
九、自然语言输出 vs JSON 输出
如果模型返回:
我认为这个用户的问题主要属于账户管理,
具体来说应该属于关闭账户。
人类看起来当然没问题。
但是程序很难稳定地提取。
而 JSON:
{
"primary": "Account Management",
"secondary": "Close account"
}
程序就可以直接解析。
例如:
result["primary"]
得到:
Account Management
然后:
result["secondary"]
得到:
Close account
于是就可以继续写程序:
if result["secondary"] == "Close account":
# 执行账户注销相关流程
所以:
结构化输出是连接 LLM 和传统程序代码的重要桥梁。
十、用户输入案例一:删除账户
教程中的第一个用户输入类似:
user_message = """
我希望你删除我的个人资料和所有用户数据。
"""
我们自己先判断一下。
“删除个人资料和所有数据”显然与:
账户管理
有关。
进一步属于:
关闭账户
所以理想分类结果应该是:
{
"primary": "Account Management",
"secondary": "Close account"
}
教程中的模型也成功把它分类到了账户管理 → 关闭账户。
十一、messages 这段代码到底什么意思?
接下来教程构造:
messages = [
{
'role': 'system',
'content': system_message
},
{
'role': 'user',
'content': f"{delimiter}{user_message}{delimiter}"
}
]
11.4 第二条消息
{
'role': 'user',
'content': f"{delimiter}{user_message}{delimiter}"
}
表示:
用户真正提交的内容。
假设:
delimiter = "####"
user_message = "我想注销账号"
那么:
f"{delimiter}{user_message}{delimiter}"
最后得到:
####我想注销账号####
所以最终 messages 可以粗略理解为:
messages = [
{
"role": "system",
"content": "请判断用户属于什么类别……"
},
{
"role": "user",
"content": "####我想注销账号####"
}
]
这样是不是就清楚很多了。
十二、这里的 f-string 又有什么作用?
代码:
f"{delimiter}{user_message}{delimiter}"
前面的:
f
表示这是 Python 的:
格式化字符串 f-string
假设:
delimiter = "####"
user_message = "我要注销账号"
那么:
f"{delimiter}{user_message}{delimiter}"
相当于:
####
+
我要注销账号
+
####
最后变成:
####我要注销账号####
所以 {} 中可以直接放变量。
十三、调用模型完成分类
然后执行:
response = get_completion_from_messages(messages)
print(response)
过程可以理解成:
messages
↓
发送给 LLM
↓
LLM 阅读 system message
↓
获得分类规则
↓
阅读 user message
↓
判断类别
↓
按照 JSON 格式输出
最终可能得到:
{
"primary": "账户管理",
"secondary": "关闭账户"
}
十四、案例二:询问产品信息
第二个案例类似:
告诉我更多有关你们平板电视的信息。
它属于:
General Inquiry
进一步属于:
Product information
因此:
{
"primary": "General Inquiry",
"secondary": "Product information"
}
教程中的案例也是按照这一分类结果返回的。
十五、这一步做完之后有什么用?
这是本章最关键的问题。
如果只是为了输出:
{
"primary": "...",
"secondary": "..."
}
其实意义并不是特别大。
分类真正有价值的地方是:
根据分类结果决定后面的程序怎么走。
例如:
if secondary == "Close account":
handle_close_account()
elif secondary == "Password reset":
handle_password_reset()
elif secondary == "Product information":
search_product_database()
elif secondary == "Technical Support":
technical_support()
于是整个系统就变成:
用户问题
│
↓
LLM 分类
│
┌─────────────────┼─────────────────┐
│ │ │
产品信息 关闭账户 技术支持
│ │ │
↓ ↓ ↓
查产品库 注销流程 故障排查
这里的 LLM 就承担了:
Router(路由器)
的角色。
十六、什么叫 Routing?
Routing 可以翻译为:
路由
意思就是:
先判断输入是什么类型
↓
再把它送到对应处理模块
例如:
“我要退订会员”
↓
Billing
↓
Unsubscribe
↓
订阅管理模块
而:
“为什么软件打不开?”
↓
Technical Support
↓
General troubleshooting
↓
技术支持模块
因此分类本身并不是最终任务。
分类更像:
系统的第一个交通指挥员。
它决定用户的问题接下来应该去哪。
十七、分类为什么可以降低 Prompt 的复杂度?
假设系统有 100 种业务。
最暴力的方法:
一个超级大的 Prompt
里面写:
情况1怎么办……
情况2怎么办……
情况3怎么办……
……
情况100怎么办……
这会导致:
Prompt 很长
规则复杂
Token 消耗增加
维护困难
不同规则容易互相干扰
另一种方式:
第一步:分类
↓
第二步:选择对应 Prompt
↓
第三步:执行具体任务
例如:
用户问题
↓
分类
↓
技术支持
↓
只加载“技术支持 Prompt”
这样后面的模型根本不需要知道:
账单怎么处理
账户怎么注销
价格怎么回答
它只专注当前任务即可。
这就是:
先分类,再处理。
十八、分类和普通问答有什么区别?
普通问答:
用户:
我怎么重置密码?
LLM:
您可以点击……
LLM 直接产生最终答案。
而本章的方法:
用户:
我怎么重置密码?
第一步:
{
"primary": "Account Management",
"secondary": "Password reset"
}
第二步:
Password reset
↓
进入密码重置处理流程
第三步:
检索公司真实的密码重置流程
↓
生成回答
因此:
普通 Chatbot:
输入 → 回答
而一个更加工程化的 LLM 系统可能是:
输入
↓
分类
↓
路由
↓
检索
↓
处理
↓
回答
这就是“调用 LLM”与“构建 LLM 系统”的重要区别之一。
十九、为什么 temperature 通常设置为 0?
教程中的辅助函数默认:
temperature = 0
这对于分类任务比较合理。
因为分类任务通常希望:
同样的问题
↓
尽量得到同样的分类结果
例如:
我要注销账号
我们不希望第一次:
Close account
第二次:
Account security
第三次又变成:
General Inquiry
分类、信息提取、结构化输出这些任务通常追求:
稳定性
一致性
确定性
因此一般不需要很高的随机性。
二十、这一章最重要的代码逻辑
把所有具体代码先放到一边,这一章真正要掌握的其实只有:
# 1. 定义分类规则
system_message = """
判断用户输入属于什么类型……
"""
# 2. 获取用户输入
user_message = """
我要注销账户
"""
# 3. 组织 messages
messages = [
{
"role": "system",
"content": system_message
},
{
"role": "user",
"content": user_message
}
]
# 4. 调用 LLM
response = get_completion_from_messages(messages)
# 5. 得到结构化分类结果
print(response)
思想就是:
定义类别
↓
输入问题
↓
LLM 判断
↓
结构化输出
二十一、为什么 JSON 特别重要?
这一章中我认为除了“分类”以外,第二重要的知识就是:
让 LLM 输出结构化数据。
因为程序最喜欢的不是:
我觉得这个问题可能属于账户管理方面,
具体似乎和账户关闭有关。
而是:
{
"primary": "Account Management",
"secondary": "Close account"
}
原因是后一种结果:
机器容易读取
↓
代码容易判断
↓
方便进入下一流程
所以现代 LLM 应用经常要求模型输出:
JSON
列表
固定字段
Schema
而不是自由发挥的一段自然语言。
二十二、分类实际上可以有很多层
教程中使用:
一级分类
+
二级分类
例如:
Account Management
↓
Close account
但实际项目中还可以有:
一级分类
↓
二级分类
↓
三级分类
二十三、把这一章思想应用到 Datasheet 提取
例如用户输入一段芯片手册:
Input Leakage Current
VIN = VSS to VDD
Maximum leakage current: ±1 μA
第一阶段可以让 LLM 分类:
{
"primary": "Electrical Test",
"secondary": "Leakage Current"
}
然后系统看到:
secondary = Leakage Current
进入:
漏电流测试项生成 Prompt
接着再提取:
测试项目
测试条件
测试上下限
测试单位
测试引脚
测试方法
整个流程就是:
Datasheet 文本
↓
LLM 分类
↓
Leakage Current
↓
加载漏电流测试规则
↓
提取测试参数
↓
生成 Test Item
这已经非常接近一个简单 Agent 或 Workflow 的设计思想。
二十四、分类器可以看成一个“意图识别器”
从另一个角度理解:
Classification
其实就是:
Intent Recognition
意图识别
例如:
用户:
我要改密码。
真正需要识别的是:
用户的 Intent 是什么?
答案:
Password reset
因此聊天机器人经常会有类似:
用户输入
↓
意图识别
↓
Intent
↓
执行对应 Action
例如:
“明天天气怎么样?”
↓
weather_query
↓
调用天气 API
“帮我发封邮件”
↓
send_email
↓
调用邮件工具
“查一下芯片漏电流指标”
↓
datasheet_query
↓
调用 RAG
所以这一章的分类思想其实是很多 Agent 系统的基础。
二十五、本章容易搞混的几个地方
1. JSON 是不是 Python 字典?
不是完全一样。
JSON:
{
"primary": "Billing"
}
是一种:
数据交换格式
Python 中:
{
"primary": "Billing"
}
则是:
dict
字典
两者长得非常像,因此 JSON 很容易转换为 Python 字典。
二十六、一张图总结本章
用户输入
│
↓
┌──────────────┐
│ LLM 分类器 │
└──────────────┘
│
↓
Primary Category
│
↓
Secondary Category
│
┌───────────────┼───────────────┐
↓ ↓ ↓
Billing Technical Account
│ │ │
↓ ↓ ↓
账单流程 技术流程 账户流程
│ │ │
└───────────────┼───────────────┘
↓
最终用户回答
因此这一章最核心的系统架构就是:
Input
↓
Classification
↓
Routing
↓
Processing
↓
Response
二十七、本章和后续 Prompt Chaining / Agent 的关系
这一章表面上只有一个简单分类案例,但实际上非常重要。
因为后面的复杂 LLM 系统很少是:
一个 Prompt
↓
解决所有问题
更加常见的是:
Step 1
判断用户想干什么
↓
Step 2
决定调用哪个 Prompt / Tool / RAG
↓
Step 3
执行具体任务
↓
Step 4
整理最终答案
因此:
Classification
实际上承担:
任务分流
而后续的:
Prompt Chaining
Workflow
Agent
Tool Calling
RAG
都会大量使用类似思路。
更多推荐




所有评论(0)