为了账号安全,请及时绑定邮箱和手机立即绑定

微软的 GraphRAG + AutoGen + Ollama + Chainlit = 本地免费多代理 RAG 超级机器人

图形摘要及关键组件

检索增强生成(RAG)是一种强大的工具,它使大型语言模型(LLMs)能够访问现实世界的数据,从而提供更加准确的响应。这是通过将模型与向量数据库集成来实现的,以实现实时学习和适应。这一特性使得RAG成为聊天机器人和虚拟助手等应用的首选,这些应用需要在实时环境中提供准确和合理的响应。一种称为图检索增强生成(GraphRAG)的高级变体,将基于图的知识检索与LLMs结合,进一步增强了自然语言处理的能力。与依赖向量相似性搜索的传统RAG方法不同,GraphRAG从原始文本构建结构化的知识图谱,捕捉实体、关系和关键声明。这可以增强LLMs理解并综合复杂数据集及其关系的能力,从而产生更准确和上下文相关的响应。

摘自麻省理工学院Markus Beuhler撰写的一篇论文(链接 here

AutoGen 是由 Microsoft 开发的一款工具,它通过自动化和优化曾经复杂且需要大量手动工作的多代理大语言模型(LLM)应用程序的开发流程,从而简化了复杂应用程序的开发。想象一下,AutoGen 是一个平台,在这里你可以与多个 GPT 进行交互,而不仅仅是单一的 GPT。每个 GPT 都作为一个独立的“代理”,在全面操作中扮演独特的角色。结合 GraphRAG 的检索功能与 AutoGen AI 代理的对话和任务导向功能,可以生成强大的 AI 助手,能够高效处理详细查询、生成和执行代码、创建多页科学报告以及进行数据分析。此外,离线本地 LLM,如 Ollama 或 LM Studio 提供的,确保了成本效益和数据处理的安全性。本地 LLM 消除了在线 LLM 的高昂成本和隐私风险,将敏感数据保留在组织内部,并降低运营成本。

本文将指导您使用 GraphRAG 检索系统构建一个多代理 AI 应用程序,该应用程序完全在您的本地机器上运行,并且无需任何费用。以下是该应用程序的关键组件:

  1. GraphRAG 的知识搜索方法通过函数调用与 AutoGen 代理集成。
  2. GraphRAG(本地和全局搜索)配置为支持 Ollama 的本地模型进行推理和嵌入。
  3. AutoGen 扩展以支持通过 Lite-LLM 代理服务器与 Ollama 的非 OpenAI 大型语言模型进行函数调用。
  4. 使用 Chainlit UI 处理连续对话、多线程和用户输入设置。

鉴于我有材料科学和计算建模的背景,我想通过构建知识图谱来测试这个应用,这些知识图谱将基于ABAQUS(一种有限元分析工程软件)的文档以及一些碳纤维和聚合物的技术数据表。考虑到这个数据集的复杂性,使用本地大语言模型的整体准确度可以更高。未来的文章将探讨使用不同模型进行嵌入和推理的基准研究中的学习成果。尽管如此,我仍然热衷于从科学期刊和数据中构建更复杂的知识图谱,测试高级工程代码生成任务,并利用一个对话助手在我的专业领域内进行科学主题的头脑风暴。这个应用看起来是这样的。

主应用程序UI及示例查询。最后两个查询相同,但第一个是全局搜索,而第二个是局部搜索。

小部件设置用于在本地搜索和全局搜索之间切换,设置社区级别和生成长度。

开发是在一个使用 Windows Subsystem for Linux (WSL) 和 Visual Studio Code 的 Windows 11 PC 上进行的,该 PC 配备了 i9 13 代处理器、64 GB 内存和 24 GB Nvidia RTX 4090。为了获得最佳的开发和测试体验,建议使用 Linux 发行版或 WSL。我未在原生 Windows 环境下进行测试。有关安装 WSL 和设置 Python 及 Conda 环境的指南,请参阅这篇文章(这里)。本文末尾还提供了其他参考资料和相关信息。

这里是可以找到源代码仓库的链接。现在,让我们开始吧!!

安装模型依赖并克隆仓库。
从 Ollama 安装语言模型用于推理和嵌入
    # Mistral 用于 GraphRAG 推理  
    ollama pull mistral  

    # Nomic-Embed-Text 用于 GraphRAG 嵌入  
    ollama pull nomic-embed-text  

    # LLama3 用于 Autogen 推理  
    ollama pull llama3  

    # 在本地服务器上运行 Ollama: http://localhost:11434  
    ollama serve
创建一个conda环境并安装这些依赖项
     # 创建并激活conda环境  
    conda create -n RAG_agents python=3.12  
    conda activate RAG_agents  

    # Lite-LLM代理服务器用于Ollama  
    pip install 'litellm[proxy]'  

    # 安装Ollama  
    pip install ollama  

    # Microsoft AutoGen  
    pip install pyautogen "pyautogen[retrievechat]"   

    # Microsoft GraphRAG  
    pip install graphrag  

    # 文本-令牌编码器-解码器  
    pip install tiktoken  

    # 安装Chainlit Python应用程序  
    pip install chainlit  

    # 克隆我的Git-hub仓库  
    git clone https://github.com/karthik-codex/autogen_graphRAG.git  

    # (额外)将PDF文件转换为Markdown用于GraphRAG  
    pip install marker-pdf  

    # (额外)仅当你安装了Marker-pdf,因为它默认移除了GPU CUDA支持  
    conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia

您将在我的 GitHub 仓库中找到以下文件。

  1. /requirements.txt包含上述所有包的列表

  2. /utils/settings.yaml包含使用 Mistral 7B 和 Ollama 的 Nomic-Text-Embedding 的 LLM 配置,用于 GraphRAG 离线嵌入和索引。你将使用此文件替换在首次初始化 GraphRAG 时创建的工作目录中的文件。

  3. /utils/chainlit_agents.py包含 AutoGen 的助手和用户代理类定义。这允许跟踪多个代理并显示它们的消息到 UI 中。(感谢 Chainlit 团队构建的 模板 )。

  4. /utils/embedding.py包含用于使用 Ollama 进行本地搜索查询的 GraphRAG 嵌入的修改嵌入函数。你将使用此文件替换 GraphRAG 包中的文件(更多信息见下文)。

  5. utils/openai_embeddings_llm.py包含用于使用 Ollama 进行 GraphRAG 索引和嵌入的修改嵌入函数。你将使用此文件替换 GraphRAG 包中的文件(更多信息见下文)。

  6. /appUI.py包含设置代理、定义 GraphRAG 搜索函数、跟踪和处理消息以及在 Chainlit UI 中显示它们的主要异步函数。

  7. /utils/pdf_to_markdown.py包含将 PDF 文件转换为 markdown 文件以供 GraphRAG 摄入的函数。
创建一个 GraphRAG 知识库。
在仓库根目录初始化 GraphRAG
     # 创建一个新文件夹 "input" 用于存放 GraphRAG 的输入文件(.txt 或 .md)
    mkdir -p ./input  

    # 初始化 GraphRAG 以在根目录中创建所需的文件和文件夹
    python -m graphrag.index --init  --root .  

    # 将 settings.yaml 文件移动到替换 GraphRAG --init 创建的文件
    mv ./utils/settings.yaml ./
配置 GraphRAG 设置以支持来自 Ollama 的本地模型

以下是 settings.yaml 文件中的片段,展示了配置LLM以创建索引和嵌入的配置。GraphRAG 需要 32k 的上下文长度来进行索引,因此选择了 Mistral 模型。对于嵌入,选择了 Nomic-embed-text,不过你也可以尝试使用 Ollama 中的其他嵌入模型。无需设置 ${GRAPHRAG_API_KEY},因为不需要访问这些本地模型的端点。

    encoding_model: cl100k_base  
    skip_workflows: []  
    llm:  
      api_key: ${GRAPHRAG_API_KEY}  
      type: openai_chat # 或者 azure_openai_chat  
      model: mistral  
      model_supports_json: true  
      api_base: http://localhost:11434/v1   
    .  
    .  
    .  
    embeddings:  
      async_mode: threaded # 或者 asyncio  
      llm:  
        api_key: ${GRAPHRAG_API_KEY}  
        type: openai_embedding # 或者 azure_openai_embedding  
        model: nomic_embed_text  
        api_base: http://localhost:11434/api   
    .  
    .  
    .  
    input:  # 更改输入文件模式为.md,或 .txt  
      type: file # 或者 blob  
      file_type: text # 或者 csv  
      base_dir: "input"  
      file_encoding: utf-8  
      file_pattern: ".*\\.md$"

你可以在根目录下的“input”文件夹中指定包含输入文件的文件夹。既可以使用文本文件,也可以使用 markdown 文件。你可以使用 /utils/pdf_to_markdown.py 脚本将 PDF 文件转换为 markdown 文件,然后将这些文件放入“input”文件夹中。虽然目前还没有解决多种文件格式的处理问题,但这是一个可以解决的问题。

在运行 GraphRAG 进行索引、创建嵌入和执行本地查询之前,您必须修改位于 GraphRAG 包内的 Python 文件 openai_embeddings_llm.pyembedding.py。如果不进行此修改,GraphRAG 在创建嵌入时会抛出错误,因为它无法识别 "nomic-embed-text" 作为 Ollama 的有效嵌入模型。在我的设置中,这些文件位于 /home/karthik/miniconda3/envs/RAG_agents/lib/python3.12/site-packages/graphrag/llm/openai/openai_embeddings_llm.py/home/karthik/miniconda3/envs/RAG_agents/lib/python3.12/site-packages/graphrag/query/llm/oai/embedding.py

你可以使用命令 sudo find / -name openai_embeddings_llm.py 来定位这些文件。

创建嵌入和知识图谱。

最后,我们创建嵌入并向量,并使用全局或局部搜索方法测试知识图谱。完成嵌入过程后,您可以在 GraphRAG 工作目录的“output”文件夹中找到输出文件(.parquet 文件)和报告(.json 和 .logs 文件),该工作目录是本实例中的根文件夹。

    # 创建知识图谱 - 这需要一些时间  
    python -m graphrag.index --root .  

    # 测试 GraphRAG  
    python -m graphrag.query --root . --method global "<插入您的查询>"
启动 Lite-LLM 服务器并在终端中运行应用

下面是运行应用前初始化服务器的命令。我选择了 Llama3:8b 来测试此应用。如果你的硬件允许,你可以使用更大的模型。有关 Lite-LLM 的更多信息,请参阅此 链接。现在,你可以从另一个终端运行应用程序。请确保你处于正确的 conda 环境中。

    # 从终端启动服务器  
    litellm --model ollama_chat/llama3  

    # 从另一个终端运行应用  
    chainlit run appUI.py
分解:appUI.py 的核心组件
导入 Python 库
    import autogen  
    from rich import print  
    import chainlit as cl  
    from typing_extensions import Annotated  
    from chainlit.input_widget import (  
       Select, Slider, Switch)  
    from autogen import AssistantAgent, UserProxyAgent  
    from utils.chainlit_agents import ChainlitUserProxyAgent, ChainlitAssistantAgent  
    from graphrag.query.cli import run_global_search, run_local_search

你会发现从 _chainlitagents 导入了两个类。这些封装了 AutoGen 代理的类使 Chainlit 能够跟踪它们的对话并处理终止或其他用户输入。你可以在这里阅读更多相关内容 这里

配置 AutoGen 代理

AutoGen 代理通过 Lite-LLM 代理服务器使用 Ollama 的模型。这是必要的,因为 AutoGen 不支持通过非 OpenAI 推理模型进行函数调用。代理服务器使得可以使用 Ollama 模型来进行函数调用和代码执行。

    # 通过 Lite-LLM 服务器为代理获取 LLama3 LLM #
    llm_config_autogen = {  
        "seed": 40,  # 更改种子以进行不同的试验  
        "temperature": 0,  
        "config_list": [{"model": "litellm",   
                         "base_url": "http://0.0.0.0:4000/",   
                         'api_key': 'ollama'},  
        ],  
        "timeout": 60000,  
    }
初始化代理并输入用户设置以开始聊天

我创建了三个Chainlit小部件(开关、选择和滑块),作为用户设置来选择GraphRAG搜索类型、社区级别和内容生成类型。当开关打开时,它会使用GraphRAG本地搜索方法进行查询。内容生成的选择选项包括“优先列表”、“单段落”、“多段落”和“多页报告”。滑块小部件用于选择社区生成级别,选项包括0、1和2。您可以在此处了解更多关于GraphRAG社区的信息这里

    @cl.on_chat_start  
    async def on_chat_start():  
      try:  
        settings = await cl.ChatSettings(  
                [        
                    Switch(id="Search_type", label="(GraphRAG) Local Search", initial=True),         
                    Select(  
                        id="Gen_type",  
                        label="(GraphRAG) Content Type",  
                        values=["prioritized list", "single paragraph", "multiple paragraphs", "multiple-page report"],  
                        initial_index=1,  
                    ),            
                    Slider(  
                        id="Community",  
                        label="(GraphRAG) Community Level",  
                        initial=0,  
                        min=0,  
                        max=2,  
                        step=1,  
                    ),  

                ]  
            ).send()  

        response_type = settings["Gen_type"]  
        community = settings["Community"]  
        local_search = settings["Search_type"]  

        cl.user_session.set("Gen_type", response_type)  
        cl.user_session.set("Community", community)  
        cl.user_session.set("Search_type", local_search)  

        retriever   = AssistantAgent(  
           name="Retriever",   
           llm_config=llm_config_autogen,   
           system_message="""仅执行query_graphRAG函数来查找上下文。   
                        当提供答案时输出'TERMINATE'。""",  
           max_consecutive_auto_reply=1,  
           human_input_mode="NEVER",   
           description="Retriever Agent"  
        )  

        user_proxy = ChainlitUserProxyAgent(  
            name="User_Proxy",  
            human_input_mode="ALWAYS",  
            llm_config=llm_config_autogen,  
            is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE"),  
            code_execution_config=False,  
            system_message='''一个管理员。与检索器互动以提供任何上下文。''',  
            description="User Proxy Agent"  
        )  

        print("设置代理。")  

        cl.user_session.set("Query Agent", user_proxy)  
        cl.user_session.set("Retriever", retriever)  

        msg = cl.Message(content=f"""你好!今天你想完成什么任务?        
                         """,   
                         author="User_Proxy")  
        await msg.send()  

        print("消息已发送。")  

      except Exception as e:  
        print("错误: ", e)  
        pass

我选择不使用 Chainlit 包装类来处理检索助手代理。这让我能够禁用检索输出的跟踪,并直接捕获来自 GraphRAG 函数的响应。原因是当响应通过检索器时,文本会失去其格式,包括空格和段落缩进。在生成带有主标题和子标题的多页报告时,这个问题尤为明显。通过绕过 Chainlit 包装类并直接从 GraphRAG 函数获取输出,我可以保留原始格式。您将在下面看到我是如何实现这一点的。

更新输入设置的更改

此函数用于检测设置中的 select、switch 和 slider 小部件所做的任何更改,以便在后续查询中反映这些更改。

    @cl.on_settings_update  
    async def setup_agent(settings):  
        response_type = settings["Gen_type"]  
        community = settings["Community"]  
        local_search = settings["Search_type"]  
        cl.user_session.set("Gen_type", response_type)  
        cl.user_session.set("Community", community)  
        cl.user_session.set("Search_type", local_search)  
        print("on_settings_update", settings)
使用来自代理和用户的传入消息更新界面。

这是应用程序的核心部分,它创建了一个包含两个代理的群聊,定义了一个“state_transition”函数来管理对话顺序,并提供了异步RAG查询功能。

你会注意到INPUT_DIR, ROOT_DIR, RESPONSE_TYPE, COMMUNTIY这些参数会被传递到本地和全局搜索的GraphRAG查询函数中,这取决于布尔参数LOCAL_SEARCH的值。ROOT_DIR被设置为'.'——如果你在初始化GraphRAG时使用了不同的目录,请注意这一点。

异步函数“query_graphRAG”调用了GraphRAG的全局或局部搜索方法。你会注意到在async def query_graphRAG函数中的这一行await cl.Message(content=result.response).send()直接获取了RAG查询的输出,并保留了检索内容的文本格式。

    @cl.on_message  
    async def run_conversation(message: cl.Message):  
        print("运行对话")  
        CONTEXT = message.content  

        MAX_ITER = 10  
        INPUT_DIR = None  
        ROOT_DIR = '.'  
        RESPONSE_TYPE = cl.user_session.get("Gen_type")  
        COMMUNITY = cl.user_session.get("Community")  
        LOCAL_SEARCH = cl.user_session.get("Search_type")  

        print("设置群聊")  

        retriever   = cl.user_session.get("Retriever")  
        user_proxy  = cl.user_session.get("Query Agent")  

        def state_transition(last_speaker, groupchat):  
            messages = groupchat.messages  
            if last_speaker is user_proxy:  
                return retriever  
            if last_speaker is retriever:  
                if messages[-1]["content"].lower() not in ['math_expert','physics_expert']:  
                    return user_proxy  
                else:  
                    if messages[-1]["content"].lower() == 'math_expert':  
                        return user_proxy  
                    else:  
                        return user_proxy  
            else:  
                pass  
                return None  

        async def query_graphRAG(  
              question: Annotated[str, '包含您希望从RAG搜索中获取的信息的查询字符串']  
                              ) -> str:  
            if LOCAL_SEARCH:  
                result = run_local_search(INPUT_DIR, ROOT_DIR, COMMUNITY ,RESPONSE_TYPE, question)  
            else:  
                result = run_global_search(INPUT_DIR, ROOT_DIR, COMMUNITY ,RESPONSE_TYPE, question)  
            await cl.Message(content=result).send()  
            return result  

        for caller in [retriever]:  
            d_retrieve_content = caller.register_for_llm(  
                description="为代码生成和问答检索内容。", api_style="function"  
            )(query_graphRAG)  

        for agents in [user_proxy, retriever]:  
            agents.register_for_execution()(d_retrieve_content)  

        groupchat = autogen.GroupChat(  
            agents=[user_proxy, retriever],  
            messages=[],  
            max_round=MAX_ITER,  
            speaker_selection_method=state_transition,  
            allow_repeat_speaker=True,  
        )  
        manager = autogen.GroupChatManager(groupchat=groupchat,  
                                           llm_config=llm_config_autogen,   
                                           is_termination_msg=lambda x: x.get("content", "") and x.get("content", "").rstrip().endswith("TERMINATE"),  
                                           code_execution_config=False,  
                                           )      

    # -------------------- 对话逻辑。编辑以根据您想要完成的任务更改您的第一条消息。 ----------------------------- #   
        if len(groupchat.messages) == 0:   
          await cl.make_async(user_proxy.initiate_chat)( manager, message=CONTEXT, )  
        elif len(groupchat.messages) < MAX_ITER:  
          await cl.make_async(user_proxy.send)( manager, message=CONTEXT, )  
        elif len(groupchat.messages) == MAX_ITER:    
          await cl.make_async(user_proxy.send)( manager, message="exit", )

对于此应用,我们只需要两个代理。你可以添加/修改代理,并配置“state_transition”函数来协调对话中的发言人选择,以适应更复杂的流程。

最终想法

这是我第一次涉足AI代理、大语言模型(LLMs)和检索增强生成(RAG),在过去几周里,我直接开始了这个实现,跳过了许多基础知识。虽然这个实现并不完美,但它是一个开发更复杂应用的优秀模板。它为集成多种功能和编写代理程序奠定了坚实的基础,应该能够帮助你构建复杂的流程,定制代理交互,并根据需要增强功能。

关于我:我是位于美国密歇根州南菲尔德的伊顿研究实验室的首席建模工程师。我探索、开发工具,并撰写有关计算力学、材料科学、工程学、语言模型和生成式AI交汇领域的内容。

如果你想保持更新,可以通过下面的方式关注我。

社交:LinkedIn, GitHub, 源代码

有意义的参考文献
  1. https://medium.com/@datadrifters/autogen-litellm-and-open-source-llms-c4c6bc8fa9c5
  2. https://medium.com/@rajib76.gcp/memory-default-compute-fallback-5ff4287d47e6
  3. https://medium.com/generative-ai/graphrag-the-rag-approach-by-microsoft-e1abc7eb9fba
  4. https://docs.chainlit.io/get-started/overview
  5. https://medium.com/@antoineross/autogen-web-application-using-chainlit-8c5ebf5a4e75
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
51
获赞与收藏
178

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消