{ "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 }