DevTurtle logo DevTurtle

Usare i tools con LangChain

guide/ Guida GenAI con LangChain

Il tool calling nell'intelligenza artificiale (AI) è un concetto che si riferisce alla capacità dei modelli di linguaggio di interagire con strumenti esterni per eseguire azioni specifiche. Questa funzionalità consente ai modelli di andare oltre la semplice generazione di testo, permettendo loro di integrare, eseguire e automatizzare compiti utilizzando una varietà di strumenti e servizi. In questo articolo, esploreremo come implementare ed usare i tools con LangChain.

Se non lo avete già fatto e siete alle prime armi con questo framework, vi suggerisco di leggere gli articoli precedenti della guida su LangChain.

Implementare un tool con LangChain

Per realizzare questo tutorial realizzeremo un tool che permetta all’AI di leggere e sintetizzare gli articoli del blog. La prima cosa da fare consiste quindi nel definire una funzione Python che permetta di fare una chiamata HTTP e recuperare il contenuto della pagina web:

Python
from langchain_core.tools import tool
import urllib.request
from bs4 import BeautifulSoup

@tool
def get_article(url:str) -> str:
    """Retrieve an article from the web starting from the URL"""
    html = urllib.request.urlopen(url).read()
    soup = BeautifulSoup(html, features="html.parser")
    text = soup.get_text()
    return text

Come si può notare, si tratta di una semplicissima funzione che recupera il contenuto di un URL e ne estrae il contenuto in formato testuale. Le uniche differenze sono le seguenti:

  • La funziona è stata marcata con il decoratore @tool che indica che la funzione get_article è uno strumento che può essere utilizzato dal modello di AI.
  • Sulla prima riga abbiamo aggiunto una descrizione della funzione secondo lo standard già visto nel precedente articolo su come usare i dati strutturati con LangChain. Questa descrizione è fondamentale per permettere al modello di invocare la funzione al momento giusto.

Usare i tools

Per testare il function calling useremo il modello GPT-4o di OpenAI come abbiamo già visto in un articolo precedente. Questa volta useremo il metodo bind_tools per comunicare al modello che può far uso della funzione implementata per poter rispondere ai prompt dell’utente.

Python
...
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, ToolMessage

... 

def invoke_with_tools(prompt:str):
    tools = [get_article]
    messages = [HumanMessage(prompt)]
    llm = ChatOpenAI(model="gpt-4o")
    llm_with_tools = llm.bind_tools(tools)
    ai_msg = llm_with_tools.invoke(prompt)
    messages.append(ai_msg)
    for tool_call in ai_msg.tool_calls:
        selected_tool = {"get_article": get_article}[tool_call["name"]]
        tool_output = selected_tool.invoke(tool_call["args"])
        messages.append(ToolMessage(tool_output, tool_call_id=tool_call["id"]))
    messages.append(llm_with_tools.invoke(messages))
    return messages
  
def main():
    response = invoke_with_tools("You can summarize in a single row the article http://www.devturtleblog.com/langchain-structured-json/ ?")
    print(response)

if __name__ == "__main__":
    main()

Fornendo al modello l’url di un articolo e chiedendogli di sintetizzarne il contenuto, l’AI sarà in grado di capire che deve usare il tool per recuperare il contenuto della pagina. Per comprendere meglio questo meccanismo, prima di commentare ulteriormente il codice, dobbiamo guardare i messaggi che vengono generati dal programma:

Plaintext
HumanMessage(
    content='You can summarize in a single row the article http://www.devturtleblog.com/langchain-structured-json/ ?'
), 
AIMessage(
    content='', 
    additional_kwargs={
        'tool_calls': [{
            'id': 'call_kOFu8D2fKgctD0I2TuiFb5Nj', 
            'function': {
                'arguments': '{"url":"http://www.devturtleblog.com/langchain-structured-json/"}', 
                'name': 'get_article'
            }, 
            'type': 'function'
        }]
    }, 
    response_metadata={
        ...
    }, 
    id='run-cc9ea039-066b-4f7b-a0d9-f1ab090bbb35-0', 
    tool_calls=[{
        'name': 'get_article', 
        'args': {'url': 'http://www.devturtleblog.com/langchain-structured-json/'}, 
        'id': 'call_kOFu8D2fKgctD0I2TuiFb5Nj'
    }], 
    usage_metadata={
        ...
    }
), 
ToolMessage(
    content='\n\nWe have already seen how to use LangChain for creating LLM-based applications ...', 
    tool_call_id='call_kOFu8D2fKgctD0I2TuiFb5Nj'
), 
AIMessage(
    content='The article from Dev Turtle Blog discusses using LangChain to generate structured JSON responses, highlighting the benefits of JSON for readability, compatibility, flexibility, and integration. It explains how to define models in Python using Pydantic for strong typing, data validation, and serialization. The article provides an example of creating a social media post model and demonstrates how to use it with LangChain to produce structured outputs, facilitating easier integration of language models into modern applications.', 
    response_metadata={
        ...
    }, 
    id='run-afef2163-d8b7-42bd-a209-6a8c950f6354-0', 
    usage_metadata={'input_tokens': 1003, 'output_tokens': 90, 'total_tokens': 1093}
)]

Come si può notare, in seguito al prompt di input, l’AI genera un AIMessage in cui ci segnala che è necessario invocare il tool. Nel dettaglio del messaggio sono inoltre indicati i valori da passare in input al tool per ottenere la risposta desiderata.

La chiamata effettiva al tool non viene eseguita direttamente dal modello ma abbiamo dovuto implementare un ciclo che, per ogni tool da invocare, effettua la chiamata e aggiunge un messaggio di risposta di tipo ToolMessage:

Python
...
    for tool_call in ai_msg.tool_calls:
        selected_tool = {"get_article": get_article}[tool_call["name"]]
        tool_output = selected_tool.invoke(tool_call["args"])
        messages.append(ToolMessage(tool_output, tool_call_id=tool_call["id"]))
...

Questo frammento di codice è alla base del meccanismo del tool calling e può essere riutilizzato in qualsiasi applicazione futura. Il funzionamento è molto semplice e non fa altro che recuperare dall’output generato dal modello il nome del tool e dei parametri da passare in input.

Nel caso in cui siano presenti più tool, la prima istruzione del ciclo deve essere modificata come segue:

Python
selected_tool = {"tool_a": tool_a, "tool_b": tool_b}[tool_call["name"]]

Ad ogni funzione viene così associato un nome sche ci permetta di identificare quale tool selezionare.

Al termine delle esecuzioni viene nuovamente invocato l’LLM che utilizza i messaggi precedenti per formulare la risposta finale contenente la sintesi dell’articolo.

Le potenzialità del tool calling

Come si può facilmente intuire, il tool calling ha una vasta gamma di applicazioni pratiche in quanto permette di integrare i modelli di AI con logiche deterministiche ottenendo i benefici di entrambi gli approcci. Ad esempio, è possibile invocare APIs, recuperare informazioni da basi dati esterne o fornire al modello algoritmi di calcolo per elaborare le informazioni ricevute in input tramite linguaggio naturale.

Si tratta quindi di una vera e propria innovazione che permette di realizzare agenti intelligenti in grado di orchestrare parti di software implementate in maniera tradizionale.