{ "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à: [searchingagents.ipynb](searchingagents.ipynb)\n", "> * aprire il notebook `searchingagents.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": 2, "metadata": {}, "outputs": [], "source": [ "import autogen\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": 3, "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 ti sono richieste informazioni su libri, usa solo la funzione disponibile per la ricerca nel tuo archivio interno, senza mai ricorrere alla tua memoria. Quando hai completato la ricerca, scrivi CONCLUSO.\",\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 (per semplicità manteniamo il contenuto dell'archivio su cui fare ricerche direttamente all'interno della funzione)." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "@user_proxy.register_for_execution()\n", "@domain_expert.register_for_llm(description=\"Cerca informazioni su un libro dal tuo archivio.\")\n", "def cerca(\n", " titolo: Annotated[str, \"titolo del libro\"],\n", " informazione: Annotated[str, \"genere di informazione da cercare: editore o prezzo\"]\n", " ) -> str:\n", " dati = {\n", " \"Questo libro ha un titolo inventato\": {\"editore\": \"Borgesiana\", \"prezzo\": \"13 €\"},\n", " \"Stiamo davvero scherzando\": {\"editore\": \"Cose serie\", \"prezzo\": \"15 €\"},\n", " \"Cose leggere\": {\"editore\": \"Siamo ambigui\", \"prezzo\": \"12 €\"},\n", " }\n", " return dati.get(titolo, \"Titolo non trovato...\").get(informazione, \"Informazione non trovata...\")" ] }, { "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': 'Cerca informazioni su un libro dal tuo archivio.',\n", " 'name': 'cerca',\n", " 'parameters': {'type': 'object',\n", " 'properties': {'titolo': {'type': 'string',\n", " 'description': 'titolo del libro'},\n", " 'informazione': {'type': 'string',\n", " 'description': 'genere di informazione da cercare: editore o prezzo'}},\n", " 'required': ['titolo', 'informazione']}}}]" ] }, "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": 8, "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 è il prezzo del libro 'Stiamo davvero scherzando'?\n", "\n", "--------------------------------------------------------------------------------\n", "Qual è il prezzo del libro 'Stiamo davvero scherzando'?\n", "\n", "--------------------------------------------------------------------------------\n", "[autogen.oai.client: 09-23 14:00:52] {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_buufxhap): cerca *****\u001b[0m\n", "Arguments: \n", "{\"informazione\":\"prezzo\",\"titolo\":\"Stiamo davvero scherzando\"}\n", "\u001b[32m******************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[35m\n", ">>>>>>>> EXECUTING FUNCTION cerca...\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_buufxhap) *****\u001b[0m\n", "15 €\n", "\u001b[32m******************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "[autogen.oai.client: 09-23 14:00:54] {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 è il prezzo del libro 'Stiamo davvero scherzando'?\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "E questa è la ricostruzione della conversazione tra i due agenti." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[{'content': \"Qual è il prezzo del libro 'Stiamo davvero scherzando'?\",\n", " 'name': \"interfaccia con l'utente ed esecutore di codice\",\n", " 'role': 'assistant'},\n", " {'content': '',\n", " 'role': 'assistant',\n", " 'tool_calls': [{'function': {'arguments': '{\"informazione\":\"prezzo\",\"titolo\":\"Stiamo '\n", " 'davvero scherzando\"}',\n", " 'name': 'cerca'},\n", " 'id': 'call_buufxhap',\n", " 'type': 'function'}]},\n", " {'content': '15 €',\n", " 'name': \"interfaccia con l'utente ed esecutore di codice\",\n", " 'role': 'tool',\n", " 'tool_responses': [{'content': '15 €',\n", " 'role': 'tool',\n", " 'tool_call_id': 'call_buufxhap'}]},\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 }