Strona główna / Artykuły / Wskazówki praktyczne: AI agent z LangChain — Część 3: Wywoływanie narzędzi w LangChain

Wskazówki praktyczne: AI agent z LangChain — Część 3: Wywoływanie narzędzi w LangChain

Krok po kroku praktyczne wskazówki: AI agentowe z LangChain — Część 3: Wywoływanie narzędzi w LangChain: kontrakty, sprawdzania oraz gotowe miejsca na kod dla zespołów wdrażających ten wzorzec.

1916 słów

Niech to służy jako wersja przeznaczona dla operatorów, zawierająca zasady przedstawione w artykule „Agentic AI with LangChain — Część 3: Wywoływanie narzędzi w LangChain”: wyraźne etapy, uporządkowane sekcje kodu oraz notatki dotyczące napraw, które przetrwają przeniesienie obowiązków. Etap Przeglądu działa najlepiej, gdy traktowany jest jako mierzalna powierzchnia do analizy. Zapisz jeden idealny zapis rozmowy, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres pracy. Zapisuj czasy wykonywania zadań oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy przechodzi się od wersji demonstracyjnej do środowisk współdzielonych.

Koncepcje wywoływania narzędzi

W fazie koncepcji wywoływania narzędzi 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 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. Autoryzacja powinna odbywać się przy bramce, a ponowna autoryzacja – na poziomie warstwy danych. Sam token nośny nie stanowi granicy między poszczególnymi użytkownikami.

1. Konfiguracje systemu

W fazie 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 udokumentować zarówno prawidłowy przebieg procesu, 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. Autoryzacja powinna odbywać się przy bramce wejściowej, a ponowna autoryzacja – na poziomie warstwy danych. Sam token nośny nie stanowi granicy między poszczególnymi użytkownikami.

OPENAI_API_KEY="<Your OpenAI API Key>"
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

W fazie 2 „Struktura 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. Należy preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy dany krok zawiedzie, powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Autoryzuj się przy bramie wejściowej, a ponownie udziel uprawnień na poziomie warstwy danych. Sam token nośny nie stanowi granicy między poszczególnymi usługami.

project/
|
├── tools
│   ├── get_sum.py    # a simple LangChain tool example
|   └── weather.py    # get_weather LangChain tool using open source APIs
|
|── tool_call.py      # implement tool-calling loop using LangChain tools
├── config.py         # pydantic BaseConfig
├── .env              # environment variable definition
├── .gitignore
└── requirements.txt  # package requirements

3. Narzędzia LangChain

W fazie 3 narzędzi LangChain 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 pliki generowane, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Zaloguj się przy bramie dostępu i ponownie udziel uprawnień na poziomie obsługi danych. Sam token nośny nie stanowi granicy między poszczególnymi użytkownikami.

3.1. Czym jest narzędzie LangChain?

W fazie 3.1 „Co to jest?” 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 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 uwierzytelniać się przy bramie wejściowej oraz ponownie autoryzować w warstwie danych. Sam token nie stanowi granicy między poszczególnymi użytkownikami.

from langchain.tools import tool


@tool
def get_sum(a: int, b: int) -> int:
    """
    get the summation of two integers
    :param a: int, input integer
    :param b: int, input integer
    :return: int, the sum of a and b
    """
    return a + b

3.2. Wdrożenie narzędzia pogodowego

Dla wersji 3.2 należy wprowadzić etap, zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją 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. Autoryzacja powinna odbywać się przy bramie wejściowej, a ponowna autoryzacja – na poziomie przetwarzania danych. Sam token nośny nie stanowi granicy między poszczególnymi użytkownikami.

3.3. Koncepcja wywoływania narzędzi

Dla koncepcji etapu 3x3 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 na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg procesu, 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. Autoryzacja powinna odbywać się przy bramce wejściowej, a ponowna autoryzacja – na poziomie warstwy danych. Sam token nośny nie stanowi granicy między poszczególnymi użytkownikami.

3.4. Wywoływanie narzędzi w LangChain

W fazie 3 4 Tool Calling 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 preferować małe, łatwe do przetestowania jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok zawiedzie, powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Uwierzytelniaj się przy bramie wejściowej, a ponownie autoryzuj na poziomie warstwy danych. Sam token nie stanowi granicy między poszczególnymi użytkownikami. W fazie 3 4 Tool Calling 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. Zapisuj czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy proces przechodzi z środowiska demonstracyjnego do współdzielonych środowisk.

3.4.1. Konfiguracja LLM i narzędzi

Podczas etapu konfiguracji 3 4 1 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. Konfigurację należy przechowywać oddzielnie od kodu aplikacji. Pliki środowiskowe, magazyny tajemnic i flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Zachowuj w pamięci podręcznej stabilne instrukcje systemowe oraz schematy narzędzi. Ponowne wysyłanie identycznego prefiksu jest częstą przyczyną marnotrawstwa zasobów.

class WeatherAssistant:
    def __init__(self):

        # initialize llm
        self.llm = ChatOpenAI(api_key=api_key, model="gpt-4o-mini", temperature=0)

        # initialize a tool dictionary
        self.tools = {"get_weather": get_weather,
                      "tavily_search": TavilySearch(max_results=3, tavily_api_key=TAVILY_API_KEY)}
        # bind LangChain tools to llm
        self.llm_with_tools = self.llm.bind_tools(list(self.tools.values()))

        # initialize messages to store message list
        self.messages = []

        # System prompt
        self.system_prompt = f"""You are a helpful assistant for question-answering tasks.
        When users ask about weather, use the get_weather tool to get weather. For other questions,
        use web_search. If you don't know the answer, just say that you don't know.
        Be conversational and helpful in your responses."""

        self.messages.append(SystemMessage(content=self.system_prompt))

3.4.2. Implementacja pętli wywoływania narzędzi

Gdy przechodzisz przez etap wdrażania 3 4 2, 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. Zdokumentuj zarówno ścieżkę prawidłowego działania, jak i ścieżkę naprawczą. Próby ponowne, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dopinane później. Zapisuj nazwę narzędzia, hash argumentów, opóźnienie oraz wynik każdej wywołania. Bez tego śladu debugowanie agenta w pętlach marnuje godziny.

async def chat(self, message: str):
    # Wrap User message in a HumanMessage and add it to message list
    self.messages.append(HumanMessage(content=message))

    # Get AI response (it may or may not contain tool calls)
    response = await self.llm_with_tools.ainvoke(self.messages)
    self.messages.append(response)

    # If there is any tool calls in the AI response
    if response.tool_calls:

        # process tool calls
        for tool_call in response.tool_calls:

            # retrieve function name from tool_call, then
            # retrieve the tool from tool dictionary, and invoke it,
            # append resulting tool message to message list
            tool = self.tools[tool_call["name"]]
            tool_result = await tool.ainvoke(tool_call)
            self.messages.append(tool_result)

        # Get final response after tool execution
        final_response = await self.llm_with_tools.ainvoke(self.messages)
        self.messages.append(final_response)

3.4.3 Testowanie pętli

Podczas przechodzenia przez etap testowania 3 4 3, 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. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok się nie powiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany proces. Zapisuj nazwę narzędzia, hash argumentów, opóźnienie oraz wynik każdego wywołania. Debugowanie bez takich informacji marnuje godziny. Podczas przechodzenia przez etap testowania 3 4 3, 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. Zapisuj czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy proces przechodzi z środowiska demonstracyjnego do wspólnych środowisk.

async def main():
    print("hello tool calling!")
    assistant = WeatherAssistant()
    message = "What is the temperature in Tokyo?"
    await assistant.chat(message)
    for msg in assistant.messages:
        msg.pretty_print()
if __name__ == "__main__":
    asyncio.run(main())

4. Ograniczenia podstawowego pętla wywoływania narzędzi

Cztery ograniczenia tego podejścia działają najlepiej, gdy traktuje się je jako mierzalną powierzchnię. Zapisz jeden idealny przypadek użycia, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres. Utrzymuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny poufnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Ujawniaj narzędzia o wąskich schematach oraz z wyraźnymi etykietami efektów ubocznych. Hostowie muszą wiedzieć, które wywołania zmieniają stan, zanim automatycznie je zatwierdzą.

5. Podsumowanie

Faza 5 „Podsumowanie końcowe” działa najlepiej, gdy traktuje się ją jako mierzalną powierzchnię do analizy. Zapisz jeden idealny przypadek działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres pracy. Zdokumentuj zarówno ścieżkę prawidłowego działania, jak i ścieżkę przywracania stanu. Próby ponowne, kontrolne punkty ludzkie oraz obsługa wiadomości błędowych stanowią część produktu, a nie elementy dodawane później. Używaj narzędzi o wąskich schematach i wyraźnych etykietach dotyczących efektów ubocznych. Hostowie muszą wiedzieć, które wywołania zmieniają stan, zanim automatycznie je zatwierdzą.

Karta kontrolna operacyjna

Podczas pracy nad fazą Karty kontrolnej operacyjnej najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowej awarii. Taka karta zapewnia uczciwość późniejszych zmian w kodzie.

Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzucaj ciche, częściowe ukończenie zadań.

Zapisuj nazwę narzędzia logowania, hash argumentów, opóźnienie oraz wynik każdej wywołania. Debugowanie bez takiego śladu marnuje godziny.

Zachowuj stan grafu w formie prostych, spójnie skategoryzowanych danych. Zagłębione struktury ukrywają informacje o tym, który węzeł zapisał dane do którego pola, i utrudniają kontynuację pracy po przerwach.

Gdy budżet na to pozwala, dodaj test sprawdzający kluczową ścieżkę w procesie CI przy użyciu przygotowanych danych testowych, a nie rzeczywistych, płatnych API.

Dokumentuj zarówno prawidłową ścieżkę działania, jak i ścieżkę naprawczą. Próby ponownych wywołań, kontrole ludzkie oraz obsługa wiadomości błędowych stanowią integralną część produktu, a nie elementy dodawane później.

Zanim wdrożysz nową architekturę, zamroź wersje oprogramowania, utwórz dokładny zapis działań na kluczowej ścieżce i potwierdź kroki konieczne do cofnięcia zmian. Środowiska współdzielone wymagają ograniczeń szybkości, weryfikacji uprawnień użytkowników oraz wyraźnego odpowiedzialnego za rotację haseł. Wolisz nudną niezawodność od pomysłowych, jednorazowych demonstracji.

Uwagi dotyczące d7ca1ebeb899: unikaj przechowywania kluczy dostawcy w repozytorium, ustaw ograniczenie liczby tokenów na sesję oraz przechowuj transkrypcje obok plików testowych, aby późniejsze zmiany modeli pozostały porównywalne.