老喻干货店的聊天robot

上一个实战项目我们为网销同事做了一个微博Agent,方便了他们快速拓展营销链。欢迎大家点赞,评论区交流,一起快乐学习AI。

前言

我们将为老喻干货店的市场、运营和客服同事,以及我们的客户开发一款Chat Robot

项目需求

Chatgpt是大家最熟悉的聊天机器人,我们学习LangChain,学习AI, 很多场景下就是为开发出更好的,更适合我们业务的Chat Robot

我们先来看下Chat Robot的架构图,方便我们理解整个流程

  • 大模型(Chat)

LangChain有相关聊天模型的列表,大模型有很多家,也有适合不同需求的模型,这里我们选择ChatOpenAI这样的大模型。

  • 提示词模板

提示词是AI应用的重要组成部分,Prompt Template我个人把它看成mvc中的model层。 传统的model层和数据库打交道,prompt和大模型打交道。Prompt Template的设计工作非常重要,等同于model层的重要性。

  • Memory

大模型每次交流,如同HTTP一样,都是无状态的。怎么样让大模型理解与我们之间的聊天上下文呢?LangChain提供了Memory模块,让我们更好的缓存聊天历史,大家可以查看# LangChain 七 Memory, 讲的挺细的。

  • 检索器

可以用LangChain,也可以用llamaindex,帮助我们以读取文件等方式,将专有知识库带给大模型, 即RAG。

所以,chat robot在聊天模型、提示词这些公共功能外,记忆和检索模块是它的核心。让我们一起来探索吧。

功能点分析

我们将先开发一个具备聊天功能的robot, 再添加RAG(老喻干货店知识库),使用敏捷开发原则,慢慢叠加功能。AI发展异常迅速,敏捷开发是明智之选。

  • LangChain的ConversationChain, 可以快速启动聊天。
  • Memory,聊天上下文,记住用户之前说的话。
  • LangChain 读取老喻干货店企业资料库,增强LLM能力
  • LangChain SQLChain, textToSql 查询订单状态、物流等
  • 部署chat robot

ConversationChain 敏捷聊天

python 复制代码
# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = 'AIzaSyDlloHNsc634fPkVmmnqaTK_8z-c7J8-wY'

# 系统消息和用户消息schema
from langchain.schema import (
    HumanMessage,
    SystemMessage
)
# ChatOpenAI openai 聊天的封装
from langchain.chat_models import ChatOpenAI

# 实例化聊天模型
chat = ChatOpenAI()

# 创建消息列表,以后交给memory 
messages = [
    SystemMessage(content="你是一个干货行家。"),
    HumanMessage(content="家人血糖有点高,过年怎么吃既有年味又健康?")
]

# 查看反馈
response = chat(messages)
print(response)

LLM返回结果是:

ini 复制代码
content='作为一个干货行家,我可以为您提供一些建议,让您的过年既有年味又健康。\n\n1. 合理控制糖分摄入量:选择低糖或无糖的食物和饮品,避免过多摄入高糖食品,如糖果、甜点和糕点等。\n\n2. 增加蔬果摄入:多食用新鲜的蔬菜和水果,它们富含纤维和维生素,有助于控制血糖水平。\n\n3. 控制淀粉类食物摄入:选择全谷物食品,如糙米、全麦面包等,避免过度摄入米饭、面条等高淀粉食物。\n\n4. 控制油脂摄入:选择低脂或无脂的食品,避免过多摄入油炸食品和油腻的肉类。\n\n5. 控制食物的烹饪方式:尽量选择清蒸、煮、炖等健康的烹饪方式,避免过多使用油炸或煎炒。\n\n6. 适量摄入蛋白质:选择瘦肉、禽肉、鱼类和豆类等富含蛋白质的食物,有助于维持饱腹感和血糖稳定。\n\n7. 合理安排餐食时间:保持规律的餐食时间,避免过度饥饿或暴饮暴食,有助于控制血糖水平。\n\n8. 注意饮食搭配:合理搭配各种食物,避免单一摄入某一种食物,如同时搭配蛋白质、蔬菜和粗粮等。\n\n最重要的是,与医生或营养师咨询,根据家人的具体情况制定适合的饮食计划。这样可以确保过年期间既能享受美食,又能保护健康。' additional_kwargs={} example=False

上面的代码稍作修改,就可以让用户持续提问:

python 复制代码
# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = 'Your OpenAI Key'

# 系统消息和用户消息schema
from langchain.schema import HumanMessage, SystemMessage
from langchain.chat_models import ChatOpenAI

# 定义一个命令行聊天机器人的类
class CommandlineChatbot:
    # 在初始化时,设置干货行家的角色并初始化聊天模型
    def __init__(self):
        self.chat = ChatOpenAI()
        self.messages = [SystemMessage(content="你是一个花卉行家。")]

    # 定义一个循环来持续与用户交互
    def chat_loop(self):
        print("Chatbot 已启动! 输入'exit'来退出程序。")
        while True:
            user_input = input("你: ")
            # 如果用户输入"exit",则退出循环
            if user_input.lower() == 'exit':
                print("再见!")
                break
            # 将用户的输入添加到消息列表中,并获取机器人的响应
            self.messages.append(HumanMessage(content=user_input))
            response = self.chat(self.messages)
            print(f"Chatbot: {response.content}")

# 如果直接运行这个脚本,启动聊天机器人
if __name__ == "__main__":
    bot = CommandlineChatbot()
    bot.chat_loop()

我们编写了一个CommandlineChatbot类,让用户可以在命令行一直与LLM交互。当用户输入exit,则退出程序。

Memory出场

上面的代码基于敏捷开发思想,专注于chat的快速实现,已完成聊天功能。虽然self.messages.append(...)会将用户的输入不断的加入数组,并发送给LLM,但是LLM的返回也是聊天上下文的重要组成部分,需要再之后传给LLM才能拥有完整上下文。再是聊天次数一多,会到达tokens上限... 所以,接下来Memory出场,专门搞定记忆功能。

ini 复制代码
...
from langchain.memory import ConversationBufferMemory
from langchain.prompts import (
    ChatPromptTemplate,
    MessagesPlaceholder,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
from langhain.chains import LLMChain
....

class ChatbotWithMemory:
    def __init__(self):
        self.llm = ChatOpenAI()
        self.prompt = ChatPromptTemplate(
            messages = [
                SystemMessagePromptTemplate.from_template(
                "你是一个干货行家。你通常的回答不超过30字。"
                ),
                MessagesPlaceHolder(variable_name="chat_history"),
                HuamnMessagePromptTemplate.from_template("{question}")
            ]
        )
        
        # 初始化Memory
        self.memory = ConversationBufferMemory(memory_key="chat_history",
        return_messages=True
        )
        self.conversation=LLMChain(
            llm = self.llm,
            prompt=self.prompt,
            verbose=True,
            memory=self.memory
        )
        
   def chat_loop(self):
       ....
       response = self.conversation({"question": user_input})
       ....
        

...省略的代码代表使用之前的代码就好。我们使用了LLMChain链式搭建聊天功能。Chain 是LangChain的特色,它将各个组件组合起来,让代码模块化更好,更好懂。ConversationBufferMemory实例化的memory对象,key 设置为chat_history,我们的messages数组由三部分组成:第一条SystemMessage是不变的,中间一条用MessagesPlaceHolder拿对应的chat_history的memory对象的内容输出,最后一条是最新的对话... ChatPromptTemplate是这块聊天三明治的面包店师傅,但愿你和我一样喜欢吃三明治,已经理解,阿里嘎多...

检索机制

在完成了聊天和记忆功能后,我们来将老喻干货店的一些pdf、word等内容,交给LangChain实现RAG机制。

ini 复制代码
...
# 文本分割器,Embedding前需要准备好
import langchain.text_splitter import RecursiveCharacterTextSplitter
# 引入Embedding模块,向量检索
from langchain.embeddings import OpenAIEmbeddings
# 使用Qdrant向量数据库
from langchain.vectorstores import Qdrant
# 检索chain
from langchain.chains import ConversationRetrievalChain
from langchain.document_loaders import PyPDFLoader
from langchain.document_loaders import Docx2txtLoader
from langchain.document_loaders import TextLoader

...

class ChatbotWithRetrieval:
    def __init__(self, dir):
        base_dir = dir
        documents = []
        for file in os.listdir(base_dir):
            file_path = os.path.join(base_dir, file)
            if (file.endswith('.pdf')):
                loader = PyPDFLoader(file_path)
                documents.extend(loader.load())
            elif file.endswith('.docx') or file.endswith('.doc'):
                loader = Docx2txtLoader(file_path)
                documents.extend(loader.load())
            elif file.endswith('.txt'):
                loader=TextLoader(file_path)
                documents.extend(loader.load())
                 text_splitter=RecursiveCharacterTextSplitter(chunk_size=200,chunk_overlap=0)
         all_splits = text_splitter.split_documents(documents)
         
         # 向量数据库
         self.vectorstore=Qdrant.drom_documents(
             documents=all_splits,
             embedding=OpenAIEmbeddings(),
             location=":memory:",
             collection_name="my_documents"
         )
         
         self.llm = ChatOpenAI()
         
         self.memory = ConversationSummaryMemory(
             llm = self.llm,
             memory_key="chat_history",
             return_messages=True
         )
         
         retriever=self.vectorstore.as_retriever()
         self.qa = ConversationRetrievalChain.from_llm(
             self.llm,
             retriever=retriever,
             memory=self.memory
         )
   

经过文档加载、文本分割、文档向量化以及检索功能,robot除了常规的聊天功能,还能够检索存储在指定目录中的文档,并基于这些文档提供答案。一个专家知识库RAG应用就可以上线了....

总结

  • 敏捷开发,focus 唯一功能,快速完成,持续集成....
  • chain 让记忆、检索优雅上线

参考资料

  • 黄佳老师的LangChain课
相关推荐
canonical_entropy16 分钟前
可逆计算:一场软件构造的世界观革命
后端·aigc·ai编程
东风西巷29 分钟前
Balabolka:免费高效的文字转语音软件
前端·人工智能·学习·语音识别·软件需求
堆栈future39 分钟前
我的个人网站上线了,AI再一次让我站起来了
程序员·llm·aigc
非门由也39 分钟前
《sklearn机器学习——管道和复合估计器》联合特征(FeatureUnion)
人工智能·机器学习·sklearn
l12345sy39 分钟前
Day21_【机器学习—决策树(1)—信息增益、信息增益率、基尼系数】
人工智能·决策树·机器学习·信息增益·信息增益率·基尼指数
非门由也40 分钟前
《sklearn机器学习——管道和复合估算器》异构数据的列转换器
人工智能·机器学习·sklearn
计算机毕业设计指导1 小时前
基于ResNet50的智能垃圾分类系统
人工智能·分类·数据挖掘
飞哥数智坊1 小时前
终端里用 Claude Code 太难受?我把它接进 TRAE,真香!
人工智能·claude·trae
小王爱学人工智能1 小时前
OpenCV的阈值处理
人工智能·opencv·计算机视觉
新智元2 小时前
刚刚,光刻机巨头 ASML 杀入 AI!豪掷 15 亿押注「欧版 OpenAI」,成最大股东
人工智能·openai