Галоўная / Артыкулы

Агентны RAG у LangGraph: грэйдары, умоўная маршрутызацыя і рэзервовы вэб-пошук

Пераносім лінейны RAG-канвеер LangChain у StateGraph LangGraph з LLM-грэйдарамі, умоўным уваходам і рэбрамі, тэстамі і endpoint-ам FastAPI, які паказвае паходжанне адказу.

Выдавец: REACTAPP.TOP7 хв чытання1605 слоў

Крыніцы: Agentic AI with LangChain — Part 2: Orchestrating Workflow by LangGraph

Семантычны ўрадок RAG, створаны з асновных элементаў LangChain, бэкенду FastAPI і фронтэнду-чатбота Streamlit, разматоўвае аплодаванні PDF-файлаў, ператварае дакументы на эмбедынгі і адпавядае на запытанні праз інтэрфейс чата. У яго таксама ёсць базовая мерыя: прыродныя элементы LangChain самі по сабе ствараюць лінейныя ланцюгі — простую лінію ад запытання праз LLM да парсера выходных даных. Усё, што трэбуе умовных галузей, цыклаў, дынамічнага маршрутавання аб адлічэнняў, якія выканае LLM, выходзіць за межы такога фармата, а простыя оператары if/else даўаюць толькі паверхневы, загадзя заданы прайсупутак.

LangGraph рашае гэтыя проблемы. Рэзультатам яе прыменення ёсць агентны рабочы працэс з такімі характэрыстыкамі:

  • умовныя галузі і цыклы, якія ўбудованы ў сам урадок
  • LLM, які выбірае между пошукам у інтэрнэце і адзысканнем даных па прынцыпе RAG, без жадных жорсткаяк закодаваных правіл
  • пераходы, карыстаючыся вузламі і рэшткамі LangGraph
  • Сістэмы ацэнкавання, базаваныя на LLM, якія ацэнкаюць запрашаваны кантэкст і створаныя адказы
  • Механізм направлення, які рэагуе на гэтыя ацэнкі
  • Стан, які пераходзіць праз транзыцыі за дапамою StateGraph
  • Готавы граф, падключаны да бэкенду FastAPI для функціонавання чат-бота у прыметнай супэрфісе
  • У канцыку вы маеце сістэму 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 дакументаў, якія ўзаемна схожы з дакументамі у Pinecone
    • generate_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, каб досягнуць ўсё боле агентскага стылю працы.