1.背景
某X互联网电商公司为了解决当前大量用户的售前咨询问题,需要建设一个不需要客服介入的简易电商售前机器人,用于回答用户的售前问题,并给出基本可靠的咨询回答。
当前大模型如gpt、baichuan、文心等均有开放使用的OpenAPI接口,且调用费用低廉。可以基于大模型来构建简易的电商售前对话服务。
本文介绍一种基于Golang + gpt-3.5-turbo构建对话服务的方案。
2.概要设计
2.1.核心需求
对话服务的核心接口为Chat,即自动对话接口:
- 输入:用户咨询文本
- 输出:咨询结果
核心要求:
- 能够判断咨询内容是否与售卖商品相关
- 能够简单回答用户对于商品的咨询问题,如价格、材质、用途
- 能够以友好的语气回答用户问题
- 对于非商品售卖问题、暴力血腥问题等, 不给予回答
2.2.核心流程
3.详细设计
核心实现代码:https://github.com/pbrong/llm_hub/tree/master/module/chatbot
3.1.内容审查
OpenAI提供了内容审查的能力,可用于检查文本是否具有潜在危险内容,如暴力、血腥、性等输入内容。
- 接口:v1/moderations
- 入参:
{
"input": "我要去轰炸米国"
}
- 响应:
- flagged:是否存在违规词
- categories:文本违规分类
- category_scores:文本违规分类得分
{
"id": "modr-9Xh7J5zoVHoyb4rokiDHHvd1CYdPD",
"model": "text-moderation-007",
"results": [
{
"flagged": true,
"categories": {
"sexual": false,
"hate": true,
"harassment": true,
"self-harm": false,
"sexual/minors": false,
"hate/threatening": false,
"violence/graphic": false,
"self-harm/intent": false,
"self-harm/instructions": false,
"harassment/threatening": true,
"violence": true
},
"category_scores": {
"sexual": 0.00008100192644633353,
"hate": 0.5908858776092529,
"harassment": 0.6599222421646118,
"self-harm": 1.1431256297100845e-7,
"sexual/minors": 9.379126595376874e-7,
"hate/threatening": 0.09888827055692673,
"violence/graphic": 0.000004452876964933239,
"self-harm/intent": 1.831264739848848e-8,
"self-harm/instructions": 1.7390346107593047e-10,
"harassment/threatening": 0.8955895900726318,
"violence": 0.9563981294631958
}
}
]
}
对于本次对话服务,直接按flagged来判断是否存在违规词即可。但对于某些儿童教育等应用,需直接判断违规分数,大于一定阈值即认定违规。
3.2.角色链拆分
一次对话流程如果都在一个Prompt中完成,那么这个Prompt会非常复杂且不利于维护和评估,可以按按照“角色链”来对复杂的Prompt进行拆分。
内容审查没问题后开始进入真正的问答环节,第一步是前置校验,会对用户的输入进行辨别,判断是否与电商咨询相关,如果询问的问题与咨询无关,则不进入下一步。
在前置校验完成后,确定是咨询问题,第二步进行真正的推理回答,回答过程中需要加载商品信息至Prompt输入中(即fewshot),让LLM能够根据商品信息进行推理回答。
在回答完成后,第三步将推理的回答进行语句优化,给出更良好体验的客服回答。
抽象接口
module
│ └── chatbot
│ ├── core_infer_role.go
│ ├── helper.go
│ ├── output_opt_role.go
│ └── pre_check_role.go
抽象出角色及角色链接口,方便扩展:
- module/chatbot/helper.go
package chatbot
import "context"
var (
// 前置校验角色
PreCheckRole ChatRole
// 核心对话推理角色
CoreInferRole ChatRole
// 对话输出优化角色
OutputOptRole ChatRole
)
type ChatRole interface {
Chat(ctx context.Context, input string, optArgs *OptArg) (output string, err error)
}
type OptArg struct {
Products []string
}
type ChatChain []ChatRole
func NewChatChain(roles ...ChatRole) ChatChain {
chain := ChatChain{}
chain = append(chain, roles...)
return chain
}
func (chain ChatChain) Run(ctx context.Context, input string, optArg *OptArg) (output string, err error) {
for _, role := range chain {
output, err = role.Chat(ctx, input, optArg)
if err != nil {
return "", err
}
if output == "N" {
return "请咨询相关问题", nil
}
if output != "" && output != "Y" {
input = output
}
}
return output, nil
}
func Init() {
PreCheckRole = NewPreCheckRole()
CoreInferRole = NewCoreInferRole()
OutputOptRole = NewOutputOptRole()
}
前置校验角色
- module/chatbot/pre_check_role.go
package chatbot
import (
"context"
"fmt"
"llm_hub/pkg/llm_caller"
)
type preCheckRole struct{}
func NewPreCheckRole() ChatRole {
return &preCheckRole{}
}
func (role *preCheckRole) Chat(ctx context.Context, input string, optArg *OptArg) (output string, err error) {
systemPrompt := `
请仔细检查{}括起来的用户输入文本,判断该输入文本是否与电商咨询/售前咨询问题相关。
你只可以输出Y或N,分别代表是相关咨询问题和不是相关咨询问题。
`
gptCaller, err := llm_caller.NewGptLLMCaller(ctx, systemPrompt, 0, 1024)
if err != nil {
return "", err
}
completion, err := gptCaller.Call(ctx, fmt.Sprintf("用户输入:{%v}", input))
if err != nil {
return "", err
}
return completion, nil
}
推理回答角色
- module/chatbot/core_infer_role.go
package chatbot
import (
"context"
"errors"
"fmt"
"llm_hub/pkg/llm_caller"
)
type coreInferRole struct{}
func NewCoreInferRole() ChatRole {
return &coreInferRole{}
}
func (role *coreInferRole) Chat(ctx context.Context, input string, optArg *OptArg) (output string, err error) {
if len(optArg.Products) == 0 {
return "", errors.New("product not exist")
}
systemPrompt := `
请仔细阅读并理解{}括起来的用户咨询问题,给出你的咨询回答。请严格遵循以下过程判断和回答:
1-首先判断用户咨询的问题中,是否存在商品相关信息
2-若存在商品相关信息,则结合{}括起来的商品信息进行回答
`
gptCaller, err := llm_caller.NewGptLLMCaller(ctx, systemPrompt, 0, 2048)
if err != nil {
return "", err
}
completion, err := gptCaller.Call(ctx, fmt.Sprintf("用户咨询问题:{%v}\n商品信息:{%v}", input, optArg.Products))
if err != nil {
return "", err
}
return completion, nil
}
语句优化角色
- module/chatbot/output_opt_role.go
package chatbot
import (
"context"
"fmt"
"llm_hub/pkg/llm_caller"
)
type outputOptRole struct{}
func NewOutputOptRole() ChatRole {
return &outputOptRole{}
}
func (role *outputOptRole) Chat(ctx context.Context, input string, optArg *OptArg) (output string, err error) {
systemPrompt := `
你是一个友好的客服机器人,现在需要对{}括起来的原始回答信息进行优化,并输出优化后的回答。
要求:
1-优化后的回答不能与原始回答存在歧义
2-优化后的回答需要显示出耐心、友好和语句通顺
3-将原始回答中的JSON格式优化为可以阅读的自然语言格式输出
4-输出的优化回答中,不要带有经过优化等字样
`
gptCaller, err := llm_caller.NewGptLLMCaller(ctx, systemPrompt, 0, 2048)
if err != nil {
return "", err
}
completion, err := gptCaller.Call(ctx, fmt.Sprintf("原始回答信息:{%v}", input))
if err != nil {
return "", err
}
return completion, nil
}
对话服务测试
- service/chatbot/chatbot_service.go
package chatbot_service
import (
"context"
"llm_hub/module/chatbot"
)
var ChatBotServiceV1 = chatBotServiceV1{}
type ChatBotService interface {
Chat(ctx context.Context, message string) (output string, err error)
}
type chatBotServiceV1 struct{}
func (service *chatBotServiceV1) Chat(ctx context.Context, message string) (output string, err error) {
// 初始化角色链
chain := chatbot.NewChatChain(
chatbot.PreCheckRole,
chatbot.CoreInferRole,
chatbot.OutputOptRole)
// 检索商品信息(RAG)
products := retrievalProducts(ctx)
// 调用llm
output, err = chain.Run(ctx, message, &chatbot.OptArg{
Products: products,
})
if err != nil {
return "", err
}
return output, nil
}
func retrievalProducts(ctx context.Context) []string {
// Mock一批数据,这里其实是一段RAG搜索逻辑(by向量匹配)
return []string{
`{
"名称": "SoundMax Soundbar",
"类别": "电视和家庭影院系统",
"品牌": "SoundMax",
"型号": "SM-SB50",
"保修期": "1 year",
"评分": 4.3,
"特色": [
"2.1 channel",
"300W output",
"Wireless subwoofer",
"Bluetooth"
],
"描述": "使用这款时尚而功能强大的声音,升级您电视的音频体验。",
"价格": 199.99
}`,
`{
"名称": "SoundMax Home Theater",
"类别": "电视和家庭影院系统",
"品牌": "SoundMax",
"型号": "SM-HT100",
"保修期": "1 year",
"评分": 4.4,
"特色": [3.3 生成用户查询的答案
"5.1 channel",
"1000W output",
"Wireless subwoofer",
"Bluetooth"
],
"描述": "一款强大的家庭影院系统,提供沉浸式音频体验。",
"价格": 399.99
}`,
`{
"名称": "CineView OLED TV",
"类别": "电视和家庭影院系统",
"品牌": "CineView",
"型号": "CV-OLED55",
"保修期": "2 years",
"评分": 4.7,
"特色": [
"55-inch display",
"4K resolution",
"HDR",
"Smart TV"
],
"描述": "通过这款OLED电视,体验真正的五彩斑斓。",
"价格": 1499.99
}`,
`{
"名称": "CineView 8K TV",
"类别": "电视和家庭影院系统",
"品牌": "CineView",
"型号": "CV-8K65",
"保修期": "2 years",
"评分": 4.9,
"特色": [
"65-inch display",
"8K resolution",
"HDR",
"Smart TV"
],
"描述": "通过这款惊艳的8K电视,体验未来。",
"价格": 2999.99
}`,
`{
"名称": "CineView 4K TV",
"类别": "电视和家庭影院系统",
"品牌": "CineView",
"型号": "CV-4K55",
"保修期": "2 years",
"评分": 4.8,
"特色": [
"55-inch display",
"4K resolution",
"HDR",
"Smart TV"
],
"描述": "一款色彩鲜艳、智能功能丰富的惊艳4K电视。",
"价格": 599.99
}`,
}
}
- service/chatbot/chatbot_service_test.go
package chatbot_service
import (
"context"
"github.com/stretchr/testify/require"
"llm_hub/conf"
"llm_hub/module/chatbot"
"testing"
"time"
)
func Test_chatBotServiceV1_Chat(t *testing.T) {
conf.Init()
chatbot.Init()
ctx := context.Background()
chatbotService := &chatBotServiceV1{}
// 模拟用户咨询
userConsults := []string{
"你们有哪些产品?",
"我可以咨询下现在天气怎么样?",
"电视相关的产品有哪些?",
"CineView OLED TV和SoundMax Home Theater有什么区别,介绍一下?",
}
for idx, consult := range userConsults {
output, err := chatbotService.Chat(ctx, consult)
require.Nil(t, err)
t.Logf("用户咨询%v:%v\n机器人回答:%v", idx+1, consult, output)
time.Sleep(time.Minute)
}
}
- 输出:
用户咨询1:你们有哪些产品?
机器人回答:我们有以下产品供您选择:
1. **SoundMax Soundbar**
- 类别:电视和家庭影院系统
- 品牌:SoundMax
- 型号:SM-SB50
- 保修期:1年
- 评分:4.3
- 特色:2.1声道、300W输出、无线低音炮、蓝牙
- 描述:这款时尚而功能强大的声音系统可升级您电视的音频体验。
- 价格:199.99
2. **SoundMax Home Theater**
- 类别:电视和家庭影院系统
- 品牌:SoundMax
- 型号:SM-HT100
- 保修期:1年
- 评分:4.4
- 特色:5.1声道、1000W输出、无线低音炮、蓝牙
- 描述:这款强大的家庭影院系统提供沉浸式音频体验。
- 价格:399.99
3. **CineView OLED TV**
- 类别:电视和家庭影院系统
- 品牌:CineView
- 型号:CV-OLED55
- 保修期:2年
- 评分:4.7
- 特色:55英寸显示屏、4K分辨率、HDR、智能电视
- 描述:这款OLED电视让您体验真正的五彩斑斓。
- 价格:1499.99
4. **CineView 8K TV**
- 类别:电视和家庭影院系统
- 品牌:CineView
- 型号:CV-8K65
- 保修期:2年
- 评分:4.9
- 特色:65英寸显示屏、8K分辨率、HDR、智能电视
- 描述:这款惊艳的8K电视让您体验未来。
- 价格:2999.99
5. **CineView 4K TV**
- 类别:电视和家庭影院系统
- 品牌:CineView
- 型号:CV-4K55
- 保修期:2年
- 评分:4.8
- 特色:55英寸显示屏、4K分辨率、HDR、智能电视
- 描述:这款色彩鲜艳、智能功能丰富的惊艳4K电视让您体验视觉盛宴。
- 价格:599.99
以上是我们目前提供的产品列表,希望能满足您的需求。
用户咨询2:我可以咨询下现在天气怎么样?
机器人回答:请咨询相关问题
用户咨询3:电视相关的产品有哪些?
机器人回答:根据您的咨询,这里有一些与电视相关的产品信息供您参考:
1. **SoundMax Soundbar**
- 类别: 电视和家庭影院系统
- 品牌: SoundMax
- 型号: SM-SB50
- 保修期: 1年
- 评分: 4.3
- 特色: 2.1声道、300W输出、无线低音炮、蓝牙
- 描述: 这款时尚而功能强大的声音设备可提升您电视的音频体验。
- 价格: 199.99美元
2. **SoundMax Home Theater**
- 类别: 电视和家庭影院系统
- 品牌: SoundMax
- 型号: SM-HT100
- 保修期: 1年
- 评分: 4.4
- 特色: 5.1声道、1000W输出、无线低音炮、蓝牙
- 描述: 这款强大的家庭影院系统可提供沉浸式音频体验。
- 价格: 399.99美元
3. **CineView OLED TV**
- 类别: 电视和家庭影院系统
- 品牌: CineView
- 型号: CV-OLED55
- 保修期: 2年
- 评分: 4.7
- 特色: 55英寸显示屏、4K分辨率、HDR、智能电视
- 描述: 这款OLED电视让您体验真正的五彩斑斓。
- 价格: 1499.99美元
4. **CineView 8K TV**
- 类别: 电视和家庭影院系统
- 品牌: CineView
- 型号: CV-8K65
- 保修期: 2年
- 评分: 4.9
- 特色: 65英寸显示屏、8K分辨率、HDR、智能电视
- 描述: 这款惊艳的8K电视让您体验未来。
- 价格: 2999.99美元
5. **CineView 4K TV**
- 类别: 电视和家庭影院系统
- 品牌: CineView
- 型号: CV-4K55
- 保修期: 2年
- 评分: 4.8
- 特色: 55英寸显示屏、4K分辨率、HDR、智能电视
- 描述: 这款色彩鲜艳、智能功能丰富的惊艳4K电视让您享受视听盛宴。
- 价格: 599.99美元
以上是一些与电视相关的产品信息,希望这些信息能帮助您根据自己的需求和预算选择适合的产品。如果您有任何疑问或需要进一步帮助,请随时告诉我。
用户咨询4:CineView OLED TV和SoundMax Home Theater有什么区别,介绍一下?
机器人回答:根据提供的商品信息,CineView OLED TV和SoundMax Home Theater有以下区别:
1. **品牌和产品类型**:
- CineView OLED TV是CineView品牌的电视产品,专注于高清晰度和色彩表现。
- SoundMax Home Theater是SoundMax品牌的家庭影院系统,致力于提供沉浸式音频体验。
2. **功能和特点**:
- CineView OLED TV是一款55英寸的OLED电视,支持4K分辨率、HDR技术和智能电视功能,旨在提供优质的视觉体验。
- SoundMax Home Theater采用5.1声道设计,具有1000W输出功率、无线低音炮和蓝牙连接,旨在提供沉浸式的音频体验。
3. **价格和保修**:
- CineView OLED TV售价为1499.99美元,保修期为2年。
- SoundMax Home Theater售价为399.99美元,保修期为1年。
综上所述,CineView OLED TV注重提供优质的视觉体验,而SoundMax Home Theater专注于带来更加沉浸式的音频体验。用户可根据个人需求和预算选择适合的产品。
3.3.检索增强生成
检索增强生成(RAG)是一种通过在检索对话所需相关上下文信息,并将其与Prompt结合,一起输入给大模型以得到更加准确回答结果的技术。上文中chatbot_service的retrievalProducts方法,其实就是一个Mock的RAG。
检索增强生成一般用于复杂的需要依赖上下文信息的推理需求中使用,比如这次商品问答,必须有商品相关的信息给到LLM,LLM才能根据这部分信息,给出准确的回答。
在Fewshot(少样本提示)中,也会需要使用检索增强生成:
检索增强生成的关键技术是数据向量化、向量数据存储、向量数据检索召回:
后续有时间会发一篇检索增强生成系统的详细设计。
3.4.系统评估体系
这次拆分了三个角色来对问答系统进行支持,但如果后续Prompt语句需要调整,要如何验证是否能回答得更好,以及对比之前的回答表现呢?
这就涉及到系统评估系统的建设,一般来说每个LLM角色都需要建立对应的验证集,去验证每次Prompt调整后在验证集中的表现。
对于简单的LLM评估,如前置校验角色,我们可以通过拟定好的Y/N来计算评估得分,但对于复杂的LLM评估,如推理回答角色,其给出的回答并没有完全准确的“Y/N”,所以要如何来评估新的回答是否更好?现在流行的一种思路是通过另一个“评估专家LLM角色”,来对新旧回答做一次对比,由它来决定哪个回答更好。