【Langchain多Agent实践】一个有推销功能的旅游聊天机器人

【Langchain+Streamlit】旅游聊天机器人_langchain streamlit-CSDN博客

视频讲解地址:【Langchain Agent】带推销功能的旅游聊天机器人_哔哩哔哩_bilibili

体验地址:   http://101.33.225.241:8503/

github地址:GitHub - jerry1900/langchain_chatbot: langchain+streamlit打造的一个有memory的旅游聊天机器人,可以和你聊旅游相关的事儿

 

        之前,我们介绍如何打算一款简单的旅游聊天机器人,而且之前我们介绍了SalesGPT,我们看能不能把这两个东西结合起来,让我们的旅游聊天机器人具有推销产品的功能。我们先来看看效果:

        首先,你可以和机器人闲聊关于旅游的事儿(如果你问的问题和旅游无关的话,会提示你只回答旅游问题) ;其次,当你连续询问有关同一个地点时(比如北京),机器人会检查自己的本地知识库,看看产品库里有没有相关的旅游产品,如果有的话就推荐给客户,如果没有就输出一个空的字符串,用户是没有感知的,我们来看一下是如何实现的。

1. 项目结构

        我们是在原来项目基础上逐步叠加的,主要增加了一个agent.py、my_tools.py、stages.py等文件。我们这次的项目是使用poetry来管理和运行的:

        项目结构如图:

        我们新加了一个产品文件用于存储旅游产品,下面是三个产品中的一个:

产品名称:北京一日游
产品价格:299元
产品内容:{
北京作为中国的首都和历史文化名城,有许多令人着迷的景点和活动。以下是一个充满活力和文化的北京一日游的建议:
早上:天安门广场: 开始您的一日游,您可以前往天安门广场,这是世界上最大的城市广场之一,也是中国的政治中心。您可以欣赏到天安门城楼,参观升国旗仪式(早上升旗时间)。
故宫博物院: 天安门广场北侧就是故宫,这是中国最大、最完整的古代皇家宫殿建筑群。在这里,您可以领略中国古代皇家建筑的壮丽和深厚的历史文化。
中午:午餐: 您可以选择在附近的餐馆品尝地道的北京菜,比如炸酱面、北京烤鸭等。
下午:颐和园: 中午过后,您可以前往颐和园,这是中国最大的皇家园林之一,也是清代的皇家园林。园内有美丽的湖泊、精致的建筑和独特的风景。
什刹海: 在下午的最后时段,您可以前往什刹海地区,这里是一个古老而又时尚的区域,有着许多酒吧、咖啡馆和特色小店。您可以漫步在湖边,欣赏夕阳下的美景,体验北京的悠闲氛围。
晚上:王府井步行街: 晚上,您可以前往王府井步行街,这是北京最繁华的购物街之一,有着各种商店、餐馆和娱乐场所。您可以尝试美食、购物或者观赏街头表演。
京剧表演: 如果时间允许,您还可以观看一场京剧表演,京剧是中国传统戏曲的代表之一,有着悠久的历史和独特的艺术魅力。
}

  2. chat.py的改动,新增了欢迎词,添加了Agent构造的方法

        这里构造一个专门负责提示词的Agent(其实就是一个LLMChain),并构造一个负责会话和判断功能的ConversationAgent,让这个agent初始化并构造一个负责判断阶段的内部agent,我们把他们都要放到session里:

#需要国内openai开发账号的请联系微信 15652965525if "welcome_word" not in st.session_state:st.session_state.welcome_word = welcome_agent()st.session_state.messages.append({'role': 'assistant', 'content': st.session_state.welcome_word['text']})st.session_state.agent = ConversationAgent()st.session_state.agent.seed_agent()st.session_state.agent.generate_stage_analyzer(verbose=True)

        在用户输入后的每一步,先进行一下阶段判断,然后调用agent的human_step方法,再调用agent的step()方法,完成一轮对话:

  

st.session_state.agent.determine_conversation_stage(prompt)st.session_state.agent.human_step(prompt)response = st.session_state.agent.step()

   

3. welcome_agent

        这个比较简单,就是一个咱们学习过无数遍的一个简单的chain:

def welcome_agent():llm = OpenAI(temperature=0.6,# openai_api_key=os.getenv("OPENAI_API_KEY"),openai_api_key=st.secrets['api']['key'],# base_url=os.getenv("OPENAI_BASE_URL")base_url=st.secrets['api']['base_url'])prompt = PromptTemplate.from_template(WELCOME_TEMPLATE)chain = LLMChain(llm=llm,prompt=prompt,verbose=True,)response = chain.invoke("简短的欢迎词")return response

        这里我们希望每次调用它的时候,可以得到一些不一样的、有创意的欢迎词,所以我们的temperature调的比较高,这样它可能生成一些有创意的欢迎词。

4. ConversationAgent类

        这个类是我们的核心类,里面有很多属性和方法,我们用python的dir()方法来看一下它里面的结构:

from agent import ConversationAgentagent = ConversationAgent()
print(dir(ConversationAgent))

        里面以_开头的是Object基本类自带的属性和方法,其他的是我们构造的属性和方法:

['__annotations__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_respond_without_tools', 'conversation_agent_with_tool', 'conversation_agent_without_tool', 'conversation_history', 'conversation_stage_id', 'current_conversation_stage', 'determine_conversation_stage', 'fake_step', 'generate_stage_analyzer', 'get_tools', 'human_step', 'llm', 'recommend_product', 'retrieve_conversation_stage', 'seed_agent', 'show_chat_history', 'stage_analyzer_chain', 'step']

        我们先来看类的属性和一些简单的方法,注意我们这里构造了一个llm,之后下面有很多方法会用到这个llm:

class ConversationAgent():stage_analyzer_chain: StageAnalyzerChain = Field(...)conversation_agent_without_tool = Field()conversation_agent_with_tool = Field()conversation_history = []conversation_stage_id: str = "1"current_conversation_stage: str = CONVERSATION_STAGES.get("1")llm = OpenAI(temperature=0,openai_api_key=st.secrets['api']['key'],base_url=st.secrets['api']['base_url'])def seed_agent(self):self.conversation_history.clear()print("——Seed Successful——")def show_chat_history(self):return self.conversation_historydef retrieve_conversation_stage(self, key):return CONVERSATION_STAGES.get(key)

        我们继续来看:

    def fake_step(self):input_text = self.conversation_history[-1]ai_message = self._respond_with_tools(str(input_text), verbose=True)print(ai_message,type(ai_message['output']))def step(self):input_text = self.conversation_history[-1]print(str(input_text)+'input_text****')if int(self.conversation_stage_id) == 1:ai_message = self._respond_without_tools(str(input_text),verbose=True)else:chat_message = self._respond_without_tools(str(input_text), verbose=True)recommend_message = self.recommend_product()print(recommend_message,len(recommend_message))if len(recommend_message)<=5:ai_message = chat_messageelse:ai_message = chat_message + '\n\n' + recommend_message# output_dic = self._respond_with_tools(str(input_text),verbose=True)# ai_message = str(output_dic['output'])print(ai_message,type(ai_message))ai_message = "AI:"+str(ai_message)self.conversation_history.append(ai_message)# print(f"——系统返回消息'{ai_message}',并添加到history里——")return ai_message.lstrip('AI:')

        fake_step是一个模拟输出的方法,不用管,测试的时候用;step方法是接收用户的输入,从聊天记录里取出来(input_text = self.conversation_history[-1]) ,然后再根据不同的对话阶段进行不同的逻辑,如果是第二个阶段推销阶段,那么就调用recommend_product方法去生成一个推销产品的信息,并把两条信息拼接起来。

        

    def human_step(self,input_text):human_message = input_texthuman_message = "用户: " + human_messageself.conversation_history.append(human_message)# print(f"——用户输入消息'{human_message}',并添加到history里——")return human_message

        human_step方法比较简单,就是把用户的输入挂到conversation_history聊天记录里。然后是构造阶段判断的agent和阶段判断的方法,这些都是模仿SalesGPT里的,做了一些调整:

    def generate_stage_analyzer(self,verbose: bool = False):self.stage_analyzer_chain = StageAnalyzerChain.from_llm(llm=self.llm,verbose=verbose)print("成功构造一个StageAnalyzerChain")def determine_conversation_stage(self,question):self.question = questionprint('-----进入阶段判断方法-----')self.conversation_stage_id = self.stage_analyzer_chain.run(conversation_history=self.conversation_history,question=self.question)print(f"Conversation Stage ID: {self.conversation_stage_id}")print(type(self.conversation_stage_id))self.current_conversation_stage = self.retrieve_conversation_stage(self.conversation_stage_id)print(f"Conversation Stage: {self.current_conversation_stage}")

         然后是_respond_without_tools这么一个内部的方法,它在step里被调用:

    def _respond_without_tools(self,input_text,verbose: bool = False):self.conversation_agent_without_tool = ConversationChain_Without_Tool.from_llm(llm=self.llm,verbose=verbose)response = self.conversation_agent_without_tool.run(question = input_text,conversation_history=self.conversation_history,)return response

        最后是get_tools方法和recommend_product方法,这里也都是模仿了SalesGPT里的写法:

    def get_tools(self):file_path = r'C:\Users\Administrator\langchain_chatbot\product.txt'knowledge_base = build_knowledge_base(file_path)tools = get_tools(knowledge_base)return toolsdef recommend_product(self,verbose =True):tools = self.get_tools()prompt = CustomPromptTemplateForTools(template=RECOMMEND_TEMPLATE,tools_getter=lambda x: tools,# This omits the `agent_scratchpad`, `tools`, and `tool_names` variables because those are generated dynamically# This includes the `intermediate_steps` variable because that is neededinput_variables=["intermediate_steps",  # 这是在调用tools时,会产生的中间变量,是一个list里面的一个tuple,一个是action,一个是observation"conversation_history",],)llm_chain = LLMChain(llm=self.llm, prompt=prompt, verbose=verbose)tool_names = [tool.name for tool in tools]# WARNING: this output parser is NOT reliable yet## It makes assumptions about output from LLM which can break and throw an erroroutput_parser = SalesConvoOutputParser()recommend_agent = LLMSingleActionAgent(llm_chain=llm_chain,output_parser=output_parser,stop=["\nObservation:"],allowed_tools=tool_names,)sales_agent_executor = AgentExecutor.from_agent_and_tools(agent=recommend_agent, tools=tools, verbose=verbose, max_iterations=5)inputs = {"conversation_history": "\n".join(self.conversation_history),}response = sales_agent_executor.invoke(inputs)return str(response['output'])

 5. chain.py

        chain这里有三个类,差异在于使用模板的不同还有部分传参的不同,这里写的有点冗余了,大家可以自己优化一下:

from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from template import STAGE_ANALYZER_INCEPTION_PROMPT,BASIC_TEMPLATE,RECOMMEND_TEMPLATEclass StageAnalyzerChain(LLMChain):"""通过查看聊天记录判断是否要转向推荐和销售阶段."""@classmethoddef from_llm(cls, llm, verbose: bool = True) -> LLMChain:"""Get the response parser."""stage_analyzer_inception_prompt_template = STAGE_ANALYZER_INCEPTION_PROMPTprompt = PromptTemplate(template=stage_analyzer_inception_prompt_template,input_variables=["conversation_history","question"],)return cls(prompt=prompt, llm=llm, verbose=verbose)class ConversationChain_Without_Tool(LLMChain):#当用户没有明确的感兴趣话题时,用这个chain和用户闲聊@classmethoddef from_llm(cls, llm, verbose: bool = True) -> LLMChain:"""Get the response parser."""conversation_without_tools_template = BASIC_TEMPLATEprompt = PromptTemplate(template=conversation_without_tools_template,input_variables=["conversation_history",],)return cls(prompt=prompt, llm=llm, verbose=verbose)class Recommend_Product(LLMChain):#当用户有明确的感兴趣话题时,用这个chain查询产品库,看是否命中,如果命中则生成一个产品推荐信息@classmethoddef from_llm(cls, llm, verbose: bool = True) -> LLMChain:"""Get the response parser."""conversation_without_tools_template = RECOMMEND_TEMPLATEprompt = PromptTemplate(template=conversation_without_tools_template,input_variables=["conversation_history",],)return cls(prompt=prompt, llm=llm, verbose=verbose)

6. my_tools.py

       这个文件里有有很多,是我把SalesGPT里的一些文件改写拿过来用的,有一些根据实际项目需要进行的微调:

import re
from typing import Unionfrom langchain.agents import Tool
from langchain.chains import RetrievalQA
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from typing import Callable
from langchain.prompts.base import StringPromptTemplate
from langchain.agents.agent import AgentOutputParser
from langchain.agents.conversational.prompt import FORMAT_INSTRUCTIONS
from langchain.schema import AgentAction, AgentFinish  # OutputParserExceptiondef build_knowledge_base(filepath):with open(filepath, "r", encoding='utf-8') as f:product_catalog = f.read()text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=10)texts = text_splitter.split_text(product_catalog)llm = ChatOpenAI(temperature=0)embeddings = OpenAIEmbeddings()docsearch = Chroma.from_texts(texts, embeddings, collection_name="product-knowledge-base")knowledge_base = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=docsearch.as_retriever())return knowledge_basedef get_tools(knowledge_base):# we only use one tool for now, but this is highly extensible!tools = [Tool(name="ProductSearch",func=knowledge_base.invoke,description="查询产品库,输入应该是'请介绍一下**的旅游产品'",)]print('tools构造正常')return toolsclass CustomPromptTemplateForTools(StringPromptTemplate):# The template to usetemplate: strtools_getter: Callabledef format(self, **kwargs) -> str:# Get the intermediate steps (AgentAction, Observation tuples)# Format them in a particular wayintermediate_steps = kwargs.pop("intermediate_steps")thoughts = ""for action, observation in intermediate_steps:thoughts += action.logthoughts += f"\nObservation: {observation}\nThought: "# Set the agent_scratchpad variable to that valueprint('——thoughts——:'+thoughts+'\n End of ——thoughts——')kwargs["agent_scratchpad"] = thoughtstools = self.tools_getter([])# Create a tools variable from the list of tools providedkwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in tools])# Create a list of tool names for the tools providedkwargs["tool_names"] = ", ".join([tool.name for tool in tools])print('prompt构造正常')return self.template.format(**kwargs)class SalesConvoOutputParser(AgentOutputParser):ai_prefix: str = "AI"  # change for salesperson_nameverbose: bool = Truedef get_format_instructions(self) -> str:return FORMAT_INSTRUCTIONSdef parse(self, text: str) -> Union[AgentAction, AgentFinish]:if self.verbose:print("TEXT")print(text)print("-------")if f"{self.ai_prefix}:" in text:if "Do I get the answer?YES." in text:print('判断Agent是否查到结果,yes')return AgentFinish({"output": text.split(f"{self.ai_prefix}:")[-1].strip()}, text)else:print('判断Agent是否查到结果,no')return AgentFinish({"output": {}}, text)regex = r"Action: (.*?)[\n]*Action Input: (.*)"match = re.search(regex, text)if not match:## TODO - this is not entirely reliable, sometimes results in an error.return AgentFinish({"output": "I apologize, I was unable to find the answer to your question. Is there anything else I can help with?"},text,)# raise OutputParserException(f"Could not parse LLM output: `{text}`")action = match.group(1)action_input = match.group(2)print('output_paserser构造正常')return AgentAction(action.strip(), action_input.strip(" ").strip('"'), text)@propertydef _type(self) -> str:return "sales-agent"

7. 结束语

        整个项目就是把之前的两个项目进行了一个组合拼装,在这个过程中可以更好地理解Sales

GPT这个项目,以及多Agent是怎么运行的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/262839.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSDN原力值怎么提升?

文章目录 前言一、原力值怎么看二、提升原力值的方法1.原力值↑2.原力值↓提示!!!禁止在csdn网站内进行违规行为!!! 结束语 前言 在前面一篇文章中&#xff0c;我讲了付费收看的条件&#xff0c;有需要的先把网址收藏起来&#xff01; https://blog.csdn.net/m0_69481332/arti…

C语言中关于#include的一些小知识

写代码的过程中&#xff0c;因为手误&#xff0c;重复包含了头文件 可以看到没有报错 如果是你自己编写的头文件&#xff0c;那么如果没加唯一包含标识的话&#xff0c;那么编译器会编译报错的。如果是系统自带的头文件&#xff0c;由于其每个头文件都加了特殊标识&#xff0c…

洛谷P5738 歌唱比赛 题解

#题外话&#xff08;第37篇题解&#xff09;&#xff08;本题为普及-难度&#xff09; #先看题目 题目链接https://www.luogu.com.cn/problem/P5738 #思路&#xff08;好像和P5726-打分有点像&#xff0c;参考一下&#xff09; #代码 #include <bits/stdc.h> using na…

Docusaurus框架——快速搭建markdown文档站点介绍sora

文章目录 ⭐前言⭐初始化项目&#x1f496; 创建项目&#xff08;react-js&#xff09;&#x1f496; 运行项目&#x1f496; 目录文件&#x1f496; 创建一个jsx页面&#x1f496; 创建一个md文档&#x1f496; 创建一个介绍sora的文档 ⭐总结⭐结束 ⭐前言 大家好&#xff0…

Conmi的正确答案——将JAVA中maven的.m2文件夹放到D盘

系统&#xff1a;WIN11 1、将.m2文件夹移动到D盘 移动后&#xff1a; 2、创建目录链接 mklink /j "C:\Users\Administrator\.m2" "D:\.m2"至此&#xff0c;maven默认的jar包会加载到D盘的.m2文件夹

安卓adb调试备忘录

由于 MAC 的 USB 口全被占用着&#xff0c;采用无线连接刚方便&#xff0c;记录一下&#xff0c;以防忘记~ ADB原理 adb devices -l ## 列出连接的设备adb tcpip [端口号] adb tcpip 6666 # 将当前已连接USB上的Mobile端切换为TCP/IP模式&#xff0c;以6666端口进行监听. adb…

Android 开发一个耳返程序(录音,实时播放)

本文目录 点击直达 Android 开发一个耳返程序程序编写1. 配置 AndroidManifast.xml2.编写耳返管理器3. 录音权限申请4. 使用注意 最后我还有一句话要说怕相思&#xff0c;已相思&#xff0c;轮到相思没处辞&#xff0c;眉间露一丝 Android 开发一个耳返程序 耳返程序是声音录入…

java面试设计模式篇

面试专题-设计模式 前言 在平时的开发中&#xff0c;涉及到设计模式的有两块内容&#xff0c;第一个是我们平时使用的框架&#xff08;比如spring、mybatis等&#xff09;&#xff0c;第二个是我们自己开发业务使用的设计模式。 面试官一般比较关心的是你在开发过程中&#…

操作系统(1)——学习导论(Ⅰ)

目录 小程一言专栏链接: [link](http://t.csdnimg.cn/6grrU) 学习导论什么是操作系统主要功能强调 操作系统历史硬件层面处理器重要特点and功能 存储器磁盘I/O设备小程常用的I/O设备及其特点 小程一言 本操作系统专栏&#xff0c;是小程在学操作系统的过程中的第一步&#xff…

算法题目中图和树的存储

邻接表的方式存储图和树 这就是邻接表&#xff0c;就是将每个结点的孩子结点用链表表示出来&#xff0c;再将所有结点以数组形式连起来。 存储树和图我们需要三个数组&#xff0c;h[N], e[N], ne[N],分别表示邻接表&#xff0c;结点值&#xff0c;结点的next值&#xff0c;h[i…

Excel 面试题及答案(2)

一、VLOOKUP+IF案例: A1 :根据左侧数据源,按姓名匹配《职级》,仅限用函数,不能做任何辅助A2 :根据左侧数据源,按姓名匹配《部门》,仅限用函数,不能做任何辅助A3 :根据右侧考核规则,匹配《绩效比例》,用函数完成(可适当做辅助的单元格区域) =VLOOKUP(F8,IF({1,0},…

CVE-2023-44313 Apache ServiceComb Service-Center SSRF 漏洞研究

本次项目基于go语言&#xff08;本人不精通&#xff09;&#xff0c;虽不是java web框架了 &#xff0c;但搭建web服务的框架一些思想理念却是通用的&#xff0c;我们由此可以得到一些蛛丝马迹....... 目录 漏洞简介 漏洞分析 漏洞复现 漏洞简介 Apache ServiceComb Servi…

C++的stack容器->基本概念、常见接口

#include<iostream> using namespace std; #include <stack> //栈stack容器常用接口 void test01() { //创建栈容器 栈容器必须符合先进后出 stack<int> s; //向栈中添加元素&#xff0c;叫做 压栈 入栈 s.push(10); s.push(20); s…

第十二天-ppt的操作

目录 创建ppt文档 安装 使用 段落的使用 段落添加数据 段落中定义多个段落 自定义段落 ppt插入表表格 PPT插入图片 读取ppt 读取ppt整体对象 ​编辑 获取ppt文本 获取表格内容 创建ppt文档 安装 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple python…

机器学习——线性回归算法、代价函数、梯度下降算法基础

线性回归 还是以之前的预测房价为例&#xff0c;根据不同尺寸的房子对应不同的售价组成的数据集画图&#xff0c;图如下 监督学习算法工作流程 假设函数其实就是我们所说的函数&#xff0c;在房价这个例子中&#xff0c;我们可以从上图中看出房价和房子面积是一个一元的线性函…

爬取数位观察城市数据知识总结

# 抓取数位观察中城市的GDP,公交车数量&#xff0c;户籍人口 # url "https://www.swguancha.com/home/query-city-page" # 1.找数据 # 1.1如果数据在页面源代码里&#xff0c;则访问&#xff0c;在本案例中并没有在源代码中 # 1.2如果数据不在页面源代码里&#xff…

QT3作业

1 2. 使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数&#xff0c;将登录按钮使用t5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#…

docker自定义网络实现容器之间的通信

Background docker原理 docker是一个Client-Server结构的系统&#xff0c;Docker的守护进程运行在主机上。通过Socket从客户端访问。docker核心三大组件&#xff1a;image–镜像、container-容器、 repository-仓库。docker使用的cpu、内存以及系统内核等资源都是直接使用宿主…

Matlab/simulink光伏发电的扰动观察法MPPT仿真(持续更新)

1.光伏发电的电导增量法MPPT仿真 2.光伏发电的恒定电压法MPPT仿真 3.光伏发电的扰动观察法MPPT仿真 4.光伏发电的占空比法MPPT仿真 5.基于神经网络的MPPT光伏发电仿真 6. 基于模糊控制的MPPT光伏发电仿真 7. 基于粒子群算法&#xff08;PSO&#xff09;的500w光伏系统MPPT控…

【自然语言处理-二-attention注意力 是什么】

自然语言处理二-attention 注意力机制 自然语言处理二-attention 注意力记忆能力回顾下RNN&#xff08;也包括LSTM GRU&#xff09;解决memory问题改进后基于attention注意力的modelmatch操作softmax操作softmax值与hidder layer的值做weight sum 计算和将计算出来的和作为memo…