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

都会大量使用类似思路。

 

 

 

 

Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐