{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Un esempio di esecuzione automatica di funzioni (\"_tool_\") da un modello linguistico locale\n",
"\n",
"Luca Mari, settembre 2024 \n",
"\n",
"Quest'opera è distribuita con Licenza Creative Commons Attribuzione - Non commerciale - Condividi allo stesso modo 4.0 Internazionale. \n",
"
\n",
"\n",
"**Obiettivo**: comprendere qualche aspetto della logica delle architetture ad agenti e dell'esecuzione automatica di funzioni. \n",
"**Precompetenze**: basi di Python.\n",
"\n",
"> Per eseguire questo notebook, supponiamo con VSCode, occorre:\n",
"> * installare un interprete Python\n",
"> * scaricare da https://ollama.com e installare Ollama\n",
"> * scaricare da Ollama un modello capace di operare con strumenti, supporremo `llama3.1:8b`: \n",
"> `ollama pull llama3.1`\n",
"> * scaricare da https://code.visualstudio.com/download e installare VSCode\n",
"> * eseguire VSCode e attivare le estensioni per Python e Jupyter\n",
"> * ancora in VSCode:\n",
"> * creare una cartella di lavoro e renderla la cartella corrente\n",
"> * copiare nella cartella il file di questa attività: [computingagents.ipynb](computingagents.ipynb)\n",
"> * aprire il notebook `computingagents.ipynb`\n",
"> * creare un ambiente virtuale locale Python (Select Kernel | Python Environments | Create Python Environment | Venv, e scegliere un interprete Python):\n",
"> * installare i moduli Python richiesti, eseguendo dal terminale: \n",
"> `pip install pyautogen`\n",
"> * eseguire dalla linea di comando: \n",
"> `OLLAMA_MODELS=xxx OLLAMA_HOST=127.0.0.1:1234 ollama serve` \n",
"> dove `xxx` è la directory che contiene i modelli Ollama (in Linux potrebbe essere `/var/lib/ollama/.ollama/models`)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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)."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import autogen\n",
"import math\n",
"from typing_extensions import Annotated\n",
"\n",
"llm_config = {\n",
" \"config_list\": [{ \"base_url\":\"http://localhost:1234/v1\",\n",
" \"model\":\"llama3.1:8b\",\n",
" \"api_key\":\"not_used\" }],\n",
" \"timeout\": 120,\n",
" \"cache_seed\": None,\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Definiamo una semplice architettura con un agente di interfaccia ed esecutore (`user_proxy`) e un agente che gestisce il modello linguistico (`domain_expert`)."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"user_proxy = autogen.UserProxyAgent(\n",
" name=\"interfaccia con l'utente ed esecutore di codice\",\n",
" is_termination_msg=(lambda msg: \"conclus\" in msg[\"content\"].lower()), # a volte potrebbe essere \"concluso\" o \"conclusa\"...\n",
" human_input_mode=\"NEVER\",\n",
" code_execution_config={\"use_docker\": False},\n",
" max_consecutive_auto_reply=5,\n",
")\n",
"\n",
"domain_expert = autogen.AssistantAgent(\n",
" name=\"esperto di dominio\",\n",
" system_message=\"Se devi calcolare una radice quadrata, usa la funzione disponibile. Scrivi CONCLUSO quando hai concluso il lavoro.\",\n",
" llm_config=llm_config,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Questa è la funzione Python che dovrebbe essere eseguita quando richiesto, resa disponibile all'agente `esperto di dominio` grazie ai decoratori."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"@user_proxy.register_for_execution()\n",
"@domain_expert.register_for_llm(description=\"Calcolatore di radici quadrate.\")\n",
"def square_root_calculator(\n",
" number: Annotated[float, \"Numero di cui calcolare la radice quadrata\"],\n",
" ) -> str:\n",
" number = float(number) # a volte potrebbe essere una stringa...\n",
" if number >= 0:\n",
" return f\"La radice quadrata di {number} è {math.sqrt(number):.4f}\"\n",
" raise ValueError(f\"Non sono capace di calcolare la radice quadrata del numero negativo {number}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Verifichiamo che, grazie ai decoratori, l'agente `esperto di dominio` sia stato dotato dello schema json con la dichiarazione della funzione (la documentazione dei \"tools\" per gli \"Assistants\" è qui: \n",
"https://platform.openai.com/docs/guides/function-calling \n",
"https://platform.openai.com/docs/api-reference/assistants/modifyAssistant#assistants-modifyassistant-tools)."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'type': 'function',\n",
" 'function': {'description': 'Calcolatore di radici quadrate.',\n",
" 'name': 'square_root_calculator',\n",
" 'parameters': {'type': 'object',\n",
" 'properties': {'number': {'type': 'number',\n",
" 'description': 'Numero di cui calcolare la radice quadrata'}},\n",
" 'required': ['number']}}}]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"domain_expert.llm_config[\"tools\"]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ecco dunque un esempio di uso di questa architettura."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33minterfaccia con l'utente ed esecutore di codice\u001b[0m (to esperto di dominio):\n",
"\n",
"Qual è la radice quadrata di 1234.56789?\n",
"\n",
"--------------------------------------------------------------------------------\n",
"Qual è la radice quadrata di 1234.56789?\n",
"\n",
"--------------------------------------------------------------------------------\n",
"[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.\n",
"\u001b[33mesperto di dominio\u001b[0m (to interfaccia con l'utente ed esecutore di codice):\n",
"\n",
"\n",
"\u001b[32m***** Suggested tool call (call_08we5q1i): square_root_calculator *****\u001b[0m\n",
"Arguments: \n",
"{\"number\":1234.56789}\n",
"\u001b[32m***********************************************************************\u001b[0m\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[35m\n",
">>>>>>>> EXECUTING FUNCTION square_root_calculator...\u001b[0m\n",
"\u001b[33minterfaccia con l'utente ed esecutore di codice\u001b[0m (to esperto di dominio):\n",
"\n",
"\u001b[33minterfaccia con l'utente ed esecutore di codice\u001b[0m (to esperto di dominio):\n",
"\n",
"\u001b[32m***** Response from calling tool (call_08we5q1i) *****\u001b[0m\n",
"La radice quadrata di 1234.56789 è 35.1364\n",
"\u001b[32m******************************************************\u001b[0m\n",
"\n",
"--------------------------------------------------------------------------------\n",
"[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.\n",
"\u001b[33mesperto di dominio\u001b[0m (to interfaccia con l'utente ed esecutore di codice):\n",
"\n",
"CONCLUSO\n",
"\n",
"--------------------------------------------------------------------------------\n"
]
}
],
"source": [
"domain_expert.reset()\n",
"res = user_proxy.initiate_chat(domain_expert, message=\"Qual è la radice quadrata di 1234.56789?\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"E questa è la ricostruzione della conversazione tra i due agenti."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[{'content': 'Qual è la radice quadrata di 1234.56789?',\n",
" 'name': \"interfaccia con l'utente ed esecutore di codice\",\n",
" 'role': 'assistant'},\n",
" {'content': '',\n",
" 'role': 'assistant',\n",
" 'tool_calls': [{'function': {'arguments': '{\"number\":1234.56789}',\n",
" 'name': 'square_root_calculator'},\n",
" 'id': 'call_08we5q1i',\n",
" 'type': 'function'}]},\n",
" {'content': 'La radice quadrata di 1234.56789 è 35.1364',\n",
" 'name': \"interfaccia con l'utente ed esecutore di codice\",\n",
" 'role': 'tool',\n",
" 'tool_responses': [{'content': 'La radice quadrata di 1234.56789 è 35.1364',\n",
" 'role': 'tool',\n",
" 'tool_call_id': 'call_08we5q1i'}]},\n",
" {'content': 'CONCLUSO', 'name': 'esperto di dominio', 'role': 'user'}]\n"
]
}
],
"source": [
"from pprint import pprint\n",
"pprint(res.chat_history)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"È 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). "
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}