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).