Агентны RAG у LangGraph: грэйдары, умоўная маршрутызацыя і рэзервовы вэб-пошук
Пераносім лінейны RAG-канвеер LangChain у StateGraph LangGraph з LLM-грэйдарамі, умоўным уваходам і рэбрамі, тэстамі і endpoint-ам FastAPI, які паказвае паходжанне адказу.
Крыніцы: Agentic AI with LangChain — Part 2: Orchestrating Workflow by LangGraph
Семантычны ўрадок RAG, створаны з асновных элементаў LangChain, бэкенду FastAPI і фронтэнду-чатбота Streamlit, разматоўвае аплодаванні PDF-файлаў, ператварае дакументы на эмбедынгі і адпавядае на запытанні праз інтэрфейс чата. У яго таксама ёсць базовая мерыя: прыродныя элементы LangChain самі по сабе ствараюць лінейныя ланцюгі — простую лінію ад запытання праз LLM да парсера выходных даных. Усё, што трэбуе умовных галузей, цыклаў, дынамічнага маршрутавання аб адлічэнняў, якія выканае LLM, выходзіць за межы такога фармата, а простыя оператары if/else даўаюць толькі паверхневы, загадзя заданы прайсупутак.
LangGraph рашае гэтыя проблемы. Рэзультатам яе прыменення ёсць агентны рабочы працэс з такімі характэрыстыкамі:
- умовныя галузі і цыклы, якія ўбудованы ў сам урадок
- LLM, які выбірае между пошукам у інтэрнэце і адзысканнем даных па прынцыпе RAG, без жадных жорсткаяк закодаваных правіл
- пераходы, карыстаючыся вузламі і рэшткамі LangGraph
У канцыку вы маеце сістэму RAG з агентам, якая дае адказы як з базы вектараў, так і з Інтэрнэту.
Тэрміны LangGraph
Вокабуляр складаецца з чатырох ідэй. StateGraph керуе выкананнем. Стан — гэта тыпаваны слоўнік, які зберагае і апдэюе даны пайплайну. Вузлы — гэта функцыі на Python, якія зменяюць стан і выступаюць як елементы вычысленняў графа. Рэглі — гэта транзыцыі межва вузламі, а умовныя рэглі дазваляюць ствараць галужэння і цыклы.
Канфігурацыя
Закладзіце клучы API у файл .env у коране праекту:
OPENAI_API_KEY="<Your OpenAI API Key>"
VECTOR_DB=pinecone
Hugging_Face_TOKEN=<your huggingface token>
PINECONE_API_KEY=<your Pinecone API Key>
PINECONE_ENVIRONMENT=gcp-starter
LANGSMITH_TRACING=true
LANGSMITH_ENDPOINT="https://api.smith.langchain.com"
LANGSMITH_API_KEY="<Your LangSmith API Key"
LANGSMITH_PROJECT="agent-ai"
TAVILY_API_KEY=<Your TAVILY API Key>
Потым прыняце іх у класе Pydantic BaseSettings:
class BaseConfig(BaseSettings):
OPENAI_API_KEY: Optional[str]
PINECONE_API_KEY: Optional[str]
TAVILY_API_KEY: Optional[str]
"""Loads the dotenv file. Including this is necessary to get
pydantic to load a .env file."""
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
Структура праекта
Структура папак выглядае так:
project/
├── rag_process/
│ ├── __init__.py
│ ├── extractor.py
| └── service.py
│
├── graphs
│ │── chains.py # llm chains
│ ├── nodes.py # graph nodes. Defined as Python functions
│ ├── graph.py # graph workflow
│ ├── consts.py # define node names as constants
│ └── state.py # typed dictionary to store graph states
|── schemas.py # pydantic data models
|── upload.py # python functions to upload files asynchronously
├── main.py # fastAPI app
├── config.py # pydantic BaseConfig
│── dependencies.py # dependency inject to fastAPI endpoint function
├── test_chains.py # pytests
├── .env # environment variable definition
├── client.py # streamlit client
├── .gitignore
└── requirements.txt # package requirements
Ланцюгі звяроўкі, сервіс FastAPI і кліент Streamlit перанесены з пакета, які быў выкарыстоўваны раней. Новымі элементамі ёсць граф, клас яго стану і алгорытмы оцэнкі, якія пераглядаюць звяроўаныя дакументы на адпаведнасць, выкрываюць галюцынаціі і вяршаюць, чы рэспансат адпаведае запытку.
Ланцюгі LLM
Ланцюгі знаходзяцца ў файле graphs/chains.py і выкарыстоўваюць мову выразав LangChain (LCEL). Запісаны семь ланцюгаў:
retrieve_documentsзапоўнюе спіс з Топ-K дакументаў, якія ўзаемна схожы з дакументамі у Pineconegenerate_answerстварае рэспансат на запытку разам з кантэкстамretrival_graderвяршае, чы звяроўаныя дакументы ёсць адпаведныміweb_searchадбывае пошук у інтэрнэце і вяртае кантэкстanswer_graderпераглядае, чы рэспансат адпаведае запытку
hallucination_grader пераканальваець, чы рашчынанне ўзгадваецца ў кантэксцеselect_routes адправляе запытанні пра агентаў, інжынерыю прапанаў і адварсальныя атакі да RAG, а ўсё інша — да пошуку ў інтэрнэцеТэсты на аддзелы
У сістэмах з агентамі тэставанне мае ўжо большое значэнне, чым зазвычай, таму што выходы LLM ёсць недэтэрміністычнымі і можу змяніцца праз абнаўленні модэляў, налагоджэнне іх з боку вэндараў або змены эмбедынгаў. Тэсты ў test_chains.py выкарыстоўваюць pytest з pytest.mark.asyncio для асінхронных аддзелаў. Позытыўны тэст для адзысквання даных у RAG выглядае так:
@pytest.mark.asyncio
async def test_rag_retrieval_yes() -> None:
prompt = "what is prompt engineer ?"
searched_documents = await retrieve_documents(prompt)
searched_document = searched_documents[0]
# logger.info(f"searched_content: {searched_document.page_content}")
retrieval_grade = await retrieval_grader(searched_document, prompt)
assert retrieval_grade
У яго запытваець пра інжынерыі прапаноў, а першы знайдзены дакумент перадаецца функцыі retrieval_grader. Паколькі гэты тэматык прадстаўлены ў дакументах, якія ужо знаходзяцца ў Pinecone, функцыя градэра должна вернуць True. Тэст заходзіць проты нештарабельнасці, калі змянююцца эмбеддынгі аб повадкі модэлю, рана выяўляе рэгрэсіі пасля апдэйтаў модэлю і пераканальваецца ў логіцы направлення даных между RAG і пошукам у Інтэрнэце. Адпаведны негатыўны тэст паўтарае, што функцыя градэра вернула False, і тады працэс пераходзіць да пошуку у Інтэрнэце.
Стан і вузлы
Стан — гэта тыпаваны слоўнік, які зберагаецца ў файле graphs/state.py:
class GraphState(TypedDict):
question: str
generation: str
web_search: bool
documents: list[str]
Узлы чытаюць і апдэююць гэты стан. Яны вялікія ў файле graphs/nodes.py — цэлкава звычныя функцыі Python, якія чытаюць стан, вызываюць ланцюгі LLM і вяртаюць апдэйтаваныя поля. Узел, які вікорыстоўваецца ў умовнай грані, вяртае назву наступнага узла для выканання. Іх семь.
generateвызываеgenerate_answerі вяртае адпаведны адказ.retrieveвызываеretrieve_documentsі вяртае дакументы.search_webвызываеweb_searchі дадае рэзультаты да дакументаў.grade_documentsзаставляе толькі актуальныя дакументы і, калі трэба, ставляеweb_search=True.grade_generationзапускае механізмы ацэнкі галюцынацый і адказаў і вяртае значэння „падмоглівае“, „не падмоглівае“ або „не падтрымванае“.
decide_to_generate выбірае WEBSEARCH або GENERATE залежна ад web_search.route_question адправляе запыткі, які стосуюцца агента, да RAG, а іншыя — да пошуку ў Інтэрнэце.Стварэнне StateGraph
Сам граф складаецца ў graphs/graph.py, а готавы працэс выглядае як розгалужаны поток між адзысканнем інфармацыі, ўражэнням яе якосці, генераваннем тэксту і пошукам. Яго стварэнне абарачаецца кількамі крокамі.
Спачатку кожнаму вузлу даецца строчковы ідэнтыфікатор. Зберагчы гэтыя назвы як константы ў graphs/consts.py упроствае чытанне графа і запобегае памылкам у напісанні:
RETRIEVE = "retrieve"
GRADE_DOCUMENTS = "grade_documents"
GENERATE = "generate"
WEBSEARCH = "websearch"
Далей створыце StateGraph, які будзе вяртаваць станы кожых узелоў:
workflow = StateGraph(GraphState)
workflow.add_node(RETRIEVE, retrieve)
workflow.add_node(GRADE_DOCUMENTS, grade_documents)
workflow.add_node(GENERATE, generate)
workflow.add_node(WEBSEARCH, search_web)
Кожны раз, калі вызываецца add_node, імя прыязначаецца да функцыі, і такое раздзелэнне іменаў і ўсіх дзеянняў спрыяе лёгкамым адтрыманню, візуалізацыі і расшырэнню рабочага працэсу.
У трохце, неабходна задаць умовны пункт запуску. У працоўнік LangGraph можна, замест таго, каб жорсткая кодаваць пачатковую вузел, дазволіць функцыі пераглянуць запіт і выбраць першы маршрут. Хутчэй route_question вяршыць, чы робіцца пачатак з пошуку ў інтэрнэце чы з адзысквання дадзеных за дапамогою RAG:
workflow.set_conditional_entry_point(
route_question,
{
WEBSEARCH: WEBSEARCH,
RETRIEVE: RETRIEVE,
}
)
Гэта функцыя запускаецца першай і вяртае або "WEBSEARCH", або "RETRIEVE" залежна ад тэмы, і LangGraph выкарыстоўвае яе значэнне для выбору пачатковага вузла. Запыткі, якія стосуюцца агента, направляюцца да RAG, а запыткі з загальнымі ведамасцямі пачынаюцца з пошуку ў інтэрнэце.
Чатверты пункт – з’яеднаваць вузлы за дапамогою рэшэтаў. Статычныя рэшэты завжды следуюць аднаму шляху, тады калі умовныя рэшэты выбіраюць наступны вузел на адной з адмов LLM:
workflow.add_edge(RETRIEVE, GRADE_DOCUMENTS)
workflow.add_conditional_edges(
GRADE_DOCUMENTS,
decide_to_generate,
{
WEBSEARCH: WEBSEARCH,
GENERATE: GENERATE,
},
)
workflow.add_conditional_edges(
GENERATE,
grade_generation,
{
"not supported": GENERATE,
"useful": END,
"not useful": WEBSEARCH,
}
)
workflow.add_edge(WEBSEARCH, GENERATE)
workflow.add_edge(GENERATE, END)
Разам этыя грані ствараюць дынамічны працэс выканання задач, у яком LLM выбірае пачатковы маршрут, значэнне адпрацоўванага дакумента вялічынае выбор между методам RAG і пошукам у Інтэрнэце, а якасць адпаведзення вялічынае рашэнне пра тое, чы робіць далейшую перработу, вяртацца да пачатковага падходу чы завершваць задачу. Такая структура ператварае простую схему RAG у болей моцную агентскую сістэму.
На завершанне складзіце граф і, за пажылу, накрэсліце яго:
app = workflow.compile()
app.get_graph().draw_mermaid_png(output_file_path="graphs.png")
Выдача праз FastAPI
У файле dependencies.py функцыя імпортуе скомпіляваны граф і запускае яго асінхронна:
from graphs.graph import app
async def get_generation(body: RAGRequest=Body(...)) -> dict:
try:
generation = await app.ainvoke(body.dict())
return {
"answer": generation.get("generation", ""),
"web_search": generation.get("web_search", False)
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
FastAPI вводзіць яго як залежна вяласць, і ён вяртае створаны адказ плюс флаг, які паказвае, чы рэшчаць быў атрыманы з RAG чы праз пошук у інтэрнэце.
Пробаваць
Фронтэнд Streamlit з пакульшай схемы можа быць перадпрацоўваны без змян. Запіт пра тое, як стаць мільйонерам, праверна направляе на пошук у інтэрнэце, тады як запіт пра тое, што такое prompt engineering, направляе на семантычны пат каналу RAG. эта дазволяе атрымляць дынамічныя направлення разам з чыстаю інформацыяй пра паводзеўкі кожнага адказу.
Ёсць калі што падсумаваць: LangGraph StateGraph цяпер карыстуецца станам, рабочы працэс мае дынамічныя пераходы з петлямі і галузямі праз умовныя рэшткі, а інструменты для оценкі LLM карыстуюцца документамі для выявлення рэлевантнасці, адкрыць галюцынацый і якосці адказу, а API паведамляе корыстніка, чы рэшчаць быў атрыманы з RAG чы з інтэрнэту. Наступны крок у гэй працы — адналежнае вжыванне інструментоў і фрэймворку ReAct, каб досягнуць ўсё боле агентскага стылю працы.