Strona główna / Artykuły

AI agentowe z LangChain — Część 5: Agentic RAG z ReAct i

Część 5 sztucznej inteligencji agentowej z LangChain: agent RAG, w którym agent ReAct decyduje, kiedy pobrać dane, a krok refleksji sprawdza odpowiedź przed jej zwróceniem.

Wydawca: REACTAPP.TOP9 min czytania2059 słów

Źródła: Agentic AI with LangChain — Part 5: Agentic RAG with ReAct and Reflection

Niech to służy jako przebudowa idei z artykułu „Agentic AI with LangChain — Część 5: Agentic RAG z ReAct i Reflection” przeznaczona dla operatorów: wyraźne etapy, uporządkowane sekcje kodu oraz notatki dotyczące przywracania stanu po przeniesieniu obowiązków. Przegląd funkcjonowania działa najlepiej, gdy traktowany jest jako mierzalna struktura. Zanim rozszerzysz zakres, zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian.

Koncepcje techniczne

Dla koncepcji technicznych należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Podaj fragmenty tekstu, które faktycznie stanowią podstawę odpowiedzi. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od luki w indeksowaniu.

1. Konfiguracje systemu

Dla 1. Konfiguracji systemu należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy rejestrować czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Należy podawać fragmenty tekstu, które faktycznie stanowiły podstawę odpowiedzi. Bez tych odniesień operatorzy nie mogą odróżnić halucynacji od luki w indeksowaniu.

OPENAI_API_KEY="<Your OpenAI API Key>"
VECTOR_DB=pinecone
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>
class BaseConfig(BaseSettings):
    OPENAI_API_KEY: Optional[str]
    PINECONE_API_KEY: Optional[str]
    TAVILY_API_KEY: Optional[str]

model_config = SettingsConfigDict(env_file=".env", extra="ignore")

2. Struktura projektu

Dla 2. Struktury projektu należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Konfigurację należy przechowywać poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Należy podawać konkretne fragmenty tekstu, na których opiera się odpowiedź. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od luki w indeksowaniu.

project/
├── rag_process/       # Pinecone RAG operations
│   ├── __init__.py
│   ├── extractor.py
|   └── service.py
│
├── graphs             # implement ReAct and Reflection StateGraph
│   ├── 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
|
|
├── tools              # defines Pinecone retriever and get_weather tools
│   ├── retriever_tools.py    # Python function tool to retrieve Pinecone RAG
|   └── weather.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
├── .env                # environment variable definition
├── client.py           # streamlit client
├── .gitignore
└── requirements.txt     # package requirements

3. Integracja wzorca ReAct

Dla 3. Integracji wzorca ReAct należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg działania, jak i ścieżkę naprawczą. Próby ponownych działań, kontrolne punkty ludzkie oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Należy podać fragmenty tekstu, które faktycznie stanowią podstawę odpowiedzi. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od luki w indeksowaniu.

3.1. Narzędzie do wyszukiwania RAG

Dla narzędzia RAG Retrieval Tool w wersji 3.1 należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, łatwe do przetestowania jednostki nad rozbudowanymi skryptami. Gdy dany krok zawiedzie, powód awarii powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Należy podawać fragmenty tekstu, które faktycznie stanowią podstawę odpowiedzi. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od braku danych w indeksie.

@tool
async def search_agentic_docs(query: str) -> str:
    """
    Good for questions about generative agents, prompt engineering, and adversarial attacks.
    Pass a natural language search query to retrieve context from the agentic ai database.
    """
    # Simply invoke your existing vector service retriever
    docs = await vector_service.retriever.ainvoke(query)

    # Flatten the document content into a single string for the LLM
    return "\n\n".join([d.page_content for d in docs])

3.2. GraphState

Dla 3.2. GraphState należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Podaj fragmenty tekstu, które faktycznie stanowią podstawę odpowiedzi. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od luki w indeksowaniu.

class GraphState(TypedDict):
    """
    Represents the state of our graphs.

    Attributes:
        question: question
        generation: LLM generation
        acceptable: whether the answer is qualified to be acceptable
        messages: list of messages
    """

    question: str
    generation: str
    acceptable: bool
    messages: Annotated[list[AnyMessage], add_messages]
    loop_count: int

3.3. Węzły ReAct

Dla wersji 3.3 węzłów ReAct należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy rejestrować czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Należy podawać fragmenty tekstu, które faktycznie stanowiły podstawę odpowiedzi. Bez tych odniesień operatorzy nie mogą odróżnić halucynacji od luki w indeksowaniu.

4. Integracja wzorca refleksji

Dla punktu 4. Integracja wzorca refleksji należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Konfigurację należy przechowywać poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Należy podawać konkretne fragmenty tekstu, na których opiera się odpowiedź. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od braku danych w indeksie. Dla punktu 4. Integracja wzorca refleksji należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, testowalne jednostki nad rozbudowanymi skryptami. Gdy dany krok zawiedzie, powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną strukturę.

eline.

4.1. Jak działa refleksja

Pracując nad rozdziałem 4.1. Jak działa refleksja, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Zmierz stopień pamięci na ustalonej serii pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe możliwości wyszukiwania.

4.2. Prompty

Gdy pracujesz nad rozdziałem 4.2. Prompts, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zapisz czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Zachowuj w pamięci tymczasowej stabilne instrukcje systemu oraz schematy narzędzi. Ponowne wysyłanie identycznego prefiksu jest częstą przyczyną marnotrawstwa zasobów.

GENERATE_SYSTEM_PROMPT = f"""You are an assistant for question-answering tasks with access to multiple tools.
    use these tools to retrieve context to answer the question. If you don't know the answer,
    just say that you don't know. Use three sentences maximum and keep the answer concise.

    Tool Use Instructions:

    - Use tools as much as possible.
    - Chain tools when needed (e.g., search for info, then use that to search
      for relevant hotels)
    - Be conversational and helpful
    - If you choose to answer a question using your internal knowledge instead of a tool,
      you must be completely certain of the facts. If there is any ambiguity,
      or if the question requires up-to-date or highly specific details, you MUST use a tool to verify.

    """
GRADER_SYSTEM_PROMPT = """""You are a grader assessing whether an LLM generation addresses/resolves a
      question using the context. Generate critique and recommendations for LLM that generated the answer.
      always provide detailed recommendations, including requests for search more relevant context, generate
      answer based on context to reduce hallucination, and generate answer relevant to the question.

      The LLM generating this answer has the access to the following tools to obtain context:
      **get_weather**: search for weather information given a city name
      **search_agentic_docs**:  Good for questions about generative agents, prompt engineering, and adversarial attacks
      **TavilySearch**: General search tool from the internet.

      INSTRUCTIONS:
      (a) Evaluate if the context is relevant to the question
      Provide recommendation if context is not relevant to the question. You can suggest the LLM to use an
      appropriate tool. If there is no context, you must be completely certain of the facts. If the question is
      covered by provided tools, or there is any ambiguity,
      or if the question requires up-to-date or highly specific details, you MUST suggest to use a tool to verify answer.

      (b) Evaluate if the answer is based on the context
      if the answer is not based on the context, it is a hallucination. You should suggest to regenerate the answer
      based on the context.
      (c) Evaluate if the answer resolves the question
      if the answer does not resolves the question, suggest to regenerate the answer

      The LLM generated this answer is strictly instructed to keep its answer CONCISE (maximum of 3 sentences).
      Do NOT penalize the answer for omitting minor details if the core question is answered accurately.
      If all the evaluations are passed, no suggestions should be provided.

      CRITERIA FOR ACCEPTABLE (True):
      - If the answer is accurate, addresses the user's question using the context, and does not hallucinate,
        you MUST set acceptable to True, even if it missed some details from the context.
      - Only set acceptable to False if there is a fundamental flaw (hallucination, wrong information,
        or completely missing the point).

      OUTPUT REQUIREMENTS:

        You must return a structured output with the following fields:

        - acceptable (boolean): True if the answer passed all evaluation,
          False if any of the evaluation fails and recommendations are provided
        - recommendations (string): recommendations you provide

        INPUTS:

        question: {question}
        answer: {answer}
        context: {context}
    """

4.3. Węzły refleksji

Gdy pracujesz nad rozdziałem 4.3. Reflection Nodes, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Trzymaj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Zmierz stopień przywoływalności na ustalonej serii pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe mechanizmy wyszukiwania. Gdy pracujesz nad rozdziałem 4.3. Reflection Nodes, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolij małe, testowalne jednostki nad rozbudowane skrypty. Gdy jakiś krok się nie powiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną sekwencję operacji.

5. Pełny proces pracy

  1. Pełny proces pracy funkcjonuje najlepiej, gdy traktowany jest jako mierzalna całość. Zanim rozszerzysz zakres, zapisz jeden idealny przykład realizacji, jeden przypadek niepowodzenia oraz notatkę dotyczącą cofnięcia działań. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć milczące, częściowe ukończenie zadań. Rozdziel politykę dzielenia na fragmenty od polityki pobierania danych. Zmiana jednej z nich nie powinna zmuszać do przepisywania drugiej, gdy zmieniają się metryki jakości.

6. Kompromis pomiędzy szybkością a niezawodnością

7. Podsumowanie końcowe

Lista kontrolna operacyjna

Literatura pokrewna

  • Praktyczne notatki: Agentic AI z LangChain — Część 4: Wzorzec ReAct w LangGraph — Szczegółowy przewodnik po Praktycznych notatkach: Agentic AI z LangChain — Część 4: Wzorzec ReAct w LangGraph, zawierający kontrakty, sprawdzenia oraz gotowe elementy kodu dostępne dla zespołów wdrażających ten wzorzec.