Un esempio di esecuzione automatica di funzioni ("tool") da un modello linguistico locale¶
Luca Mari, settembre 2024
Quest'opera è distribuita con Licenza Creative Commons Attribuzione - Non commerciale - Condividi allo stesso modo 4.0 Internazionale.
Obiettivo: comprendere qualche aspetto della logica delle architetture ad agenti e dell'esecuzione automatica di funzioni.
Precompetenze: basi di Python.
Per eseguire questo notebook, supponiamo con VSCode, occorre:
- installare un interprete Python
- scaricare da https://ollama.com e installare Ollama
- scaricare da Ollama un modello capace di operare con strumenti, supporremo
llama3.1:8b
:
ollama pull llama3.1
- scaricare da https://code.visualstudio.com/download e installare VSCode
- eseguire VSCode e attivare le estensioni per Python e Jupyter
- ancora in VSCode:
- creare una cartella di lavoro e renderla la cartella corrente
- copiare nella cartella il file di questa attività: computingagents.ipynb
- aprire il notebook
computingagents.ipynb
- creare un ambiente virtuale locale Python (Select Kernel | Python Environments | Create Python Environment | Venv, e scegliere un interprete Python):
- installare i moduli Python richiesti, eseguendo dal terminale:
pip install pyautogen
- eseguire dalla linea di comando:
OLLAMA_MODELS=xxx OLLAMA_HOST=127.0.0.1:1234 ollama serve
dovexxx
è la directory che contiene i modelli Ollama (in Linux potrebbe essere/var/lib/ollama/.ollama/models
)
Importiamo i moduli Python necessari e specifichiamo la configurazione per il modello linguistico che sarà usato (deve essere in grado di operare con strumenti) e l'indirizzo del server su cui sarà in esecuzione in locale (a sua volta, il server deve essere in grado di gestire strumenti: al momento Ollama, ma non LM Studio).
import autogen
import math
from typing_extensions import Annotated
llm_config = {
"config_list": [{ "base_url":"http://localhost:1234/v1",
"model":"llama3.1:8b",
"api_key":"not_used" }],
"timeout": 120,
"cache_seed": None,
}
Definiamo una semplice architettura con un agente di interfaccia ed esecutore (user_proxy
) e un agente che gestisce il modello linguistico (chatbot
).
user_proxy = autogen.UserProxyAgent(
name="interfaccia con l'utente ed esecutore di codice",
is_termination_msg=(lambda msg: "conclus" in msg["content"].lower()), # a volte potrebbe essere "concluso" o "conclusa"...
human_input_mode="NEVER",
code_execution_config={"use_docker": False},
max_consecutive_auto_reply=5,
)
domain_expert = autogen.AssistantAgent(
name="esperto di dominio",
system_message="Se devi calcolare una radice quadrata, usa la funzione disponibile. Scrivi CONCLUSO quando hai concluso il lavoro.",
llm_config=llm_config,
)
Questa è la funzione Python che dovrebbe essere eseguita quando richiesto, resa disponibile all'agente esperto di dominio
grazie ai decoratori.
@user_proxy.register_for_execution()
@domain_expert.register_for_llm(description="Calcolatore di radici quadrate.")
def square_root_calculator(
number: Annotated[float, "Numero di cui calcolare la radice quadrata"],
) -> str:
number = float(number) # a volte potrebbe essere una stringa...
if number >= 0:
return f"La radice quadrata di {number} è {math.sqrt(number):.4f}"
raise ValueError(f"Non sono capace di calcolare la radice quadrata del numero negativo {number}")
Verifichiamo che, grazie ai decoratori, l'agente esperto di dominio
sia stato dotato dello schema json con la dichiarazione della funzione.
domain_expert.llm_config["tools"]
[{'type': 'function', 'function': {'description': 'Calcolatore di radici quadrate.', 'name': 'square_root_calculator', 'parameters': {'type': 'object', 'properties': {'number': {'type': 'number', 'description': 'Numero di cui calcolare la radice quadrata'}}, 'required': ['number']}}}]
Ecco dunque un esempio di uso di questa architettura.
domain_expert.reset()
res = user_proxy.initiate_chat(domain_expert, message="Qual è la radice quadrata di 1234.56789?")
interfaccia con l'utente ed esecutore di codice (to esperto di dominio): Qual è la radice quadrata di 1234.56789? -------------------------------------------------------------------------------- Qual è la radice quadrata di 1234.56789? -------------------------------------------------------------------------------- [autogen.oai.client: 09-23 13:23:30] {349} WARNING - Model llama3.1:8b is not found. The cost will be 0. In your config_list, add field {"price" : [prompt_price_per_1k, completion_token_price_per_1k]} for customized pricing. esperto di dominio (to interfaccia con l'utente ed esecutore di codice): ***** Suggested tool call (call_08we5q1i): square_root_calculator ***** Arguments: {"number":1234.56789} *********************************************************************** -------------------------------------------------------------------------------- >>>>>>>> EXECUTING FUNCTION square_root_calculator... interfaccia con l'utente ed esecutore di codice (to esperto di dominio): interfaccia con l'utente ed esecutore di codice (to esperto di dominio): ***** Response from calling tool (call_08we5q1i) ***** La radice quadrata di 1234.56789 è 35.1364 ****************************************************** -------------------------------------------------------------------------------- [autogen.oai.client: 09-23 13:23:33] {349} WARNING - Model llama3.1:8b is not found. The cost will be 0. In your config_list, add field {"price" : [prompt_price_per_1k, completion_token_price_per_1k]} for customized pricing. esperto di dominio (to interfaccia con l'utente ed esecutore di codice): CONCLUSO --------------------------------------------------------------------------------
E questa è la ricostruzione della conversazione tra i due agenti.
from pprint import pprint
pprint(res.chat_history)
[{'content': 'Qual è la radice quadrata di 1234.56789?', 'name': "interfaccia con l'utente ed esecutore di codice", 'role': 'assistant'}, {'content': '', 'role': 'assistant', 'tool_calls': [{'function': {'arguments': '{"number":1234.56789}', 'name': 'square_root_calculator'}, 'id': 'call_08we5q1i', 'type': 'function'}]}, {'content': 'La radice quadrata di 1234.56789 è 35.1364', 'name': "interfaccia con l'utente ed esecutore di codice", 'role': 'tool', 'tool_responses': [{'content': 'La radice quadrata di 1234.56789 è 35.1364', 'role': 'tool', 'tool_call_id': 'call_08we5q1i'}]}, {'content': 'CONCLUSO', 'name': 'esperto di dominio', 'role': 'user'}]
È un semplice ma già interessante esempio (modello linguistico di piccole dimensioni, esecuzione locale, in italiano...) di un mix tra un "Sistema 1" (il modello linguistico stesso) e un "Sistema 2" (la funzione Python).