تولید افزوده با بازیابی (Retrieval-augmented generation یا RAG) به عنوان روشی قدرتمند برای افزایش دقت و ارتباط زمینهای خروجیهای هوش مصنوعی مولد پدیدار شده است. رویکردهای سنتی عمدتاً بر جستجوی شباهت معنایی در ذخیرهسازهای برداری (vector stores) تکیه داشتهاند. این رویکرد، با وجود مؤثر بودن، محدودیتهای ذاتی دارد. ممکن است روابط زمینهای ظریف یا ارتباطات ساختاریافته بین اسناد را نادیده بگیرد.
روشهای RAG مبتنی بر گراف، که تکنیکهای RAG را به طرق مختلف با گرافهای دانش (knowledge graphs) ادغام میکنند، نوید دقت بیشتری را میدهند، اما پیادهسازی آنها به طور بدنامی چالشبرانگیز بوده است. پیش از این، ساخت، پیمایش و نگهداری این «گرافهای دانش» دشوار بود. این کار شامل استخراج دستی روابط ساختاریافته از اسناد، پایگاههای داده گراف ایستا و غیرقابل انعطاف و زیرساختهای اختصاصی پایگاه داده گراف میشد.
خوشبختانه، پیشرفتهای اخیر - به ویژه ابزارهایی مانند Unstructured و کتابخانه تازه منتشر شده Graph Retriever - این گردش کارها را به شدت ساده کردهاند. Unstructured با استفاده از پرامپتنویسی سفارشی مدلهای زبان بزرگ (LLM) پیشرفته برای استخراج خودکار موجودیتها و پایگاههای داده برداری برای ذخیرهسازی، تبدیل اسناد بدون ساختار به دادههای ساختاریافته و آماده برای گراف را با یک کلیک فراهم میکند. سپس کتابخانه Graph Retriever به صورت پویا پرسوجوهای مبتنی بر گراف را بر روی این ذخیرهسازهای برداری غنی از فراداده (metadata) ایجاد میکند و نیاز به پایگاههای داده گراف اختصاصی را از بین میبرد.
در اینجا، ما نسل جدیدی از ابزارها را که GraphRAG را ساده میکنند، با مرور یک مثال کاربردی بررسی خواهیم کرد.
GraphRAG چگونه کار میکند
GraphRAG از بسیاری از ابزارها و تکنیکهای مشابه RAG مبتنی بر شباهت معنایی سنتی استفاده میکند، اما همچنین برخی ویژگیهای مهم را اضافه میکند:
- اسناد با فراداده ساختاریافته، مانند موجودیتها (افراد، مکانها، سازمانها)، غنی میشوند.
- یک گراف به صورت پویا بر اساس این فراداده ساختاریافته ساخته میشود که روابط صریح بین اسناد را ثبت میکند.
- بازیابی با پیمایش این اتصالات ساختاریافته انجام میشود و امکان بازیابی اسناد مرتبطتر از نظر زمینهای را فراهم میکند.
این رویکرد ساختاریافته، پیمایش زمینه (context navigation) برتری را ارائه میدهد و به اپلیکیشنها امکان میدهد اسنادی را نه تنها از نظر معنایی، بلکه بر اساس روابط و موجودیتهای موجود در فراداده به طور صریح، بازیابی کنند.
نقش Unstructured و کتابخانه Graph Retriever
Unstructured
همانند اکثر مشکلات هوش مصنوعی/یادگیری ماشین، دادههای با کیفیت بالا ضروری هستند. در GraphRAG، داشتن فراداده دقیق برای هر سند یا تکه سند بسیار مهم است، زیرا فراداده اساس ساخت و استفاده از گراف دانش است. به طور سنتی، تخصیص فراداده شبیه برچسبگذاری دستی یک مجموعه داده بوده است، اما Unstructured فراداده گسترده و قابل توسعهای را به صورت آماده ارائه میدهد. با بهبودهایی در ETL استاندارد (استخراج، تبدیل، بارگذاری) مانند پرامپتنویسی سفارشی، Unstructured از تشخیص موجودیت نامگذاری شده (NER) مبتنی بر LLM برای تولید خودکار جفتهای کلید-مقدار فراداده استفاده میکند. این به نوبه خود، دقت بازیابی را افزایش میدهد.
ETL+ برای GenAI از Unstructured به طور مداوم دادههای بدون ساختار تازه تولید شده را از سیستمهای ثبت برداشت میکند، آنها را با استفاده از پایپلاینهای بهینهسازی شده و از پیش ساخته شده به فرمتهای آماده برای LLM تبدیل میکند و آنها را در DataStax Astra DB مینویسد. شما میتوانید پایپلاینهای کامل دریافت و پیشپردازش را در چند ثانیه مستقر کنید، با گزینههای پیکربندی و ادغامهای شخص ثالث برای مراحل پارتیشنبندی، غنیسازی، تکهتکه کردن و تعبیهسازی. این امکان ساخت گراف دانش را بدون نیاز به نوشتن هیچ کدی یا ایجاد هیچ مرحله سفارشی فراهم میکند. مرحله حیاتی غنیسازی NER را میتوان به راحتی در پایپلاین کامل ETL+ که در رابط کاربری یا API Unstructured موجود است، پیکربندی کرد:
و پرامپت شما میتواند سفارشیسازی و آزمایش شود تا اطمینان حاصل شود که فراداده شما موجودیتهایی را که میخواهید استخراج شوند و همچنین فرمت پاسخ مورد نیاز برای گراف دانش شما را ثبت میکند:
رویکرد اعلانی Unstructured به افراد غیرتوسعهدهنده امکان میدهد تا با اتصال ساده مؤلفهها، گردش کار ایجاد کنند. این نه تنها توسعه را تسریع میکند، بلکه اجرای کارآمد در مقیاس را نیز تضمین میکند، زیرا گردش کارها به طور یکپارچه بر روی Unstructured اجرا میشوند.
کتابخانه Graph Retriever
کتابخانه متنباز Graph Retriever بر پایه ذخیرهسازهای برداری LangChain ساخته شده است و امکان ساخت گراف پویا از فراداده ساختاریافته (در این مورد، فراداده تولید شده توسط Unstructured) را فراهم میکند. این به اپلیکیشنها اجازه میدهد تا گرافها را به صورت پویا در زمان اجرا بسازند، انعطافپذیری بازیابی، آگاهی از زمینه و دقت را بدون زیرساختهای پیچیده اضافی افزایش میدهد.
گام به گام: استفاده از Unstructured برای غنیسازی اسناد
تمام این مراحل را میتوان بدون کد در رابط کاربری Unstructured با دنبال کردن مراحل راهاندازی در مرورگر پلتفرم انجام داد. به طور متناوب، ما یک نوتبوک با پایپلاین کامل ETL + غنیسازی از طریق API Unstructured با استفاده از Workflow Endpoint ارائه کردهایم. این نوتبوک پیشنیازی برای جریان کامل GraphRAG است که فرض میکند یک گردش کار از قبل راهاندازی شده است. در زیر، چند مرحله کلیدی از نوتبوک ایجاد گردش کار لینک شده را برجسته میکنیم.
پس از تنظیم اعتبارنامههای خود (همانطور که در نوتبوک توضیح داده شده است)، کانکتور مقصد Astra DB خود را ایجاد کنید:
import os
from unstructured_client import UnstructuredClient
from unstructured_client.models.operations import CreateDestinationRequest
from unstructured_client.models.shared import (
CreateDestinationConnector,
DestinationConnectorType,
AstraDBConnectorConfigInput
)
with UnstructuredClient(api_key_auth=os.getenv("UNSTRUCTURED_API_KEY")) as client:
destination_response = client.destinations.create_destination(
request=CreateDestinationRequest(
create_destination_connector=CreateDestinationConnector(
name="graphrag_astra_destination",
type=DestinationConnectorType.ASTRADB,
config=AstraDBConnectorConfigInput(
token=os.environ.get('ASTRA_DB_APPLICATION_TOKEN'),
api_endpoint=os.environ.get('ASTRA_DB_API_ENDPOINT'),
collection_name=os.environ.get('ASTRA_DB_COLLECTION_NAME'),
keyspace=os.environ.get('ASTRA_DB_KEYSPACE'),
batch_size=20,
flatten_metadata=True
)
)
)
)
سپس، میتوانید تمام گرهها (nodes) را برای گردش کار خود ایجاد کنید:
from unstructured_client.models.shared import (
WorkflowNode,
WorkflowNodeType,
WorkflowType,
Schedule
)
# Partition the content by using a vision language model (VLM).
partition_node = WorkflowNode(
name="Partitioner",
subtype="vlm",
type=WorkflowNodeType.PARTITION,
settings={
"provider": "anthropic",
"provider_api_key": None,
"model": "claude-3-5-sonnet-20241022",
"output_format": "text/html",
"user_prompt": None,
"format_html": True,
"unique_element_ids": True,
"is_dynamic": True,
"allow_fast": True
}
)
# Summarize each detected image.
image_summarizer_node = WorkflowNode(
name="Image summarizer",
subtype="openai_image_description",
type=WorkflowNodeType.PROMPTER,
settings={}
)
# Summarize each detected table.
table_summarizer_node = WorkflowNode(
name="Table summarizer",
subtype="anthropic_table_description",
type=WorkflowNodeType.PROMPTER,
settings={}
)
# Chunk the partitioned content.
chunk_node = WorkflowNode(
name="Chunker",
subtype="chunk_by_title",
type=WorkflowNodeType.CHUNK,
settings={
"unstructured_api_url": None,
"unstructured_api_key": None,
"multipage_sections": False,
"combine_text_under_n_chars": 0,
"include_orig_elements": True,
"new_after_n_chars": 1500,
"max_characters": 2048,
"overlap": 160,
"overlap_all": False,
"contextual_chunking_strategy": None
}
)
# Label each recognized named entity.
named_entity_recognizer_node = WorkflowNode(
name="Named entity recognizer",
subtype="openai_ner",
type=WorkflowNodeType.PROMPTER,
settings={
"prompt_interface_overrides": {
"prompt": {
"user": (
"Extract all named entities, including people and locations, from the given text segments "
"and provide structured metadata for each entity identified.\n\n"
'Response format: {{"PLACES": ["England", "Middlesex"]}}'
)
}
}
}
)
# Generate vector embeddings.
embed_node = WorkflowNode(
name="Embedder",
subtype="azure_openai",
type=WorkflowNodeType.EMBED,
settings={
"model_name": "text-embedding-3-large"
}
)
توجه داشته باشید که `named_entity_recognizer_node` فراداده را برای گرهها و یالها ایجاد میکند و قرار دادن آن پس از گره تکهتکه کردن (chunking node) بسیار مهم است، چه گردش کار خود را از طریق رابط کاربری ایجاد کنید و چه از طریق API.
این پرامپتی است که گردش کار بالا شامل آن میشود، که هم انواع موجودیتهای قابل استخراج و هم فرمت پاسخی را که برای ساخت گراف دانش استفاده خواهد شد، مشخص میکند.
Extract all named entities, including people, and locations, from the given text segments and provide structured metadata for each entity identified.
Response format: {"PLACES": ["England" , "Middlesex"] }
در مرحله بعد، پس از راهاندازی گردش کار (برای کد کامل به نوتبوک ایجاد گردش کار مراجعه کنید)، آن را اجرا کرده و پاسخها را مشاهده خواهیم کرد:
from unstructured_client.models.operations import RunWorkflowRequest
response = client.workflows.run_workflow(
request=RunWorkflowRequest(
workflow_id=info.id
)
)
print(response.raw_response)
این به طور خودکار موجودیتهایی مانند افراد (مانند "نیوتن") و مکانها ("Woolsthorpe") را ثبت میکند و آنها را مستقیماً در فراداده ساختاریافته تعبیه میکند:
{
"content": "Newton was born on 25 December 1642 in Woolsthorpe, Lincolnshire, England. He died on 20 March 1726/27 in Kensington, Middlesex, England.",
"metadata": {
"type": "NarrativeText",
"element_id": "bd2de89e6b456f86e8ef09391fc3c4b9",
"metadata": {
"entities": {
"PEOPLE": [
"Newton"
],
"PLACES": [
"Woolsthorpe",
"Lincolnshire",
"England",
"Kensington",
"Middlesex"
]
}
}
}
}
این فراداده ساختاریافته اساس ساخت گراف پویا را تشکیل میدهد. و تمام موارد فوق را میتوان به طور متناوب بدون حتی یک خط کد در رابط کاربری Unstructured ساخت.
گام به گام: بهرهگیری از Graph Retriever برای بازیابی پویا
کتابخانه Graph Retriever شما را قادر میسازد تا جستجوی شباهت بدون ساختار را با پیمایش گراف ساختاریافته ترکیب کنید. برخلاف پایگاه داده گراف اختصاصی، کتابخانه Graph Retriever بر روی ذخیرهسازهای برداری ساخته میشود و از فراداده برای ایجاد پویا اتصالات بین اسناد استفاده میکند.
در بخش قبل، دیدیم که چگونه میتوان از Unstructured برای پر کردن Astra DB با تکههای سند و فراداده مرتبط استفاده کرد. با آماده بودن فراداده غنیشده، Graph Retriever میتواند به صورت پویا بازیابگرهای مبتنی بر گراف ایجاد کند. قبل از اینکه چنین بازیابگری بسازیم، بیایید ببینیم چگونه میتوان از این مجموعه داده برای ساخت پرسوجوهای RAG سنتی استفاده کرد.
ابتدا، باید مدل تعبیهسازی را مشخص کرده و ذخیرهساز برداری را راهاندازی کنید. سپس، میتوانید یک بازیابگر پایه بسازید و اطلاعات را جستجو کنید. به عنوان مثال، برای یافتن اطلاعات در مورد افلاطون (Plato):
from langchain_astradb import AstraDBVectorStore
from langchain_openai import OpenAIEmbeddings
embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = AstraDBVectorStore(
collection_name=os.getenv("ASTRA_DB_COLLECTION"),
namespace=os.getenv("ASTRA_DB_KEYSPACE"),
embedding=embedding_model,
)
query_text = "Information about Plato"
results = vectorstore.similarity_search(query_text, k=3)
# Sample output (translated to Persian for clarity)
# Plato
# {'PEOPLE': 'Plato'}
# -------------------
# Plato was a philosopher in Classical Greece and the founder of the Academy
# in Athens, the first institution of higher learning in the Western world. He
# is widely considered one of the most pivotal figures in the development of
# Western philosophy.
# {'EVENTS': ['Development of Western philosophy'],
# 'PEOPLE': ['Plato'],
# 'PLACES': ['Classical Greece', 'Athens']}
# -------------------
# Plato was born in 428/427 or 424/423 BC in Athens, Greece. He died in
# 348/347 BC in Athens, Greece.
# {'DATES': ['428/427 BC', '424/423 BC', '348/347 BC'],
# 'PEOPLE': ['Plato'],
# 'PLACES': ['Athens', 'Greece']}
# -------------------
همانطور که میبینید، این پرسوجو به درستی تکههای سند توصیفکننده افلاطون را بازیابی کرد، اما توجه داشته باشید که نتایج به طور محدودی بر افلاطون متمرکز شدهاند. Graph Retriever ما را قادر میسازد تا مجموعه متنوعتری از نتایج را بازیابی کنیم و اطلاعات زمینهای غنیتری برای پرسوجوی خود فراهم کنیم.
برای استفاده از Graph Retriever، باید ذخیرهساز برداری، یالهای گراف و استراتژی جستجو را مشخص کنید. پیکربندی "یالها" (edges) شمای گراف را توصیف میکند؛ در این مورد، ما بازیابگر را برای اتصال اسناد مرتبط با یک مکان معین پیکربندی کردهایم. به طور مشابه، پیکربندی "استراتژی" (strategy) نحوه استفاده از گراف را توصیف میکند. در این مورد، ما با جستجوی سه سند مرتبط با افلاطون شروع میکنیم، و سپس با جستجو برای اسناد اضافی مرتبط با مکانهایی که در اسناد اولیه ذکر شدهاند، گسترش مییابیم.
from langchain_community.retrievers.graph_retriever import GraphRetriever
graph_retriever = GraphRetriever(
vectorstore=vectorstore,
edges=[{"key": "PLACES"}],
strategy="SIMILARITY_THEN_ENTITY_NODE",
strategy_args={"k": 3, "entity_node_distance": 1},
)
graph_retriever.get_relevant_documents("Information about Plato")
# Sample output (translated to Persian for clarity)
# افلاطون
# {'PEOPLE': 'Plato'}
# -------------------
# افلاطون فیلسوفی در یونان کلاسیک و بنیانگذار آکادمی
# در آتن، اولین مؤسسه آموزش عالی در جهان غرب بود. او
# به طور گسترده یکی از مهمترین چهرهها در توسعه
# فلسفه غرب در نظر گرفته میشود.
# {'EVENTS': ['توسعه فلسفه غرب'],
# 'PEOPLE': ['Plato'],
# 'PLACES': ['یونان کلاسیک', 'آتن']}
# -------------------
# افلاطون در ۴۲۸/۴۲۷ یا ۴۲۴/۴۲۳ قبل از میلاد در آتن، یونان متولد شد. او در
# ۳۴۸/۳۴۷ قبل از میلاد در آتن، یونان درگذشت.
# {'DATES': ['۴۲۸/۴۲۷ قبل از میلاد', '۴۲۴/۴۲۳ قبل از میلاد', '۳۴۸/۳۴۷ قبل از میلاد'],
# 'PEOPLE': ['Plato'],
# 'PLACES': ['آتن', 'یونان']}
# -------------------
# سقراط فیلسوفی یونانی اهل آتن بود که به عنوان یکی از بنیانگذاران
# فلسفه غرب و به عنوان اولین فیلسوف اخلاق سنت فکری غربی
# اعتبار داده میشود. او چهرهای مرموز است که هیچ نوشتهای از خود به جا نگذاشت و
# عمدتاً از طریق گزارشهای نویسندگان کلاسیک که پس از او نوشتهاند، به ویژه شاگردانش
# افلاطون و گزنفون شناخته میشود.
# {'PEOPLE': ['Socrates', 'Plato', 'Xenophon'], 'PLACES': ['Athens']}
# -------------------
همانطور که انتظار میرفت، این نتایج متنوعتر هستند و شامل اسناد مرتبط با سقراط (Socrates) نیز میشوند، زیرا تکههای اصلی سند مرتبط با افلاطون، مکان "آتن" ("Athens") را ذکر کرده بودند.
با بهرهگیری از گردش کار پیشپردازش قوی Unstructured و قابلیتهای بازیابی پویای Graph Retriever، توسعهدهندگان میتوانند به راحتی اپلیکیشنهای GraphRAG پیچیده بسازند. این نه تنها دقت و آگاهی از زمینه را افزایش میدهد، بلکه فرآیند توسعه را نیز به طور قابل توجهی ساده میکند.