ai-agents-for-beginners

Jak projektować dobre agentów AI

(Kliknij powyższy obraz, aby obejrzeć wideo z tej lekcji)

Wzorzec projektowy korzystania z narzędzi

Narzędzia są interesujące, ponieważ pozwalają agentom AI na posiadanie szerszego zakresu możliwości. Zamiast agenta mającego ograniczony zestaw działań, które może wykonać, dodanie narzędzia pozwala agentowi wykonywać szeroki zakres działań. W tym rozdziale przyjrzymy się wzorcowi projektowemu korzystania z narzędzi, który opisuje, jak agenci AI mogą używać konkretnych narzędzi do osiągania swoich celów.

Wprowadzenie

W tej lekcji chcemy odpowiedzieć na następujące pytania:

Cele nauki

Po ukończeniu tej lekcji będziesz potrafił:

Czym jest wzorzec projektowy korzystania z narzędzi?

Wzorzec projektowy korzystania z narzędzi koncentruje się na umożliwieniu LLM interakcji z zewnętrznymi narzędziami w celu osiągnięcia konkretnych celów. Narzędzia to kod, który może być wykonywany przez agenta, aby wykonać akcje. Narzędzie może być prostą funkcją, taką jak kalkulator, lub wywołaniem API do usługi zewnętrznej, np. wyszukiwanie cen akcji lub prognoza pogody. W kontekście agentów AI, narzędzia są projektowane tak, aby były wykonywane przez agentów jako odpowiedź na modelowo generowane wywołania funkcji.

Do jakich przypadków użycia można go zastosować?

Agenci AI mogą wykorzystywać narzędzia do wykonywania złożonych zadań, pozyskiwania informacji lub podejmowania decyzji. Wzorzec korzystania z narzędzi jest często stosowany w scenariuszach wymagających dynamicznej interakcji z zewnętrznymi systemami, takimi jak bazy danych, usługi internetowe czy interpretery kodu. Ta zdolność jest użyteczna w wielu różnych zastosowaniach, w tym:

Jakie elementy/bloki budulcowe są potrzebne do implementacji wzorca korzystania z narzędzi?

Te bloki budulcowe pozwalają agentowi AI wykonywać szeroki zakres zadań. Przyjrzyjmy się kluczowym elementom potrzebnym do implementacji wzorca korzystania z narzędzi:

Następnie przyjrzyjmy się szczegółowo wywoływaniu funkcji/narzędzi.

Wywoływanie funkcji/narzędzi

Wywoływanie funkcji jest głównym sposobem, w jaki umożliwiamy dużym modelom językowym (LLM) interakcję z narzędziami. Często zobaczysz użycie terminów ‘Funkcja’ i ‘Narzędzie’ zamiennie, ponieważ ‘funkcje’ (bloki wielokrotnego użytku kodu) są ‘narzędziami’, których agenci używają do wykonywania zadań. Aby kod funkcji mógł zostać wywołany, LLM musi porównać zapytanie użytkownika z opisem funkcji. W tym celu do LLM wysyłany jest schemat zawierający opisy wszystkich dostępnych funkcji. LLM wybiera następnie najbardziej odpowiednią funkcję do zadania i zwraca jej nazwę oraz argumenty. Wybrana funkcja jest wywoływana, jej odpowiedź jest przesyłana z powrotem do LLM, które wykorzystuje tę informację, by odpowiedzieć na zapytanie użytkownika.

Aby deweloperzy mogli zaimplementować wywoływanie funkcji dla agentów, potrzebne będą:

  1. Model LLM, który obsługuje wywoływanie funkcji
  2. Schemat zawierający opisy funkcji
  3. Kod dla każdej opisanej funkcji

Użyjmy przykładu uzyskania aktualnego czasu w mieście, aby to zilustrować:

  1. Zainicjuj LLM, który obsługuje wywoływanie funkcji:

    Nie wszystkie modele obsługują wywoływanie funkcji, dlatego ważne jest, aby sprawdzić czy używany LLM to wspiera. Azure OpenAI obsługuje wywoływanie funkcji. Możemy zacząć od inicjacji klienta Azure OpenAI.

     # Zainicjuj klienta Azure OpenAI
     client = AzureOpenAI(
         azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT"), 
         api_key=os.getenv("AZURE_OPENAI_API_KEY"),  
         api_version="2024-05-01-preview"
     )
    
  2. Utwórz schemat funkcji:

    Następnie zdefiniujemy schemat JSON, który zawiera nazwę funkcji, opis tego, co funkcja robi, oraz nazwy i opisy parametrów funkcji. Następnie przekażemy ten schemat do wcześniej utworzonego klienta, wraz z zapytaniem użytkownika o czas w San Francisco. Ważne jest, aby zauważyć, że wywołanie narzędzia jest zwracane, nie końcowa odpowiedź na pytanie. Jak wspomniano wcześniej, LLM zwraca nazwę wybranej funkcji do zadania oraz argumenty, które zostaną do niej przekazane.

     # Opis funkcji do odczytu przez model
     tools = [
         {
             "type": "function",
             "function": {
                 "name": "get_current_time",
                 "description": "Get the current time in a given location",
                 "parameters": {
                     "type": "object",
                     "properties": {
                         "location": {
                             "type": "string",
                             "description": "The city name, e.g. San Francisco",
                         },
                     },
                     "required": ["location"],
                 },
             }
         }
     ]
    
      
     # Początkowa wiadomość użytkownika
     messages = [{"role": "user", "content": "What's the current time in San Francisco"}] 
      
     # Pierwsze wywołanie API: Poproś model o użycie funkcji
       response = client.chat.completions.create(
           model=deployment_name,
           messages=messages,
           tools=tools,
           tool_choice="auto",
       )
      
       # Przetwórz odpowiedź modelu
       response_message = response.choices[0].message
       messages.append(response_message)
      
       print("Model's response:")  
    
       print(response_message)
      
    
     Model's response:
     ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_pOsKdUlqvdyttYB67MOj434b', function=Function(arguments='{"location":"San Francisco"}', name='get_current_time'), type='function')])
    
  3. Kod funkcji potrzebny do wykonania zadania:

    Gdy LLM wybrało, która funkcja ma zostać uruchomiona, kod wykonujący zadanie musi zostać zaimplementowany i wykonany. Możemy zaimplementować kod pobierający aktualny czas w Pythonie. Będziemy też musieli napisać kod do wydobycia nazwy i argumentów z response_message, aby uzyskać ostateczny wynik.

       def get_current_time(location):
         """Get the current time for a given location"""
         print(f"get_current_time called with location: {location}")  
         location_lower = location.lower()
            
         for key, timezone in TIMEZONE_DATA.items():
             if key in location_lower:
                 print(f"Timezone found for {key}")  
                 current_time = datetime.now(ZoneInfo(timezone)).strftime("%I:%M %p")
                 return json.dumps({
                     "location": location,
                     "current_time": current_time
                 })
          
         print(f"No timezone data found for {location_lower}")  
         return json.dumps({"location": location, "current_time": "unknown"})
    
      # Obsługa wywołań funkcji
       if response_message.tool_calls:
           for tool_call in response_message.tool_calls:
               if tool_call.function.name == "get_current_time":
         
                   function_args = json.loads(tool_call.function.arguments)
         
                   time_response = get_current_time(
                       location=function_args.get("location")
                   )
         
                   messages.append({
                       "tool_call_id": tool_call.id,
                       "role": "tool",
                       "name": "get_current_time",
                       "content": time_response,
                   })
       else:
           print("No tool calls were made by the model.")  
      
       # Drugie wywołanie API: Pobierz ostateczną odpowiedź z modelu
       final_response = client.chat.completions.create(
           model=deployment_name,
           messages=messages,
       )
      
       return final_response.choices[0].message.content
    
       get_current_time called with location: San Francisco
       Timezone found for san francisco
       The current time in San Francisco is 09:24 AM.
    

Wywoływanie funkcji jest sercem większości, jeśli nie wszystkich projektów korzystania z narzędzi agentów, jednak implementacja od podstaw może być czasem wyzwaniem. Jak nauczyliśmy się w Lekcji 2, ramy agentowe zapewniają nam gotowe bloki budulcowe do implementacji korzystania z narzędzi.

Przykłady korzystania z narzędzi z ramami agentowymi

Oto kilka przykładów, jak można implementować wzorzec projektowy korzystania z narzędzi, używając różnych ram agentowych:

Semantic Kernel

Semantic Kernel to otwartoźródłowy framework AI dla programistów .NET, Python i Java pracujących z dużymi modelami językowymi (LLM). Upraszcza proces korzystania z wywoływania funkcji, automatycznie opisując twoje funkcje i ich parametry modelowi przez proces zwany serializacją. Obsługuje również komunikację dwukierunkową między modelem a twoim kodem. Kolejną zaletą używania frameworka agentowego, takiego jak Semantic Kernel, jest to, że daje dostęp do gotowych narzędzi, takich jak Wyszukiwanie plików oraz Interpreter kodu.

Poniższy diagram ilustruje proces wywoływania funkcji z Semantic Kernel:

function calling

W Semantic Kernel funkcje/narzędzia nazywane są wtyczkami (Plugins). Możemy przekonwertować funkcję get_current_time, którą widzieliśmy wcześniej, na wtyczkę poprzez przekształcenie jej w klasę z tą funkcją. Możemy także zaimportować dekorator kernel_function, który przyjmuje opis funkcji. Kiedy następnie tworzysz kernel z GetCurrentTimePlugin, kernel automatycznie zserializuje funkcję i jej parametry, tworząc schemat do wysłania do LLM.

from semantic_kernel.functions import kernel_function

class GetCurrentTimePlugin:
    async def __init__(self, location):
        self.location = location

    @kernel_function(
        description="Get the current time for a given location"
    )
    def get_current_time(location: str = ""):
        ...

from semantic_kernel import Kernel

# Utwórz jądro
kernel = Kernel()

# Utwórz wtyczkę
get_current_time_plugin = GetCurrentTimePlugin(location)

# Dodaj wtyczkę do jądra
kernel.add_plugin(get_current_time_plugin)

Azure AI Agent Service

Azure AI Agent Service to nowszy framework agentowy zaprojektowany tak, aby umożliwić deweloperom bezpieczne tworzenie, wdrażanie i skalowanie wysokiej jakości, rozszerzalnych agentów AI bez konieczności zarządzania zapleczem obliczeniowym i przechowywaniem danych. Jest szczególnie przydatny dla zastosowań korporacyjnych, ponieważ jest w pełni zarządzaną usługą z zabezpieczeniami na poziomie korporacyjnym.

W porównaniu do bezpośredniej pracy z API LLM, Azure AI Agent Service oferuje kilka zalet, w tym:

Narzędzia dostępne w Azure AI Agent Service można podzielić na dwie kategorie:

  1. Narzędzia wiedzy:
  2. Narzędzia akcji:

Agent Service pozwala na używanie tych narzędzi razem jako zbiór narzędzi (toolset). Wykorzystuje także wątki (threads), które śledzą historię wiadomości z konkretnej rozmowy.

Wyobraź sobie, że jesteś agentem sprzedaży w firmie Contoso. Chcesz stworzyć agenta konwersacyjnego, który będzie odpowiadał na pytania dotyczące danych sprzedażowych.

Poniższy obraz ilustruje jak można użyć Azure AI Agent Service do analizy danych sprzedażowych:

Agentic Service In Action

Aby użyć któregokolwiek z tych narzędzi z usługą, możemy utworzyć klienta i zdefiniować narzędzie lub zbiór narzędzi. Aby praktycznie to zaimplementować, możemy użyć następującego kodu w Pythonie. LLM będzie mógł spojrzeć na toolset i zdecydować, czy użyć funkcji stworzonej przez użytkownika fetch_sales_data_using_sqlite_query, czy też wbudowanego Interpreter kodu, w zależności od zapytania użytkownika.

import os
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
from fetch_sales_data_functions import fetch_sales_data_using_sqlite_query # funkcja fetch_sales_data_using_sqlite_query, którą można znaleźć w pliku fetch_sales_data_functions.py.
from azure.ai.projects.models import ToolSet, FunctionTool, CodeInterpreterTool

project_client = AIProjectClient.from_connection_string(
    credential=DefaultAzureCredential(),
    conn_str=os.environ["PROJECT_CONNECTION_STRING"],
)

# Inicjalizacja zestawu narzędzi
toolset = ToolSet()

# Inicjalizacja agenta wywołującego funkcje z funkcją fetch_sales_data_using_sqlite_query i dodanie go do zestawu narzędzi
fetch_data_function = FunctionTool(fetch_sales_data_using_sqlite_query)
toolset.add(fetch_data_function)

# Inicjalizacja narzędzia Code Interpreter i dodanie go do zestawu narzędzi.
code_interpreter = code_interpreter = CodeInterpreterTool()
toolset.add(code_interpreter)

agent = project_client.agents.create_agent(
    model="gpt-4o-mini", name="my-agent", instructions="You are helpful agent", 
    toolset=toolset
)

Jakie są szczególne względy dotyczące użycia wzorca korzystania z narzędzi, aby budować godnych zaufania agentów AI?

Częstym problemem w przypadku dynamicznie generowanego przez LLM SQL jest bezpieczeństwo, a szczególnie ryzyko wstrzyknięcia SQL lub złośliwych działań, jak usunięcie lub manipulacja bazą danych. Chociaż te obawy są uzasadnione, można je skutecznie złagodzić odpowiednią konfiguracją uprawnień dostępu do bazy danych. W większości baz danych obejmuje to ustawienie bazy jako tylko do odczytu. Dla usług bazodanowych takich jak PostgreSQL czy Azure SQL, aplikacja powinna być przypisana do roli tylko do odczytu (SELECT). Uruchamianie aplikacji w bezpiecznym środowisku dodatkowo zwiększa ochronę. W scenariuszach korporacyjnych dane są zazwyczaj wyodrębniane i przekształcane z systemów operacyjnych do bazy danych tylko do odczytu lub hurtowni danych ze schematem przyjaznym dla użytkownika. Takie podejście zapewnia, że dane są bezpieczne, zoptymalizowane pod kątem wydajności i dostępności, a aplikacja ma ograniczony, tylko do odczytu dostęp.

Przykładowe kody

Masz więcej pytań dotyczących użycia wzorców projektowych narzędzia?

Dołącz do Azure AI Foundry Discord, aby spotkać innych uczących się, uczestniczyć w godzinach konsultacji i uzyskać odpowiedzi na pytania dotyczące AI Agents.

Dodatkowe zasoby

Poprzednia lekcja

Understanding Agentic Design Patterns

Następna lekcja

Agentic RAG


Zastrzeżenie:
Ten dokument został przetłumaczony przy użyciu usługi tłumaczeń AI Co-op Translator. Mimo że dążymy do jak największej dokładności, prosimy pamiętać, że tłumaczenia automatyczne mogą zawierać błędy lub nieścisłości. Oryginalny dokument w języku źródłowym należy traktować jako źródło ostateczne. W przypadku informacji krytycznych zaleca się skorzystanie z tłumaczenia wykonanego przez profesjonalnego tłumacza. Nie ponosimy odpowiedzialności za jakiekolwiek nieporozumienia lub błędne interpretacje wynikające z korzystania z tego tłumaczenia.