前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >构建智能代理:使用Elasticsearch与Langchain实现Agentic RAG

构建智能代理:使用Elasticsearch与Langchain实现Agentic RAG

原创
作者头像
点火三周
发布2024-12-17 22:46:17
发布2024-12-17 22:46:17
20200
代码可运行
举报
文章被收录于专栏:Elastic Stack专栏Elastic Stack专栏
运行总次数:0
代码可运行

介绍

在实际应用中,使用LLMs(大型语言模型)的下一步逻辑是引入智能代理。本文旨在介绍智能代理在RAG(检索增强生成)工作流中的概念和使用。总的来说,智能代理代表了一个非常令人兴奋的领域,具有许多雄心勃勃的应用可能性。

我希望在未来的文章中涵盖更多这些想法。现在,我们来看一下如何使用Elasticsearch作为知识库,并使用Langchain作为代理框架来实现Agentic RAG。

背景

最初,使用LLMs只是简单地提示它们执行任务,比如回答问题和进行简单计算。

然而,现有模型知识的缺陷意味着LLMs无法应用于需要专业技能的领域,例如企业客户服务和商业智能。

很快,提示转变为检索增强生成(RAG),这对Elasticsearch来说是一个自然的契合点。RAG作为一种有效且简单的方法,在查询时快速为LLM提供上下文和事实信息。替代方法是漫长且非常昂贵的重新训练过程,成功率极低。

RAG的主要操作优势是允许LLM应用程序以近乎实时的方式获取更新的信息。

实现起来很简单:获取一个向量数据库,如Elasticsearch,部署嵌入模型如ELSER,并调用搜索API以检索相关文档。

一旦检索到文档,它们可以插入到LLM的提示中,基于内容生成答案。这提供了上下文和真实性,LLM单独可能缺乏这些要素。

从文本生成到决策的上下文
从文本生成到决策的上下文

仅仅调用LLM、使用RAG和使用智能代理之间的区别

然而,标准的RAG部署模型有一个缺点——它是刚性的。LLM不能选择从哪个知识库获取信息。它也不能使用额外的工具,比如Google或Bing的Web搜索引擎API。它不能检查当前的天气,或者使用计算器,或者考虑任何超出其被赋予的知识库的工具的输出。

智能代理模型的区别在于选择。

术语说明

工具使用,在Langchain上下文中使用的术语,也称为函数调用。两者的用途相同——指的是给LLM提供一组它可以使用的功能或工具,以补充其能力或影响世界。在本文中,我将使用“工具使用”这一术语。

选择

赋予LLM做出决策的能力,并给它一组工具。基于对话的状态和历史,LLM将选择是否使用每个工具,并将工具的输出纳入其响应中。

这些工具可以是知识库、计算器、Web搜索引擎和爬虫——种类没有限制或终止。LLM变得能够执行复杂的操作和任务,而不仅仅是生成文本。

研究机器人示例
研究机器人示例

研究特定主题的智能代理流程示例

让我们实现一个简单的智能代理示例。Elastic的核心优势在于我们的知识库。因此,这个示例将重点放在通过编写比简单的向量搜索更复杂的查询来使用一个相对大型和复杂的知识库。

设置

首先,在你的项目目录中定义一个 .env 文件,并填写以下字段。我使用的是Azure OpenAI部署的GPT-4o作为我的LLM,并使用Elastic Cloud部署作为我的知识库。我的Python版本是 python 3.12.4,在我的Macbook上工作。

代码语言:javascript
代码运行次数:0
复制
ELASTIC_ENDPOINT="YOUR ELASTIC ENDPOINT"
ELASTIC_API_KEY="YOUR ELASTIC API KEY"
OPENAI_API_TYPE="azure"
AZURE_OPENAI_ENDPOINT="YOUR AZURE ENDPOINT"
AZURE_OPENAI_API_VERSION="2024-06-01"
AZURE_OPENAI_API_KEY="YOUR AZURE API KEY"
AZURE_OPENAI_GPT4O_MODEL_NAME="gpt-4o"
AZURE_OPENAI_GPT4O_DEPLOYMENT_NAME="YOUR AZURE OPENAI GPT-4o DEPLOYMENT NAME"

你可能需要在终端中安装以下依赖项。

代码语言:javascript
代码运行次数:0
复制
pip install langchain elasticsearch

在你的项目目录中创建一个名为 chat.py 的Python文件,并粘贴以下代码以初始化你的LLM和Elastic Cloud连接:

代码语言:javascript
代码运行次数:0
复制
import os
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import AzureChatOpenAI
from langchain.agents import initialize_agent, AgentType, Tool
from langchain.tools import StructuredTool  # Import StructuredTool
from langchain.memory import ConversationBufferMemory
from typing import Optional
from pydantic import BaseModel, Field

# LLM设置
llm = AzureChatOpenAI(
    openai_api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
    azure_deployment=os.getenv("AZURE_OPENAI_GPT4O_DEPLOYMENT_NAME"),
    temperature=0.5,
    max_tokens=4096
)

from elasticsearch import Elasticsearch

# Elasticsearch设置
try:
    es_endpoint = os.environ.get("ELASTIC_ENDPOINT")
    es_client = Elasticsearch(
        es_endpoint,
        api_key=os.environ.get("ELASTIC_API_KEY")
    )
except Exception as e:
    es_client = None

Hello World!我们的第一个工具

在初始化并定义了我们的LLM和Elastic客户端后,让我们做一个Elastic版的Hello World。我们将定义一个函数来检查与Elastic Cloud的连接状态,并创建一个简单的代理对话链来调用它。

将以下函数定义为Langchain的 Tool。名称和描述是你的提示工程的重要组成部分。LLM依靠它们来确定在对话中是否使用该工具。

代码语言:javascript
代码运行次数:0
复制
# 定义检查ES状态的函数
def es_ping(*args, **kwargs):
    if es_client is None:
        return "ES客户端未初始化。"
    else:
        try:
            if es_client.ping():
                return "ES ping返回True,ES已连接。"
            else:
                return "ES未连接。"
        except Exception as e:
            return f"ping ES时出错:{e}"

es_status_tool = Tool(
    name="ES Status",
    func=es_ping,
    description="检查Elasticsearch是否已连接。"
)

tools = [es_status_tool]

现在,让我们初始化一个对话记忆组件来跟踪对话,以及我们的代理本身。

代码语言:javascript
代码运行次数:0
复制
# 初始化记忆以跟踪对话
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

# 初始化代理
agent_chain = initialize_agent(
    tools,
    llm,
    agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
    memory=memory,
    verbose=True,
)

最后,让我们使用以下代码片段运行对话循环:

代码语言:javascript
代码运行次数:0
复制
# 与代理的交互对话
def main():
    print("欢迎来到聊天代理。输入'exit'退出。")
    while True:
        user_input = input("你: ")
        if user_input.lower() in ['exit', 'quit']:
            print("再见!")
            break
        response = agent_chain.run(input=user_input)
        print("助手:", response)

if __name__ == "__main__":
    main()

在你的终端中运行 python chat.py 以初始化对话。以下是我的对话结果:

代码语言:javascript
代码运行次数:0
复制
你: 你好
助手: 你好!今天我能帮你什么?
你: Elasticsearch连接了吗?

> 进入新的AgentExecutor链...
思考:我需要使用工具吗?是的
行动:ES Status
行动输入:

观察:ES ping返回True,ES已连接。
思考:我需要使用工具吗?不
AI: 是的,Elasticsearch已连接。还有什么我可以帮你的吗?

当我问Elasticsearch是否连接时,LLM使用了 ES Status 工具,ping了我的Elastic Cloud部署,得到了True的结果,然后确认Elastic Cloud确实已连接。

恭喜你!这是一个成功的Hello World示例 :)

请注意,观察结果是 es_ping 函数的输出。这个观察结果的格式和内容是我们的提示工程的关键部分,因为LLM会使用它来决定下一步。

让我们看看如何修改这个工具以用于RAG。

Agentic RAG

我最近在Elastic Cloud部署中构建了一个大型且复杂的知识库,使用了POLITICS数据集。这个数据集包含大约246万篇从美国新闻来源抓取的政治文章。我将其导入Elastic Cloud并使用 elser_v2 推理端点嵌入,按照这个之前的博客中定义的流程。

要部署 elser_v2 推理端点,请确保启用了ML节点自动扩展,并在你的Elastic Cloud控制台中运行以下命令。

代码语言:javascript
代码运行次数:0
复制
PUT _inference/sparse_embedding/elser_v2
{
  "service": "elser",
  "service_settings": {
    "num_allocations": 4,
    "num_threads": 8
  }
}

现在,让我们定义一个新工具,在我们的政治知识库索引上进行简单的语义搜索。我称之为 bignews_embedded。这个函数接受一个搜索查询,将其添加到一个标准的语义搜索查询模板中,并使用Elasticsearch运行查询。一旦有了搜索结果,它会将文章内容连接成一个文本块,并将其作为LLM的观察结果返回。

我们将搜索结果的数量限制为3。Agentic RAG 的一个优势是我们可以通过多个对话步骤开发答案。换句话说,更复杂的查询可以通过引导问题来设定舞台和上下文。问答变成了基于事实的对话,而不是一次性的答案生成。

日期

为了突出使用智能代理的一个重要优势,RAG搜索功能除了查询外还包括一个 dates 参数。当搜索新闻文章时,我们可能希望将搜索结果限制在特定的时间范围内,比如“在2020年”或“在2008年至2012年之间”。通过添加 dates 和一个解析器,我们允许LLM为搜索指定一个日期范围。

简而言之,如果我指定“2020年的加州野火”,我不希望看到2017年或其他年份的新闻。

这个 rag_search 函数包括一个日期解析器(从输入中提取日期并将其添加到查询中)和一个Elastic语义搜索查询。

代码语言:javascript
代码运行次数:0
复制
# 定义RAG搜索函数
def rag_search(query: str, dates: str):
    if es_client is None:
        return "ES客户端未初始化。"
    else:
        try:
            # 构建Elasticsearch查询
            must_clauses = []

            # 如果提供了日期,则解析并包含在查询中
            if dates:
                # 日期必须为 'YYYY-MM-DD' 或 'YYYY-MM-DD to YYYY-MM-DD' 格式
                date_parts = dates.strip().split(' to ')
                if len(date_parts) == 1:
                    # 单个日期
                    start_date = date_parts[0]
                    end_date = date_parts[0]
                elif len(date_parts) == 2:
                    start_date = date_parts[0]
                    end_date = date_parts[1]
                else:
                    return "日期格式无效。请使用YYYY-MM-DD或YYYY-MM-DD到YYYY-MM-DD格式。"

                date_range = {
                    "range": {
                        "date": {
                            "gte": start_date,
                            "lte": end_date
                        }
                    }
                }
                must_clauses.append(date_range)

            # 添加主查询子句
            main_query = {
                "nested": {
                    "path": "text.inference.chunks",
                    "query": {
                        "sparse_vector": {
                            "inference_id": "elser_v2",
                            "field": "text.inference.chunks.embeddings",
                            "query": query
                        }
                    },
                    "inner_hits": {
                        "size": 2,
                        "name": "bignews_embedded.text",
                        "_source": False
                    }
                }
            }
            must_clauses.append(main_query)

            es_query = {
                "_source": ["text.text", "title", "date"],
                "query": {
                    "bool": {
                        "must": must_clauses
                    }
                },
                "size": 3
            }

            response = es_client.search(index="bignews_embedded", body=es_query)
            hits = response["hits"]["hits"]
            if not hits:
                return "未找到符合你查询的文章。"
            result_docs = []
            for hit in hits:
                source = hit["_source"]
                title = source.get("title", "无标题")
                text_content = source.get("text", {}).get("text", "")
                date = source.get("date", "无日期")
                doc = f"标题: {title}\n日期: {date}\n{text_content}\n"
                result_docs.append(doc)
            return "\n".join(result_docs)
        except Exception as e:
            return f"RAG搜索期间出错:{e}"

在运行完整的搜索查询后,结果会连接成一个文本块,并作为LLM的“观察”结果返回。

为了处理多个可能的参数,使用pydantic的 BaseModel 定义一个有效的输入格式:

代码语言:javascript
代码运行次数:0
复制
class RagSearchInput(BaseModel):
    query: str = Field(..., description="知识库的搜索查询。")
    dates: str = Field(
        ...,
        description="用于过滤结果的日期或日期范围。指定格式为YYYY-MM-DD或YYYY-MM-DD到YYYY-MM-DD。"
    )

我们还需要使用 StructuredTool 来定义一个多输入函数,使用上面定义的输入格式:

代码语言:javascript
代码运行次数:0
复制
# 使用StructuredTool定义RAG搜索工具
rag_search_tool = StructuredTool(
    name="RAG_Search",
    func=rag_search,
    description=(
        "使用此工具从知识库中搜索有关美国政治的信息。"
        "**输入必须包括搜索查询和日期或日期范围。**"
        "日期必须指定为YYYY-MM-DD或YYYY-MM-DD到YYYY-MM-DD格式。"
    ),
    args_schema=RagSearchInput
)

描述是工具定义的关键部分,是你的提示工程的一部分。它应该详尽,提供足够的上下文,以便LLM知道何时使用该工具以及使用该工具的目的。

描述还应包括LLM必须提供的输入类型,以正确使用工具。指定格式和期望在这里有巨大的影响。

不充分的描述会严重影响LLM使用工具的能力!

记得将新工具添加到代理可以使用的工具列表中:

代码语言:javascript
代码运行次数:0
复制
tools = [es_status_tool, rag_search_tool]

我们还需要进一步修改代理,添加系统提示,以进一步控制代理的行为。系统提示对于确保不发生格式错误的输出和函数输入非常重要。我们需要明确说明每个函数期望的内容,以及模型应该输出的内容,因为Langchain在看到格式错误的LLM响应时会抛出错误。

我们还需要设置 agent=AgentType.OPENAI_FUNCTIONS 以使用OpenAI的函数调用功能。这允许LLM根据我们指定的结构模板与函数交互。

请注意,系统提示包括一个关于LLM应该生成的输入格式的规定,以及一个具体的示例。

LLM不仅应该检测应使用哪个工具,还应该检测工具期望的输入!Langchain只负责函数调用/工具使用,但由LLM正确使用。

代码语言:javascript
代码运行次数:0
复制
agent_chain = initialize_agent(
    tools,
    llm,
    agent=AgentType.OPENAI_FUNCTIONS,
    memory=memory,
    verbose=True,
    handle_parsing_errors=True,
    system_message="""
    你是一个AI助手,帮助回答有关美国政治的问题,使用一个知识库。请简洁、锋利、直奔主题,并在一个段落内回应。
    你可以访问以下工具:
    - **ES_Status**: 检查Elasticsearch是否已连接。
    - **RAG_Search**: 使用此工具在知识库中搜索信息。**输入必须包括搜索查询和日期或日期范围。** 日期必须指定为YYYY-MM-DD或YYYY-MM-DD到YYYY-MM-DD格式。

    **重要说明:**
    - **从用户的问题中提取日期或日期范围。**
    - **如果用户未提供日期或日期范围,请礼貌地询问他们提供一个,然后继续。**

    当你决定使用工具时,请严格按照以下格式使用:
    思考:[你对接下来需要做什么的思考]
    行动:[采取的行动,应为 [ES_Status, RAG_Search] 之一]
    行动输入:{"query": "搜索查询", "dates": "日期或日期范围"}

    如果在采取行动后收到观察结果,你应该考虑它然后决定下一步。如果你有足够的信息回答用户的问题,请回应:
    思考:[你的思考]
    助手:[你对用户的最终回答]

    **示例:**
    - **用户的问题:** "告诉我关于2020年加州野火的情况。"  
      思考:我需要搜索2020年加州野火的信息。
      行动:RAG_Search
      行动输入:{"query": "加州野火", "dates": "2020-01-01到2020-12-31"}
    - **用户的问题:** "总统选举期间发生了什么?"  
      思考:用户没有指定日期。我应该询问一个日期范围。
      助手:你能否提供一个你感兴趣的总统选举的日期或日期范围?

    始终确保你的输出严格遵循上述格式之一,并且不要包含任何额外的文本或格式。
    记住:
    - **不要**在指定格式之前或之后包含任何文本。
    - **不要**添加额外的解释。
    - **不要**包含Markdown、项目符号或编号列表,除非它们是助手最终回答的一部分。

    你的目标是通过有效使用工具,在必要时为用户提供清晰简洁的答案。
    """
)

现在,在终端中运行 python chat.py,让我们测试一下!

测试Agentic RAG

让我们通过以下查询来测试:

代码语言:javascript
代码运行次数:0
复制
总结2020年加州野火的联邦响应

Langchain会输出中间步骤,包括 RAG_Search 函数的输入、搜索结果和最终输出。

代码语言:javascript
代码运行次数:0
复制
> 进入新的AgentExecutor链...
调用:`RAG_Search`,使用 `{'query': 'federal response to California wildfires', 'dates': '2020-01-01 to 2020-12-31'}`

标题:加州联邦野火援助:纽瑟姆说特朗普改变了主意
日期:2020-10-16T00:00:00.000Z
总统唐纳德·特朗普突然改变了方向...
标题:加州州长在多个县宣布紧急状态,野火肆虐
日期:2020-09-07T00:00:00.000Z
在声明中,纽瑟姆指出,数十...
标题:特朗普批准加州灾难声明,改变了方向
日期:2020-10-16T00:00:00.000Z
加州获得了重大灾难声明...
> 完成链。
助手:联邦对2020年加州野火的响应包括几个关键行动和时刻:
1. **初始拒绝和联邦援助的逆转**:特朗普总统最初拒绝了加州的联邦援助请求。然而,在州长加文·纽瑟姆誓言上诉后,特朗普改变了方向,批准了重大灾难声明。这一声明使加州能够获得数亿美元的联邦援助。
2. **全州紧急状态声明**:纽瑟姆州长因广泛的火灾和极端天气状况宣布全州进入紧急状态。州政府与联邦当局紧密合作,确保援助,包括FEMA的消防管理援助赠款。
3. **总统重大灾难声明**:纽瑟姆获得了总统重大灾难声明,以加强州对北加州野火的应急响应。这一声明使受影响县的居民有资格获得各种形式的联邦支持,包括危机咨询、住房、失业援助和法律服务。
4. **联邦和州合作**:尽管州政府与特朗普政府在森林管理和气候变化方面存在持续的紧张关系和分歧,联邦机构如国家公园管理局、美国森林服务局和土地管理局参与了加州的消防工作。
5. **野火的影响和规模**:2020年加州的野火季节历史性地毁灭,超过8,500场大火烧毁了6,400平方英里的土地,摧毁了数千座建筑物,并夺去了生命。联邦援助和灾难声明在支持州的响应和恢复工作中至关重要。

总体而言,联邦响应包括初始的抵制,然后是关键的支持和合作,以应对加州前所未有的野火危机。

最显著的一点是,LLM创建了一个搜索查询,然后添加了从2020年初到年底的日期范围。通过将搜索结果限制在指定年份内,我们确保只有相关文档会传递给LLM。

我们还可以做很多事情,例如根据类别约束,或某些实体的出现,或与其他事件的关系。

可能性是无穷的,我觉得这非常酷!

错误处理注意事项

可能会出现LLM未能在需要时正确使用适当工具/函数的情况。例如,它可能选择使用自己的知识回答有关当前事件的问题,而不是使用可用的知识库。

需要仔细测试和调整系统提示和工具/函数描述。

另一个选项可能是增加可用工具的种类,以增加基于知识库内容生成答案的概率,而不是LLM的内在知识。

请注意,LLM会偶尔出现故障,这是其概率性质的自然结果。帮助性的错误消息或免责声明也可能是用户体验的重要组成部分。

结论和未来前景

对我来说,主要的收获是创建更高级搜索应用程序的可能性。LLM可能能够在自然语言对话的上下文中动态生成非常复杂的搜索查询。这为极大提高搜索应用程序的准确性和相关性开辟了道路,这是我很兴奋去探索的一个领域。

知识库与其他工具(如Web搜索引擎和监控工具API)之间的交互,通过LLM的媒介,也可能实现一些令人兴奋和复杂的用例。知识库的搜索结果可能会补充实时信息,使得LLM能够进行有效且及时的实时推理。

还有可能性是多代理工作流。在Elastic环境中,这可能是多个代理探索不同的知识库集,以协作解决复杂问题。也许是一种联邦模型的搜索,其中多个组织构建协作的、共享的应用程序,类似于联邦学习的想法?

多代理流程
多代理流程

多代理流程示例

我希望与你们一起探索一些这些用例,使用Elasticsearch。

下次见!

附录:chat.py的完整代码

代码语言:javascript
代码运行次数:0
复制
import os
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import AzureChatOpenAI
from langchain.agents import initialize_agent, AgentType, Tool
from langchain.tools import StructuredTool  # Import StructuredTool
from langchain.memory import ConversationBufferMemory
from typing import Optional
from pydantic import BaseModel, Field

llm = AzureChatOpenAI(
    openai_api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
    azure_deployment=os.getenv("AZURE_OPENAI_GPT4O_DEPLOYMENT_NAME"),
    temperature=0.5,
    max_tokens=4096
)

from elasticsearch import Elasticsearch

try:
    es_endpoint = os.environ.get("ELASTIC_ENDPOINT")
    es_client = Elasticsearch(
        es_endpoint,
        api_key=os.environ.get("ELASTIC_API_KEY")
    )
except Exception as e:
    es_client = None

# 定义检查ES状态的函数
def es_ping(_input):
    if es_client is None:
        return "ES客户端未初始化。"
    else:
        try:
            if es_client.ping():
                return "ES已连接。"
            else:
                return "ES未连接。"
        except Exception as e:
            return f"ping ES时出错:{e}"

# 定义ES状态工具
es_status_tool = Tool(
    name="ES_Status",
    func=es_ping,
    description="检查Elasticsearch是否已连接。"
)

# 定义RAG搜索函数
def rag_search(query: str, dates: str):
    if es_client is None:
        return "ES客户端未初始化。"
    else:
        try:
            # 构建Elasticsearch查询
            must_clauses = []

            # 如果提供了日期,则解析并包含在查询中
            if dates:
                date_parts = dates.strip().split(' to ')
                if len(date_parts) == 1:
                    start_date = date_parts[0]
                    end_date = date_parts[0]
                elif len(date_parts) == 2:
                    start_date = date_parts[0]
                    end_date = date_parts[1]
                else:
                    return "日期格式无效。请使用YYYY-MM-DD或YYYY-MM-DD到YYYY-MM-DD格式。"

                date_range = {
                    "range": {
                        "date": {
                            "gte": start_date,
                            "lte": end_date
                        }
                    }
                }
                must_clauses.append(date_range)

            main_query = {
                "nested": {
                    "path": "text.inference.chunks",
                    "query": {
                        "sparse_vector": {
                            "inference_id": "elser_v2",
                            "field": "text.inference.chunks.embeddings",
                            "query": query
                        }
                    },
                    "inner_hits": {
                        "size": 2,
                        "name": "bignews_embedded.text",
                        "_source": False
                    }
                }
            }
            must_clauses.append(main_query)

            es_query = {
                "_source": ["text.text", "title", "date"],
                "query": {
                    "bool": {
                        "must": must_clauses
                    }
                },
                "size": 3
            }

            response = es_client.search(index="bignews_embedded", body=es_query)
            hits = response["hits"]["hits"]
            if not hits:
                return "未找到符合你查询的文章。"
            result_docs = []
            for hit in hits:
                source = hit["_source"]
                title = source.get("title", "无标题")
                text_content = source.get("text", {}).get("text", "")
                date = source.get("date", "无日期")
                doc = f"标题: {title}\n日期: {date}\n{text_content}\n"
                result_docs.append(doc)
            return "\n".join(result_docs)
        except Exception as e:
            return f"RAG搜索期间出错:{e}"

class RagSearchInput(BaseModel):
    query: str = Field(..., description="知识库的搜索查询。")
    dates: str = Field(
        ...,
        description="用于过滤结果的日期或日期范围。指定格式为YYYY-MM-DD或YYYY-MM-DD到YYYY-MM-DD。"
    )

# 使用StructuredTool定义RAG搜索工具
rag_search_tool = StructuredTool(
    name="RAG_Search",
    func=rag_search,
    description=(
        "使用此工具从知识库中搜索有关美国政治的信息。"
        "**输入必须包括搜索查询和日期或日期范围。**"
        "日期必须指定为YYYY-MM-DD或YYYY-MM-DD到YYYY-MM-DD格式。"
    ),
    args_schema=RagSearchInput
)

# 工具列表
tools = [es_status_tool, rag_search_tool]

# 初始化记忆以跟踪对话
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

agent_chain = initialize_agent(
    tools,
    llm,
    agent=AgentType.OPENAI_FUNCTIONS,
    memory=memory,
    verbose=True,
    handle_parsing_errors=True,
    system_message="""
    你是一个AI助手,帮助回答有关美国政治的问题,使用一个知识库。请简洁、锋利、直奔主题,并在一个段落内回应。
    你可以访问以下工具:
    - **ES_Status**: 检查Elasticsearch是否已连接。
    - **RAG_Search**: 使用此工具在知识库中搜索信息。**输入必须包括搜索查询和日期或日期范围。** 日期必须指定为YYYY-MM-DD或YYYY-MM-DD到YYYY-MM-DD格式。

    **重要说明:**
    - **从用户的问题中提取日期或日期范围。**
    - **如果用户未提供日期或日期范围,请礼貌地询问他们提供一个,然后继续。**

    当你决定使用工具时,请严格按照以下格式使用:
    思考:[你对接下来需要做什么的思考]
    行动:[采取的行动,应为 [ES_Status, RAG_Search] 之一]
    行动输入:{"query": "搜索查询", "dates": "日期或日期范围"}

    如果在采取行动后收到观察结果,你应该考虑它然后决定下一步。如果你有足够的信息回答用户的问题,请回应:
    思考:[你的思考]
    助手:[你对用户的最终回答]

    **示例:**
    - **用户的问题:** "告诉我关于2020年加州野火的情况。"  
      思考:我需要搜索2020年加州野火的信息。
      行动:RAG_Search
      行动输入:{"query": "加州野火", "dates": "2020-01-01到2020-12-31"}
    - **用户的问题:** "总统选举期间发生了什么?"  
      思考:用户没有指定日期。我应该询问一个日期范围。
      助手:你能否提供一个你感兴趣的总统选举的日期或日期范围?

    始终确保你的输出严格遵循上述格式之一,并且不要包含任何额外的文本或格式。
    记住:
    - **不要**在指定格式之前或之后包含任何文本。
    - **不要**添加额外的解释。
    - **不要**包含Markdown、项目符号或编号列表,除非它们是助手最终回答的一部分。

    你的目标是通过有效使用工具,在必要时为用户提供清晰简洁的答案。
    """
)

# 与代理的交互对话
def main():
    print("欢迎来到聊天代理。输入'exit'退出。")
    while True:
        user_input = input("你: ")
        if user_input.lower() in ['exit', 'quit']:
            print("再见!")
            break
        # 更新方法调用以解决弃用警告
        response = agent_chain.invoke(input=user_input)
        print("助手:", response['output'])

if __name__ == "__main__":
    main()

Elasticsearch具有许多新功能,可以帮助你为你的用例构建最佳的搜索解决方案。深入了解我们的示例笔记本以了解更多信息,开始免费云试用,或者现在在你的本地机器上试试Elastic。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
  • 背景
  • 术语说明
  • 选择
  • 设置
  • Hello World!我们的第一个工具
  • Agentic RAG
    • 日期
  • 测试Agentic RAG
  • 错误处理注意事项
  • 结论和未来前景
  • 附录:chat.py的完整代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档