【Langchain Agent研究】SalesGPT项目介绍(四)-CSDN博客        

        上节课,我们分析了一下salesGPT项目里源代码的一些问题,重新写了一个运行方法,换了一个模型并修改了一些源代码开始把项目跑起来了,我们已经可以通过console和模型进行对话了。

        我们之前选的模式是不使用工具的模式,所以我们启用的是sales_conversation_utterance_chain,这节课我们尝试使用工具,也就是之前我们介绍过的sales_agent_executor和用setup_knowledge_base()方法构造的knowledge_base。

1. 运行使用工具的Agent

        我们重新写一个my_test_with_tools.py的方法,其他的地方一样,注意在构造sales_agent的时候,use_tools=True,product_catalog要改一下,用你项目本地的地址:

sales_agent = SalesGPT.from_llm(
            llm,
            verbose=True,
            use_tools=True,
            product_catalog=r"C:\Users\Administrator\SalesGPT\examples\sample_product_catalog.txt",
            salesperson_name="Ted Lasso",
            salesperson_role="Sales Representative",
            company_name="Sleep Haven",
            company_business="""Sleep Haven 
                                    is a premium mattress company that provides
                                    customers with the most comfortable and
                                    supportive sleeping experience possible. 
                                    We offer a range of high-quality mattresses,
                                    pillows, and bedding accessories 
                                    that are designed to meet the unique 
                                    needs of our customers.""",
        )

        我们运行一下这块代码,发现在我们询问产品价格后,Agent开始思考是否要使用工具(就是我们之前构造的另外一个Agent——knowledge_base,它是一个RetrievalQA类型的Chain,也可以认为是一个Agent),下面是它的思考过程,我直接用prompt模板里面的内容了:

        这里能看到我和机器人对话的聊天记录(我使用的是中文询问),也能看到当我询问产品和价格后,我们启用了 sales_agent_executor,它开始思考是否使用它的工具——另外一个Agent进行QA查询。它的决定是YES,启用knowledge_base去查询价格。

        我们在【Langchain Agent研究】SalesGPT项目介绍(三)-CSDN博客中留下了两个遗留问题,我们在能运行项目之后再来看一下这里面的东西就清晰了:

2. CustomPromptTemplateForTools

        首先在调用CustomPromptTemplateForTools时,有三个地方需要注意,第一个是tools_getter=lambda x: tools。

prompt = CustomPromptTemplateForTools(
                template=SALES_AGENT_TOOLS_PROMPT,
                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 needed
                input_variables=[
                    "input",
                    "intermediate_steps",  #这是在调用tools时,会产生的中间变量,是一个list里面的一个tuple,一个是action,一个是observation
                    "salesperson_name",
                    "salesperson_role",
                    "company_name",
                    "company_business",
                    "company_values",
                    "conversation_purpose",
                    "conversation_type",
                    "conversation_history",
                ],
            )

        这一行的意思是,tools_getter这个lambda函数接收任意一个参数,返回tools。所以在后面的代码里,我改变了原来代码的输入,这完全不影响后续的结果:

  # tools = self.tools_getter(kwargs["input"])
  tools = self.tools_getter([])

         第二个是input_variables里的input是没有值的,这一点困惑了我很久,后来我才发现其实这里根本不需要input应该也是不影响代码调用的,大家可以自己试试。

         第三个是intermediate_steps,这是一个只有在代码运行中并调用了tools时才会产生的中间变量,我把它找了出来贴一下:

        这个是一个LIST,里面有一个tuple,tuple里面又是两个tuple ,一个是action,一个是observation,我们就用action的log和observation去构造agent_scratchpad,agent_scratchpad是构造有memory的agent必要的一个组件。至此,我们就基本搞明白了CustomPromptTemplateForTools,用它的format方法把输入的参数进行一些调整、拼接,获得一个新的prompt。但是有一点我有点不太明白的是,CustomPromptTemplateForTools这个类的format方法我并没有调用过,为啥就生效呢?这个确实不太明白,可能涉及到langchain对于prompt这块的底层设计原理,我现在还接触不到。

3. SalesConvoOutputParser

        我们来看一下这个输出解析器。这块也是之前遗留的一个难点,因为这个Parser处理的是sales_agent_executor执行后的结果,不看到具体的输出我们确实很难理解这块代码在干什么。

        我们启动代码,然后输入查询价格的指令:

        首先,stage_analyzer_chain进行阶段判断,判定到了第三个阶段:

        然后,AgentExecutor开始执行,AgentExecutor在读取了历史聊天记录后开始进行分析,这个是它的输出:

        到这里,我们就理解了SalesConvoOutputParser代码中:

        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)
        return AgentAction(action.strip(), action_input.strip(" ").strip('"'), text)

        regex = r"Action: (.*?)[\n]*Action Input: (.*)" 是一个正则表达式,这个正则表达式可以用来匹配文本中以"Action:"开头,接着是动作内容,然后是零个或多个换行符,最后以"Action Input:"开头,接着是动作输入内容的部分。后面做的事情就是要用这个正则表达式去查询TEXT内容,去把"Action:"和"Action Input:"后面的东西提取出来,并赋值给action和action_input。

        这是AgentExecutor第一轮的输出,然后到第二轮的输出:

        这个时候,已经通过调用工具进行了知识库查询,获得了这段TEXT,然后我们就理解了这段代码的作用:

        if f"{self.ai_prefix}:" in text:
            return AgentFinish(
                {"output": text.split(f"{self.ai_prefix}:")[-1].strip()}, text
            )

         就是如果发现了Ted Lasso:为开头的输出,那就表明AgentExecutor已经获得了执行结果,代表可以返回AgentFinish,代表这个流程结束了。

 4.结束语

        至此,我们就基本掌握了SalesGPT的基本原理,也了解了它的核心代码逻辑和一些有难点的地方,这对于我们理解多Agent系统,对话阶段判断和prompt与parser的内容组装调整有巨大的好处。

        下一步,我们将尝试把我们之前构造的聊天机器人加入多Agent系统,让他可以根据用户对话的情况进行分析,给用户推销适配的旅游产品。

02-16 19:17