与依赖矢量相似性搜索的传统 RAG 方法不同,GraphRAG 可从原始文本中构建结构化知识图谱,捕捉实体、关系和关键主张。这可以提高 LLM 理解和综合复杂数据集的能力,从而得到更准确、更符合上下文的回复。将 GraphRAG 的检索增强生成(RAG)优势与 AutoGen 人工智能代理的对话和任务导向功能相结合,就能产生强大的人工智能助手,能够高效处理详细查询、生成和执行代码、创建多页科学报告并进行数据分析。此外,离线本地 LLM(如 Ollama 或 LM Studio 的 LLM)与 GraphRAG 和 AutoGen 可确保数据处理的成本效益和安全性。本地 LLM 消除了与在线 LLM 相关的高成本和隐私风险,将敏感数据保留在组织内部,降低了运营成本。
以下是此次开发的关键组成部分:
开发是在 Linux 环境中使用 Windows Subsystem for Linux (WSL) 和 Visual Studio Code 在 Windows 11 PC 上完成的,该 PC 配备 i9 13th Gen 处理器、64 GB 内存和 24 GB Nvidia RTX 4090。为获得最佳的开发和测试体验,建议使用 Linux 发行版或 WSL。我尚未在原生 Windows 环境中进行过测试。
我通过询问与ABAQUS 文档相关的问题以及供应商 Toray 的碳纤维技术数据表相关的问题来测试该系统。鉴于我在材料科学和计算建模方面的背景,这些都是自然而然的选择。我的目标是从该领域的科学期刊和数据中构建更复杂的知识图谱,测试高级工程代码生成任务,并利用对话助手对我专业领域内的科学话题进行集思广益。应用程序如下所示:
安装模型依赖项和克隆源
从 Ollama 安装用于推理和嵌入的语言模型
# Mistral for GraphRAG Inference
ollama pull mistral
# Nomic-Embed-Text for GraphRAG Embedding
ollama pull nomic-embed-text
# LLama3 for Autogen Inference
ollama pull llama3
# Host Ollama on a local server: http://localhost:11434
ollama serve
创建 conda 环境并安装这些依赖项
# Create and activate a conda environment
conda create -n RAG_agents python=3.12
conda activate RAG_agents
# Lite-LLM proxy server for Ollama
pip install 'litellm[proxy]'
# Install Ollama
pip install ollama
# Microsoft AutoGen
pip install pyautogen "pyautogen[retrievechat]"
# Microsoft GraphRAG
pip install graphrag
# Text-Token Encoder-Decoder
pip install tiktoken
# Chainlit Python application
pip install chainlit
# Clone my Git-hub repository
git clone https://github.com/karthik-codex/autogen_graphRAG.git
# (BONUS) To Convert PDF files to Markdown for GraphRAG
pip install marker-pdf
# (BONUS) Only if you installed Marker-pdf since it removes GPU CUDA support by default
conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia
你可以在我的 GitHub 仓库中找到以下文件。
创建 GraphRAG 知识库
在知识库根目录下初始化 GraphRAG
#make a new folder "input" to place your input files for GraphRAG (.txt or .md)
mkdir -p ./input
# Initialize GraphRAG to create the required files and folders in the root dir
python -m graphrag.index --init --root .
# Move the settings.yaml file to replace the one created by 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 # or azure_openai_chat
model: mistral
model_supports_json: true
api_base: http://localhost:11434/v1
.
.
.
embeddings:
async_mode: threaded # or asyncio
llm:
api_key: ${GRAPHRAG_API_KEY}
type: openai_embedding # or azure_openai_embedding
model: nomic_embed_text
api_base: http://localhost:11434/api
.
.
.
input: #Change input file pattern to.md, or .txt
type: file # or blob
file_type: text # or csv
base_dir: "input"
file_encoding: utf-8
file_pattern: ".*\\.md$"
你可以在根目录下的 “input ”文件夹中指定包含输入文件的文件夹。文本和标记符文件都可以使用。你可以使用/utils/pdf_to_markdown.py 将 PDF 转换为 markdown 文件,然后将其放置在 “input ”文件夹中。处理多种文件格式的问题尚未解决,但这是一个可以解决的问题。
在运行 GraphRAG 进行索引、创建嵌入和本地查询之前,你必须修改位于 GraphRAG 软件包中的 Python 文件 openai_embeddings_llm.py 和 embedding.pyl。如果不进行修改,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)。
# Create knowledge graph - this takes some time
python -m graphrag.index --root .
# Test GraphRAG
python -m graphrag.query --root . --method global "<insert your query>"
启动 Lite-LLM 服务器并从终端运行应用程序
下面是运行应用程序前初始化服务器的命令。我选择了 Llama3:8b 来测试此应用程序。如果硬件条件允许,可以使用更大的型号。有关 Lite-LLM 的更多信息,请访问此链接。现在你可以从另一个终端运行应用程序了。确保处于正确的 conda 环境中。
# start server from terminal
litellm --model ollama_chat/llama3
# run app from another terminal
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
你会发现从 chainlit_agents 中导入了两个类。这些 AutoGen 代理的封装类能让 Chainlit 跟踪它们的对话,并处理终止或其他用户输入。
配置 AutoGen 代理
AutoGen 代理通过 Lite-LLM 代理服务器使用来自 Ollama 的模型。这是必要的,因为 AutoGen 不支持通过非 OpenAI 推理模型调用函数。代理服务器允许使用 Ollama 模型进行函数调用和代码执行。
# LLama3 LLM from Lite-LLM Server for Agents #
llm_config_autogen = {
"seed": 40, # change the seed for different trials
"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 选项选择社区生成级别。
@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="""Only execute the function query_graphRAG to look for context.
Output 'TERMINATE' when an answer has been provided.""",
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='''A human admin. Interact with the retriever to provide any context''',
description="User Proxy Agent"
)
print("Set agents.")
cl.user_session.set("Query Agent", user_proxy)
cl.user_session.set("Retriever", retriever)
msg = cl.Message(content=f"""Hello! What task would you like to get done today?
""",
author="User_Proxy")
await msg.send()
print("Message sent.")
except Exception as e:
print("Error: ", e)
pass
我选择不使用检索器助理代理的 Chainlit 封装类。这样,我就可以禁用对 Retriever 输出的跟踪,直接捕获 GraphRAG 函数的响应。原因是当响应通过检索器时,文本会丢失格式,包括空格和段落缩进。在生成带有主标题和副标题的多页报告时,这个问题尤其明显。我可以绕过 Chainlit 包装器,直接从 GraphRAG 函数中获取输出,从而保留原始格式。
更新输入设置中的更改
该函数可检测对选择、切换和滑块部件设置所做的任何更改,以便在后续查询中反映这些更改。
@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 参数,这些参数会根据 bool 参数 LOCAL_SEARCH 传入本地和全局搜索 GraphRAG 查询函数。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("Running conversation")
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("Setting groupchat")
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, 'Query string containing information that you want from RAG search']
) -> 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="retrieve content for code generation and question answering.", 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,
)
# -------------------- Conversation Logic. Edit to change your first message based on the Task you want to get done. ----------------------------- #
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 ”功能,以便在会话中协调发言人的选择,从而实现更复杂的工作流程。
结论
虽然这个实现并不完美,但它是开发更复杂应用的绝佳模板。它为集成多种功能和编码代理打下了坚实的基础,应能让你构建复杂的工作流、定制代理交互并根据需要增强功能。