Strona główna / Artykuły / Pozwolenie Bliźniakom na wybór źródła: FAISS, Tavily i bezpośrednie odpowiedzi w LangGraph

Pozwolenie Bliźniakom na wybór źródła: FAISS, Tavily i bezpośrednie odpowiedzi w LangGraph

Stwórz mały przepływ pracy LangGraph, w którym Gemini kieruje każde pytanie do bazy wiedzy FAISS, wyszukiwarki internetowej Tavily lub bezpośredniej odpowiedzi, z wykorzystaniem routingu strukturyzowanych wyników.

3771 słów

Większość prototypów do odpowiadania na pytania przekazuje każde zapytanie przez jeden stały proces, mimo że pytania różnią się pod względem wymagań: niektóre wymagają dokumentów prywatnych firmy, inne faktów zmieniających się codziennie, a jeszcze inne jedynie wiedzy ogólnej. W tym przewodniku stworzono kompaktowy workflow w Pythonie, w którym model językowy najpierw analizuje każde pytanie i kieruje je we właściwe miejsce: do magazynu wektorowego FAISS dla wiedzy wewnętrznej, do Tavily w celu uzyskania aktualnych wyników z internetu lub bezpośrednio do Gemini. Pod koniec zrozumiesz, jak stan, węzły i warunkowe krawędzie współpracują w LangGraph, dlaczego ustrukturyzowany wynik sprawia, że router LLM jest godny zaufania, oraz gdzie ten projekt o rozmiarze edukacyjnym wymaga udoskonaleń przed wprowadzeniem go do użycia przez rzeczywistych użytkowników. Jeśli chcesz szerszy katalog kształtów grafów, przeczytaj artykuł na temat routingu, rozprzestrzeniania się.

Wzorce krytyki i zatwierdzenia w LangGraph uzupełniają to praktyczne tworzenie.

Trzy pytania, trzy różne źródła

Załóżmy trzech użytkowników. Pierwszy pyta o politykę urlopową firmy; tylko dokumenty wewnętrzne mogą na to odpowiedzieć. Drugi chce poznać dzisiejszą pogodę w Uttarakhand; żaden dokument wewnętrzny jej nie zawiera, więc system musi wyszukać odpowiedź w internecie. Trzeci pyta, czym jest RAG; model już to wie, a pobieranie dodatkowych informacji tylko wydłużyłoby czas reakcji.

Dlatego architektura docelowa umieszcza router napędzany Gemini przed trzema gałęziami, które wszystkie prowadzą do jednego kroku generowania odpowiedzi:

User Question
                               |
                               v
                       +----------------+
                       |   AI Router    |
                       |    (Gemini)    |
                       +----------------+
                         /      |      \
                        /       |       \
                       v        v        v
                    FAISS    Tavily    Gemini
                 Internal DB  Web      Direct
                       \        |        /
                        \       |       /
                         v      v       v
                       +----------------+
                       | Generate Answer|
                       |     Gemini     |
                       +----------------+
                               |
                               v
                            Answer

Router stanowi serce tego projektu. Kuszącym skrótem jest dopasowywanie słów kluczowych, jak pokazano w poniższym pseudokodzie, gdzie określone słowa w pytaniu wybierają odpowiednią gałąź:

if "weather" in question:
    use_tavily()
elif "leave" in question:
    use_faiss()
else:
    use_gemini()

Tutaj decyzja jest zamiast tego delegowana do Gemini. Ponieważ model interpretuje pytanie, zamiast skanować je w poszukiwaniu słów wyzwalających, ten proces pracy otrzymuje miano agencyjnego.

Sztuczna inteligencja generatywna, agenci i procesy pracy agencyjne

Termy te są często używane zamiennie, ale opisują różne poziomy zaangażowania modelu.

Zwykła sztuczna inteligencja generatywna

Najprostsze zastosowanie polega na przekazaniu modelowi promptu i otrzymaniu tego, co ten wygeneruje:

User Question
      |
      v
     LLM
      |
      v
    Answer

Może wyjaśnić RAG na podstawie danych treningowych, ale nie wie nic o twojej polityce urlopów.

Agenci, którzy wykorzystują narzędzia

Agent rozszerza model o narzędzia takie jak baza danych, silnik wyszukiwania lub zewnętrzna API i pozwala mu zdecydować, czy skorzystać z jednego z nich przed udzieleniem odpowiedzi:

User Question
      |
      v
     LLM
      |
      +------> Database
      |
      +------> Web Search
      |
      +------> API
      |
      v
    Answer

Na przykład pytanie dotyczące pogody można odpowiedzieć, wysyłając zapytanie do usługi pogodowej, zamiast pozwalać modelowi wymyślić wiarygodną prognozę.

Przepływy pracy oparte na agentach

Przepływ pracy oparty na agencie daje modelowi możliwość wpływu na ścieżkę wykonywania w czasie rzeczywistym. W tym projekcie przepływ wygląda następująco:

Question
   |
   v
Router
   |
   +----> FAISS
   |
   +----> Tavily
   |
   +----> Gemini

Rozwijający definiuje trzy możliwe ścieżki; model jedynie wybiera spośród nich odpowiedź na każde przychodzące pytanie. Nie otrzymuje on pełnej kontroli nad aplikacją, lecz jedynie menu dozwolonych działań. „Przepływ routingu oparty na agentach” to najdokładniejsza nazwa dla takiego układu.

Ustawianie projektu

Potrzebujesz Pythona 3.10 lub nowszego, klucza API do Google Gemini oraz klucza API do Tavily. Zainstaluj biblioteki jednocześnie:

pip install -U langchain langchain-google-genai langchain-community langgraph faiss-cpu tavily-python python-dotenv pydantic

Zachowaj oba klucze w pliku .env, a nie w kodzie źródłowym:

GOOGLE_API_KEY=your_google_api_key
TAVILY_API_KEY=your_tavily_api_key

Importy obejmują narzędzia do typowania, Pydantic do schematów routingu, wrappery dokumentów i FAISS od LangChain, klasy do czatu i generowania embeddingów od Gemini, prymitywy grafowe od LangGraph oraz klienta Tavily:

import os
from typing import List, Literal
from typing_extensions import TypedDict
from dotenv import load_dotenv
from pydantic import BaseModel
from langchain_core.documents import Document
from langchain_community.vectorstores import FAISS
from langchain_google_genai import (
    ChatGoogleGenerativeAI,
    GoogleGenerativeAIEmbeddings,
)
from langgraph.graph import StateGraph, START, END
from tavily import TavilyClient

Kolejnym krokiem, przedstawionym jako fragment jednej linii, jest po prostu załadowanie zmiennych środowiskowych:

Load the environment variables:

Przy użyciu override=True wartości z pliku .env mają pierwszeństwo przed zmiennymi już ustawionymi w shellu:

load_dotenv(override=True)

Konfiguracja Gemini do routingu, odpowiadania i generowania embeddingów

Model do czatu pełni dwie funkcje. Najpierw decyduje, który źródło powinno obsłużyć pytanie, a następnie komponuje ostateczną odpowiedź na podstawie zebranego kontekstu. Poniższa konfiguracja umożliwia dwa automatyczne próby ponowne i pozostawia bez ustawień limity tokenów oraz czasu oczekiwania:

model = ChatGoogleGenerativeAI(
    model="gemini-3.6-flash",
    max_tokens=None,
    timeout=None,
    max_retries=2,
    api_key=os.getenv("GOOGLE_API_KEY"),
)

Identyfikatory modeli zmieniają się często, dlatego przed uruchomieniem należy sprawdzić nazwę w fragmencie kodu z aktualną listą modeli Gemini.

Embeddingi to odrębna kwestia, którą obsługuje osobny model. Gemini Embedding przekształca każdy dokument w wektor liczbowy:

embeddings = GoogleGenerativeAIEmbeddings(
    model="models/gemini-embedding-001",
    google_api_key=os.getenv("GOOGLE_API_KEY"),
)

To właśnie wektory umożliwiają wyszukiwanie semantyczne: teksty o podobnym znaczeniu znajdują się blisko siebie w przestrzeni wektorowej, dzięki czemu pytanie może odnaleźć istotne fragmenty, nawet jeśli ma z nimi niewiele identycznych słów.

Niewielka wewnętrzna baza wiedzy w FAISS

Aby skupić uwagę na procesie pracy, baza wiedzy zawiera zaledwie trzy krótkie dokumenty: politykę zwrotów pieniędzy na okres 30 dni, przyznanie 20 dni urlopu płatnego z wymogiem uprzedzenia o siedem dni oraz godziny obsługi w dni powszednie. W rzeczywistym systemie te teksty pochodziłyby z podręczników, plików PDF, stron w Notion, zgłoszeń obsługi klienta, dokumentów wewnętrznych lub wierszy w bazie danych.

documents = [
    Document(
        page_content="""
        Our company provides a 30-day refund policy.
        Customers can request a refund within 30 days of purchase.
        """
    ),
    Document(
        page_content="""
        Employees receive 20 paid vacation days per year.
        Vacation requests must be submitted at least 7 days in advance.
        """
    ),
    Document(
        page_content="""
        The company provides technical support from Monday to Friday,
        9 AM to 6 PM IST.
        """
    ),
]

Budowa sklepu i jego zapakowanie jako mechanizmu wyszukiwania wymaga dwóch wywołań. Ustawienie k na 3 oznacza żądanie trzech najbliższych dokumentów, co w tym uproszczonym zbiorze danych oznacza, że każdy dokument jest zwracany uszeregowany według stopnia podobieństwa:

vector_store = FAISS.from_documents(
    documents,
    embeddings,
)

retriever = vector_store.as_retriever(
    search_kwargs={"k": 3}
)

FAISS nie rozumie języka; jest to indeks służący do wyszukiwania najbliższych sąsiadów. Model embedding przekształca wprowadzone pytanie w wektor, a FAISS zwraca przechowywane dokumenty, których wektory znajdują się najbliżej niego.

Dodawanie klienta Tavily dla informacji na żywo

Wyszukiwanie w sieci wymaga jedynie instancji klienta utworzonej na podstawie drugiego klucza API:

tavily_client = TavilyClient(
    api_key=os.getenv("TAVILY_API_KEY")
)

Projektowanie stanu współdzielonego

Stan jest kluczową koncepcją w LangGraph: to skategoryzowany słownik, który przemieszcza się po grafie, z którego każdy węzeł odczytuje dane i do którego same wprowadzają informacje. Ten proces wymaga pytania, wszystkich pobranych dokumentów, wyników Tavily, wybranej źródła oraz ostatecznej odpowiedzi:

class AgentState(TypedDict):
    question: str
    documents: List[Document]
    tavily_response: str
    source: str
    answer: str

Koncepcyjnie funkcjonuje jak wspólna tabela, którą może widzieć każdy krok:

AgentState
                    |
        +-----------+-----------+
        |           |           |
    question    documents    source
                    |
             tavily_response
                    |
                  answer

Węzeł otrzymuje aktualny stan, wykorzystuje pola, które go interesują, i zwraca aktualizacje, które LangGraph łączy przed przekazaniem stanu następnemu węzłowi.

Węzeł wyszukiwania FAISS

Ten węzeł pobiera pytanie ze stanu, przekazuje je narzędziu wyszukiwania i przechowuje odpowiadające dokumenty:

def retrieve_from_faiss(state : AgentState) -> AgentState:
    question = state['question']

    """ Fetch the details from the FAISS vector database
    """

    result = retriever.invoke(question)

    return {**state, "documents": result}

Wykonuje się tylko wtedy, gdy router wybrał wewnętrzną bazę wiedzy. Typowym wyzwalaczem jest pytanie takie jak poniżej:

"What is our company leave policy?"

Dla tego wpisu system wyszukiwania powinien wyświetlić dokument dotyczący wakacji:

Employees receive 20 paid vacation days per year.
Vacation requests must be submitted at least 7 days in advance.

Węzeł wyszukiwania Tavily

Węzeł web-search wysyła pytanie do Tavily z ustawioną wartością search_depth na advanced, a następnie pobiera pole content z każdego wyniku:

def search_with_tavily(state: AgentState) -> AgentState:
    question = state['question']
    """ Using the Tavily to search the web and
      fetch the latest information about user query
    """

    response = tavilyClient.search(
        query=question,
        search_depth='advanced'
    )

    contents = [result["content"] for result in response["results"]]
    return {**state, "tavilyResponse":contents}

Ty fragmenty stają się kontekstem, który Gemini będzie czytać podczas tworzenia odpowiedzi. Należy pamiętać, że węzeł zwraca listę ciągów znaków; jeśli wolisz jeden blok tekstu, połącz elementy przed ich przechowywaniem, aby pole odpowiadało typowi str zdefiniowanemu w stanie.

Rozwijanie niezawodności routera dzięki ustrukturyzowanemu wyjściu

Naiwny router prosi model o odpowiedź jednym z trzech prostych słów:

Return only:
faiss
tavily
gemini

Modele językowe nie zawsze przestrzegają instrukcji formatowania. Możesz otrzymać całe zdanie w odpowiedzi:

I would choose tavily.

Albo nieco inny sformułowanie:

The best option is: tavily

Każda z tych odpowiedzi narusza strukturę grafu, który wymaga dokładnego ciągu znaków do wyboru krawędzi. Ustrukturyzowana odpowiedź eliminuje ten problem. Najpierw opisz dozwolone decyzje jako model Pydantic, którego jedyny pola może przyjmować tylko trzy wartości literalne:

class RouteDecision(BaseModel):
    source: Literal["faiss", "tavily", "gemini"]

Następnie stwórz model routera, który musi zwracać instancję tego schematu. Metoda json_schema zmusza dostawcę do ograniczenia generowania do tego schematu, zamiast polegać wyłącznie na sformułowaniu promptu:

router_model = model.with_structured_output(
    RouteDecision,
    method="json_schema",
)

Wynik jest weryfikowany pod kątem typu Literal, więc nieprawidłowa wartość jest wykrywana jako błąd, zamiast skierować graf w nieokreślone miejsce.

Pisanie węzła decyzyjnego

Instrukcja routingu opisuje każdą opcję: faiss w przypadku pytań dotyczących polityki firmy i innej wiedzy wewnętrznej, tavily dla wszystkiego, co wymaga aktualnych lub internetowych informacji, oraz gemini dla wiedzy ogólnej, która nie wymaga żadnej z tych opcji:

def decide_source(state:AgentState)-> AgentState:
    question = state["question"]
    prompt = f"""
    Decide the best source for answering this question.

    Choose exactly one:

    faiss:
    Use when the question can be answered using our internal
    knowledge base.related to company policy and all

    tavily:
    Use when the question requires current, recent, or web-based
    information.

    gemini:
    Use when the question is general knowledge and does not
    require our internal documents or current web information.

    Question:
    {question}

    Return only one word:
    faiss, tavily, or gemini
    """

    response = model.invoke(prompt)
    # return response

    return {**state,"source":response.text}

Przyjrzyj się uważnie ostatnim wierszom. W obecnym stanie węzeł nadal wywołuje zwykły model i przechowuje response.text, co jest dokładnie tym kruchym podejściem opartym na tekście prostym, o którym mowa powyżej. Aby skorzystać z zalet schematu, należy zamiast tego wywołać router_model.invoke(prompt) i przechować atrybut source wyniku. Dzięki tej zmianie router zwraca obiekt typowany, podobny do tego poniżej, zamiast dowolnego tekstu:

RouteDecision(source="faiss")

Podanie LangGraph, dokąd iść dalej

Krawędzie warunkowe wymagają funkcji, która określa, któryą gałąź należy obrać. Ta funkcja sama nie podejmuje żadnych decyzji; odczytuje wybór, który router już zapisał w stanie, i przekazuje go z powrotem do LangGraph:

def route_source(
    state: AgentState,
) -> Literal["faiss", "tavily", "gemini"]:
    return state["source"]

Jeden węzeł odpowiedzi na każdą trasę

Wszystkie trzy gałęzie kończą się w tym samym kroku generowania. System sprawdza source i tworzy odpowiedni prompt: pobrane dokumenty łączone w kontekst dla FAISS, fragmenty wyszukiwania jako materiał referencyjny dla Tavily lub sama pytanie w celu uzyskania bezpośrednich odpowiedzi:

# Generate the answer for the user
def generateAnswer(state:AgentState) -> AgentState:
    source = state["source"]
    question = state['question']
    documents = state['documents']
    tavilyResponse = state['tavilyResponse']

    if source == "faiss":
        context = "\n\n".join([doc.page_content for doc in documents])
        prompt = f"""Based on the following context answer the question below
           Context:
           {context}

        Question:
        {question}
           """
    elif source == "tavily":
        prompt = f""" Based on the following search result , use this as an reference and provdie the
         answer to the below question

         Context:
         {tavilyResponse}

         Question:
         {question}
        """
    else:
        prompt = f" Answer the following question : {question}"
    response = model.invoke(prompt)
    answer = response.content

    return {**state, "answer":answer}

Każdą odpowiedź generuje ten sam model Gemini. Jedyne, co się zmienia, to kontekst umieszczony przed nią – to właśnie jest kluczowym założeniem RAG: lepsze dane wejściowe, a nie inny model, zapewniają bardziej trafne odpowiedzi.

Zachowywanie spójności nazw podczas kompletowania fragmentów

Kawałki kodu łączą dwa style nazewnicze, a różnice te spowodują błędy, jeśli wkleisz je razem bez zmian. Stan deklaruje tavily_response, podczas gdy węzły odczytują i zapisują tavilyResponse; klient jest tworzony jako tavily_client, ale jest nazywany tavilyClient; funkcja odpowiedzi jest definiowana jako generateAnswer, ale rejestrowana jako generate_answer. Wybierz jedną konwencję i stosuj ją wszędzie przed uruchomieniem grafu.

Konstruowanie grafu

W tym momencie składniki to węzeł decyzyjny plus trzy możliwe kontynuacje:

decide_source
      |
      +----> faiss
      |
      +----> tavily
      |
      +----> gemini

Jest tu pewna subtelność. Ścieżka gemini wcale nie jest krokiem pobierania danych; oznacza ona „pomijaj pobieranie i odpowiadaj bezpośrednio”. Dlatego zamiast tworzyć dla niej pusty węzeł, ta gałąź może bezpośrednio odwoływać się do wspólnego węzła generowania.

Najpierw utwórz graf przedstawiający typ stanu:

workflow = StateGraph(AgentState)

Zarejestruj cztery węzły:

workflow.add_node("decide", decide_source)
workflow.add_node("faiss", retrieve_from_faiss)
workflow.add_node("tavily", search_with_tavily)
workflow.add_node("generate", generate_answer)

Uczyń węzeł decyzyjny punktem wejścia:

workflow.add_edge(START, "decide")

Połącz krawędzie warunkowe. Mapowanie przekształca każdą wartość, którą może zwrócić funkcja routingu, na nazwę węzła – to właśnie tam gemini jest wysyłany bezpośrednio do funkcji generate:

workflow.add_conditional_edges(
    "decide",
    route_source,
    {
        "faiss": "faiss",
        "tavily": "tavily",
        "gemini": "generate",
    },
)

Następnie gałęzie pozyskiwania i wyszukiwania muszą prowadzić do generowania odpowiedzi:

workflow.add_edge("faiss", "generate")
workflow.add_edge("tavily", "generate")

Generowanie to ostatni krok przed zakończeniem grafu:

workflow.add_edge("generate", END)

Kompilacja przekształca tę definicję w aplikację uruchamialną:

app = workflow.compile()

Gotowy graf

Pełny proces, od początku do końca:

START
                           |
                           v
                    +--------------+
                    |    decide    |
                    |    source    |
                    +--------------+
                     /      |      \
                    /       |       \
                   v        v        v
               +------+ +--------+ +---------+
               |FAISS | | Tavily | | Generate|
               |      | |        | | directly|
               +------+ +--------+ +---------+
                   \        |         /
                    \       |        /
                     v      v       v
                    +----------------+
                    |    generate    |
                    |     answer     |
                    +----------------+
                            |
                            v
                           END

Pamiętaj o kluczowej zasadzie: graf określa zbiór możliwych ścieżek, a model wybiera jedną z nich w czasie rzeczywistym. To połączenie sprawia, że proces jest zorientowany na działanie, ale nie staje się nieprzewidywalny.

Próba trzech ścieżek

Niewielki pomocnik tworzy początkowy stan i wywołuje skompilowaną aplikację:

def ask_question(question: str):
    initial_state = {
        "question":question,
        "documents":[],
        "tavilyResponse":"",
        "source":""
    }

    result = app.invoke(initial_state)
    return result

Pytanie wymagające wykorzystania sieci

Pytanie dotyczące pogody powinno zostać wysłane do Tavily:

result = ask_question(
    "What is the current weather in Uttarakhand?"
)

Wyświetl zarówno wybrany źródło, jak i utworzoną odpowiedź:

print("Source:", result["source"])
print("Answer:", result["answer"])

Oczekiwana ścieżka:

Source: tavily

Bieżące warunki istnieją tylko w sieci.

Pytanie z zakresu wiedzy ogólnej

Następnie zadaj pytanie o Retrieval Augmented Generation:

result = ask_question(
    "What is Retrieval Augmented Generation?"
)

Wyświetl wynik w taki sam sposób:

print("Source:", result["source"])
print("Answer:", result["answer"])

Router powinien całkowicie pominąć etap pozyskiwania danych:

Source: gemini

Model może sam wyjaśnić ten pojęcie.

Pytanie dotyczące wewnętrznej polityki

Na koniec pytanie o politykę urlopów:

result = ask_question(
    "What is our company leave policy?"
)

I te same instrukcje wydruku:

print("Source:", result["source"])
print("Answer:", result["answer"])

Tym razem baza wiedzy wewnętrzna powinna przeważyć:

Source: faiss

Łączenie z LLM odbywa się w sposób probabilistyczny, więc należy traktować je jako oczekiwane wyniki, a nie gwarancje.

Dlaczego routing semantyczny jest lepszy od reguł słów kluczowych

Oto ponownie alternatywa zapisana w kodzie:

if "weather" in question:
    use_tavily()
elif "leave" in question:
    use_faiss()
else:
    use_gemini()

Takie reguły szybko tracą na efektywności. Weźmy przykład użytkownika pytającego, czy biuro jest otwarte w sobotę. W tym zdaniu nie ma żadnej wzmianki o polityce, a mimo to odpowiedź może znajdować się w dokumentacji wewnętrznej. Router oparty na modelu może wywnioskować intencję; lista słów kluczowych nie jest w stanie tego zrobić, chyba że ktoś przewidzi każdą możliwą formułację.

To podejście radzi sobie również lepiej z skalowaniem. Gdy pojawiają się nowe serwery backendowe, takie jak te poniżej, rozszerza się schemat oraz instrukcje zamiast tworzyć skomplikowane struktury warunkowe:

SQL Database
Internal API
CRM
Customer Support System
Documentation
Web Search

Kosztem jest dodatkowy wywołanie modelu, wraz z jego kosztami i opóźnieniami, zanim rozpocznie się rzeczywła praca.

Czy to naprawdę agent AI?

Dokładniej byłoby nazwać to małym, zautomatyzowanym procesem pracy niż autonomicznym agentem. Dostępne działania są z góry ustalone:

FAISS
Tavily
Direct Gemini

Model nie ma możliwości samodzielnej decyzji o wykonaniu czegoś w tym rodzaju, ponieważ takie możliwości mu nigdy nie zostały nadane:

delete a database
send an email
call an arbitrary API

Architektura funkcjonuje według zasady łańcucha odpowiedzialności:

Developer defines possible actions
              |
              v
        LLM chooses action
              |
              v
        LangGraph executes
              |
              v
           Result

To ograniczenie jest zaletą: ograniczone opcje zapewniają przewidywalne i sprawdzalne zachowanie w środowisku produkcyjnym.

Rola każdego komponentu

LangGraph: silnik procesów pracy

LangGraph kontroluje strukturę aplikacji: stan, węzły, krawędzie, warunkowe routowanie oraz kolejność wykonywania operacji. W uproszczeniu każdy krok follows ten sam schemat:

State
  |
  v
Node
  |
  v
Updated State
  |
  v
Conditional Edge
  |
  +----> Node A
  |
  +----> Node B
  |
  +----> Node C

Graf składający się z małych kroków jest łatwiejszy do testowania i obserwacji niż jedna rozległa funkcja.

FAISS: warstwa wyszukiwania

FAISS zapewnia część systemu odpowiedzialną za RAG. W uproszczonej formie:

Company Documents
       |
       v
   Embeddings
       |
       v
     FAISS
       |
       v
Similar Documents
       |
       v
     Gemini
       |
       v
     Answer

Rzeczywiste wdrożenie dodaje przed nią pipeline do przetwarzania danych:

Documents
   |
   v
Load
   |
   v
Split into chunks
   |
   v
Generate embeddings
   |
   v
Store vectors
   |
   v
Retrieve relevant chunks
   |
   v
Generate answer

Przykład pomija kroki ładowania i dzielenia na fragmenty, aby skupić się wyłącznie na procesie pracy; rzeczywiste przewodniki wymagają obu tych elementów.

Tavily: wyszukiwanie w sieci w czasie rzeczywistym

Tavily obsługuje informacje, które zmieniają się z upływem czasu:

User Question
      |
      v
   Router
      |
      v
    Tavily
      |
      v
 Search Results
      |
      v
    Gemini
      |
      v
    Answer

Typowymi przypadkami są aktualna pogoda, najważniejsze wiadomości, nowe wersje produktów, aktualna dokumentacja, najnowsze wydarzenia oraz dane rynkowe w czasie rzeczywistym. W środowisku produkcyjnym należy również zdecydować, w jaki sposób wyniki wyszukiwania będą cytowane, filtrowane, weryfikowane i prezentowane, ponieważ treści internetowe nie gwarantują ani dokładności, ani bezpieczeństwa przy przekazywaniu ich do modelu bez sprawdzenia.

Wzorzec, który warto zapamiętać

Komponenty są wzajemnie zamienne. Kluczową koncepcją jest routowanie: dopasowywanie każdego pytania do najodpowiedniejszej zdolności, która może na nie odpowiedzieć.

Internal Knowledge
        |
        +------ FAISS
Current Information
        |
        +------ Tavily
General Knowledge
        |
        +------ Gemini

To wybieranie zdolności na podstawie każdej prośby pojawia się niemal we wszystkich poważnych aplikacjach agentowych.

Kуда iść dalej

Dodaj więcej narzędzi

Router mógłby wybierać spośród znacznie większej liczby backendów:

SQL Database
REST APIs
CRM
Email
Calendar
Internal Documentation

Wzmocnienie procesu wyszukiwania

Trzy dokumenty stanowią jedynie demonstrację, a nie bazę wiedzy. Prawdziwy RAG wymaga mechanizmów ładowania dokumentów, dzielenia ich na fragmenty, metadanych, lepszych strategii wyszukiwania, ponownego sortowania wyników, odnoszenia się do źródeł oraz kontroli dostępu, aby użytkownicy mogli uzyskać tylko to, na co mają prawo.

Walidacja decyzji o kierowaniu

Krok walidacji pomiędzy routerem a narzędziami może odrzucić nierozsądne wybory i zapewnić bezpieczny powrót do poprzedniego stanu:

Router
   |
   v
Validator
   |
   +---- valid ----> Tool
   |
   +---- invalid --> Fallback

To staje się coraz ważniejsze w miarę wzrostu liczby narzędzi.

Rozwiązywanie problemów

Ustrukturyzowany wynik gwarantuje istnienie ważnej trasy, a nie koniecznie udane wezwanie narzędzia. API do wyszukiwania może wygasnąć lub nie zwrócić żadnych danych:

Router
   |
   v
Tavily
   |
   X
Search failed
   |
   v
Fallback

Systemy produkcyjne wymagają powtórzeń prób oraz rozwiązania awaryjnego, takiego jak bezpośrednia odpowiedź z wyraźnym ostrzeżeniem. Artykuł na temat projektowania odpornych grafów agentów z powtórnymi próbami i rozwiązaniami awaryjnymi omawia to bardziej szczegółowo.

Zrób to widocznym

Gdy odpowiedź jest błędna, musisz wiedzieć, na którym etapie doszło do błędu:

Wrong route?
      |
      v
Bad retrieval?
      |
      v
Bad search results?
      |
      v
Bad generation?

Zapisywanie wybranych źródeł i wyników pośrednich umożliwia udzielenie odpowiedzi na to pytanie.

Wprowadź pętle

Bieżący graf podejmuje dokładnie jedną decyzję:

Question
   |
   v
Router
   |
   v
Tool
   |
   v
Answer

Zdolniejszy agent ocenia to, co znalazł, i decyduje, czy podejmować ponownie działania:

Question
   |
   v
Reason
   |
   v
Tool
   |
   v
Evaluate Result
   |
   +---- Need more information?
   |            |
   |            v
   |          Tool
   |            |
   +------------+
   |
   v
Final Answer

Tutaj systemy agentowe zyskują prawdziwą moc: model ocenia, czy dostępne informacje są wystarczające, czy potrzebna jest inna akcja. To również miejsce, gdzie konieczne są ograniczenia liczby iteracji, aby pętla nie mogła działać nieskończenie.

Główne wnioski

Cały system odpowiada na jedno pytanie: jak aplikacja AI powinna zdecydować, skąd pochodzi odpowiedź? Gotowy proces wygląda następująco:

User Question
                   |
                   v
                Gemini
                Router
                   |
        +----------+----------+
        |          |          |
        v          v          v
      FAISS      Tavily    Gemini
   Internal DB    Web      Direct
        |          |          |
        +----------+----------+
                   |
                   v
                Gemini
              Final Answer
  • Zdefiniuj w grafie możliwości i granice; pozwól modelowi wybierać spośród nich w czasie działania.
  • Używaj strukturyzowanego wyniku do podejmowania decyzji o kierowaniu, upewniając się, że węzeł decyzyjny faktycznie wywołuje strukturyzowany model.
  • Zachowaj jeden węzeł generujący odpowiedzi i zmieniaj jedynie kontekst, który mu podajesz.
  • Traktuj kierowanie jako komponent poddawalny testom: rejestruj decyzje i sprawdzaj je w odniesieniu do reprezentatywnych pytań.
  • Najpierw dodaj walidację, rozwiązania awaryjne oraz możliwości monitorowania, zanim dodasz więcej narzędzi, ponieważ każde nowe narzędzie zwiększa liczbę sposobów, w jakie żądanie może zawieść się.
  • Na tej podstawie ta sama struktura naturalnie rozszerza się na bazy danych SQL, API, pamięć, ludzką aprobatę, węzły oceny, próby ponowne oraz konfiguracje wielu agentów. Graf się rozrasta, ale zasada pozostaje niezmienna: daj modelowi przydatne funkcje, określ, w jaki sposób mogą być one wykorzystywane, i pozwól mu zdecydować, która z nich pasuje do zadania.

    Literatura pokrewna