From 172ed53c0fb4d08e0c88b46b1793da3059eafbdb Mon Sep 17 00:00:00 2001 From: Kevin Cogan Date: Tue, 20 May 2025 16:09:35 +0100 Subject: [PATCH 01/13] fix: updated required installations and added python versio in docs. --- .../notebooks/A2A_Agentic_RAG.ipynb | 167 ++++++++++++------ 1 file changed, 117 insertions(+), 50 deletions(-) diff --git a/demos/a2a_llama_stack/notebooks/A2A_Agentic_RAG.ipynb b/demos/a2a_llama_stack/notebooks/A2A_Agentic_RAG.ipynb index cc3eeae..20a1644 100644 --- a/demos/a2a_llama_stack/notebooks/A2A_Agentic_RAG.ipynb +++ b/demos/a2a_llama_stack/notebooks/A2A_Agentic_RAG.ipynb @@ -23,7 +23,10 @@ "## Prerequisites\n", "\n", "Before starting, ensure you have the following:\n", - "- Followed the instructions in the [Setup Guide](./Level0_getting_started_with_Llama_Stack.ipynb) notebook. \n", + "- `python_requires >= 3.13`\n", + "\n", + "- Followed the instructions in the [Setup Guide](./Level0_getting_started_with_Llama_Stack.ipynb) notebook.\n", + "\n", "- Llama Stack server should be using milvus as its vector DB provider.\n", "\n", "## Additional environment variables\n", @@ -42,26 +45,96 @@ }, { "cell_type": "code", - "execution_count": 1, - "id": "46cc193c-03da-4e4d-8b75-e59df3e4569e", + "execution_count": 6, + "id": "5e50535c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Cloning into 'A2A'...\n", - "remote: Enumerating objects: 1750, done.\u001b[K\n", - "remote: Counting objects: 100% (61/61), done.\u001b[K\n", - "remote: Compressing objects: 100% (39/39), done.\u001b[K\n", - "remote: Total 1750 (delta 40), reused 22 (delta 22), pack-reused 1689 (from 2)\u001b[K\n", - "Receiving objects: 100% (1750/1750), 4.14 MiB | 10.28 MiB/s, done.\n", - "Resolving deltas: 100% (905/905), done.\n" + "Collecting git+https://github.com/google/A2A.git#subdirectory=samples/python\n", + " Cloning https://github.com/google/A2A.git to /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-aswa9mq1\n", + " Running command git clone --filter=blob:none --quiet https://github.com/google/A2A.git /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-aswa9mq1\n", + " Resolved https://github.com/google/A2A.git to commit 081fa20bdfede24922c49e8e56fcdfbee0db0c28\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build wheel ... \u001b[?25ldone\n", + "\u001b[?25h Preparing metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: a2a-sdk>=0.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.2.1)\n", + "Requirement already satisfied: httpx-sse>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.4.0)\n", + "Requirement already satisfied: httpx>=0.28.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.28.1)\n", + "Requirement already satisfied: jwcrypto>=1.5.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (1.5.6)\n", + "Requirement already satisfied: pydantic>=2.10.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.11.4)\n", + "Requirement already satisfied: pyjwt>=2.10.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.10.1)\n", + "Requirement already satisfied: sse-starlette>=2.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.3.5)\n", + "Requirement already satisfied: starlette>=0.46.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.46.2)\n", + "Requirement already satisfied: typing-extensions>=4.12.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (4.13.2)\n", + "Requirement already satisfied: uvicorn>=0.34.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.34.2)\n", + "Requirement already satisfied: opentelemetry-api>=1.33.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.33.1)\n", + "Requirement already satisfied: opentelemetry-sdk>=1.33.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.33.1)\n", + "Requirement already satisfied: anyio in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (4.9.0)\n", + "Requirement already satisfied: certifi in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (2025.4.26)\n", + "Requirement already satisfied: httpcore==1.* in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (1.0.9)\n", + "Requirement already satisfied: idna in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (3.10)\n", + "Requirement already satisfied: h11>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpcore==1.*->httpx>=0.28.1->a2a-samples==0.1.0) (0.16.0)\n", + "Requirement already satisfied: cryptography>=3.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from jwcrypto>=1.5.6->a2a-samples==0.1.0) (45.0.2)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.33.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (2.33.2)\n", + "Requirement already satisfied: typing-inspection>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (0.4.0)\n", + "Requirement already satisfied: click>=7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from uvicorn>=0.34.0->a2a-samples==0.1.0) (8.2.0)\n", + "Requirement already satisfied: sniffio>=1.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from anyio->httpx>=0.28.1->a2a-samples==0.1.0) (1.3.1)\n", + "Requirement already satisfied: cffi>=1.14 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from cryptography>=3.4->jwcrypto>=1.5.6->a2a-samples==0.1.0) (1.17.1)\n", + "Requirement already satisfied: deprecated>=1.2.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.2.18)\n", + "Requirement already satisfied: importlib-metadata<8.7.0,>=6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (8.6.1)\n", + "Requirement already satisfied: opentelemetry-semantic-conventions==0.54b1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from opentelemetry-sdk>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (0.54b1)\n", + "Requirement already satisfied: pycparser in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from cffi>=1.14->cryptography>=3.4->jwcrypto>=1.5.6->a2a-samples==0.1.0) (2.22)\n", + "Requirement already satisfied: wrapt<2,>=1.10 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from deprecated>=1.2.6->opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.17.2)\n", + "Requirement already satisfied: zipp>=3.20 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from importlib-metadata<8.7.0,>=6.0->opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (3.21.0)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", + "Requirement already satisfied: llama_stack_client in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (0.2.7)\n", + "Requirement already satisfied: dotenv in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (0.9.9)\n", + "Requirement already satisfied: anyio<5,>=3.5.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (4.9.0)\n", + "Requirement already satisfied: click in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (8.2.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (1.9.0)\n", + "Requirement already satisfied: httpx<1,>=0.23.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (0.28.1)\n", + "Requirement already satisfied: pandas in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (2.2.3)\n", + "Requirement already satisfied: prompt-toolkit in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (3.0.51)\n", + "Requirement already satisfied: pyaml in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (25.1.0)\n", + "Requirement already satisfied: pydantic<3,>=1.9.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (2.11.4)\n", + "Requirement already satisfied: rich in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (14.0.0)\n", + "Requirement already satisfied: sniffio in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (1.3.1)\n", + "Requirement already satisfied: termcolor in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (3.1.0)\n", + "Requirement already satisfied: tqdm in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (4.67.1)\n", + "Requirement already satisfied: typing-extensions<5,>=4.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (4.13.2)\n", + "Requirement already satisfied: python-dotenv in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from dotenv) (1.1.0)\n", + "Requirement already satisfied: idna>=2.8 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from anyio<5,>=3.5.0->llama_stack_client) (3.10)\n", + "Requirement already satisfied: certifi in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpx<1,>=0.23.0->llama_stack_client) (2025.4.26)\n", + "Requirement already satisfied: httpcore==1.* in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpx<1,>=0.23.0->llama_stack_client) (1.0.9)\n", + "Requirement already satisfied: h11>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->llama_stack_client) (0.16.0)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.33.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (2.33.2)\n", + "Requirement already satisfied: typing-inspection>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (0.4.0)\n", + "Requirement already satisfied: numpy>=1.26.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pandas->llama_stack_client) (2.2.6)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pandas->llama_stack_client) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pandas->llama_stack_client) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pandas->llama_stack_client) (2025.2)\n", + "Requirement already satisfied: wcwidth in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from prompt-toolkit->llama_stack_client) (0.2.13)\n", + "Requirement already satisfied: PyYAML in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pyaml->llama_stack_client) (6.0.2)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from rich->llama_stack_client) (3.0.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from rich->llama_stack_client) (2.19.1)\n", + "Requirement already satisfied: mdurl~=0.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich->llama_stack_client) (0.1.2)\n", + "Requirement already satisfied: six>=1.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas->llama_stack_client) (1.17.0)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n" ] } ], "source": [ - "! git clone https://github.com/google/A2A.git" + "! pip install \"git+https://github.com/google/A2A.git#subdirectory=samples/python\"\n", + "! pip install llama_stack_client dotenv" ] }, { @@ -74,7 +147,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 7, "id": "92301f97-17f4-4f48-a3da-a5288b8b02dd", "metadata": {}, "outputs": [], @@ -96,7 +169,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 8, "id": "839cf607-0301-41dc-ab13-d57b9ac20bd8", "metadata": {}, "outputs": [], @@ -120,7 +193,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 9, "id": "9b87b139-bd18-47b2-889a-1b8ed3018655", "metadata": {}, "outputs": [ @@ -130,8 +203,8 @@ "text": [ "Connected to Llama Stack server\n", "Inference Parameters:\n", - "\tModel: granite32-8b\n", - "\tSampling Parameters: {'strategy': {'type': 'greedy'}, 'max_tokens': 4096}\n", + "\tModel: llama32-3b\n", + "\tSampling Parameters: {'strategy': {'type': 'greedy'}, 'max_tokens': 512}\n", "\tstream: False\n" ] } @@ -216,17 +289,17 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 10, "id": "3b8ee65a-5925-44ed-a81b-f0df2c52465e", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "VectorDBRegisterResponse(embedding_dimension=384, embedding_model='all-MiniLM-L6-v2', identifier='test_vector_db_56361858-f5f2-4b61-9fea-28efb200c36c', provider_id='milvus', provider_resource_id='test_vector_db_56361858-f5f2-4b61-9fea-28efb200c36c', type='vector_db', access_attributes=None)" + "VectorDBRegisterResponse(embedding_dimension=384, embedding_model='all-MiniLM-L6-v2', identifier='test_vector_db_0efe2f59-9ec0-48bc-b673-4d4559a7e882', provider_id='milvus', provider_resource_id='test_vector_db_0efe2f59-9ec0-48bc-b673-4d4559a7e882', type='vector_db', access_attributes=None)" ] }, - "execution_count": 4, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -252,7 +325,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 11, "id": "93e86b69-7b8e-4418-976c-2a694a99a55b", "metadata": {}, "outputs": [], @@ -286,7 +359,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 12, "id": "2dd4664a-ff7f-4474-b6af-3a4ad3f73052", "metadata": { "tags": [] @@ -300,7 +373,7 @@ " sampling_params=sampling_params,\n", " tools=[\n", " dict(\n", - " name=\"builtin::rag\",\n", + " name=\"builtin::rag/knowledge_search\",\n", " args={\n", " \"vector_db_ids\": [vector_db_id],\n", " },\n", @@ -322,7 +395,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "2f11c6a2-2568-4181-88e8-0e5eb514ed6c", "metadata": {}, "outputs": [ @@ -330,26 +403,23 @@ "name": "stderr", "output_type": "stream", "text": [ - "INFO: Started server process [82835]\n", + "INFO: Started server process [14257]\n", "INFO: Waiting for application startup.\n", "INFO: Application startup complete.\n", - "INFO: Uvicorn running on http://localhost:8080 (Press CTRL+C to quit)\n" + "INFO: Uvicorn running on http://localhost:10020 (Press CTRL+C to quit)\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "INFO: ::1:56243 - \"GET /.well-known/agent.json HTTP/1.1\" 200 OK\n", - "INFO: ::1:56247 - \"POST / HTTP/1.1\" 200 OK\n", - "INFO: ::1:56252 - \"POST / HTTP/1.1\" 200 OK\n", - "INFO: ::1:56260 - \"POST / HTTP/1.1\" 200 OK\n", - "INFO: ::1:56267 - \"POST / HTTP/1.1\" 200 OK\n" + "INFO: ::1:58564 - \"GET /.well-known/agent.json HTTP/1.1\" 200 OK\n", + "INFO: ::1:58565 - \"POST / HTTP/1.1\" 200 OK\n" ] } ], "source": [ - "rag_agent_local_port = int(os.getenv(\"RAG_AGENT_LOCAL_PORT\"))\n", + "rag_agent_local_port = int(os.getenv(\"RAG_AGENT_LOCAL_PORT\", \"10010\"))\n", "rag_agent_url = f\"http://localhost:{rag_agent_local_port}\"\n", "\n", "agent_card = AgentCard(\n", @@ -388,7 +458,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 14, "id": "7414837e-b04e-432d-82c3-6719a8f62132", "metadata": {}, "outputs": [], @@ -398,6 +468,7 @@ " client,\n", " model=model_id,\n", " instructions=\"You are a helpful assistant. When a tool is used, only print its output without adding more content.\",\n", + " sampling_params=sampling_params,\n", " tools=[rag_agent_tool],\n", ")" ] @@ -412,7 +483,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 15, "id": "95b9baa2-4739-426a-b79a-2ff90f44c023", "metadata": { "tags": [] @@ -427,7 +498,7 @@ "\n", "---------- 📍 Step 1: InferenceStep ----------\n", "🛠️ Tool call Generated:\n", - "\u001b[35mTool call: OpenShift Knowledge Source Agent, Arguments: {'query': 'How to install OpenShift'}\u001b[0m\n", + "\u001b[35mTool call: OpenShift Knowledge Source Agent, Arguments: {'query': 'install OpenShift'}\u001b[0m\n", "\n", "---------- 📍 Step 2: ToolExecutionStep ----------\n", "🔧 Executing tool...\n" @@ -436,11 +507,11 @@ { "data": { "text/html": [ - "
'<tool_call>Tool:knowledge_search Args:{\\'query\\': \\'How to install OpenShift\\'}Tool:knowledge_search Response:[TextContentItem(text=\\'knowledge_search tool found 5 chunks:\\\\nBEGIN of knowledge_search tool results.\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 1:\\\\nDocument_id:num-0\\\\nContent:  \"Import from Git\" entry. Click on it, and paste the URL of a project, for example,\\\\ngitlab.com/akosma/simple-deno-api.git.\\\\nAs soon as you paste the URL, OpenShift will immediately analyze the structure and programming\\\\nlanguage of the project and automatically recommend options for its build process. In our case, it’s\\\\na small application built with the Go programming language, and as such, it will advise the options\\\\nshown on the screen.\\\\nFigure 5. Deploying a project directly from its Git repository\\\\n25\\\\nThis particular example doesn’t require more configurations than the ones shown on the screen;\\\\nclick the [\\\\u2009Create\\\\u2009] button.\\\\nAfter a few seconds, you will see your application running on the \"Topology\" screen. OpenShift will\\\\ndownload the source code and trigger your project’s build. Click on the Topology screen icon to see\\\\nthe \"Build\" section, indicating that a build is running. The compilation and deployment of your\\\\napplication can take some time, depending on the complexity of the source code and the\\\\nprogramming language used.\\\\nOnce the build has finished, on the same pane, you will see a route available under the \"Routes\"\\\\nsection. Click on it, and you will see your application in action.\\\\n10.2. Container Registry\\\\nOpenShift has built your application source code, and the product of this build process is a\\\\ncontainer. You can see the container that OpenShift made for you on the \"Administrator\"\\\\nperspective, selecting the \"Builds\" menu and then the \"ImageStreams\" menu entry.\\\\nOpenShift includes a container registry; developers can use it as any other registry from outside the\\\\ncluster. Let us use \"podman\" to access the container registry and run the container locally on your\\\\nworkstation.\\\\nUsers must have the \"registry-editor\" and the \"system:image-builder\" roles to access the container\\\\nregistry. Since we’re connected to the Web Console using the \"kubeadmin\" user, we can provide\\\\nthose roles directly from the user interface without using the command line.\\\\nNavigate to the \"User Management\" section and select \"RoleBindings.\" Click on the [\\\\u2009Create\\\\nbinding\\\\u2009] button, and fill the form using the following values:\\\\n• Name: developer-sourcecode-registry-editor\\\\n• Namespace: sourcecode\\\\n• Role name: registry-editor\\\\n• Subject: User\\\\n• Subject name: developer\\\\nDo the same for the \"system:image-builder\" role, using a different \"Name\" field\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 2:\\\\nDocument_id:num-0\\\\nContent: 23\\\\ninstall OpenShift Local, 16\\\\nJ\\\\nJAR file, 23\\\\nJava, 14, 24, 44\\\\nJavaScript, 24, 44\\\\nJenkins, 28\\\\nK\\\\nKiali, 36\\\\nKibana, 40, 40\\\\nKnative, 34\\\\nKubernetes, 7\\\\nL\\\\nlogs, 40\\\\nM\\\\nMicroservices, 36\\\\nmonitor, 40\\\\nN\\\\nNode.js, 14\\\\nnon-root accounts, 20\\\\nO\\\\nOpenShift 4.12, 33\\\\nOpenShift Kubernetes Distribution, 8\\\\nOpenShift Service Mesh, 36\\\\noperator, 28, 36\\\\nOperatorHub, 33, 36\\\\nOperators, 33\\\\n48\\\\nP\\\\nperspectives, 22\\\\nPHP, 14, 24\\\\nPlatform-as-a-Service, 8\\\\nprivilege escalation, 19\\\\nprivileged ports, 20\\\\nProject, 9\\\\nPrometheus, 40, 44\\\\nPromQL, 45\\\\nPython, 14, 24, 44\\\\nQ\\\\nQuarkus, 14, 44\\\\nR\\\\nRed Hat developer account, 13\\\\nRed Hat OpenShift, 7\\\\nRed Hat OpenShift Dev Spaces, 14\\\\nRed Hat OpenShift Local, 8, 15\\\\nRed Hat OpenShift Pipelines, 28\\\\nRed Hat Quay, 20\\\\nRed Hat Universal Base Images, 19\\\\nrole, 19\\\\nRoute, 9\\\\nRust, 14\\\\nS\\\\nScala, 14\\\\nScaling, 42\\\\nsecure by default, 19\\\\nSecurity Context Constraints, 19\\\\nServerless, 34\\\\nservice mesh, 36\\\\nsource code project, 22\\\\nstateful applications, 33\\\\nT\\\\nTekton, 28\\\\ntemplates, 32\\\\nTopology, 27\\\\nTwelve-Factor App, 21, 40\\\\nTypeScript, 24\\\\nU\\\\nUBI, 19\\\\nV\\\\nVertical scaling, 42\\\\nVisual Studio Code, 14\\\\nW\\\\nWeb Console, 22, 40\\\\n49\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 3:\\\\nDocument_id:num-0\\\\nContent: .\\\\nThese characteristics set OpenShift apart as an excellent Kubernetes platform for enterprise users.\\\\nThe latest version of OpenShift available at the time of this writing is 4.12.\\\\n3.2. Is Red Hat OpenShift Open Source?\\\\nRed Hat OpenShift is a commercial product based on an open-source project called OKD. This\\\\nacronym means \" OpenShift Kubernetes Distribution\" and is publicly available for everyone to\\\\ninspect and contribute. Like the upstream Kubernetes project, OKD developers use the Go\\\\nprogramming language.\\\\n3.3. How can I run OpenShift?\\\\nToday, Red Hat OpenShift is available through various mechanisms and formats:\\\\n• DevOps teams can install it in their data centers \"on-premise.\"\\\\n• Major hyperscalers such as AWS, Azure, Google Cloud Platform, and IBM Cloud offer managed\\\\nRed Hat OpenShift installations.\\\\n• Developers can either run OpenShift locally on their workstations using Red Hat OpenShift\\\\nLocal, also known as CRC or \"Code-Ready Containers\"\\\\n• They can also request a 30-day trial OpenShift cluster, offered by Red Hat, at no charge, for\\\\ntesting and evaluation purposes.\\\\nRed Hat OpenShift is an integrated Platform-as-a-Service for enterprise users based on Kubernetes.\\\\nIt is tightly integrated with advanced security settings, developer tooling, and monitoring\\\\nmechanisms, allowing DevOps teams to be more productive.\\\\n8\\\\nChapter 4. OpenShift-only Custom Resource\\\\nDefinitions\\\\nRed Hat OpenShift is a complete DevOps platform extending Kubernetes in various ways. It bundles\\\\na constellation of Custom Resource Definitions (CRDs) to make the life of developers and cluster\\\\nadministrators easier.\\\\nLet us talk first about the CRDs only available on OpenShift.\\\\n4.1. Project\\\\nAn OpenShift Project is similar to a Kubernetes namespace, but more tightly integrated into the\\\\nsecurity system of OpenShift through additional annotations.\\\\napiVersion: project.openshift.io/v1\\\\nkind: Project\\\\nmetadata:\\\\n\\\\xa0 name: linkedin-learning-project\\\\n\\\\xa0 annotations:\\\\n\\\\xa0   openshift.io/description: \"Project description\"\\\\n\\\\xa0   openshift.io/display-name: \"Display name\"\\\\n4.2. Route\\\\nThe OpenShift Route object was one of the primary inspirations during the development of the\\\\nIngress object. In OpenShift, Ingress and Route objects work together to ensure your applications\\\\nare available outside the cluster.\\\\napiVersion: route.openshift.io/v1\\\\nkind: Route\\\\nmetadata:\\\\n\\\\xa0 name: my-route\\\\nspec:\\\\n\\\\xa0 host:\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 4:\\\\nDocument_id:num-0\\\\nContent:  We\\\\nrecommend you to check the official Red Hat OpenShift Local documentation for an updated list of\\\\nrequirements at the official documentation website.\\\\n\\\\uf05a\\\\nRegarding Linux, even if Red Hat does not officially support them, OpenShift Local\\\\ncan run on other distributions, such as Ubuntu or Debian, with minor caveats.\\\\nRunning OpenShift Local on any Linux distribution requires a few additional\\\\nsoftware packages to be installed through your default package manager. The\\\\n15\\\\ndocumentation at crc.dev/crc has more information about this subject.\\\\n7.2. Hardware Requirements\\\\nIn terms of hardware, OpenShift Local has some strict requirements. Your system must use a recent\\\\nIntel CPU (except for Macs, where Apple Silicon machines are supported) with at least four physical\\\\ncores and have at least 16 GB of RAM. Be aware that the base installation of OpenShift Local\\\\nrequires at least 9 GB free to start. Of course, to run other applications on OpenShift Local, you will\\\\nneed more RAM, so using a computer with at least 32 GB of RAM is strongly recommended.\\\\nOpenShift Local also requires at least 35 GB of free disk space for its installation. The memory\\\\nrequirements are likely to increase in the future, so please check the documentation at crc.dev for\\\\nmore up-to-date information.\\\\n7.3. Installation\\\\nTo install OpenShift Local, open your web browser and navigate to console.redhat.com/openshift/\\\\ncreate/local . Download the latest release of OpenShift Local and the \"pull secret\" file. The latter is a\\\\nfile containing a key identifying your copy of OpenShift Local to your Red Hat Developer account.\\\\nUnzip the file containing the OpenShift Local executable, and using your terminal, run the\\\\ncommand crc setup . This command will prepare your copy of OpenShift Local, verifying\\\\nrequirements and setting the required configuration values.\\\\nOnce the crc setup command is ready, launch crc start. Running crc start can take a long time,\\\\naround 20 minutes, on a recent PC.\\\\nOnce started, access the OpenShift Web Console with the crc console command, which will open\\\\nyour default browser. OpenShift Local uses the developer username and password to log in as a\\\\nlow-privilege user, while the kubeadmin user uses a random-generated password. Use the crc\\\\nconsole --credentials command to find the credentials required to log in as the kubeadmin user.\\\\nOpenShift Local allows developers to perform various everyday tasks as if it were a standard\\\\nOpenShift cluster, like deploying applications\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 5:\\\\nDocument_id:num-0\\\\nContent: _02 branch of the GitHub\\\\nrepository for this course.\\\\nThe whole point of OpenShift is to be able to deploy, run, and monitor containerized applications.\\\\nDevOps engineers can deploy containers in OpenShift clusters through various means, for example:\\\\n• Using a YAML manifest.\\\\n• Deploying a single container using the web console.\\\\n• Building a project stored in a Git repository anywhere on the Internet and deploying the\\\\nresulting container.\\\\n• Using the integrated CI/CD pipelines.\\\\n• Using the odo tool together with \"Devfiles.\"\\\\nEach approach has pros and cons; in this chapter, we will review how to use the web console and\\\\nhow to use the odo tool.\\\\n9.1. Deploying Applications with the Web Console\\\\nDevOps engineers can deploy applications immediately using the Web Console. Launch your CRC\\\\ninstance and open the web console in your preferred web browser. The URL of the OpenShift web\\\\nconsole is \"https://console-openshift-console.apps-crc.testing.\"\\\\nThe OpenShift Web Console offers two major perspectives:\\\\n• The \"Administrator\" perspective.\\\\n• And the \"Developer\" perspective.\\\\nFor this explanation, select the \"Developer\" perspective.\\\\nThe first time you open the Developer perspective, a popup invites you to follow a user interface\\\\ntour.\\\\nOn the left-hand side, the perspective menu shows an entry titled \"Add,\" which, as the name\\\\nimplies, provides various mechanisms to deploy applications on an OpenShift cluster.\\\\nThe \"Add\" screen shows the various ways DevOps engineers can deploy applications on a cluster:\\\\n• Using the Developer Catalog, browsing and choosing among a long list of available databases,\\\\nmessage queues, and other valuable components to build applications, or entering your\\\\npreferred Helm chart repository to extend the catalog.\\\\n• Specifying the URL to a specific container on any standard container registry.\\\\n• Specifying the URL of a source code project stored on a Git repository, for example, but not\\\\n22\\\\nlimited to GitHub, GitLab, Gitea, or other locations.\\\\n• Importing YAML directly or even a JAR file with a Java application.\\\\nLet us select the \"Container Image\" option, where we can specify the URL of a ready-to-use\\\\ncontainer.\\\\nEnter the URL of the container on the field, and click on the [\\\\u2009Create\\\\u2009] button at the bottom of the\\\\npage. You do not need to change any other value on the form.\\\\nA few seconds later, depending on the size of the container and the speed of your Internet\\\\nconnection,\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'END of knowledge_search tool results.\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'The above results were retrieved to help answer the user\\\\\\'s query: \"How to install OpenShift\". Use them as supporting information only in answering this query.\\\\n\\', type=\\'text\\')]To install OpenShift, you can follow these steps:\\n\\n1. **Install on your data center (on-premise):** You can install OpenShift in your own data center. This involves setting up a Kubernetes cluster and then deploying OpenShift on top of it. Detailed instructions can be found in the official Red Hat OpenShift documentation.\\n\\n2. **Use managed services from hyperscalers:** Major cloud providers like AWS, Azure, Google Cloud Platform, and IBM Cloud offer managed OpenShift services. You can provision an OpenShift cluster through their respective web interfaces.\\n\\n3. **Run OpenShift Locally on your workstation:** Red Hat OpenShift Local, also known as CodeReady Containers (CRC), allows you to run OpenShift on your local machine. Here\\'s how:\\n   - Download the latest release of OpenShift Local and the \"pull secret\" file from the official Red Hat website.\\n   - Unzip the downloaded file and run the command `crc setup` in your terminal to prepare your OpenShift Local instance.\\n   - After the setup is complete, start OpenShift Local with the command `crc start`. This process can take around 20 minutes.\\n   - Access the OpenShift Web Console with the command `crc console`. The default login credentials are the developer username and password for a low-privilege user, and the kubeadmin user uses a random-generated password. You can find the kubeadmin credentials with the command `crc console --credentials`.\\n\\nRemember, running OpenShift Locally requires a recent Intel CPU (or Apple Silicon for Macs), at least 4 physical cores, 16 GB of RAM, and 35 GB of free disk space.\\n\\nFor more detailed and up-to-date information, always refer to the official Red Hat OpenShift documentation.'\n",
+       "
'Tool:knowledge_search Args:{\\'query\\': \\'OpenShift installation\\'}Tool:knowledge_search Response:[TextContentItem(text=\\'knowledge_search tool found 5 chunks:\\\\nBEGIN of knowledge_search tool results.\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 1:\\\\nDocument_id:num-0\\\\nContent: 23\\\\ninstall OpenShift Local, 16\\\\nJ\\\\nJAR file, 23\\\\nJava, 14, 24, 44\\\\nJavaScript, 24, 44\\\\nJenkins, 28\\\\nK\\\\nKiali, 36\\\\nKibana, 40, 40\\\\nKnative, 34\\\\nKubernetes, 7\\\\nL\\\\nlogs, 40\\\\nM\\\\nMicroservices, 36\\\\nmonitor, 40\\\\nN\\\\nNode.js, 14\\\\nnon-root accounts, 20\\\\nO\\\\nOpenShift 4.12, 33\\\\nOpenShift Kubernetes Distribution, 8\\\\nOpenShift Service Mesh, 36\\\\noperator, 28, 36\\\\nOperatorHub, 33, 36\\\\nOperators, 33\\\\n48\\\\nP\\\\nperspectives, 22\\\\nPHP, 14, 24\\\\nPlatform-as-a-Service, 8\\\\nprivilege escalation, 19\\\\nprivileged ports, 20\\\\nProject, 9\\\\nPrometheus, 40, 44\\\\nPromQL, 45\\\\nPython, 14, 24, 44\\\\nQ\\\\nQuarkus, 14, 44\\\\nR\\\\nRed Hat developer account, 13\\\\nRed Hat OpenShift, 7\\\\nRed Hat OpenShift Dev Spaces, 14\\\\nRed Hat OpenShift Local, 8, 15\\\\nRed Hat OpenShift Pipelines, 28\\\\nRed Hat Quay, 20\\\\nRed Hat Universal Base Images, 19\\\\nrole, 19\\\\nRoute, 9\\\\nRust, 14\\\\nS\\\\nScala, 14\\\\nScaling, 42\\\\nsecure by default, 19\\\\nSecurity Context Constraints, 19\\\\nServerless, 34\\\\nservice mesh, 36\\\\nsource code project, 22\\\\nstateful applications, 33\\\\nT\\\\nTekton, 28\\\\ntemplates, 32\\\\nTopology, 27\\\\nTwelve-Factor App, 21, 40\\\\nTypeScript, 24\\\\nU\\\\nUBI, 19\\\\nV\\\\nVertical scaling, 42\\\\nVisual Studio Code, 14\\\\nW\\\\nWeb Console, 22, 40\\\\n49\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 2:\\\\nDocument_id:num-0\\\\nContent:  We\\\\nrecommend you to check the official Red Hat OpenShift Local documentation for an updated list of\\\\nrequirements at the official documentation website.\\\\n\\\\uf05a\\\\nRegarding Linux, even if Red Hat does not officially support them, OpenShift Local\\\\ncan run on other distributions, such as Ubuntu or Debian, with minor caveats.\\\\nRunning OpenShift Local on any Linux distribution requires a few additional\\\\nsoftware packages to be installed through your default package manager. The\\\\n15\\\\ndocumentation at crc.dev/crc has more information about this subject.\\\\n7.2. Hardware Requirements\\\\nIn terms of hardware, OpenShift Local has some strict requirements. Your system must use a recent\\\\nIntel CPU (except for Macs, where Apple Silicon machines are supported) with at least four physical\\\\ncores and have at least 16 GB of RAM. Be aware that the base installation of OpenShift Local\\\\nrequires at least 9 GB free to start. Of course, to run other applications on OpenShift Local, you will\\\\nneed more RAM, so using a computer with at least 32 GB of RAM is strongly recommended.\\\\nOpenShift Local also requires at least 35 GB of free disk space for its installation. The memory\\\\nrequirements are likely to increase in the future, so please check the documentation at crc.dev for\\\\nmore up-to-date information.\\\\n7.3. Installation\\\\nTo install OpenShift Local, open your web browser and navigate to console.redhat.com/openshift/\\\\ncreate/local . Download the latest release of OpenShift Local and the \"pull secret\" file. The latter is a\\\\nfile containing a key identifying your copy of OpenShift Local to your Red Hat Developer account.\\\\nUnzip the file containing the OpenShift Local executable, and using your terminal, run the\\\\ncommand crc setup . This command will prepare your copy of OpenShift Local, verifying\\\\nrequirements and setting the required configuration values.\\\\nOnce the crc setup command is ready, launch crc start. Running crc start can take a long time,\\\\naround 20 minutes, on a recent PC.\\\\nOnce started, access the OpenShift Web Console with the crc console command, which will open\\\\nyour default browser. OpenShift Local uses the developer username and password to log in as a\\\\nlow-privilege user, while the kubeadmin user uses a random-generated password. Use the crc\\\\nconsole --credentials command to find the credentials required to log in as the kubeadmin user.\\\\nOpenShift Local allows developers to perform various everyday tasks as if it were a standard\\\\nOpenShift cluster, like deploying applications\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 3:\\\\nDocument_id:num-0\\\\nContent:  \"Import from Git\" entry. Click on it, and paste the URL of a project, for example,\\\\ngitlab.com/akosma/simple-deno-api.git.\\\\nAs soon as you paste the URL, OpenShift will immediately analyze the structure and programming\\\\nlanguage of the project and automatically recommend options for its build process. In our case, it’s\\\\na small application built with the Go programming language, and as such, it will advise the options\\\\nshown on the screen.\\\\nFigure 5. Deploying a project directly from its Git repository\\\\n25\\\\nThis particular example doesn’t require more configurations than the ones shown on the screen;\\\\nclick the [\\\\u2009Create\\\\u2009] button.\\\\nAfter a few seconds, you will see your application running on the \"Topology\" screen. OpenShift will\\\\ndownload the source code and trigger your project’s build. Click on the Topology screen icon to see\\\\nthe \"Build\" section, indicating that a build is running. The compilation and deployment of your\\\\napplication can take some time, depending on the complexity of the source code and the\\\\nprogramming language used.\\\\nOnce the build has finished, on the same pane, you will see a route available under the \"Routes\"\\\\nsection. Click on it, and you will see your application in action.\\\\n10.2. Container Registry\\\\nOpenShift has built your application source code, and the product of this build process is a\\\\ncontainer. You can see the container that OpenShift made for you on the \"Administrator\"\\\\nperspective, selecting the \"Builds\" menu and then the \"ImageStreams\" menu entry.\\\\nOpenShift includes a container registry; developers can use it as any other registry from outside the\\\\ncluster. Let us use \"podman\" to access the container registry and run the container locally on your\\\\nworkstation.\\\\nUsers must have the \"registry-editor\" and the \"system:image-builder\" roles to access the container\\\\nregistry. Since we’re connected to the Web Console using the \"kubeadmin\" user, we can provide\\\\nthose roles directly from the user interface without using the command line.\\\\nNavigate to the \"User Management\" section and select \"RoleBindings.\" Click on the [\\\\u2009Create\\\\nbinding\\\\u2009] button, and fill the form using the following values:\\\\n• Name: developer-sourcecode-registry-editor\\\\n• Namespace: sourcecode\\\\n• Role name: registry-editor\\\\n• Subject: User\\\\n• Subject name: developer\\\\nDo the same for the \"system:image-builder\" role, using a different \"Name\" field\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 4:\\\\nDocument_id:num-0\\\\nContent: .\\\\nThese characteristics set OpenShift apart as an excellent Kubernetes platform for enterprise users.\\\\nThe latest version of OpenShift available at the time of this writing is 4.12.\\\\n3.2. Is Red Hat OpenShift Open Source?\\\\nRed Hat OpenShift is a commercial product based on an open-source project called OKD. This\\\\nacronym means \" OpenShift Kubernetes Distribution\" and is publicly available for everyone to\\\\ninspect and contribute. Like the upstream Kubernetes project, OKD developers use the Go\\\\nprogramming language.\\\\n3.3. How can I run OpenShift?\\\\nToday, Red Hat OpenShift is available through various mechanisms and formats:\\\\n• DevOps teams can install it in their data centers \"on-premise.\"\\\\n• Major hyperscalers such as AWS, Azure, Google Cloud Platform, and IBM Cloud offer managed\\\\nRed Hat OpenShift installations.\\\\n• Developers can either run OpenShift locally on their workstations using Red Hat OpenShift\\\\nLocal, also known as CRC or \"Code-Ready Containers\"\\\\n• They can also request a 30-day trial OpenShift cluster, offered by Red Hat, at no charge, for\\\\ntesting and evaluation purposes.\\\\nRed Hat OpenShift is an integrated Platform-as-a-Service for enterprise users based on Kubernetes.\\\\nIt is tightly integrated with advanced security settings, developer tooling, and monitoring\\\\nmechanisms, allowing DevOps teams to be more productive.\\\\n8\\\\nChapter 4. OpenShift-only Custom Resource\\\\nDefinitions\\\\nRed Hat OpenShift is a complete DevOps platform extending Kubernetes in various ways. It bundles\\\\na constellation of Custom Resource Definitions (CRDs) to make the life of developers and cluster\\\\nadministrators easier.\\\\nLet us talk first about the CRDs only available on OpenShift.\\\\n4.1. Project\\\\nAn OpenShift Project is similar to a Kubernetes namespace, but more tightly integrated into the\\\\nsecurity system of OpenShift through additional annotations.\\\\napiVersion: project.openshift.io/v1\\\\nkind: Project\\\\nmetadata:\\\\n\\\\xa0 name: linkedin-learning-project\\\\n\\\\xa0 annotations:\\\\n\\\\xa0   openshift.io/description: \"Project description\"\\\\n\\\\xa0   openshift.io/display-name: \"Display name\"\\\\n4.2. Route\\\\nThe OpenShift Route object was one of the primary inspirations during the development of the\\\\nIngress object. In OpenShift, Ingress and Route objects work together to ensure your applications\\\\nare available outside the cluster.\\\\napiVersion: route.openshift.io/v1\\\\nkind: Route\\\\nmetadata:\\\\n\\\\xa0 name: my-route\\\\nspec:\\\\n\\\\xa0 host:\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 5:\\\\nDocument_id:num-0\\\\nContent: _02 branch of the GitHub\\\\nrepository for this course.\\\\nThe whole point of OpenShift is to be able to deploy, run, and monitor containerized applications.\\\\nDevOps engineers can deploy containers in OpenShift clusters through various means, for example:\\\\n• Using a YAML manifest.\\\\n• Deploying a single container using the web console.\\\\n• Building a project stored in a Git repository anywhere on the Internet and deploying the\\\\nresulting container.\\\\n• Using the integrated CI/CD pipelines.\\\\n• Using the odo tool together with \"Devfiles.\"\\\\nEach approach has pros and cons; in this chapter, we will review how to use the web console and\\\\nhow to use the odo tool.\\\\n9.1. Deploying Applications with the Web Console\\\\nDevOps engineers can deploy applications immediately using the Web Console. Launch your CRC\\\\ninstance and open the web console in your preferred web browser. The URL of the OpenShift web\\\\nconsole is \"https://console-openshift-console.apps-crc.testing.\"\\\\nThe OpenShift Web Console offers two major perspectives:\\\\n• The \"Administrator\" perspective.\\\\n• And the \"Developer\" perspective.\\\\nFor this explanation, select the \"Developer\" perspective.\\\\nThe first time you open the Developer perspective, a popup invites you to follow a user interface\\\\ntour.\\\\nOn the left-hand side, the perspective menu shows an entry titled \"Add,\" which, as the name\\\\nimplies, provides various mechanisms to deploy applications on an OpenShift cluster.\\\\nThe \"Add\" screen shows the various ways DevOps engineers can deploy applications on a cluster:\\\\n• Using the Developer Catalog, browsing and choosing among a long list of available databases,\\\\nmessage queues, and other valuable components to build applications, or entering your\\\\npreferred Helm chart repository to extend the catalog.\\\\n• Specifying the URL to a specific container on any standard container registry.\\\\n• Specifying the URL of a source code project stored on a Git repository, for example, but not\\\\n22\\\\nlimited to GitHub, GitLab, Gitea, or other locations.\\\\n• Importing YAML directly or even a JAR file with a Java application.\\\\nLet us select the \"Container Image\" option, where we can specify the URL of a ready-to-use\\\\ncontainer.\\\\nEnter the URL of the container on the field, and click on the [\\\\u2009Create\\\\u2009] button at the bottom of the\\\\npage. You do not need to change any other value on the form.\\\\nA few seconds later, depending on the size of the container and the speed of your Internet\\\\nconnection,\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'END of knowledge_search tool results.\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'The above results were retrieved to help answer the user\\\\\\'s query: \"OpenShift installation\". Use them as supporting information only in answering this query.\\\\n\\', type=\\'text\\')]To install OpenShift, you can follow these steps:\\n\\n1. Check the system requirements for OpenShift Local, which is a version of OpenShift that can be installed on a local machine. The requirements include a recent Intel CPU with at least four physical cores, 16 GB of RAM, and 35 GB of free disk space.\\n2. Download the latest version of OpenShift Local from the official Red Hat website.\\n3. Unzip the downloaded file and run the `crc setup` command to prepare the installation.\\n4. Launch the `crc start` command to start the installation process, which may take around 20 minutes.\\n5. Once the installation is complete, access the OpenShift Web Console using the `crc console` command, which will open your default browser.\\n6. Log in to the Web Console using the developer username and password, or use the `crc console --credentials` command to find the credentials required to log in as the kubeadmin user.\\n7. You can then deploy applications using the web console or using the odo tool.\\n\\nNote that OpenShift is also available through various mechanisms and formats, including installation in data centers, managed installations by hyperscalers, and trial clusters offered by Red Hat.'\n",
        "
\n" ], "text/plain": [ - "\u001b[32m'\u001b[0m\u001b[32m<\u001b[0m\u001b[32mtool_call\u001b[0m\u001b[32m>\u001b[0m\u001b[32mTool:knowledge_search Args:\u001b[0m\u001b[32m{\u001b[0m\u001b[32m\\'query\\': \\'How to install OpenShift\\'\u001b[0m\u001b[32m}\u001b[0m\u001b[32mTool:knowledge_search Response:\u001b[0m\u001b[32m[\u001b[0m\u001b[32mTextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'knowledge_search tool found 5 chunks:\\\\nBEGIN of knowledge_search tool results.\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 1:\\\\nDocument_id:num-0\\\\nContent: \"Import from Git\" entry. Click on it, and paste the URL of a project, for example,\\\\ngitlab.com/akosma/simple-deno-api.git.\\\\nAs soon as you paste the URL, OpenShift will immediately analyze the structure and programming\\\\nlanguage of the project and automatically recommend options for its build process. In our case, it’s\\\\na small application built with the Go programming language, and as such, it will advise the options\\\\nshown on the screen.\\\\nFigure 5. Deploying a project directly from its Git repository\\\\n25\\\\nThis particular example doesn’t require more configurations than the ones shown on the screen;\\\\nclick the \u001b[0m\u001b[32m[\u001b[0m\u001b[32m\\\\u2009Create\\\\u2009\u001b[0m\u001b[32m]\u001b[0m\u001b[32m button.\\\\nAfter a few seconds, you will see your application running on the \"Topology\" screen. OpenShift will\\\\ndownload the source code and trigger your project’s build. Click on the Topology screen icon to see\\\\nthe \"Build\" section, indicating that a build is running. The compilation and deployment of your\\\\napplication can take some time, depending on the complexity of the source code and the\\\\nprogramming language used.\\\\nOnce the build has finished, on the same pane, you will see a route available under the \"Routes\"\\\\nsection. Click on it, and you will see your application in action.\\\\n10.2. Container Registry\\\\nOpenShift has built your application source code, and the product of this build process is a\\\\ncontainer. You can see the container that OpenShift made for you on the \"Administrator\"\\\\nperspective, selecting the \"Builds\" menu and then the \"ImageStreams\" menu entry.\\\\nOpenShift includes a container registry; developers can use it as any other registry from outside the\\\\ncluster. Let us use \"podman\" to access the container registry and run the container locally on your\\\\nworkstation.\\\\nUsers must have the \"registry-editor\" and the \"system:image-builder\" roles to access the container\\\\nregistry. Since we’re connected to the Web Console using the \"kubeadmin\" user, we can provide\\\\nthose roles directly from the user interface without using the command line.\\\\nNavigate to the \"User Management\" section and select \"RoleBindings.\" Click on the \u001b[0m\u001b[32m[\u001b[0m\u001b[32m\\\\u2009Create\\\\nbinding\\\\u2009\u001b[0m\u001b[32m]\u001b[0m\u001b[32m button, and fill the form using the following values:\\\\n• Name: developer-sourcecode-registry-editor\\\\n• Namespace: sourcecode\\\\n• Role name: registry-editor\\\\n• Subject: User\\\\n• Subject name: developer\\\\nDo the same for the \"system:image-builder\" role, using a different \"Name\" field\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 2:\\\\nDocument_id:num-0\\\\nContent: 23\\\\ninstall OpenShift Local, 16\\\\nJ\\\\nJAR file, 23\\\\nJava, 14, 24, 44\\\\nJavaScript, 24, 44\\\\nJenkins, 28\\\\nK\\\\nKiali, 36\\\\nKibana, 40, 40\\\\nKnative, 34\\\\nKubernetes, 7\\\\nL\\\\nlogs, 40\\\\nM\\\\nMicroservices, 36\\\\nmonitor, 40\\\\nN\\\\nNode.js, 14\\\\nnon-root accounts, 20\\\\nO\\\\nOpenShift 4.12, 33\\\\nOpenShift Kubernetes Distribution, 8\\\\nOpenShift Service Mesh, 36\\\\noperator, 28, 36\\\\nOperatorHub, 33, 36\\\\nOperators, 33\\\\n48\\\\nP\\\\nperspectives, 22\\\\nPHP, 14, 24\\\\nPlatform-as-a-Service, 8\\\\nprivilege escalation, 19\\\\nprivileged ports, 20\\\\nProject, 9\\\\nPrometheus, 40, 44\\\\nPromQL, 45\\\\nPython, 14, 24, 44\\\\nQ\\\\nQuarkus, 14, 44\\\\nR\\\\nRed Hat developer account, 13\\\\nRed Hat OpenShift, 7\\\\nRed Hat OpenShift Dev Spaces, 14\\\\nRed Hat OpenShift Local, 8, 15\\\\nRed Hat OpenShift Pipelines, 28\\\\nRed Hat Quay, 20\\\\nRed Hat Universal Base Images, 19\\\\nrole, 19\\\\nRoute, 9\\\\nRust, 14\\\\nS\\\\nScala, 14\\\\nScaling, 42\\\\nsecure by default, 19\\\\nSecurity Context Constraints, 19\\\\nServerless, 34\\\\nservice mesh, 36\\\\nsource code project, 22\\\\nstateful applications, 33\\\\nT\\\\nTekton, 28\\\\ntemplates, 32\\\\nTopology, 27\\\\nTwelve-Factor App, 21, 40\\\\nTypeScript, 24\\\\nU\\\\nUBI, 19\\\\nV\\\\nVertical scaling, 42\\\\nVisual Studio Code, 14\\\\nW\\\\nWeb Console, 22, 40\\\\n49\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 3:\\\\nDocument_id:num-0\\\\nContent: .\\\\nThese characteristics set OpenShift apart as an excellent Kubernetes platform for enterprise users.\\\\nThe latest version of OpenShift available at the time of this writing is 4.12.\\\\n3.2. Is Red Hat OpenShift Open Source?\\\\nRed Hat OpenShift is a commercial product based on an open-source project called OKD. This\\\\nacronym means \" OpenShift Kubernetes Distribution\" and is publicly available for everyone to\\\\ninspect and contribute. Like the upstream Kubernetes project, OKD developers use the Go\\\\nprogramming language.\\\\n3.3. How can I run OpenShift?\\\\nToday, Red Hat OpenShift is available through various mechanisms and formats:\\\\n• DevOps teams can install it in their data centers \"on-premise.\"\\\\n• Major hyperscalers such as AWS, Azure, Google Cloud Platform, and IBM Cloud offer managed\\\\nRed Hat OpenShift installations.\\\\n• Developers can either run OpenShift locally on their workstations using Red Hat OpenShift\\\\nLocal, also known as CRC or \"Code-Ready Containers\"\\\\n• They can also request a 30-day trial OpenShift cluster, offered by Red Hat, at no charge, for\\\\ntesting and evaluation purposes.\\\\nRed Hat OpenShift is an integrated Platform-as-a-Service for enterprise users based on Kubernetes.\\\\nIt is tightly integrated with advanced security settings, developer tooling, and monitoring\\\\nmechanisms, allowing DevOps teams to be more productive.\\\\n8\\\\nChapter 4. OpenShift-only Custom Resource\\\\nDefinitions\\\\nRed Hat OpenShift is a complete DevOps platform extending Kubernetes in various ways. It bundles\\\\na constellation of Custom Resource Definitions \u001b[0m\u001b[32m(\u001b[0m\u001b[32mCRDs\u001b[0m\u001b[32m)\u001b[0m\u001b[32m to make the life of developers and cluster\\\\nadministrators easier.\\\\nLet us talk first about the CRDs only available on OpenShift.\\\\n4.1. Project\\\\nAn OpenShift Project is similar to a Kubernetes namespace, but more tightly integrated into the\\\\nsecurity system of OpenShift through additional annotations.\\\\napiVersion: project.openshift.io/v1\\\\nkind: Project\\\\nmetadata:\\\\n\\\\xa0 name: linkedin-learning-project\\\\n\\\\xa0 annotations:\\\\n\\\\xa0 openshift.io/description: \"Project description\"\\\\n\\\\xa0 openshift.io/display-name: \"Display name\"\\\\n4.2. Route\\\\nThe OpenShift Route object was one of the primary inspirations during the development of the\\\\nIngress object. In OpenShift, Ingress and Route objects work together to ensure your applications\\\\nare available outside the cluster.\\\\napiVersion: route.openshift.io/v1\\\\nkind: Route\\\\nmetadata:\\\\n\\\\xa0 name: my-route\\\\nspec:\\\\n\\\\xa0 host:\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 4:\\\\nDocument_id:num-0\\\\nContent: We\\\\nrecommend you to check the official Red Hat OpenShift Local documentation for an updated list of\\\\nrequirements at the official documentation website.\\\\n\\\\uf05a\\\\nRegarding Linux, even if Red Hat does not officially support them, OpenShift Local\\\\ncan run on other distributions, such as Ubuntu or Debian, with minor caveats.\\\\nRunning OpenShift Local on any Linux distribution requires a few additional\\\\nsoftware packages to be installed through your default package manager. The\\\\n15\\\\ndocumentation at crc.dev/crc has more information about this subject.\\\\n7.2. Hardware Requirements\\\\nIn terms of hardware, OpenShift Local has some strict requirements. Your system must use a recent\\\\nIntel CPU \u001b[0m\u001b[32m(\u001b[0m\u001b[32mexcept for Macs, where Apple Silicon machines are supported\u001b[0m\u001b[32m)\u001b[0m\u001b[32m with at least four physical\\\\ncores and have at least 16 GB of RAM. Be aware that the base installation of OpenShift Local\\\\nrequires at least 9 GB free to start. Of course, to run other applications on OpenShift Local, you will\\\\nneed more RAM, so using a computer with at least 32 GB of RAM is strongly recommended.\\\\nOpenShift Local also requires at least 35 GB of free disk space for its installation. The memory\\\\nrequirements are likely to increase in the future, so please check the documentation at crc.dev for\\\\nmore up-to-date information.\\\\n7.3. Installation\\\\nTo install OpenShift Local, open your web browser and navigate to console.redhat.com/openshift/\\\\ncreate/local . Download the latest release of OpenShift Local and the \"pull secret\" file. The latter is a\\\\nfile containing a key identifying your copy of OpenShift Local to your Red Hat Developer account.\\\\nUnzip the file containing the OpenShift Local executable, and using your terminal, run the\\\\ncommand crc setup . This command will prepare your copy of OpenShift Local, verifying\\\\nrequirements and setting the required configuration values.\\\\nOnce the crc setup command is ready, launch crc start. Running crc start can take a long time,\\\\naround 20 minutes, on a recent PC.\\\\nOnce started, access the OpenShift Web Console with the crc console command, which will open\\\\nyour default browser. OpenShift Local uses the developer username and password to log in as a\\\\nlow-privilege user, while the kubeadmin user uses a random-generated password. Use the crc\\\\nconsole --credentials command to find the credentials required to log in as the kubeadmin user.\\\\nOpenShift Local allows developers to perform various everyday tasks as if it were a standard\\\\nOpenShift cluster, like deploying applications\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 5:\\\\nDocument_id:num-0\\\\nContent: _02 branch of the GitHub\\\\nrepository for this course.\\\\nThe whole point of OpenShift is to be able to deploy, run, and monitor containerized applications.\\\\nDevOps engineers can deploy containers in OpenShift clusters through various means, for example:\\\\n• Using a YAML manifest.\\\\n• Deploying a single container using the web console.\\\\n• Building a project stored in a Git repository anywhere on the Internet and deploying the\\\\nresulting container.\\\\n• Using the integrated CI/CD pipelines.\\\\n• Using the odo tool together with \"Devfiles.\"\\\\nEach approach has pros and cons; in this chapter, we will review how to use the web console and\\\\nhow to use the odo tool.\\\\n9.1. Deploying Applications with the Web Console\\\\nDevOps engineers can deploy applications immediately using the Web Console. Launch your CRC\\\\ninstance and open the web console in your preferred web browser. The URL of the OpenShift web\\\\nconsole is \"https://console-openshift-console.apps-crc.testing.\"\\\\nThe OpenShift Web Console offers two major perspectives:\\\\n• The \"Administrator\" perspective.\\\\n• And the \"Developer\" perspective.\\\\nFor this explanation, select the \"Developer\" perspective.\\\\nThe first time you open the Developer perspective, a popup invites you to follow a user interface\\\\ntour.\\\\nOn the left-hand side, the perspective menu shows an entry titled \"Add,\" which, as the name\\\\nimplies, provides various mechanisms to deploy applications on an OpenShift cluster.\\\\nThe \"Add\" screen shows the various ways DevOps engineers can deploy applications on a cluster:\\\\n• Using the Developer Catalog, browsing and choosing among a long list of available databases,\\\\nmessage queues, and other valuable components to build applications, or entering your\\\\npreferred Helm chart repository to extend the catalog.\\\\n• Specifying the URL to a specific container on any standard container registry.\\\\n• Specifying the URL of a source code project stored on a Git repository, for example, but not\\\\n22\\\\nlimited to GitHub, GitLab, Gitea, or other locations.\\\\n• Importing YAML directly or even a JAR file with a Java application.\\\\nLet us select the \"Container Image\" option, where we can specify the URL of a ready-to-use\\\\ncontainer.\\\\nEnter the URL of the container on the field, and click on the \u001b[0m\u001b[32m[\u001b[0m\u001b[32m\\\\u2009Create\\\\u2009\u001b[0m\u001b[32m]\u001b[0m\u001b[32m button at the bottom of the\\\\npage. You do not need to change any other value on the form.\\\\nA few seconds later, depending on the size of the container and the speed of your Internet\\\\nconnection,\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'END of knowledge_search tool results.\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'The above results were retrieved to help answer the user\\\\\\'s query: \"How to install OpenShift\". Use them as supporting information only in answering this query.\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m]\u001b[0m\u001b[32mTo install OpenShift, you can follow these steps:\\n\\n1. **Install on your data center \u001b[0m\u001b[32m(\u001b[0m\u001b[32mon-premise\u001b[0m\u001b[32m)\u001b[0m\u001b[32m:** You can install OpenShift in your own data center. This involves setting up a Kubernetes cluster and then deploying OpenShift on top of it. Detailed instructions can be found in the official Red Hat OpenShift documentation.\\n\\n2. **Use managed services from hyperscalers:** Major cloud providers like AWS, Azure, Google Cloud Platform, and IBM Cloud offer managed OpenShift services. You can provision an OpenShift cluster through their respective web interfaces.\\n\\n3. **Run OpenShift Locally on your workstation:** Red Hat OpenShift Local, also known as CodeReady Containers \u001b[0m\u001b[32m(\u001b[0m\u001b[32mCRC\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, allows you to run OpenShift on your local machine. Here\\'s how:\\n - Download the latest release of OpenShift Local and the \"pull secret\" file from the official Red Hat website.\\n - Unzip the downloaded file and run the command `crc setup` in your terminal to prepare your OpenShift Local instance.\\n - After the setup is complete, start OpenShift Local with the command `crc start`. This process can take around 20 minutes.\\n - Access the OpenShift Web Console with the command `crc console`. The default login credentials are the developer username and password for a low-privilege user, and the kubeadmin user uses a random-generated password. You can find the kubeadmin credentials with the command `crc console --credentials`.\\n\\nRemember, running OpenShift Locally requires a recent Intel CPU \u001b[0m\u001b[32m(\u001b[0m\u001b[32mor Apple Silicon for Macs\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, at least 4 physical cores, 16 GB of RAM, and 35 GB of free disk space.\\n\\nFor more detailed and up-to-date information, always refer to the official Red Hat OpenShift documentation.'\u001b[0m\n" + "\u001b[32m'Tool:knowledge_search Args:\u001b[0m\u001b[32m{\u001b[0m\u001b[32m\\'query\\': \\'OpenShift installation\\'\u001b[0m\u001b[32m}\u001b[0m\u001b[32mTool:knowledge_search Response:\u001b[0m\u001b[32m[\u001b[0m\u001b[32mTextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'knowledge_search tool found 5 chunks:\\\\nBEGIN of knowledge_search tool results.\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 1:\\\\nDocument_id:num-0\\\\nContent: 23\\\\ninstall OpenShift Local, 16\\\\nJ\\\\nJAR file, 23\\\\nJava, 14, 24, 44\\\\nJavaScript, 24, 44\\\\nJenkins, 28\\\\nK\\\\nKiali, 36\\\\nKibana, 40, 40\\\\nKnative, 34\\\\nKubernetes, 7\\\\nL\\\\nlogs, 40\\\\nM\\\\nMicroservices, 36\\\\nmonitor, 40\\\\nN\\\\nNode.js, 14\\\\nnon-root accounts, 20\\\\nO\\\\nOpenShift 4.12, 33\\\\nOpenShift Kubernetes Distribution, 8\\\\nOpenShift Service Mesh, 36\\\\noperator, 28, 36\\\\nOperatorHub, 33, 36\\\\nOperators, 33\\\\n48\\\\nP\\\\nperspectives, 22\\\\nPHP, 14, 24\\\\nPlatform-as-a-Service, 8\\\\nprivilege escalation, 19\\\\nprivileged ports, 20\\\\nProject, 9\\\\nPrometheus, 40, 44\\\\nPromQL, 45\\\\nPython, 14, 24, 44\\\\nQ\\\\nQuarkus, 14, 44\\\\nR\\\\nRed Hat developer account, 13\\\\nRed Hat OpenShift, 7\\\\nRed Hat OpenShift Dev Spaces, 14\\\\nRed Hat OpenShift Local, 8, 15\\\\nRed Hat OpenShift Pipelines, 28\\\\nRed Hat Quay, 20\\\\nRed Hat Universal Base Images, 19\\\\nrole, 19\\\\nRoute, 9\\\\nRust, 14\\\\nS\\\\nScala, 14\\\\nScaling, 42\\\\nsecure by default, 19\\\\nSecurity Context Constraints, 19\\\\nServerless, 34\\\\nservice mesh, 36\\\\nsource code project, 22\\\\nstateful applications, 33\\\\nT\\\\nTekton, 28\\\\ntemplates, 32\\\\nTopology, 27\\\\nTwelve-Factor App, 21, 40\\\\nTypeScript, 24\\\\nU\\\\nUBI, 19\\\\nV\\\\nVertical scaling, 42\\\\nVisual Studio Code, 14\\\\nW\\\\nWeb Console, 22, 40\\\\n49\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 2:\\\\nDocument_id:num-0\\\\nContent: We\\\\nrecommend you to check the official Red Hat OpenShift Local documentation for an updated list of\\\\nrequirements at the official documentation website.\\\\n\\\\uf05a\\\\nRegarding Linux, even if Red Hat does not officially support them, OpenShift Local\\\\ncan run on other distributions, such as Ubuntu or Debian, with minor caveats.\\\\nRunning OpenShift Local on any Linux distribution requires a few additional\\\\nsoftware packages to be installed through your default package manager. The\\\\n15\\\\ndocumentation at crc.dev/crc has more information about this subject.\\\\n7.2. Hardware Requirements\\\\nIn terms of hardware, OpenShift Local has some strict requirements. Your system must use a recent\\\\nIntel CPU \u001b[0m\u001b[32m(\u001b[0m\u001b[32mexcept for Macs, where Apple Silicon machines are supported\u001b[0m\u001b[32m)\u001b[0m\u001b[32m with at least four physical\\\\ncores and have at least 16 GB of RAM. Be aware that the base installation of OpenShift Local\\\\nrequires at least 9 GB free to start. Of course, to run other applications on OpenShift Local, you will\\\\nneed more RAM, so using a computer with at least 32 GB of RAM is strongly recommended.\\\\nOpenShift Local also requires at least 35 GB of free disk space for its installation. The memory\\\\nrequirements are likely to increase in the future, so please check the documentation at crc.dev for\\\\nmore up-to-date information.\\\\n7.3. Installation\\\\nTo install OpenShift Local, open your web browser and navigate to console.redhat.com/openshift/\\\\ncreate/local . Download the latest release of OpenShift Local and the \"pull secret\" file. The latter is a\\\\nfile containing a key identifying your copy of OpenShift Local to your Red Hat Developer account.\\\\nUnzip the file containing the OpenShift Local executable, and using your terminal, run the\\\\ncommand crc setup . This command will prepare your copy of OpenShift Local, verifying\\\\nrequirements and setting the required configuration values.\\\\nOnce the crc setup command is ready, launch crc start. Running crc start can take a long time,\\\\naround 20 minutes, on a recent PC.\\\\nOnce started, access the OpenShift Web Console with the crc console command, which will open\\\\nyour default browser. OpenShift Local uses the developer username and password to log in as a\\\\nlow-privilege user, while the kubeadmin user uses a random-generated password. Use the crc\\\\nconsole --credentials command to find the credentials required to log in as the kubeadmin user.\\\\nOpenShift Local allows developers to perform various everyday tasks as if it were a standard\\\\nOpenShift cluster, like deploying applications\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 3:\\\\nDocument_id:num-0\\\\nContent: \"Import from Git\" entry. Click on it, and paste the URL of a project, for example,\\\\ngitlab.com/akosma/simple-deno-api.git.\\\\nAs soon as you paste the URL, OpenShift will immediately analyze the structure and programming\\\\nlanguage of the project and automatically recommend options for its build process. In our case, it’s\\\\na small application built with the Go programming language, and as such, it will advise the options\\\\nshown on the screen.\\\\nFigure 5. Deploying a project directly from its Git repository\\\\n25\\\\nThis particular example doesn’t require more configurations than the ones shown on the screen;\\\\nclick the \u001b[0m\u001b[32m[\u001b[0m\u001b[32m\\\\u2009Create\\\\u2009\u001b[0m\u001b[32m]\u001b[0m\u001b[32m button.\\\\nAfter a few seconds, you will see your application running on the \"Topology\" screen. OpenShift will\\\\ndownload the source code and trigger your project’s build. Click on the Topology screen icon to see\\\\nthe \"Build\" section, indicating that a build is running. The compilation and deployment of your\\\\napplication can take some time, depending on the complexity of the source code and the\\\\nprogramming language used.\\\\nOnce the build has finished, on the same pane, you will see a route available under the \"Routes\"\\\\nsection. Click on it, and you will see your application in action.\\\\n10.2. Container Registry\\\\nOpenShift has built your application source code, and the product of this build process is a\\\\ncontainer. You can see the container that OpenShift made for you on the \"Administrator\"\\\\nperspective, selecting the \"Builds\" menu and then the \"ImageStreams\" menu entry.\\\\nOpenShift includes a container registry; developers can use it as any other registry from outside the\\\\ncluster. Let us use \"podman\" to access the container registry and run the container locally on your\\\\nworkstation.\\\\nUsers must have the \"registry-editor\" and the \"system:image-builder\" roles to access the container\\\\nregistry. Since we’re connected to the Web Console using the \"kubeadmin\" user, we can provide\\\\nthose roles directly from the user interface without using the command line.\\\\nNavigate to the \"User Management\" section and select \"RoleBindings.\" Click on the \u001b[0m\u001b[32m[\u001b[0m\u001b[32m\\\\u2009Create\\\\nbinding\\\\u2009\u001b[0m\u001b[32m]\u001b[0m\u001b[32m button, and fill the form using the following values:\\\\n• Name: developer-sourcecode-registry-editor\\\\n• Namespace: sourcecode\\\\n• Role name: registry-editor\\\\n• Subject: User\\\\n• Subject name: developer\\\\nDo the same for the \"system:image-builder\" role, using a different \"Name\" field\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 4:\\\\nDocument_id:num-0\\\\nContent: .\\\\nThese characteristics set OpenShift apart as an excellent Kubernetes platform for enterprise users.\\\\nThe latest version of OpenShift available at the time of this writing is 4.12.\\\\n3.2. Is Red Hat OpenShift Open Source?\\\\nRed Hat OpenShift is a commercial product based on an open-source project called OKD. This\\\\nacronym means \" OpenShift Kubernetes Distribution\" and is publicly available for everyone to\\\\ninspect and contribute. Like the upstream Kubernetes project, OKD developers use the Go\\\\nprogramming language.\\\\n3.3. How can I run OpenShift?\\\\nToday, Red Hat OpenShift is available through various mechanisms and formats:\\\\n• DevOps teams can install it in their data centers \"on-premise.\"\\\\n• Major hyperscalers such as AWS, Azure, Google Cloud Platform, and IBM Cloud offer managed\\\\nRed Hat OpenShift installations.\\\\n• Developers can either run OpenShift locally on their workstations using Red Hat OpenShift\\\\nLocal, also known as CRC or \"Code-Ready Containers\"\\\\n• They can also request a 30-day trial OpenShift cluster, offered by Red Hat, at no charge, for\\\\ntesting and evaluation purposes.\\\\nRed Hat OpenShift is an integrated Platform-as-a-Service for enterprise users based on Kubernetes.\\\\nIt is tightly integrated with advanced security settings, developer tooling, and monitoring\\\\nmechanisms, allowing DevOps teams to be more productive.\\\\n8\\\\nChapter 4. OpenShift-only Custom Resource\\\\nDefinitions\\\\nRed Hat OpenShift is a complete DevOps platform extending Kubernetes in various ways. It bundles\\\\na constellation of Custom Resource Definitions \u001b[0m\u001b[32m(\u001b[0m\u001b[32mCRDs\u001b[0m\u001b[32m)\u001b[0m\u001b[32m to make the life of developers and cluster\\\\nadministrators easier.\\\\nLet us talk first about the CRDs only available on OpenShift.\\\\n4.1. Project\\\\nAn OpenShift Project is similar to a Kubernetes namespace, but more tightly integrated into the\\\\nsecurity system of OpenShift through additional annotations.\\\\napiVersion: project.openshift.io/v1\\\\nkind: Project\\\\nmetadata:\\\\n\\\\xa0 name: linkedin-learning-project\\\\n\\\\xa0 annotations:\\\\n\\\\xa0 openshift.io/description: \"Project description\"\\\\n\\\\xa0 openshift.io/display-name: \"Display name\"\\\\n4.2. Route\\\\nThe OpenShift Route object was one of the primary inspirations during the development of the\\\\nIngress object. In OpenShift, Ingress and Route objects work together to ensure your applications\\\\nare available outside the cluster.\\\\napiVersion: route.openshift.io/v1\\\\nkind: Route\\\\nmetadata:\\\\n\\\\xa0 name: my-route\\\\nspec:\\\\n\\\\xa0 host:\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 5:\\\\nDocument_id:num-0\\\\nContent: _02 branch of the GitHub\\\\nrepository for this course.\\\\nThe whole point of OpenShift is to be able to deploy, run, and monitor containerized applications.\\\\nDevOps engineers can deploy containers in OpenShift clusters through various means, for example:\\\\n• Using a YAML manifest.\\\\n• Deploying a single container using the web console.\\\\n• Building a project stored in a Git repository anywhere on the Internet and deploying the\\\\nresulting container.\\\\n• Using the integrated CI/CD pipelines.\\\\n• Using the odo tool together with \"Devfiles.\"\\\\nEach approach has pros and cons; in this chapter, we will review how to use the web console and\\\\nhow to use the odo tool.\\\\n9.1. Deploying Applications with the Web Console\\\\nDevOps engineers can deploy applications immediately using the Web Console. Launch your CRC\\\\ninstance and open the web console in your preferred web browser. The URL of the OpenShift web\\\\nconsole is \"https://console-openshift-console.apps-crc.testing.\"\\\\nThe OpenShift Web Console offers two major perspectives:\\\\n• The \"Administrator\" perspective.\\\\n• And the \"Developer\" perspective.\\\\nFor this explanation, select the \"Developer\" perspective.\\\\nThe first time you open the Developer perspective, a popup invites you to follow a user interface\\\\ntour.\\\\nOn the left-hand side, the perspective menu shows an entry titled \"Add,\" which, as the name\\\\nimplies, provides various mechanisms to deploy applications on an OpenShift cluster.\\\\nThe \"Add\" screen shows the various ways DevOps engineers can deploy applications on a cluster:\\\\n• Using the Developer Catalog, browsing and choosing among a long list of available databases,\\\\nmessage queues, and other valuable components to build applications, or entering your\\\\npreferred Helm chart repository to extend the catalog.\\\\n• Specifying the URL to a specific container on any standard container registry.\\\\n• Specifying the URL of a source code project stored on a Git repository, for example, but not\\\\n22\\\\nlimited to GitHub, GitLab, Gitea, or other locations.\\\\n• Importing YAML directly or even a JAR file with a Java application.\\\\nLet us select the \"Container Image\" option, where we can specify the URL of a ready-to-use\\\\ncontainer.\\\\nEnter the URL of the container on the field, and click on the \u001b[0m\u001b[32m[\u001b[0m\u001b[32m\\\\u2009Create\\\\u2009\u001b[0m\u001b[32m]\u001b[0m\u001b[32m button at the bottom of the\\\\npage. You do not need to change any other value on the form.\\\\nA few seconds later, depending on the size of the container and the speed of your Internet\\\\nconnection,\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'END of knowledge_search tool results.\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'The above results were retrieved to help answer the user\\\\\\'s query: \"OpenShift installation\". Use them as supporting information only in answering this query.\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m]\u001b[0m\u001b[32mTo install OpenShift, you can follow these steps:\\n\\n1. Check the system requirements for OpenShift Local, which is a version of OpenShift that can be installed on a local machine. The requirements include a recent Intel CPU with at least four physical cores, 16 GB of RAM, and 35 GB of free disk space.\\n2. Download the latest version of OpenShift Local from the official Red Hat website.\\n3. Unzip the downloaded file and run the `crc setup` command to prepare the installation.\\n4. Launch the `crc start` command to start the installation process, which may take around 20 minutes.\\n5. Once the installation is complete, access the OpenShift Web Console using the `crc console` command, which will open your default browser.\\n6. Log in to the Web Console using the developer username and password, or use the `crc console --credentials` command to find the credentials required to log in as the kubeadmin user.\\n7. You can then deploy applications using the web console or using the odo tool.\\n\\nNote that OpenShift is also available through various mechanisms and formats, including installation in data centers, managed installations by hyperscalers, and trial clusters offered by Red Hat.'\u001b[0m\n" ] }, "metadata": {}, @@ -455,19 +526,15 @@ "🤖 Model Response:\n", "\u001b[35mTo install OpenShift, you can follow these steps:\n", "\n", - "1. **Install on your data center (on-premise):** You can install OpenShift in your own data center. This involves setting up a Kubernetes cluster and then deploying OpenShift on top of it. Detailed instructions can be found in the official Red Hat OpenShift documentation.\n", - "\n", - "2. **Use managed services from hyperscalers:** Major cloud providers like AWS, Azure, Google Cloud Platform, and IBM Cloud offer managed OpenShift services. You can provision an OpenShift cluster through their respective web interfaces.\n", - "\n", - "3. **Run OpenShift Locally on your workstation:** Red Hat OpenShift Local, also known as CodeReady Containers (CRC), allows you to run OpenShift on your local machine. Here's how:\n", - " - Download the latest release of OpenShift Local and the \"pull secret\" file from the official Red Hat website.\n", - " - Unzip the downloaded file and run the command `crc setup` in your terminal to prepare your OpenShift Local instance.\n", - " - After the setup is complete, start OpenShift Local with the command `crc start`. This process can take around 20 minutes.\n", - " - Access the OpenShift Web Console with the command `crc console`. The default login credentials are the developer username and password for a low-privilege user, and the kubeadmin user uses a random-generated password. You can find the kubeadmin credentials with the command `crc console --credentials`.\n", - "\n", - "Remember, running OpenShift Locally requires a recent Intel CPU (or Apple Silicon for Macs), at least 4 physical cores, 16 GB of RAM, and 35 GB of free disk space.\n", + "1. Check the system requirements for OpenShift Local, which is a version of OpenShift that can be installed on a local machine. The requirements include a recent Intel CPU with at least four physical cores, 16 GB of RAM, and 35 GB of free disk space.\n", + "2. Download the latest version of OpenShift Local from the official Red Hat website.\n", + "3. Unzip the downloaded file and run the `crc setup` command to prepare the installation.\n", + "4. Launch the `crc start` command to start the installation process, which may take around 20 minutes.\n", + "5. Once the installation is complete, access the OpenShift Web Console using the `crc console` command, which will open your default browser.\n", + "6. Log in to the Web Console using the developer username and password, or use the `crc console --credentials` command to find the credentials required to log in as the kubeadmin user.\n", + "7. You can then deploy applications using the web console or using the odo tool.\n", "\n", - "For more detailed and up-to-date information, always refer to the official Red Hat OpenShift documentation.\n", + "Note that OpenShift is also available through various mechanisms and formats, including installation in data centers, managed installations by hyperscalers, and trial clusters offered by Red Hat.\n", "\u001b[0m\n", "========== Query processing completed ========== \n", "\n" @@ -516,7 +583,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "venv7", "language": "python", "name": "python3" }, @@ -530,7 +597,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.16" + "version": "3.13.3" } }, "nbformat": 4, From db1986bae1d1b65e477b88e8d932257f108774d4 Mon Sep 17 00:00:00 2001 From: Kevin Cogan Date: Tue, 20 May 2025 16:14:20 +0100 Subject: [PATCH 02/13] chore: replaced old A2A agent code with new code. --- demos/a2a_llama_stack/__init__.py | 0 demos/a2a_llama_stack/__main__.py | 64 ------------------------------- demos/a2a_llama_stack/agent.py | 17 -------- 3 files changed, 81 deletions(-) delete mode 100644 demos/a2a_llama_stack/__init__.py delete mode 100644 demos/a2a_llama_stack/__main__.py delete mode 100644 demos/a2a_llama_stack/agent.py diff --git a/demos/a2a_llama_stack/__init__.py b/demos/a2a_llama_stack/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/demos/a2a_llama_stack/__main__.py b/demos/a2a_llama_stack/__main__.py deleted file mode 100644 index a645fcf..0000000 --- a/demos/a2a_llama_stack/__main__.py +++ /dev/null @@ -1,64 +0,0 @@ -import os -import logging - -from llama_stack_client import LlamaStackClient, Agent - -from common.server import A2AServer -from common.types import AgentCard, AgentCapabilities, AgentSkill - -from .agent import random_number_tool, date_tool -from .task_manager import AgentTaskManager, SUPPORTED_CONTENT_TYPES - -logging.basicConfig(level=logging.INFO) - - -def build_server(host: str = "0.0.0.0", port: int = 10010): - # 1) instantiate your agent with the required parameters - agent = Agent( - client=LlamaStackClient(base_url=os.getenv("LLAMA_STACK_URL", "http://localhost:8321")), - model=os.getenv("MODEL_ID", "llama3.2:3b-instruct-fp16"), - instructions=( - "You have access to two tools:\n" - "- random_number_tool: generates a random integer between 1 and 100\n" - "- date_tool: returns today's date in YYYY-MM-DD format\n" - "Use the appropriate tool to answer user queries." - ), - tools=[random_number_tool, date_tool], - max_infer_iters=3, - ) - - # 2) wrap it in the A2A TaskManager - task_manager = AgentTaskManager(agent=agent, internal_session_id=True) - - # 3) advertise your tools as AgentSkills - card = AgentCard( - name="Custom Agent", - description="Generates random numbers or dates", - url=f"http://{host}:{port}/", - version="0.1.0", - defaultInputModes=["text/plain"], - defaultOutputModes=SUPPORTED_CONTENT_TYPES, - capabilities=AgentCapabilities(streaming=True), - skills=[ - AgentSkill(id="random_number", name="Random Number Generator"), - AgentSkill(id="get_date", name="Date Provider"), - ], - ) - - return A2AServer( - agent_card=card, - task_manager=task_manager, - host=host, - port=port, - ) - -if __name__ == "__main__": - import click - - @click.command() - @click.option("--host", default="0.0.0.0") - @click.option("--port", default=10010, type=int) - def main(host, port): - build_server(host, port).start() - - main() diff --git a/demos/a2a_llama_stack/agent.py b/demos/a2a_llama_stack/agent.py deleted file mode 100644 index fa3649b..0000000 --- a/demos/a2a_llama_stack/agent.py +++ /dev/null @@ -1,17 +0,0 @@ -import random -from datetime import datetime - - -def random_number_tool() -> int: - """ - Generate a random integer between 1 and 100. - """ - print("\n\nGenerating a random number...\n\n") - return random.randint(1, 100) - - -def date_tool() -> str: - """ - Return today's date in YYYY-MM-DD format. - """ - return datetime.utcnow().date().isoformat() From dfc7d5290a6ab574ff03d51ccdddb924815ccb05 Mon Sep 17 00:00:00 2001 From: Kevin Cogan Date: Tue, 20 May 2025 16:19:14 +0100 Subject: [PATCH 03/13] feature: Added A2A server code for multi-agent demo. --- .../agents/a2a_composer/__init__.py | 0 .../agents/a2a_composer/__main__.py | 72 ++++++++ .../agents/a2a_composer/agent.py | 0 .../agents/a2a_composer/task_manager.py | 134 +++++++++++++++ .../agents/a2a_custom_tools/__init__.py | 0 .../agents/a2a_custom_tools/__main__.py | 85 ++++++++++ .../agents/a2a_custom_tools/agent.py | 17 ++ .../agents/a2a_custom_tools/task_manager.py | 134 +++++++++++++++ .../agents/a2a_planner/__init__.py | 0 .../agents/a2a_planner/__main__.py | 72 ++++++++ .../agents/a2a_planner/agent.py | 0 .../agents/a2a_planner/task_manager.py | 158 ++++++++++++++++++ 12 files changed, 672 insertions(+) create mode 100644 demos/a2a_llama_stack/agents/a2a_composer/__init__.py create mode 100644 demos/a2a_llama_stack/agents/a2a_composer/__main__.py create mode 100644 demos/a2a_llama_stack/agents/a2a_composer/agent.py create mode 100644 demos/a2a_llama_stack/agents/a2a_composer/task_manager.py create mode 100644 demos/a2a_llama_stack/agents/a2a_custom_tools/__init__.py create mode 100644 demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py create mode 100644 demos/a2a_llama_stack/agents/a2a_custom_tools/agent.py create mode 100644 demos/a2a_llama_stack/agents/a2a_custom_tools/task_manager.py create mode 100644 demos/a2a_llama_stack/agents/a2a_planner/__init__.py create mode 100644 demos/a2a_llama_stack/agents/a2a_planner/__main__.py create mode 100644 demos/a2a_llama_stack/agents/a2a_planner/agent.py create mode 100644 demos/a2a_llama_stack/agents/a2a_planner/task_manager.py diff --git a/demos/a2a_llama_stack/agents/a2a_composer/__init__.py b/demos/a2a_llama_stack/agents/a2a_composer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/demos/a2a_llama_stack/agents/a2a_composer/__main__.py b/demos/a2a_llama_stack/agents/a2a_composer/__main__.py new file mode 100644 index 0000000..296bdd3 --- /dev/null +++ b/demos/a2a_llama_stack/agents/a2a_composer/__main__.py @@ -0,0 +1,72 @@ +import os +import logging + +from llama_stack_client import LlamaStackClient, Agent + +from common.server import A2AServer +from common.types import AgentCard, AgentCapabilities, AgentSkill + +from .task_manager import AgentTaskManager, SUPPORTED_CONTENT_TYPES + +logging.basicConfig(level=logging.INFO) + + +def build_server(host: str = "0.0.0.0", port: int = 10012): + # 1) instantiate your agent with the required parameters + agent = Agent( + client=LlamaStackClient(base_url=os.getenv("REMOTE_BASE_URL", "http://localhost:8321")), + model=os.getenv("INFERENCE_MODEL_ID", "llama3.2:3b-instruct-fp16"), + instructions=("You are skilled at writing human-friendly text based on the query and associated skills."), + max_infer_iters=3, + sampling_params = { + "strategy": {"type": "greedy"}, + "max_tokens": 4096, + }, + ) + + # 2) wrap it in the A2A TaskManager + task_manager = AgentTaskManager(agent=agent, internal_session_id=True) + + # 3) advertise your tools as AgentSkills + card = AgentCard( + name="Writing Agent", + description="Generate human-friendly text based on the query and associated skills", + url=f"http://{host}:{port}/", + version="0.1.0", + defaultInputModes=["text/plain"], + defaultOutputModes=SUPPORTED_CONTENT_TYPES, + capabilities=AgentCapabilities( + streaming=False, + pushNotifications=False, + stateTransitionHistory=False, + ), + skills = [ + AgentSkill( + id="writing_agent", + name="Writing Agent", + description="Write human-friendly text based on the query and associated skills", + tags=["writing"], + examples=["Write human-friendly text based on the query and associated skills"], + inputModes=["text/plain"], + outputModes=["application/json"], + ) + ] + ) + + return A2AServer( + agent_card=card, + task_manager=task_manager, + host=host, + port=port, + ) + +if __name__ == "__main__": + import click + + @click.command() + @click.option("--host", default="0.0.0.0") + @click.option("--port", default=10010, type=int) + def main(host, port): + build_server(host, port).start() + + main() diff --git a/demos/a2a_llama_stack/agents/a2a_composer/agent.py b/demos/a2a_llama_stack/agents/a2a_composer/agent.py new file mode 100644 index 0000000..e69de29 diff --git a/demos/a2a_llama_stack/agents/a2a_composer/task_manager.py b/demos/a2a_llama_stack/agents/a2a_composer/task_manager.py new file mode 100644 index 0000000..9d71003 --- /dev/null +++ b/demos/a2a_llama_stack/agents/a2a_composer/task_manager.py @@ -0,0 +1,134 @@ +import logging +from typing import AsyncIterable, Union, AsyncIterator + +from llama_stack_client import Agent, AgentEventLogger + +import common.server.utils as utils +from common.server.task_manager import InMemoryTaskManager +from common.types import ( + SendTaskRequest, SendTaskResponse, + SendTaskStreamingRequest, SendTaskStreamingResponse, + TaskStatus, Artifact, + Message, TaskState, + TaskStatusUpdateEvent, TaskArtifactUpdateEvent, + JSONRPCResponse, +) + +logger = logging.getLogger(__name__) + +SUPPORTED_CONTENT_TYPES = ["text", "text/plain", "application/json"] + + +class AgentTaskManager(InMemoryTaskManager): + def __init__(self, agent: Agent, internal_session_id=False): + super().__init__() + self.agent = agent + if internal_session_id: + self.session_id = self.agent.create_session("custom-agent-session") + else: + self.session_id = None + + def _validate_request( + self, request: Union[SendTaskRequest, SendTaskStreamingRequest] + ) -> JSONRPCResponse | None: + params = request.params + if not utils.are_modalities_compatible( + params.acceptedOutputModes, + SUPPORTED_CONTENT_TYPES + ): + logger.warning("Unsupported output modes: %s", params.acceptedOutputModes) + return utils.new_incompatible_types_error(request.id) + return None + + async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse: + err = self._validate_request(request) + if err: + return err + + await self.upsert_task(request.params) + result = self._invoke( + request.params.message.parts[0].text, + request.params.sessionId + ) + parts = [{"type": "text", "text": result}] + status = TaskStatus(state=TaskState.COMPLETED, message=Message(role="agent", parts=parts)) + task = await self._update_store(request.params.id, status, [Artifact(parts=parts)]) + return SendTaskResponse(id=request.id, result=task) + + async def on_send_task_subscribe( + self, request: SendTaskStreamingRequest + ) -> AsyncIterable[SendTaskStreamingResponse] | JSONRPCResponse: + err = self._validate_request(request) + if err: + return err + + await self.upsert_task(request.params) + return self._stream_generator(request) + + async def _stream_generator( + self, request: SendTaskStreamingRequest + ) -> AsyncIterable[SendTaskStreamingResponse]: + params = request.params + query = params.message.parts[0].text + + async for update in self._stream(query, params.sessionId): + done = update["is_task_complete"] + content = update["content"] + delta = update["updates"] + + state = TaskState.COMPLETED if done else TaskState.WORKING + text = content if done else delta + parts = [{"type": "text", "text": text}] + artifacts = [Artifact(parts=parts)] if done else None + + status = TaskStatus(state=state, message=Message(role="agent", parts=parts)) + await self._update_store(request.params.id, status, artifacts or []) + + yield SendTaskStreamingResponse( + id=request.id, + result=TaskStatusUpdateEvent(id=params.id, status=status, final=done) + ) + if artifacts: + yield SendTaskStreamingResponse( + id=request.id, + result=TaskArtifactUpdateEvent(id=params.id, artifact=artifacts[0]) + ) + + async def _update_store(self, task_id: str, status: TaskStatus, artifacts): + async with self.lock: + task = self.tasks[task_id] + task.status = status + if artifacts: + task.artifacts = (task.artifacts or []) + artifacts + return task + + def _invoke(self, query: str, session_id: str) -> str: + """ + Route the user query through the Agent, executing tools as needed. + """ + # Determine which session to use + if self.session_id is not None: + sid = self.session_id + else: + sid = self.agent.create_session(session_id) + + # Send the user query to the Agent + turn_resp = self.agent.create_turn( + messages=[{"role": "user", "content": query}], + session_id=sid, + ) + + # Extract tool and LLM outputs from events + logs = AgentEventLogger().log(turn_resp) + output = "" + for event in logs: + if hasattr(event, "content") and event.content: + output += event.content + return output + + async def _stream(self, query: str, session_id: str) -> AsyncIterator[dict]: + """ + Simplest streaming stub: synchronously invoke and emit once. + """ + result = self._invoke(query, session_id) + yield {"updates": result, "is_task_complete": True, "content": result} diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/__init__.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py new file mode 100644 index 0000000..3d370fa --- /dev/null +++ b/demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py @@ -0,0 +1,85 @@ +import os +import logging + +from llama_stack_client import LlamaStackClient, Agent + +from common.server import A2AServer +from common.types import AgentCard, AgentCapabilities, AgentSkill + +from .agent import random_number_tool, date_tool +from .task_manager import AgentTaskManager, SUPPORTED_CONTENT_TYPES + +logging.basicConfig(level=logging.INFO) + + +def build_server(host: str = "0.0.0.0", port: int = 10010): + # 1) instantiate your agent with the required parameters + agent = Agent( + client=LlamaStackClient(base_url=os.getenv("REMOTE_BASE_URL", "http://localhost:8321")), + model=os.getenv("INFERENCE_MODEL_ID", "llama3.1:8b-instruct-fp16"), + instructions=( + "You have access to two tools:\n" + "- random_number_tool: generates one random integer between 1 and 100\n" + "- date_tool: returns today's date in YYYY-MM-DD format\n" + "Always use the appropriate tool to answer user queries." + ), + tools=[random_number_tool, date_tool], + max_infer_iters=3, + ) + + # 2) wrap it in the A2A TaskManager + task_manager = AgentTaskManager(agent=agent, internal_session_id=True) + + # 3) advertise your tools as AgentSkills + card = AgentCard( + name="Custom Agent", + description="Generates random numbers or retrieve today's dates", + url=f"http://{host}:{port}/", + version="0.1.0", + defaultInputModes=["text/plain"], + defaultOutputModes=SUPPORTED_CONTENT_TYPES, + capabilities=AgentCapabilities( + streaming=False, + pushNotifications=False, + stateTransitionHistory=False, + ), + skills=[ + AgentSkill( + id="random_number_tool", + name="Random Number Generator", + description="Generates a random number between 1 and 100", + tags=["random"], + examples=["Give me a random number between 1 and 100"], + inputModes=["text/plain"], + outputModes=["text/plain"], + ), + + AgentSkill( + id="date_tool", + name="Date Provider", + description="Returns today's date in YYYY-MM-DD format", + tags=["date"], + examples=["What's the date today?"], + inputModes=["text/plain"], + outputModes=["text/plain"], + ), + ], + ) + + return A2AServer( + agent_card=card, + task_manager=task_manager, + host=host, + port=port, + ) + +if __name__ == "__main__": + import click + + @click.command() + @click.option("--host", default="0.0.0.0") + @click.option("--port", default=10010, type=int) + def main(host, port): + build_server(host, port).start() + + main() diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/agent.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/agent.py new file mode 100644 index 0000000..fa3649b --- /dev/null +++ b/demos/a2a_llama_stack/agents/a2a_custom_tools/agent.py @@ -0,0 +1,17 @@ +import random +from datetime import datetime + + +def random_number_tool() -> int: + """ + Generate a random integer between 1 and 100. + """ + print("\n\nGenerating a random number...\n\n") + return random.randint(1, 100) + + +def date_tool() -> str: + """ + Return today's date in YYYY-MM-DD format. + """ + return datetime.utcnow().date().isoformat() diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/task_manager.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/task_manager.py new file mode 100644 index 0000000..9d71003 --- /dev/null +++ b/demos/a2a_llama_stack/agents/a2a_custom_tools/task_manager.py @@ -0,0 +1,134 @@ +import logging +from typing import AsyncIterable, Union, AsyncIterator + +from llama_stack_client import Agent, AgentEventLogger + +import common.server.utils as utils +from common.server.task_manager import InMemoryTaskManager +from common.types import ( + SendTaskRequest, SendTaskResponse, + SendTaskStreamingRequest, SendTaskStreamingResponse, + TaskStatus, Artifact, + Message, TaskState, + TaskStatusUpdateEvent, TaskArtifactUpdateEvent, + JSONRPCResponse, +) + +logger = logging.getLogger(__name__) + +SUPPORTED_CONTENT_TYPES = ["text", "text/plain", "application/json"] + + +class AgentTaskManager(InMemoryTaskManager): + def __init__(self, agent: Agent, internal_session_id=False): + super().__init__() + self.agent = agent + if internal_session_id: + self.session_id = self.agent.create_session("custom-agent-session") + else: + self.session_id = None + + def _validate_request( + self, request: Union[SendTaskRequest, SendTaskStreamingRequest] + ) -> JSONRPCResponse | None: + params = request.params + if not utils.are_modalities_compatible( + params.acceptedOutputModes, + SUPPORTED_CONTENT_TYPES + ): + logger.warning("Unsupported output modes: %s", params.acceptedOutputModes) + return utils.new_incompatible_types_error(request.id) + return None + + async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse: + err = self._validate_request(request) + if err: + return err + + await self.upsert_task(request.params) + result = self._invoke( + request.params.message.parts[0].text, + request.params.sessionId + ) + parts = [{"type": "text", "text": result}] + status = TaskStatus(state=TaskState.COMPLETED, message=Message(role="agent", parts=parts)) + task = await self._update_store(request.params.id, status, [Artifact(parts=parts)]) + return SendTaskResponse(id=request.id, result=task) + + async def on_send_task_subscribe( + self, request: SendTaskStreamingRequest + ) -> AsyncIterable[SendTaskStreamingResponse] | JSONRPCResponse: + err = self._validate_request(request) + if err: + return err + + await self.upsert_task(request.params) + return self._stream_generator(request) + + async def _stream_generator( + self, request: SendTaskStreamingRequest + ) -> AsyncIterable[SendTaskStreamingResponse]: + params = request.params + query = params.message.parts[0].text + + async for update in self._stream(query, params.sessionId): + done = update["is_task_complete"] + content = update["content"] + delta = update["updates"] + + state = TaskState.COMPLETED if done else TaskState.WORKING + text = content if done else delta + parts = [{"type": "text", "text": text}] + artifacts = [Artifact(parts=parts)] if done else None + + status = TaskStatus(state=state, message=Message(role="agent", parts=parts)) + await self._update_store(request.params.id, status, artifacts or []) + + yield SendTaskStreamingResponse( + id=request.id, + result=TaskStatusUpdateEvent(id=params.id, status=status, final=done) + ) + if artifacts: + yield SendTaskStreamingResponse( + id=request.id, + result=TaskArtifactUpdateEvent(id=params.id, artifact=artifacts[0]) + ) + + async def _update_store(self, task_id: str, status: TaskStatus, artifacts): + async with self.lock: + task = self.tasks[task_id] + task.status = status + if artifacts: + task.artifacts = (task.artifacts or []) + artifacts + return task + + def _invoke(self, query: str, session_id: str) -> str: + """ + Route the user query through the Agent, executing tools as needed. + """ + # Determine which session to use + if self.session_id is not None: + sid = self.session_id + else: + sid = self.agent.create_session(session_id) + + # Send the user query to the Agent + turn_resp = self.agent.create_turn( + messages=[{"role": "user", "content": query}], + session_id=sid, + ) + + # Extract tool and LLM outputs from events + logs = AgentEventLogger().log(turn_resp) + output = "" + for event in logs: + if hasattr(event, "content") and event.content: + output += event.content + return output + + async def _stream(self, query: str, session_id: str) -> AsyncIterator[dict]: + """ + Simplest streaming stub: synchronously invoke and emit once. + """ + result = self._invoke(query, session_id) + yield {"updates": result, "is_task_complete": True, "content": result} diff --git a/demos/a2a_llama_stack/agents/a2a_planner/__init__.py b/demos/a2a_llama_stack/agents/a2a_planner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/demos/a2a_llama_stack/agents/a2a_planner/__main__.py b/demos/a2a_llama_stack/agents/a2a_planner/__main__.py new file mode 100644 index 0000000..f091dab --- /dev/null +++ b/demos/a2a_llama_stack/agents/a2a_planner/__main__.py @@ -0,0 +1,72 @@ +import os +import logging + +from llama_stack_client import LlamaStackClient, Agent + +from common.server import A2AServer +from common.types import AgentCard, AgentCapabilities, AgentSkill + +from .task_manager import AgentTaskManager, SUPPORTED_CONTENT_TYPES + +logging.basicConfig(level=logging.INFO) + + +def build_server(host: str = "0.0.0.0", port: int = 10010): + # 1) instantiate your agent with the required parameters + agent = Agent( + client=LlamaStackClient(base_url=os.getenv("REMOTE_BASE_URL", "http://localhost:8321")), + model=os.getenv("INFERENCE_MODEL_ID", "llama3.1:8b-instruct-fp16"), + instructions="You are an orchestration assistant. Ensure you count correctly the number of skills needed.", + max_infer_iters=10, + sampling_params = { + "strategy": {"type": "greedy"}, + "max_tokens": 4096, + }, + ) + + # 2) wrap it in the A2A TaskManager + task_manager = AgentTaskManager(agent=agent, internal_session_id=True) + + # 3) advertise your tools as AgentSkills + card = AgentCard( + name="Orchestration Agent", + description="Plans which tool to call for each user question", + url=f"http://{host}:{port}/", + version="0.1.0", + defaultInputModes=["text/plain"], + defaultOutputModes=SUPPORTED_CONTENT_TYPES, + capabilities=AgentCapabilities( + streaming=False, + pushNotifications=False, + stateTransitionHistory=False, + ), + skills = [ + AgentSkill( + id="orchestrate", + name="Orchestration Planner", + description="Plan user questions into JSON steps of {skill_id}", + tags=["orchestration"], + examples=["Plan: What's today's date and a random number?"], + inputModes=["text/plain"], + outputModes=["application/json"], + ) + ] + ) + + return A2AServer( + agent_card=card, + task_manager=task_manager, + host=host, + port=port, + ) + +if __name__ == "__main__": + import click + + @click.command() + @click.option("--host", default="0.0.0.0") + @click.option("--port", default=10010, type=int) + def main(host, port): + build_server(host, port).start() + + main() diff --git a/demos/a2a_llama_stack/agents/a2a_planner/agent.py b/demos/a2a_llama_stack/agents/a2a_planner/agent.py new file mode 100644 index 0000000..e69de29 diff --git a/demos/a2a_llama_stack/agents/a2a_planner/task_manager.py b/demos/a2a_llama_stack/agents/a2a_planner/task_manager.py new file mode 100644 index 0000000..5fb17eb --- /dev/null +++ b/demos/a2a_llama_stack/agents/a2a_planner/task_manager.py @@ -0,0 +1,158 @@ +import logging +from typing import AsyncIterable, Union, AsyncIterator + +from llama_stack_client import Agent, AgentEventLogger + +import common.server.utils as utils +from common.server.task_manager import InMemoryTaskManager +from common.types import ( + SendTaskRequest, SendTaskResponse, + SendTaskStreamingRequest, SendTaskStreamingResponse, + TaskStatus, Artifact, + Message, TaskState, + TaskStatusUpdateEvent, TaskArtifactUpdateEvent, + JSONRPCResponse, +) + +logger = logging.getLogger(__name__) + +SUPPORTED_CONTENT_TYPES = ["text", "text/plain", "application/json"] + + +class AgentTaskManager(InMemoryTaskManager): + def __init__(self, agent: Agent, internal_session_id=False): + super().__init__() + self.agent = agent + if internal_session_id: + self.session_id = self.agent.create_session("custom-agent-session") + else: + self.session_id = None + + def _validate_request( + self, request: Union[SendTaskRequest, SendTaskStreamingRequest] + ) -> JSONRPCResponse | None: + params = request.params + if not utils.are_modalities_compatible( + params.acceptedOutputModes, + SUPPORTED_CONTENT_TYPES + ): + logger.warning("Unsupported output modes: %s", params.acceptedOutputModes) + return utils.new_incompatible_types_error(request.id) + return None + + async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse: + err = self._validate_request(request) + if err: + return err + + await self.upsert_task(request.params) + result = self._invoke( + request.params.message.parts[0].text, + request.params.sessionId + ) + parts = [{"type": "text", "text": result}] + status = TaskStatus(state=TaskState.COMPLETED, message=Message(role="agent", parts=parts)) + task = await self._update_store(request.params.id, status, [Artifact(parts=parts)]) + return SendTaskResponse(id=request.id, result=task) + + async def on_send_task_subscribe( + self, request: SendTaskStreamingRequest + ) -> AsyncIterable[SendTaskStreamingResponse] | JSONRPCResponse: + err = self._validate_request(request) + if err: + return err + + await self.upsert_task(request.params) + return self._stream_generator(request) + + async def _stream_generator( + self, request: SendTaskStreamingRequest + ) -> AsyncIterable[SendTaskStreamingResponse]: + params = request.params + query = params.message.parts[0].text + + async for update in self._stream(query, params.sessionId): + done = update["is_task_complete"] + content = update["content"] + delta = update["updates"] + + state = TaskState.COMPLETED if done else TaskState.WORKING + text = content if done else delta + parts = [{"type": "text", "text": text}] + artifacts = [Artifact(parts=parts)] if done else None + + status = TaskStatus(state=state, message=Message(role="agent", parts=parts)) + await self._update_store(request.params.id, status, artifacts or []) + + yield SendTaskStreamingResponse( + id=request.id, + result=TaskStatusUpdateEvent(id=params.id, status=status, final=done) + ) + if artifacts: + yield SendTaskStreamingResponse( + id=request.id, + result=TaskArtifactUpdateEvent(id=params.id, artifact=artifacts[0]) + ) + + async def _update_store(self, task_id: str, status: TaskStatus, artifacts): + async with self.lock: + task = self.tasks[task_id] + task.status = status + if artifacts: + task.artifacts = (task.artifacts or []) + artifacts + return task + + def _invoke(self, query: str, session_id: str) -> str: + """ + Route the user query through the Agent, executing tools as needed. + """ + # Determine which session to use + if self.session_id is not None: + sid = self.session_id + else: + sid = self.agent.create_session(session_id) + + import json + + # parse payload JSON + data = json.loads(query) + skills_meta, question = data["skills"], data["question"] + print(f"\nskills_meta: {skills_meta}\n") + + # rebuild instructions on the fly + plan_instructions = ( + "You are an orchestration assistant.\n" + "Available skills (id & name):\n" + f"{json.dumps(skills_meta, indent=2)}\n\n" + "When given a user question, respond _only_ with a JSON array of objects, " + "each with key `skill_id`, without any surrounding object. You may be asked to write single or multiple skills.\n" + "For example for multiple tools:\n" + "[" + "{\"skill_id\": \"tool_1\"}, " + "{\"skill_id\": \"tool_2\"}" + "]" + ) + + # send it all as one user message (no 'system' role!) + combined = plan_instructions + "\n\nUser question: " + question + + # Send the plan instructions to the Agent + turn_resp = self.agent.create_turn( + messages=[{"role": "user", "content": combined}], + session_id=sid, + ) + + # Extract tool and LLM outputs from events + logs = AgentEventLogger().log(turn_resp) + output = "" + for event in logs: + if hasattr(event, "content") and event.content: + output += event.content + return output + + async def _stream(self, query: str, session_id: str) -> AsyncIterator[dict]: + """ + Simplest streaming stub: synchronously invoke and emit once. + """ + result = self._invoke(query, session_id) + yield {"updates": result, "is_task_complete": True, "content": result} From 0d98606d7aaace44d9f524470ab60393853c7249 Mon Sep 17 00:00:00 2001 From: Kevin Cogan Date: Tue, 20 May 2025 16:20:35 +0100 Subject: [PATCH 04/13] feature: Adding A2A Multi-Agent Notebook demo. --- .../agents/a2a_custom_tools/__main__.py | 12 +- .../agents/a2a_planner/task_manager.py | 2 +- .../notebooks/A2A_Multi_Agent.ipynb | 1047 +++++++++++++++++ 3 files changed, 1054 insertions(+), 7 deletions(-) create mode 100644 demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py index 3d370fa..0c96410 100644 --- a/demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py +++ b/demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py @@ -45,9 +45,9 @@ def build_server(host: str = "0.0.0.0", port: int = 10010): ), skills=[ AgentSkill( - id="random_number_tool", - name="Random Number Generator", - description="Generates a random number between 1 and 100", + id="random_number_tool", + name="Random Number Generator", + description="Generates a random number between 1 and 100", tags=["random"], examples=["Give me a random number between 1 and 100"], inputModes=["text/plain"], @@ -55,9 +55,9 @@ def build_server(host: str = "0.0.0.0", port: int = 10010): ), AgentSkill( - id="date_tool", - name="Date Provider", - description="Returns today's date in YYYY-MM-DD format", + id="date_tool", + name="Date Provider", + description="Returns today's date in YYYY-MM-DD format", tags=["date"], examples=["What's the date today?"], inputModes=["text/plain"], diff --git a/demos/a2a_llama_stack/agents/a2a_planner/task_manager.py b/demos/a2a_llama_stack/agents/a2a_planner/task_manager.py index 5fb17eb..cc41ecc 100644 --- a/demos/a2a_llama_stack/agents/a2a_planner/task_manager.py +++ b/demos/a2a_llama_stack/agents/a2a_planner/task_manager.py @@ -135,7 +135,7 @@ def _invoke(self, query: str, session_id: str) -> str: # send it all as one user message (no 'system' role!) combined = plan_instructions + "\n\nUser question: " + question - + # Send the plan instructions to the Agent turn_resp = self.agent.create_turn( messages=[{"role": "user", "content": combined}], diff --git a/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb b/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb new file mode 100644 index 0000000..b8bee55 --- /dev/null +++ b/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb @@ -0,0 +1,1047 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c55ab371", + "metadata": {}, + "source": [ + "# A2A Multi Agent - Quick-Start Notebook\n", + "\n", + "Welcome to the A2A Multi-Agent Quick-Start notebook! This guide will show you how to set up and interact with a multi-agent system using the Agent-to-Agent (A2A) protocol within the Llama Stack environment.\n", + "\n", + "## Overview\n", + "\n", + "This notebook guides you through the following key steps to set up and interact with a multi-agent system:\n", + "\n", + "1. **Setting up the Environment**: Preparing your Python environment by installing necessary libraries and configuring asynchronous operations.\n", + "\n", + "2. **Agent Management**: Understanding how to connect to and manage the different agents within the system.\n", + "\n", + "3. **Defining Agent Task Flow**: Exploring the multi-phase process (planning, execution, and composition) by which agents collaboratively solve a query.\n", + "\n", + "4. **Launching Agent Servers**: Starting the Agent-to-Agent (A2A) orchestrator and skill agent servers.\n", + "\n", + "5. **Interacting with Agents**: Sending questions to the agent team and observing their orchestrated responses.\n", + "\n", + "## Prerequisites\n", + "\n", + "Before you begin, ensure that you have:\n", + "\n", + "* `python_requires >= 3.13`.\n", + "\n", + "* Completed the initial setup as outlined in the [Setup Guide](../../rag_agentic/notebooks/Level0_getting_started_with_Llama_Stack.ipynb) notebook.\n", + "\n", + "## Environment Variables\n", + "\n", + "This notebook is designed to be flexible, allowing you to connect to either a local or a remote Llama Stack instance, and to specify the inference models used by the agents. You can configure these aspects using the following environment variables:\n", + "\n", + "* `REMOTE_BASE_URL`: Set this variable if you intend to connect to a **remote Llama Stack instance**. If this variable is not set, the notebook will default to running with a local instance.\n", + "\n", + "* `INFERENCE_MODEL_ID`: Define this variable to specify the default Large Language Model (LLM) that agents should use for inference. We recommend using `llama3.1:8b-instruct-fp16` for optimal performance.\n", + "\n", + "**Note on Agent-Specific Models:**\n", + "If you require different inference models for individual agents, you can achieve this by directly opening and modifying the `__main__.py` file within each respective agent's folder (e.g. `demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py`)." + ] + }, + { + "cell_type": "markdown", + "id": "b853d0ba", + "metadata": {}, + "source": [ + "## 1. Setting Up the Notebook Environment\n", + "\n", + "We'll start by setting up the necessary environment and installing the required packages to enable A2A communication." + ] + }, + { + "cell_type": "markdown", + "id": "2d1a1075", + "metadata": {}, + "source": [ + "We'll install the official [A2A sample implementation by Google](https://github.com/google/A2A/tree/main/samples/python), the Llama Stack client, and other essential packages for asynchronous operations in Jupyter. Run the following command:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "eb5bce08", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collecting git+https://github.com/google/A2A.git#subdirectory=samples/python\n", + " Cloning https://github.com/google/A2A.git to /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-11e356th\n", + " Running command git clone --filter=blob:none --quiet https://github.com/google/A2A.git /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-11e356th\n", + " Resolved https://github.com/google/A2A.git to commit 081fa20bdfede24922c49e8e56fcdfbee0db0c28\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build wheel ... \u001b[?25ldone\n", + "\u001b[?25h Preparing metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: a2a-sdk>=0.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.2.1)\n", + "Requirement already satisfied: httpx-sse>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.4.0)\n", + "Requirement already satisfied: httpx>=0.28.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.28.1)\n", + "Requirement already satisfied: jwcrypto>=1.5.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (1.5.6)\n", + "Requirement already satisfied: pydantic>=2.10.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.11.4)\n", + "Requirement already satisfied: pyjwt>=2.10.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.10.1)\n", + "Requirement already satisfied: sse-starlette>=2.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.3.5)\n", + "Requirement already satisfied: starlette>=0.46.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.46.2)\n", + "Requirement already satisfied: typing-extensions>=4.12.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (4.13.2)\n", + "Requirement already satisfied: uvicorn>=0.34.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.34.2)\n", + "Requirement already satisfied: opentelemetry-api>=1.33.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.33.1)\n", + "Requirement already satisfied: opentelemetry-sdk>=1.33.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.33.1)\n", + "Requirement already satisfied: anyio in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (4.9.0)\n", + "Requirement already satisfied: certifi in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (2025.4.26)\n", + "Requirement already satisfied: httpcore==1.* in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (1.0.9)\n", + "Requirement already satisfied: idna in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (3.10)\n", + "Requirement already satisfied: h11>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpcore==1.*->httpx>=0.28.1->a2a-samples==0.1.0) (0.16.0)\n", + "Requirement already satisfied: cryptography>=3.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from jwcrypto>=1.5.6->a2a-samples==0.1.0) (45.0.2)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.33.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (2.33.2)\n", + "Requirement already satisfied: typing-inspection>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (0.4.0)\n", + "Requirement already satisfied: click>=7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from uvicorn>=0.34.0->a2a-samples==0.1.0) (8.2.0)\n", + "Requirement already satisfied: sniffio>=1.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from anyio->httpx>=0.28.1->a2a-samples==0.1.0) (1.3.1)\n", + "Requirement already satisfied: cffi>=1.14 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from cryptography>=3.4->jwcrypto>=1.5.6->a2a-samples==0.1.0) (1.17.1)\n", + "Requirement already satisfied: deprecated>=1.2.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.2.18)\n", + "Requirement already satisfied: importlib-metadata<8.7.0,>=6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (8.6.1)\n", + "Requirement already satisfied: opentelemetry-semantic-conventions==0.54b1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from opentelemetry-sdk>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (0.54b1)\n", + "Requirement already satisfied: pycparser in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from cffi>=1.14->cryptography>=3.4->jwcrypto>=1.5.6->a2a-samples==0.1.0) (2.22)\n", + "Requirement already satisfied: wrapt<2,>=1.10 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from deprecated>=1.2.6->opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.17.2)\n", + "Requirement already satisfied: zipp>=3.20 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from importlib-metadata<8.7.0,>=6.0->opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (3.21.0)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", + "Requirement already satisfied: llama_stack_client in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (0.2.7)\n", + "Requirement already satisfied: asyncclick in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (8.1.8.0)\n", + "Requirement already satisfied: nest_asyncio in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (1.6.0)\n", + "Requirement already satisfied: ipywidgets in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (8.1.7)\n", + "Requirement already satisfied: dotenv in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (0.9.9)\n", + "Requirement already satisfied: anyio<5,>=3.5.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (4.9.0)\n", + "Requirement already satisfied: click in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (8.2.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (1.9.0)\n", + "Requirement already satisfied: httpx<1,>=0.23.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (0.28.1)\n", + "Requirement already satisfied: pandas in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (2.2.3)\n", + "Requirement already satisfied: prompt-toolkit in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (3.0.51)\n", + "Requirement already satisfied: pyaml in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (25.1.0)\n", + "Requirement already satisfied: pydantic<3,>=1.9.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (2.11.4)\n", + "Requirement already satisfied: rich in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (14.0.0)\n", + "Requirement already satisfied: sniffio in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (1.3.1)\n", + "Requirement already satisfied: termcolor in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (3.1.0)\n", + "Requirement already satisfied: tqdm in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (4.67.1)\n", + "Requirement already satisfied: typing-extensions<5,>=4.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (4.13.2)\n", + "Requirement already satisfied: comm>=0.1.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipywidgets) (0.2.2)\n", + "Requirement already satisfied: ipython>=6.1.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipywidgets) (9.2.0)\n", + "Requirement already satisfied: traitlets>=4.3.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipywidgets) (5.14.3)\n", + "Requirement already satisfied: widgetsnbextension~=4.0.14 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipywidgets) (4.0.14)\n", + "Requirement already satisfied: jupyterlab_widgets~=3.0.15 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipywidgets) (3.0.15)\n", + "Requirement already satisfied: python-dotenv in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from dotenv) (1.1.0)\n", + "Requirement already satisfied: idna>=2.8 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from anyio<5,>=3.5.0->llama_stack_client) (3.10)\n", + "Requirement already satisfied: certifi in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpx<1,>=0.23.0->llama_stack_client) (2025.4.26)\n", + "Requirement already satisfied: httpcore==1.* in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpx<1,>=0.23.0->llama_stack_client) (1.0.9)\n", + "Requirement already satisfied: h11>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->llama_stack_client) (0.16.0)\n", + "Requirement already satisfied: decorator in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (5.2.1)\n", + "Requirement already satisfied: ipython-pygments-lexers in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (1.1.1)\n", + "Requirement already satisfied: jedi>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (0.19.2)\n", + "Requirement already satisfied: matplotlib-inline in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (0.1.7)\n", + "Requirement already satisfied: pexpect>4.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (4.9.0)\n", + "Requirement already satisfied: pygments>=2.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (2.19.1)\n", + "Requirement already satisfied: stack_data in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (0.6.3)\n", + "Requirement already satisfied: wcwidth in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from prompt-toolkit->llama_stack_client) (0.2.13)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.33.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (2.33.2)\n", + "Requirement already satisfied: typing-inspection>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (0.4.0)\n", + "Requirement already satisfied: numpy>=1.26.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pandas->llama_stack_client) (2.2.6)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pandas->llama_stack_client) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pandas->llama_stack_client) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pandas->llama_stack_client) (2025.2)\n", + "Requirement already satisfied: PyYAML in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pyaml->llama_stack_client) (6.0.2)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from rich->llama_stack_client) (3.0.0)\n", + "Requirement already satisfied: parso<0.9.0,>=0.8.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from jedi>=0.16->ipython>=6.1.0->ipywidgets) (0.8.4)\n", + "Requirement already satisfied: mdurl~=0.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich->llama_stack_client) (0.1.2)\n", + "Requirement already satisfied: ptyprocess>=0.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pexpect>4.3->ipython>=6.1.0->ipywidgets) (0.7.0)\n", + "Requirement already satisfied: six>=1.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas->llama_stack_client) (1.17.0)\n", + "Requirement already satisfied: executing>=1.2.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from stack_data->ipython>=6.1.0->ipywidgets) (2.2.0)\n", + "Requirement already satisfied: asttokens>=2.1.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from stack_data->ipython>=6.1.0->ipywidgets) (3.0.0)\n", + "Requirement already satisfied: pure-eval in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from stack_data->ipython>=6.1.0->ipywidgets) (0.2.3)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n" + ] + } + ], + "source": [ + "! pip install \"git+https://github.com/google/A2A.git#subdirectory=samples/python\"\n", + "! pip install llama_stack_client asyncclick nest_asyncio ipywidgets dotenv" + ] + }, + { + "cell_type": "markdown", + "id": "3f49992c", + "metadata": {}, + "source": [ + "Next, we'll add the necessary paths to `sys.path` to ensure we can import the required libraries later in the notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c49b3fcf", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "# the path of the A2A library\n", + "sys.path.append('./A2A/samples/python')\n", + "# the path to our own utils\n", + "sys.path.append('../..')" + ] + }, + { + "cell_type": "markdown", + "id": "36e1216b", + "metadata": {}, + "source": [ + "We will now proceed with importing all the necessary Python libraries and modules for the notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "95a70138", + "metadata": {}, + "outputs": [], + "source": [ + "from common.server import A2AServer\n", + "from common.types import AgentCard, AgentSkill, AgentCapabilities\n", + "from a2a_llama_stack.A2ATool import A2ATool\n", + "from a2a_llama_stack.task_manager import AgentTaskManager\n", + "\n", + "# for asynchronously serving the A2A agent\n", + "import os\n", + "import time\n", + "import json\n", + "import logging\n", + "from dotenv import load_dotenv\n", + "import urllib.parse\n", + "from uuid import uuid4\n", + "from typing import Any, Dict, List, Tuple\n", + "import subprocess\n", + "import socket\n", + "import asyncio\n", + "import nest_asyncio\n", + "import threading\n", + "import concurrent.futures as cf\n", + "\n", + "\n", + "\n", + "# Importing custom modules from the common package\n", + "from common.client import A2AClient, A2ACardResolver\n", + "from common.utils.push_notification_auth import PushNotificationReceiverAuth\n", + "from hosts.cli.push_notification_listener import PushNotificationListener" + ] + }, + { + "cell_type": "markdown", + "id": "4943937a", + "metadata": {}, + "source": [ + "Next, we will initialize our environment as described in detail in our [\"Getting Started\" notebook](../../rag_agentic/notebooks/Level0_getting_started_with_Llama_Stack.ipynb). Please refer to it for additional explanations." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c5253d5c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connected to Llama Stack server\n", + "Inference Parameters:\n", + "\tModel: llama3.1:8b-instruct-fp16\n", + "\tSampling Parameters: {'strategy': {'type': 'greedy'}, 'max_tokens': 4096}\n", + "\tstream: False\n" + ] + } + ], + "source": [ + "# for accessing the environment variables\n", + "import os\n", + "from dotenv import load_dotenv\n", + "load_dotenv()\n", + "\n", + "\n", + "base_url = os.getenv(\"REMOTE_BASE_URL\", \"http://localhost:8321\")\n", + " \n", + "print(f\"Connected to Llama Stack server\")\n", + "\n", + "# the model you wish to use that is configured with the Llama Stack server\n", + "model_id = os.getenv(\"INFERENCE_MODEL_ID\", \"llama3.1:8b-instruct-fp16\")\n", + "\n", + "temperature = float(os.getenv(\"TEMPERATURE\", 0.0))\n", + "if temperature > 0.0:\n", + " top_p = float(os.getenv(\"TOP_P\", 0.95))\n", + " strategy = {\"type\": \"top_p\", \"temperature\": temperature, \"top_p\": top_p}\n", + "else:\n", + " strategy = {\"type\": \"greedy\"}\n", + "\n", + "max_tokens = int(os.getenv(\"MAX_TOKENS\", 4096))\n", + "\n", + "# sampling_params will later be used to pass the parameters to Llama Stack Agents/Inference APIs\n", + "sampling_params = {\n", + " \"strategy\": strategy,\n", + " \"max_tokens\": max_tokens,\n", + "}\n", + "\n", + "stream_env = os.getenv(\"STREAM\", \"False\")\n", + "# the Boolean 'stream' parameter will later be passed to Llama Stack Agents/Inference APIs\n", + "# any value non equal to 'False' will be considered as 'True'\n", + "stream = (stream_env != \"False\")\n", + "\n", + "print(f\"Inference Parameters:\\n\\tModel: {model_id}\\n\\tSampling Parameters: {sampling_params}\\n\\tstream: {stream}\")" + ] + }, + { + "cell_type": "markdown", + "id": "5fb3a22f", + "metadata": {}, + "source": [ + "We'll configure basic logging to provide informative output from the agents as they run, which can be very helpful for debugging and understanding the flow." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "86bde581", + "metadata": {}, + "outputs": [], + "source": [ + "# Configuring basic logging for the application\n", + "logging.basicConfig(level=logging.INFO,\n", + " format=\"%(asctime)s %(levelname)s %(name)s: %(message)s\")\n", + "logger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "id": "91f8fbb5", + "metadata": {}, + "source": [ + "We need to use `nest_asyncio` to make sure things run smoothly when your Python code tries to do multiple tasks at the same time in Jupyter." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "62b7e70c", + "metadata": {}, + "outputs": [], + "source": [ + "nest_asyncio.apply()" + ] + }, + { + "cell_type": "markdown", + "id": "1b8ac79e", + "metadata": {}, + "source": [ + "## 2. Understanding the `AgentManager`\n", + "\n", + "The `AgentManager` class is key to connecting with and managing the different agents in our system.\n", + "\n", + "It handles:\n", + "\n", + "* Connecting to your orchestrator agent.\n", + "\n", + "* Connecting to individual skill agents (the ones that perform specific tasks).\n", + "\n", + "* Managing unique session IDs for each connection.\n", + "\n", + "* Using a helper function (`_send_payload`) to send tasks to the agents." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e7cea371", + "metadata": {}, + "outputs": [], + "source": [ + "AgentInfo = Tuple[str, Any, A2AClient, str]\n", + "\n", + "class AgentManager:\n", + " def __init__(self, urls: List[str]):\n", + " # first URL is your orchestrator…\n", + " self.orchestrator: AgentInfo = self._make_agent_info(urls[0])\n", + " # …the rest are skill agents, each keyed by skill.id\n", + " self.skills: Dict[str, AgentInfo] = {\n", + " skill.id: info\n", + " for url in urls[1:]\n", + " for info in (self._make_agent_info(url),)\n", + " for skill in info[1].skills\n", + " }\n", + "\n", + " @staticmethod\n", + " def _make_agent_info(url: str) -> AgentInfo:\n", + " card = A2ACardResolver(url).get_agent_card()\n", + " client = A2AClient(agent_card=card)\n", + " session = uuid4().hex\n", + " return url, card, client, session\n", + "\n", + "\n", + "async def _send_payload(client, card, session, payload, streaming: bool) -> str:\n", + " if not streaming:\n", + " res = await client.send_task(payload)\n", + " return res.result.status.message.parts[0].text.strip()\n", + "\n", + " text = \"\"\n", + " async for ev in client.send_task_streaming(payload):\n", + " part = ev.result.status.message.parts[0].text or \"\"\n", + " print(part, end=\"\", flush=True)\n", + " text = part\n", + " print()\n", + " return text" + ] + }, + { + "cell_type": "markdown", + "id": "4ee4d979", + "metadata": {}, + "source": [ + "## 3. Preparing and Sending Tasks to Agents\n", + "\n", + "This section defines functions that format the user's question into a structured message (a JSON payload) that the agents can understand and process. It then uses the `_send_payload` helper from the `AgentManager` to send this task to the appropriate agent." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a2f9f439", + "metadata": {}, + "outputs": [], + "source": [ + "def _build_skill_meta(mgr):\n", + " \"\"\"Gather metadata for every skill in all executor cards.\"\"\"\n", + " return [\n", + " {\n", + " \"skill_id\": s.id, \"name\": s.name,\n", + " \"description\": getattr(s, \"description\", None),\n", + " \"inputSchema\": getattr(s, \"inputSchema\", None),\n", + " \"outputSchema\": getattr(s, \"outputSchema\", None),\n", + " }\n", + " for _, card, _, _ in mgr.skills.values()\n", + " for s in card.skills\n", + " ]\n", + "\n", + "async def _send_task_to_agent(mgr, client, card, session, question, push=False, host=None, port=None) -> str:\n", + " \"\"\"Build a card-driven payload (with optional push) and dispatch it.\"\"\"\n", + " # Skill metadata + input parts\n", + " skills = _build_skill_meta(mgr)\n", + " content = {\"skills\": skills, \"question\": question}\n", + " modes = getattr(card, \"acceptedInputModes\", [\"text\"])\n", + " parts = ([{\"type\": \"json\", \"json\": content}]\n", + " if \"json\" in modes\n", + " else [{\"type\": \"text\", \"text\": json.dumps(content)}])\n", + "\n", + " # Optional push URL & auth\n", + " can_push = push and getattr(card.capabilities, \"pushNotifications\", False)\n", + " push_url = (urllib.parse.urljoin(f\"http://{host}:{port}\", \"/notify\")\n", + " if can_push and host and port else None)\n", + " schemes = getattr(card.authentication, \"supportedSchemes\", [\"bearer\"])\n", + "\n", + " # Assemble payload\n", + " payload = {\n", + " \"id\": uuid4().hex,\n", + " \"sessionId\": session,\n", + " \"acceptedOutputModes\": card.defaultOutputModes,\n", + " \"message\": {\"role\": \"user\", \"parts\": parts},\n", + " **({\"pushNotification\": {\"url\": push_url,\n", + " \"authentication\": {\"schemes\": schemes}}}\n", + " if push_url else {})\n", + " }\n", + "\n", + " # Dispatch, letting the card decide streaming vs one-shot\n", + " stream = getattr(card.capabilities, \"streaming\", False)\n", + " return await _send_payload(client, card, session, payload, stream)\n" + ] + }, + { + "cell_type": "markdown", + "id": "a5862103", + "metadata": {}, + "source": [ + "## 4. Defining the Agent Task Flow: Planning, Execution, and Composition\n", + "This section contains the core logic for how the agents work together to answer a question. It's broken down into three distinct phases:\n", + "\n", + "* **Planning:** The orchestrator agent figures out the steps needed to answer the question and which skill agents to use.\n", + "\n", + "* **Execution:** The notebook code calls the necessary skill agents based on the plan and collects their results.\n", + "\n", + "* **Composition:** A final agent combines the results from the skill agents into a human-friendly answer." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b86bd386", + "metadata": {}, + "outputs": [], + "source": [ + "async def _planning_phase(agent_manager, question, push, host, port):\n", + " \"\"\"Ask orchestrator for a plan, parse/fix JSON if necessary.\"\"\"\n", + " _, card, client, sess = agent_manager.orchestrator\n", + "\n", + " raw = await _send_task_to_agent(agent_manager, client, card, sess, question, push=push, host=host, port=port)\n", + " print(f\"Raw plan ➡️ {raw}\")\n", + "\n", + " try:\n", + " return json.loads(raw[: raw.rfind(\"]\") + 1])\n", + " except ValueError:\n", + " print(\"\\033[31mPlan parse failed, fixing invalid JSON...\\033[0m\")\n", + " fixer = \"Fix this json to be valid: \" + raw\n", + " fixed = await _send_task_to_agent(agent_manager, client, card, sess, fixer, push=push, host=host, port=port)\n", + " return json.loads(fixed)\n", + "\n", + "\n", + "async def _execution_phase(agent_manager, plan, push, host, port):\n", + " \"\"\"Run each step in the plan via its skill and collect outputs.\"\"\"\n", + " results = []\n", + " for i, step in enumerate(plan, 1):\n", + " sid, inp = step[\"skill_id\"], json.dumps(step.get(\"input\", {}))\n", + " print(f\"➡️ Step {i}: {sid}({inp})\")\n", + "\n", + " info = agent_manager.skills.get(sid)\n", + " if not info:\n", + " print(f\"\\033[31mNo executor for '{sid}', skipping.\\033[0m\")\n", + " results.append({\"skill_id\": sid, \"output\": None})\n", + " continue\n", + "\n", + " _, skill_card, skill_client, skill_sess = info\n", + " out = await _send_task_to_agent(agent_manager, skill_client, skill_card, skill_sess, f\"{sid}({inp})\", push=push, host=host, port=port)\n", + " print(f\" ✅ → {out}\")\n", + " results.append({\"skill_id\": sid, \"output\": out})\n", + "\n", + " return results\n", + "\n", + "def _compose_prompt(parts, question):\n", + " \"\"\"Create the final composition prompt for the orchestrator.\"\"\"\n", + " return (\n", + " f\"Using the following information: {json.dumps(parts)}, \"\n", + " f\"write a clear and human-friendly response to the question: '{question}'. \"\n", + " \"Keep it concise and easy to understand and respond like a human with character. \"\n", + " \"Only use the information provided. If you cannot answer the question, say 'I don't know'. \"\n", + " \"Never show any code or JSON, just the answer.\\n\\n\"\n", + " )\n" + ] + }, + { + "cell_type": "markdown", + "id": "6c6d3fe2", + "metadata": {}, + "source": [ + "## 5. Orchestrating the Agent Interaction\n", + "This is the main function that ties together the planning, execution, and composition phases to answer a user's question using the agent team." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "86d7612a", + "metadata": {}, + "outputs": [], + "source": [ + "async def ask_question(\n", + " agent_manager: AgentManager,\n", + " question: str,\n", + " push: bool = False,\n", + " push_receiver: str = \"http://localhost:5000\",\n", + ") -> str:\n", + " # Unpack orchestrator info\n", + " orch_url, orch_card, orch_client, orch_session = agent_manager.orchestrator\n", + "\n", + " # Optionally start push listener\n", + " host = port = None\n", + " if push:\n", + " parsed = urllib.parse.urlparse(push_receiver)\n", + " host, port = parsed.hostname, parsed.port\n", + " auth = PushNotificationReceiverAuth()\n", + " await auth.load_jwks(f\"{orch_url}/.well-known/jwks.json\")\n", + " PushNotificationListener(\n", + " host=host,\n", + " port=port,\n", + " notification_receiver_auth=auth\n", + " ).start()\n", + "\n", + " # --- Planning Phase ---\n", + " print(\"\\n\\033[1;33m=========== 🧠 Planning Phase ===========\\033[0m\")\n", + " plan = await _planning_phase(agent_manager, question, push=push, host=host, port=port)\n", + " print(f\"\\n\\033[1;32mFinal plan ➡️ {plan}\\033[0m\")\n", + "\n", + " # --- Execution Phase ---\n", + " print(\"\\n\\033[1;33m=========== ⚡️ Execution Phase ===========\\033[0m\")\n", + " parts = await _execution_phase(agent_manager, plan, push=push, host=host, port=port)\n", + "\n", + " # --- Composing Answer ---\n", + " print(\"\\n\\033[1;33m=========== 🛠️ Composing Answer ===========\\033[0m\")\n", + " comp_prompt = _compose_prompt(parts, question)\n", + " WRITING_AGENT_ID = \"writing_agent\"\n", + "\n", + " _, skill_card, skill_client, skill_sess = agent_manager.skills.get(WRITING_AGENT_ID)\n", + " final = await _send_task_to_agent(agent_manager, skill_client, skill_card, skill_sess, comp_prompt, push=push, host=host, port=port)\n", + "\n", + " print(\"\\n\\033[1;36m🎉 FINAL ANSWER\\033[0m\")\n", + " print(final)\n", + " print(\"\\033[1;36m====================================\\033[0m\")\n", + " return final\n" + ] + }, + { + "cell_type": "markdown", + "id": "4d580d4f", + "metadata": {}, + "source": [ + "## 6. Launching the A2A Agent Servers\n", + "Before we can interact with the agents, we need to start their servers. This bootstrap script handles bringing the complete A2A stack online.\n", + "\n", + "It performs three key actions in sequence:\n", + "\n", + "1. Defines connection details (ports and modules).\n", + "\n", + "2. Starts each agent in parallel and waits for them to be ready.\n", + "\n", + "3. Connects to the running agents and summarizes their status." + ] + }, + { + "cell_type": "markdown", + "id": "60bd2d20", + "metadata": {}, + "source": [ + "Below, we set up the network addresses (`URLS`) for our orchestrator and skill agents, and specify the Python modules that implement their functionality. These definitions are crucial for starting and connecting to the agents in the next steps." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ceb43397", + "metadata": {}, + "outputs": [], + "source": [ + "ORCHESTRATOR_URL = \"http://localhost:10010\"\n", + "EXECUTOR_URLS = [\"http://localhost:10011\", \"http://localhost:10012\"]\n", + "URLS = [ORCHESTRATOR_URL, *EXECUTOR_URLS]\n", + "MODULES = [\n", + " \"agents.a2a_planner\",\n", + " \"agents.a2a_custom_tools\",\n", + " \"agents.a2a_composer\",\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "9580373e", + "metadata": {}, + "source": [ + "This launches the agent processes and wait until each server is ready and listening on its assigned port." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "3083dd64", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ agents.a2a_planner ready on port 10010\n", + "✅ agents.a2a_custom_tools ready on port 10011\n", + "✅ agents.a2a_composer ready on port 10012\n" + ] + } + ], + "source": [ + "os.chdir('..') # change to the directory where the script is located\n", + "\n", + "def _launch(mod, url):\n", + " port = int(url.split(\":\")[-1])\n", + " subprocess.Popen([sys.executable, \"-m\", mod, \"--port\", str(port)],\n", + " stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)\n", + " while socket.socket().connect_ex((\"127.0.0.1\", port)): time.sleep(.1)\n", + " return f\"✅ {mod} ready on port {port}\"\n", + "\n", + "with cf.ThreadPoolExecutor() as pool:\n", + " print(*pool.map(_launch, MODULES, URLS), sep=\"\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "96c1a74a", + "metadata": {}, + "source": [ + "Now that the agents should be running, we'll use our `AgentManager` to connect to them, confirm they are online, and see what skills they offer." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "0ea51ded", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:02:19,356 INFO httpx: HTTP Request: GET http://localhost:10010/.well-known/agent.json \"HTTP/1.1 200 OK\"\n", + "2025-05-20 16:02:19,366 INFO httpx: HTTP Request: GET http://localhost:10011/.well-known/agent.json \"HTTP/1.1 200 OK\"\n", + "2025-05-20 16:02:19,376 INFO httpx: HTTP Request: GET http://localhost:10012/.well-known/agent.json \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\u001b[1;36m===================== 🛰️ Connected Agents =====================\u001b[0m\n", + "Orchestrator: http://localhost:10010 (Orchestration Agent)\n", + "Executors:\n", + " • random_number_tool -> http://localhost:10011 (Custom Agent)\n", + " • date_tool -> http://localhost:10011 (Custom Agent)\n", + " • writing_agent -> http://localhost:10012 (Writing Agent)\n", + "\u001b[1;36m===============================================================\u001b[0m\n" + ] + } + ], + "source": [ + "_agent_manager = AgentManager(URLS)\n", + "orch_url, orch_card, *_ = _agent_manager.orchestrator\n", + "\n", + "print(\"\\n\\033[1;36m===================== 🛰️ Connected Agents =====================\\033[0m\")\n", + "print(f\"Orchestrator: {orch_url} ({orch_card.name})\")\n", + "print(\"Executors:\")\n", + "for sid, (u, card, *_) in _agent_manager.skills.items():\n", + " print(f\" • {sid} -> {u} ({card.name})\")\n", + "print(\"\\033[1;36m===============================================================\\033[0m\")" + ] + }, + { + "cell_type": "markdown", + "id": "f32892ab", + "metadata": {}, + "source": [ + "## 7. Asking the Agent Team a Question!\n", + "\n", + "Finally, it's time to put our agent team to work! We'll use the `ask_question` function we defined earlier to send our queries and see the multi-agent system in action." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "6476a816", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\u001b[1;33m=========== 🧠 Planning Phase ===========\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:02:31,947 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Raw plan ➡️ [\n", + " {\"skill_id\": \"date_tool\"},\n", + " {\"skill_id\": \"random_number_tool\"},\n", + " {\"skill_id\": \"random_number_tool\"},\n", + " {\"skill_id\": \"random_number_tool\"},\n", + " {\"skill_id\": \"random_number_tool\"},\n", + " {\"skill_id\": \"random_number_tool\"}\n", + "]\n", + "\n", + "\u001b[1;32mFinal plan ➡️ [{'skill_id': 'date_tool'}, {'skill_id': 'random_number_tool'}, {'skill_id': 'random_number_tool'}, {'skill_id': 'random_number_tool'}, {'skill_id': 'random_number_tool'}, {'skill_id': 'random_number_tool'}]\u001b[0m\n", + "\n", + "\u001b[1;33m=========== ⚡️ Execution Phase ===========\u001b[0m\n", + "➡️ Step 1: date_tool({})\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:02:37,550 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ✅ → {\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": {}}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-05-20\"The date today is 2023-05-20.\n", + "➡️ Step 2: random_number_tool({})\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:02:41,982 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:1The random number generated is 1.\n", + "➡️ Step 3: random_number_tool({})\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:02:46,690 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:86The random number generated is 86.\n", + "➡️ Step 4: random_number_tool({})\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:02:51,154 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:20The random number generated is 20.\n", + "➡️ Step 5: random_number_tool({})\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:02:55,637 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:5The random number generated is 5.\n", + "➡️ Step 6: random_number_tool({})\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:03:00,145 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:44The random number generated is 44.\n", + "\n", + "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:03:05,302 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\u001b[1;36m🎉 FINAL ANSWER\u001b[0m\n", + "Here's your response:\n", + "\n", + "\"Today's date is May 20th, 2023. Here are five random numbers: 1, 86, 20, 5, and 44.\"\n", + "\u001b[1;36m====================================\u001b[0m\n", + "\n", + "\u001b[1;33m=========== 🧠 Planning Phase ===========\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:03:11,257 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Raw plan ➡️ [\n", + " {\"skill_id\": \"date_tool\"}\n", + "]\n", + "\n", + "\u001b[1;32mFinal plan ➡️ [{'skill_id': 'date_tool'}]\u001b[0m\n", + "\n", + "\u001b[1;33m=========== ⚡️ Execution Phase ===========\u001b[0m\n", + "➡️ Step 1: date_tool({})\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:03:28,192 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ✅ → {\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": {}}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-05-20\"The current date is May 20, 2025.\n", + "\n", + "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:03:30,939 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\u001b[1;36m🎉 FINAL ANSWER\u001b[0m\n", + "\"Today's date is May 20, 2025.\"\n", + "\u001b[1;36m====================================\u001b[0m\n", + "\n", + "\u001b[1;33m=========== 🧠 Planning Phase ===========\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:03:37,648 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Raw plan ➡️ [\n", + " {\"skill_id\": \"random_number_tool\"}\n", + "]\n", + "\n", + "\u001b[1;32mFinal plan ➡️ [{'skill_id': 'random_number_tool'}]\u001b[0m\n", + "\n", + "\u001b[1;33m=========== ⚡️ Execution Phase ===========\u001b[0m\n", + "➡️ Step 1: random_number_tool({})\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:03:54,412 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:21The random number generated is 21.\n", + "\n", + "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-05-20 16:03:57,080 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\u001b[1;36m🎉 FINAL ANSWER\u001b[0m\n", + "\"Here's a random number: 21.\"\n", + "\u001b[1;36m====================================\u001b[0m\n" + ] + } + ], + "source": [ + "questions = [ \n", + " \"Get todays date then generate five random numbers\",\n", + " \"Get todays date?\",\n", + " \"generate a random number\",\n", + " ]\n", + "\n", + "for question in questions:\n", + " await ask_question(_agent_manager, question)" + ] + }, + { + "cell_type": "markdown", + "id": "3d0c00af", + "metadata": {}, + "source": [ + "## Key Takeaways\n", + "Congratulations! You've successfully set up and interacted with a multi-agent system using the A2A protocol. You saw how an orchestrator agent planned the task, how skill agents executed the steps, and how a composition agent put it all together for you.\n", + "\n", + "Future demos will cover more advanced aspects of agent-to-agent communication." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv8", + "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.13.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From e03548d6cda921ca18630d342c5a36619506362c Mon Sep 17 00:00:00 2001 From: Kevin Cogan Date: Tue, 27 May 2025 13:12:00 +0100 Subject: [PATCH 05/13] refactor: Modularize core components and agent configurations. --- demos/a2a_llama_stack/__main__.py | 87 +++++ .../agents/a2a_composer/__init__.py | 0 .../agents/a2a_composer/__main__.py | 72 ---- .../agents/a2a_composer/agent.py | 0 .../agents/a2a_composer/config.py | 43 +++ .../agents/a2a_composer/task_manager.py | 134 ------- .../agents/a2a_custom_tools/__init__.py | 0 .../agents/a2a_custom_tools/__main__.py | 85 ----- .../agents/a2a_custom_tools/config.py | 55 +++ .../agents/a2a_custom_tools/task_manager.py | 134 ------- .../a2a_custom_tools/{agent.py => tools.py} | 3 +- .../agents/a2a_planner/__init__.py | 0 .../agents/a2a_planner/__main__.py | 72 ---- .../agents/a2a_planner/agent.py | 0 .../agents/a2a_planner/config.py | 43 +++ .../agents/a2a_planner/task_manager.py | 158 -------- .../notebooks/A2A_Multi_Agent.ipynb | 349 ++++++++++-------- 17 files changed, 429 insertions(+), 806 deletions(-) create mode 100644 demos/a2a_llama_stack/__main__.py delete mode 100644 demos/a2a_llama_stack/agents/a2a_composer/__init__.py delete mode 100644 demos/a2a_llama_stack/agents/a2a_composer/__main__.py delete mode 100644 demos/a2a_llama_stack/agents/a2a_composer/agent.py create mode 100644 demos/a2a_llama_stack/agents/a2a_composer/config.py delete mode 100644 demos/a2a_llama_stack/agents/a2a_composer/task_manager.py delete mode 100644 demos/a2a_llama_stack/agents/a2a_custom_tools/__init__.py delete mode 100644 demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py create mode 100644 demos/a2a_llama_stack/agents/a2a_custom_tools/config.py delete mode 100644 demos/a2a_llama_stack/agents/a2a_custom_tools/task_manager.py rename demos/a2a_llama_stack/agents/a2a_custom_tools/{agent.py => tools.py} (72%) delete mode 100644 demos/a2a_llama_stack/agents/a2a_planner/__init__.py delete mode 100644 demos/a2a_llama_stack/agents/a2a_planner/__main__.py delete mode 100644 demos/a2a_llama_stack/agents/a2a_planner/agent.py create mode 100644 demos/a2a_llama_stack/agents/a2a_planner/config.py delete mode 100644 demos/a2a_llama_stack/agents/a2a_planner/task_manager.py diff --git a/demos/a2a_llama_stack/__main__.py b/demos/a2a_llama_stack/__main__.py new file mode 100644 index 0000000..0ddb39b --- /dev/null +++ b/demos/a2a_llama_stack/__main__.py @@ -0,0 +1,87 @@ +import os +import logging +import importlib +import click + +from llama_stack_client import LlamaStackClient, Agent +# Assuming these are accessible from your project structure +from common.server import A2AServer +from common.types import AgentCard, AgentCapabilities, AgentSkill + +logging.basicConfig(level=logging.INFO) + +def build_server(agent_name: str, host: str, port: int = None): + try: + config_module_path = f"{__package__}.agents.{agent_name.replace('-', '_')}.config" + config_module = importlib.import_module(config_module_path) + agent_config_data = config_module.AGENT_CONFIG + except ModuleNotFoundError: + logging.error(f"Configuration module not found for agent: {agent_name} at {config_module_path}") + raise + except AttributeError: + logging.error(f"AGENT_CONFIG not found in {config_module_path}") + raise + + if port is None: # Use default from config if not overridden by CLI + port = agent_config_data.get("default_port", 8000) + + agent_params_config = agent_config_data["agent_params"] + tools_to_pass = agent_params_config.get("tools", []) + + agent = Agent( + client=LlamaStackClient(base_url=os.getenv("REMOTE_BASE_URL", "http://localhost:8321")), + model=os.getenv(agent_params_config["model_env_var"], agent_params_config["default_model"]), + instructions=agent_params_config["instructions"], + tools=tools_to_pass, + max_infer_iters=agent_params_config.get("max_infer_iters", 3), + sampling_params=agent_params_config.get("sampling_params", None) + ) + + TaskManagerClass = agent_config_data["task_manager_class"] + task_manager = TaskManagerClass(agent=agent, internal_session_id=True) + + card_params_config = agent_config_data["agent_card_params"] + agent_skills = [AgentSkill(**skill_p) for skill_p in card_params_config.get("skills_params", [])] + capabilities = AgentCapabilities(**card_params_config.get("capabilities_params", {})) + + card = AgentCard( + name=card_params_config["name"], + description=card_params_config["description"], + url=f"http://{host}:{port}/", + version=card_params_config.get("version", "0.1.0"), + defaultInputModes=card_params_config.get("default_input_modes", ["text/plain"]), + defaultOutputModes=card_params_config.get("default_output_modes", ["text/plain"]), + capabilities=capabilities, + skills=agent_skills + ) + + return A2AServer( + agent_card=card, + task_manager=task_manager, + host=host, + port=port, + ) + +@click.command() +@click.option("--agent-name", required=True, help="The name of the agent to run (e.g., a2a_planner, a2a_custom_tools). Corresponds to the directory name.") +@click.option("--host", default="0.0.0.0", help="Host to bind the server to.") +@click.option("--port", type=int, default=None, help="Port to bind the server to (overrides agent's default).") +def main(agent_name, host, port): + effective_port = port # Will be replaced by default from config if None in build_server + if port is None: # For logging purposes, find the default port if not specified + try: + config_module_path = f"agents.a2a_llama_stack.agents.{agent_name.replace('-', '_')}.config" + config_module = importlib.import_module(config_module_path) + effective_port = config_module.AGENT_CONFIG.get("default_port", "config_default") + except Exception: + effective_port = "unknown_default" + + + logging.info(f"Attempting to start server for agent: {agent_name} on {host}:{effective_port}") + server = build_server(agent_name=agent_name, host=host, port=port) + server.start() + logging.info(f"Server for agent {agent_name} should be running.") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/demos/a2a_llama_stack/agents/a2a_composer/__init__.py b/demos/a2a_llama_stack/agents/a2a_composer/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/demos/a2a_llama_stack/agents/a2a_composer/__main__.py b/demos/a2a_llama_stack/agents/a2a_composer/__main__.py deleted file mode 100644 index 296bdd3..0000000 --- a/demos/a2a_llama_stack/agents/a2a_composer/__main__.py +++ /dev/null @@ -1,72 +0,0 @@ -import os -import logging - -from llama_stack_client import LlamaStackClient, Agent - -from common.server import A2AServer -from common.types import AgentCard, AgentCapabilities, AgentSkill - -from .task_manager import AgentTaskManager, SUPPORTED_CONTENT_TYPES - -logging.basicConfig(level=logging.INFO) - - -def build_server(host: str = "0.0.0.0", port: int = 10012): - # 1) instantiate your agent with the required parameters - agent = Agent( - client=LlamaStackClient(base_url=os.getenv("REMOTE_BASE_URL", "http://localhost:8321")), - model=os.getenv("INFERENCE_MODEL_ID", "llama3.2:3b-instruct-fp16"), - instructions=("You are skilled at writing human-friendly text based on the query and associated skills."), - max_infer_iters=3, - sampling_params = { - "strategy": {"type": "greedy"}, - "max_tokens": 4096, - }, - ) - - # 2) wrap it in the A2A TaskManager - task_manager = AgentTaskManager(agent=agent, internal_session_id=True) - - # 3) advertise your tools as AgentSkills - card = AgentCard( - name="Writing Agent", - description="Generate human-friendly text based on the query and associated skills", - url=f"http://{host}:{port}/", - version="0.1.0", - defaultInputModes=["text/plain"], - defaultOutputModes=SUPPORTED_CONTENT_TYPES, - capabilities=AgentCapabilities( - streaming=False, - pushNotifications=False, - stateTransitionHistory=False, - ), - skills = [ - AgentSkill( - id="writing_agent", - name="Writing Agent", - description="Write human-friendly text based on the query and associated skills", - tags=["writing"], - examples=["Write human-friendly text based on the query and associated skills"], - inputModes=["text/plain"], - outputModes=["application/json"], - ) - ] - ) - - return A2AServer( - agent_card=card, - task_manager=task_manager, - host=host, - port=port, - ) - -if __name__ == "__main__": - import click - - @click.command() - @click.option("--host", default="0.0.0.0") - @click.option("--port", default=10010, type=int) - def main(host, port): - build_server(host, port).start() - - main() diff --git a/demos/a2a_llama_stack/agents/a2a_composer/agent.py b/demos/a2a_llama_stack/agents/a2a_composer/agent.py deleted file mode 100644 index e69de29..0000000 diff --git a/demos/a2a_llama_stack/agents/a2a_composer/config.py b/demos/a2a_llama_stack/agents/a2a_composer/config.py new file mode 100644 index 0000000..f82058a --- /dev/null +++ b/demos/a2a_llama_stack/agents/a2a_composer/config.py @@ -0,0 +1,43 @@ +# Updated import to use AgentTaskManager from the root a2a_llama_stack directory +from ...task_manager import AgentTaskManager, SUPPORTED_CONTENT_TYPES +# common.types.AgentSkill, AgentCapabilities are imported in generic_main + +AGENT_CONFIG = { + "agent_params": { + "model_env_var": "INFERENCE_MODEL_ID", + "default_model": "llama3.2:3b-instruct-fp16", + "instructions": "You are skilled at writing human-friendly text based on the query and associated skills.", + "max_infer_iters": 3, + "sampling_params": { + "strategy": {"type": "greedy"}, + "max_tokens": 4096, + }, + "tools": [] + }, + # Updated to use AgentTaskManager + "task_manager_class": AgentTaskManager, + "agent_card_params": { + "name": "Writing Agent", + "description": "Generate human-friendly text based on the query and associated skills", + "version": "0.1.0", + "default_input_modes": ["text/plain"], + "default_output_modes": SUPPORTED_CONTENT_TYPES, + "capabilities_params": { + "streaming": False, + "pushNotifications": False, + "stateTransitionHistory": False, + }, + "skills_params": [ + { + "id": "writing_agent", + "name": "Writing Agent", + "description": "Write human-friendly text based on the query and associated skills", + "tags": ["writing"], + "examples": ["Write human-friendly text based on the query and associated skills"], + "inputModes": ["text/plain"], + "outputModes": ["application/json"], # As per original file + } + ] + }, + "default_port": 10012, +} \ No newline at end of file diff --git a/demos/a2a_llama_stack/agents/a2a_composer/task_manager.py b/demos/a2a_llama_stack/agents/a2a_composer/task_manager.py deleted file mode 100644 index 9d71003..0000000 --- a/demos/a2a_llama_stack/agents/a2a_composer/task_manager.py +++ /dev/null @@ -1,134 +0,0 @@ -import logging -from typing import AsyncIterable, Union, AsyncIterator - -from llama_stack_client import Agent, AgentEventLogger - -import common.server.utils as utils -from common.server.task_manager import InMemoryTaskManager -from common.types import ( - SendTaskRequest, SendTaskResponse, - SendTaskStreamingRequest, SendTaskStreamingResponse, - TaskStatus, Artifact, - Message, TaskState, - TaskStatusUpdateEvent, TaskArtifactUpdateEvent, - JSONRPCResponse, -) - -logger = logging.getLogger(__name__) - -SUPPORTED_CONTENT_TYPES = ["text", "text/plain", "application/json"] - - -class AgentTaskManager(InMemoryTaskManager): - def __init__(self, agent: Agent, internal_session_id=False): - super().__init__() - self.agent = agent - if internal_session_id: - self.session_id = self.agent.create_session("custom-agent-session") - else: - self.session_id = None - - def _validate_request( - self, request: Union[SendTaskRequest, SendTaskStreamingRequest] - ) -> JSONRPCResponse | None: - params = request.params - if not utils.are_modalities_compatible( - params.acceptedOutputModes, - SUPPORTED_CONTENT_TYPES - ): - logger.warning("Unsupported output modes: %s", params.acceptedOutputModes) - return utils.new_incompatible_types_error(request.id) - return None - - async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse: - err = self._validate_request(request) - if err: - return err - - await self.upsert_task(request.params) - result = self._invoke( - request.params.message.parts[0].text, - request.params.sessionId - ) - parts = [{"type": "text", "text": result}] - status = TaskStatus(state=TaskState.COMPLETED, message=Message(role="agent", parts=parts)) - task = await self._update_store(request.params.id, status, [Artifact(parts=parts)]) - return SendTaskResponse(id=request.id, result=task) - - async def on_send_task_subscribe( - self, request: SendTaskStreamingRequest - ) -> AsyncIterable[SendTaskStreamingResponse] | JSONRPCResponse: - err = self._validate_request(request) - if err: - return err - - await self.upsert_task(request.params) - return self._stream_generator(request) - - async def _stream_generator( - self, request: SendTaskStreamingRequest - ) -> AsyncIterable[SendTaskStreamingResponse]: - params = request.params - query = params.message.parts[0].text - - async for update in self._stream(query, params.sessionId): - done = update["is_task_complete"] - content = update["content"] - delta = update["updates"] - - state = TaskState.COMPLETED if done else TaskState.WORKING - text = content if done else delta - parts = [{"type": "text", "text": text}] - artifacts = [Artifact(parts=parts)] if done else None - - status = TaskStatus(state=state, message=Message(role="agent", parts=parts)) - await self._update_store(request.params.id, status, artifacts or []) - - yield SendTaskStreamingResponse( - id=request.id, - result=TaskStatusUpdateEvent(id=params.id, status=status, final=done) - ) - if artifacts: - yield SendTaskStreamingResponse( - id=request.id, - result=TaskArtifactUpdateEvent(id=params.id, artifact=artifacts[0]) - ) - - async def _update_store(self, task_id: str, status: TaskStatus, artifacts): - async with self.lock: - task = self.tasks[task_id] - task.status = status - if artifacts: - task.artifacts = (task.artifacts or []) + artifacts - return task - - def _invoke(self, query: str, session_id: str) -> str: - """ - Route the user query through the Agent, executing tools as needed. - """ - # Determine which session to use - if self.session_id is not None: - sid = self.session_id - else: - sid = self.agent.create_session(session_id) - - # Send the user query to the Agent - turn_resp = self.agent.create_turn( - messages=[{"role": "user", "content": query}], - session_id=sid, - ) - - # Extract tool and LLM outputs from events - logs = AgentEventLogger().log(turn_resp) - output = "" - for event in logs: - if hasattr(event, "content") and event.content: - output += event.content - return output - - async def _stream(self, query: str, session_id: str) -> AsyncIterator[dict]: - """ - Simplest streaming stub: synchronously invoke and emit once. - """ - result = self._invoke(query, session_id) - yield {"updates": result, "is_task_complete": True, "content": result} diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/__init__.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py deleted file mode 100644 index 0c96410..0000000 --- a/demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py +++ /dev/null @@ -1,85 +0,0 @@ -import os -import logging - -from llama_stack_client import LlamaStackClient, Agent - -from common.server import A2AServer -from common.types import AgentCard, AgentCapabilities, AgentSkill - -from .agent import random_number_tool, date_tool -from .task_manager import AgentTaskManager, SUPPORTED_CONTENT_TYPES - -logging.basicConfig(level=logging.INFO) - - -def build_server(host: str = "0.0.0.0", port: int = 10010): - # 1) instantiate your agent with the required parameters - agent = Agent( - client=LlamaStackClient(base_url=os.getenv("REMOTE_BASE_URL", "http://localhost:8321")), - model=os.getenv("INFERENCE_MODEL_ID", "llama3.1:8b-instruct-fp16"), - instructions=( - "You have access to two tools:\n" - "- random_number_tool: generates one random integer between 1 and 100\n" - "- date_tool: returns today's date in YYYY-MM-DD format\n" - "Always use the appropriate tool to answer user queries." - ), - tools=[random_number_tool, date_tool], - max_infer_iters=3, - ) - - # 2) wrap it in the A2A TaskManager - task_manager = AgentTaskManager(agent=agent, internal_session_id=True) - - # 3) advertise your tools as AgentSkills - card = AgentCard( - name="Custom Agent", - description="Generates random numbers or retrieve today's dates", - url=f"http://{host}:{port}/", - version="0.1.0", - defaultInputModes=["text/plain"], - defaultOutputModes=SUPPORTED_CONTENT_TYPES, - capabilities=AgentCapabilities( - streaming=False, - pushNotifications=False, - stateTransitionHistory=False, - ), - skills=[ - AgentSkill( - id="random_number_tool", - name="Random Number Generator", - description="Generates a random number between 1 and 100", - tags=["random"], - examples=["Give me a random number between 1 and 100"], - inputModes=["text/plain"], - outputModes=["text/plain"], - ), - - AgentSkill( - id="date_tool", - name="Date Provider", - description="Returns today's date in YYYY-MM-DD format", - tags=["date"], - examples=["What's the date today?"], - inputModes=["text/plain"], - outputModes=["text/plain"], - ), - ], - ) - - return A2AServer( - agent_card=card, - task_manager=task_manager, - host=host, - port=port, - ) - -if __name__ == "__main__": - import click - - @click.command() - @click.option("--host", default="0.0.0.0") - @click.option("--port", default=10010, type=int) - def main(host, port): - build_server(host, port).start() - - main() diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py new file mode 100644 index 0000000..31ab54b --- /dev/null +++ b/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py @@ -0,0 +1,55 @@ +# Updated import to use AgentTaskManager from the root a2a_llama_stack directory +from ...task_manager import AgentTaskManager, SUPPORTED_CONTENT_TYPES +from .tools import random_number_tool, date_tool # Import tools +# common.types.AgentSkill, AgentCapabilities are imported in generic_main + +AGENT_CONFIG = { + "agent_params": { + "model_env_var": "INFERENCE_MODEL_ID", + "default_model": "llama3.1:8b-instruct-fp16", + "instructions": ( + "You have access to two tools:\n" + "- random_number_tool: generates one random integer between 1 and 100\n" + "- date_tool: returns today's date in YYYY-MM-DD format\n" + "Always use the appropriate tool to answer user queries." + ), + "tools": [random_number_tool, date_tool], + "max_infer_iters": 3, + "sampling_params": None + }, + # Updated to use AgentTaskManager + "task_manager_class": AgentTaskManager, + "agent_card_params": { + "name": "Custom Agent", + "description": "Generates random numbers or retrieve today's dates", + "version": "0.1.0", + "default_input_modes": ["text/plain"], + "default_output_modes": SUPPORTED_CONTENT_TYPES, + "capabilities_params": { + "streaming": False, + "pushNotifications": False, + "stateTransitionHistory": False, + }, + "skills_params": [ + { + "id": "random_number_tool", + "name": "Random Number Generator", + "description": "Generates a random number between 1 and 100", + "tags": ["random"], + "examples": ["Give me a random number between 1 and 100"], + "inputModes": ["text/plain"], + "outputModes": ["text/plain"], + }, + { + "id": "date_tool", + "name": "Date Provider", + "description": "Returns today's date in YYYY-MM-DD format", + "tags": ["date"], + "examples": ["What's the date today?"], + "inputModes": ["text/plain"], + "outputModes": ["text/plain"], + } + ] + }, + "default_port": 10011, +} \ No newline at end of file diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/task_manager.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/task_manager.py deleted file mode 100644 index 9d71003..0000000 --- a/demos/a2a_llama_stack/agents/a2a_custom_tools/task_manager.py +++ /dev/null @@ -1,134 +0,0 @@ -import logging -from typing import AsyncIterable, Union, AsyncIterator - -from llama_stack_client import Agent, AgentEventLogger - -import common.server.utils as utils -from common.server.task_manager import InMemoryTaskManager -from common.types import ( - SendTaskRequest, SendTaskResponse, - SendTaskStreamingRequest, SendTaskStreamingResponse, - TaskStatus, Artifact, - Message, TaskState, - TaskStatusUpdateEvent, TaskArtifactUpdateEvent, - JSONRPCResponse, -) - -logger = logging.getLogger(__name__) - -SUPPORTED_CONTENT_TYPES = ["text", "text/plain", "application/json"] - - -class AgentTaskManager(InMemoryTaskManager): - def __init__(self, agent: Agent, internal_session_id=False): - super().__init__() - self.agent = agent - if internal_session_id: - self.session_id = self.agent.create_session("custom-agent-session") - else: - self.session_id = None - - def _validate_request( - self, request: Union[SendTaskRequest, SendTaskStreamingRequest] - ) -> JSONRPCResponse | None: - params = request.params - if not utils.are_modalities_compatible( - params.acceptedOutputModes, - SUPPORTED_CONTENT_TYPES - ): - logger.warning("Unsupported output modes: %s", params.acceptedOutputModes) - return utils.new_incompatible_types_error(request.id) - return None - - async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse: - err = self._validate_request(request) - if err: - return err - - await self.upsert_task(request.params) - result = self._invoke( - request.params.message.parts[0].text, - request.params.sessionId - ) - parts = [{"type": "text", "text": result}] - status = TaskStatus(state=TaskState.COMPLETED, message=Message(role="agent", parts=parts)) - task = await self._update_store(request.params.id, status, [Artifact(parts=parts)]) - return SendTaskResponse(id=request.id, result=task) - - async def on_send_task_subscribe( - self, request: SendTaskStreamingRequest - ) -> AsyncIterable[SendTaskStreamingResponse] | JSONRPCResponse: - err = self._validate_request(request) - if err: - return err - - await self.upsert_task(request.params) - return self._stream_generator(request) - - async def _stream_generator( - self, request: SendTaskStreamingRequest - ) -> AsyncIterable[SendTaskStreamingResponse]: - params = request.params - query = params.message.parts[0].text - - async for update in self._stream(query, params.sessionId): - done = update["is_task_complete"] - content = update["content"] - delta = update["updates"] - - state = TaskState.COMPLETED if done else TaskState.WORKING - text = content if done else delta - parts = [{"type": "text", "text": text}] - artifacts = [Artifact(parts=parts)] if done else None - - status = TaskStatus(state=state, message=Message(role="agent", parts=parts)) - await self._update_store(request.params.id, status, artifacts or []) - - yield SendTaskStreamingResponse( - id=request.id, - result=TaskStatusUpdateEvent(id=params.id, status=status, final=done) - ) - if artifacts: - yield SendTaskStreamingResponse( - id=request.id, - result=TaskArtifactUpdateEvent(id=params.id, artifact=artifacts[0]) - ) - - async def _update_store(self, task_id: str, status: TaskStatus, artifacts): - async with self.lock: - task = self.tasks[task_id] - task.status = status - if artifacts: - task.artifacts = (task.artifacts or []) + artifacts - return task - - def _invoke(self, query: str, session_id: str) -> str: - """ - Route the user query through the Agent, executing tools as needed. - """ - # Determine which session to use - if self.session_id is not None: - sid = self.session_id - else: - sid = self.agent.create_session(session_id) - - # Send the user query to the Agent - turn_resp = self.agent.create_turn( - messages=[{"role": "user", "content": query}], - session_id=sid, - ) - - # Extract tool and LLM outputs from events - logs = AgentEventLogger().log(turn_resp) - output = "" - for event in logs: - if hasattr(event, "content") and event.content: - output += event.content - return output - - async def _stream(self, query: str, session_id: str) -> AsyncIterator[dict]: - """ - Simplest streaming stub: synchronously invoke and emit once. - """ - result = self._invoke(query, session_id) - yield {"updates": result, "is_task_complete": True, "content": result} diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/agent.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/tools.py similarity index 72% rename from demos/a2a_llama_stack/agents/a2a_custom_tools/agent.py rename to demos/a2a_llama_stack/agents/a2a_custom_tools/tools.py index fa3649b..dcfda1e 100644 --- a/demos/a2a_llama_stack/agents/a2a_custom_tools/agent.py +++ b/demos/a2a_llama_stack/agents/a2a_custom_tools/tools.py @@ -6,7 +6,6 @@ def random_number_tool() -> int: """ Generate a random integer between 1 and 100. """ - print("\n\nGenerating a random number...\n\n") return random.randint(1, 100) @@ -14,4 +13,4 @@ def date_tool() -> str: """ Return today's date in YYYY-MM-DD format. """ - return datetime.utcnow().date().isoformat() + return datetime.utcnow().date().isoformat() \ No newline at end of file diff --git a/demos/a2a_llama_stack/agents/a2a_planner/__init__.py b/demos/a2a_llama_stack/agents/a2a_planner/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/demos/a2a_llama_stack/agents/a2a_planner/__main__.py b/demos/a2a_llama_stack/agents/a2a_planner/__main__.py deleted file mode 100644 index f091dab..0000000 --- a/demos/a2a_llama_stack/agents/a2a_planner/__main__.py +++ /dev/null @@ -1,72 +0,0 @@ -import os -import logging - -from llama_stack_client import LlamaStackClient, Agent - -from common.server import A2AServer -from common.types import AgentCard, AgentCapabilities, AgentSkill - -from .task_manager import AgentTaskManager, SUPPORTED_CONTENT_TYPES - -logging.basicConfig(level=logging.INFO) - - -def build_server(host: str = "0.0.0.0", port: int = 10010): - # 1) instantiate your agent with the required parameters - agent = Agent( - client=LlamaStackClient(base_url=os.getenv("REMOTE_BASE_URL", "http://localhost:8321")), - model=os.getenv("INFERENCE_MODEL_ID", "llama3.1:8b-instruct-fp16"), - instructions="You are an orchestration assistant. Ensure you count correctly the number of skills needed.", - max_infer_iters=10, - sampling_params = { - "strategy": {"type": "greedy"}, - "max_tokens": 4096, - }, - ) - - # 2) wrap it in the A2A TaskManager - task_manager = AgentTaskManager(agent=agent, internal_session_id=True) - - # 3) advertise your tools as AgentSkills - card = AgentCard( - name="Orchestration Agent", - description="Plans which tool to call for each user question", - url=f"http://{host}:{port}/", - version="0.1.0", - defaultInputModes=["text/plain"], - defaultOutputModes=SUPPORTED_CONTENT_TYPES, - capabilities=AgentCapabilities( - streaming=False, - pushNotifications=False, - stateTransitionHistory=False, - ), - skills = [ - AgentSkill( - id="orchestrate", - name="Orchestration Planner", - description="Plan user questions into JSON steps of {skill_id}", - tags=["orchestration"], - examples=["Plan: What's today's date and a random number?"], - inputModes=["text/plain"], - outputModes=["application/json"], - ) - ] - ) - - return A2AServer( - agent_card=card, - task_manager=task_manager, - host=host, - port=port, - ) - -if __name__ == "__main__": - import click - - @click.command() - @click.option("--host", default="0.0.0.0") - @click.option("--port", default=10010, type=int) - def main(host, port): - build_server(host, port).start() - - main() diff --git a/demos/a2a_llama_stack/agents/a2a_planner/agent.py b/demos/a2a_llama_stack/agents/a2a_planner/agent.py deleted file mode 100644 index e69de29..0000000 diff --git a/demos/a2a_llama_stack/agents/a2a_planner/config.py b/demos/a2a_llama_stack/agents/a2a_planner/config.py new file mode 100644 index 0000000..a6a98e2 --- /dev/null +++ b/demos/a2a_llama_stack/agents/a2a_planner/config.py @@ -0,0 +1,43 @@ +# Updated import to use AgentTaskManager from the root a2a_llama_stack directory +from ...task_manager import AgentTaskManager, SUPPORTED_CONTENT_TYPES +# common.types.AgentSkill, AgentCapabilities are imported in generic_main + +AGENT_CONFIG = { + "agent_params": { + "model_env_var": "INFERENCE_MODEL_ID", + "default_model": "llama3.1:8b-instruct-fp16", + "instructions": "You are an orchestration assistant. Ensure you count correctly the number of skills needed.", + "max_infer_iters": 10, + "sampling_params": { + "strategy": {"type": "greedy"}, + "max_tokens": 4096, + }, + "tools": [] + }, + # Updated to use AgentTaskManager + "task_manager_class": AgentTaskManager, + "agent_card_params": { + "name": "Orchestration Agent", + "description": "Plans which tool to call for each user question", + "version": "0.1.0", + "default_input_modes": ["text/plain"], + "default_output_modes": SUPPORTED_CONTENT_TYPES, + "capabilities_params": { + "streaming": False, + "pushNotifications": False, + "stateTransitionHistory": False, + }, + "skills_params": [ + { + "id": "orchestrate", + "name": "Orchestration Planner", + "description": "Plan user questions into JSON steps of {skill_id}", + "tags": ["orchestration"], + "examples": ["Plan: What's today's date and a random number?"], + "inputModes": ["text/plain"], + "outputModes": ["application/json"], + } + ] + }, + "default_port": 10010, +} \ No newline at end of file diff --git a/demos/a2a_llama_stack/agents/a2a_planner/task_manager.py b/demos/a2a_llama_stack/agents/a2a_planner/task_manager.py deleted file mode 100644 index cc41ecc..0000000 --- a/demos/a2a_llama_stack/agents/a2a_planner/task_manager.py +++ /dev/null @@ -1,158 +0,0 @@ -import logging -from typing import AsyncIterable, Union, AsyncIterator - -from llama_stack_client import Agent, AgentEventLogger - -import common.server.utils as utils -from common.server.task_manager import InMemoryTaskManager -from common.types import ( - SendTaskRequest, SendTaskResponse, - SendTaskStreamingRequest, SendTaskStreamingResponse, - TaskStatus, Artifact, - Message, TaskState, - TaskStatusUpdateEvent, TaskArtifactUpdateEvent, - JSONRPCResponse, -) - -logger = logging.getLogger(__name__) - -SUPPORTED_CONTENT_TYPES = ["text", "text/plain", "application/json"] - - -class AgentTaskManager(InMemoryTaskManager): - def __init__(self, agent: Agent, internal_session_id=False): - super().__init__() - self.agent = agent - if internal_session_id: - self.session_id = self.agent.create_session("custom-agent-session") - else: - self.session_id = None - - def _validate_request( - self, request: Union[SendTaskRequest, SendTaskStreamingRequest] - ) -> JSONRPCResponse | None: - params = request.params - if not utils.are_modalities_compatible( - params.acceptedOutputModes, - SUPPORTED_CONTENT_TYPES - ): - logger.warning("Unsupported output modes: %s", params.acceptedOutputModes) - return utils.new_incompatible_types_error(request.id) - return None - - async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse: - err = self._validate_request(request) - if err: - return err - - await self.upsert_task(request.params) - result = self._invoke( - request.params.message.parts[0].text, - request.params.sessionId - ) - parts = [{"type": "text", "text": result}] - status = TaskStatus(state=TaskState.COMPLETED, message=Message(role="agent", parts=parts)) - task = await self._update_store(request.params.id, status, [Artifact(parts=parts)]) - return SendTaskResponse(id=request.id, result=task) - - async def on_send_task_subscribe( - self, request: SendTaskStreamingRequest - ) -> AsyncIterable[SendTaskStreamingResponse] | JSONRPCResponse: - err = self._validate_request(request) - if err: - return err - - await self.upsert_task(request.params) - return self._stream_generator(request) - - async def _stream_generator( - self, request: SendTaskStreamingRequest - ) -> AsyncIterable[SendTaskStreamingResponse]: - params = request.params - query = params.message.parts[0].text - - async for update in self._stream(query, params.sessionId): - done = update["is_task_complete"] - content = update["content"] - delta = update["updates"] - - state = TaskState.COMPLETED if done else TaskState.WORKING - text = content if done else delta - parts = [{"type": "text", "text": text}] - artifacts = [Artifact(parts=parts)] if done else None - - status = TaskStatus(state=state, message=Message(role="agent", parts=parts)) - await self._update_store(request.params.id, status, artifacts or []) - - yield SendTaskStreamingResponse( - id=request.id, - result=TaskStatusUpdateEvent(id=params.id, status=status, final=done) - ) - if artifacts: - yield SendTaskStreamingResponse( - id=request.id, - result=TaskArtifactUpdateEvent(id=params.id, artifact=artifacts[0]) - ) - - async def _update_store(self, task_id: str, status: TaskStatus, artifacts): - async with self.lock: - task = self.tasks[task_id] - task.status = status - if artifacts: - task.artifacts = (task.artifacts or []) + artifacts - return task - - def _invoke(self, query: str, session_id: str) -> str: - """ - Route the user query through the Agent, executing tools as needed. - """ - # Determine which session to use - if self.session_id is not None: - sid = self.session_id - else: - sid = self.agent.create_session(session_id) - - import json - - # parse payload JSON - data = json.loads(query) - skills_meta, question = data["skills"], data["question"] - print(f"\nskills_meta: {skills_meta}\n") - - # rebuild instructions on the fly - plan_instructions = ( - "You are an orchestration assistant.\n" - "Available skills (id & name):\n" - f"{json.dumps(skills_meta, indent=2)}\n\n" - "When given a user question, respond _only_ with a JSON array of objects, " - "each with key `skill_id`, without any surrounding object. You may be asked to write single or multiple skills.\n" - "For example for multiple tools:\n" - "[" - "{\"skill_id\": \"tool_1\"}, " - "{\"skill_id\": \"tool_2\"}" - "]" - ) - - # send it all as one user message (no 'system' role!) - combined = plan_instructions + "\n\nUser question: " + question - - # Send the plan instructions to the Agent - turn_resp = self.agent.create_turn( - messages=[{"role": "user", "content": combined}], - session_id=sid, - ) - - # Extract tool and LLM outputs from events - logs = AgentEventLogger().log(turn_resp) - output = "" - for event in logs: - if hasattr(event, "content") and event.content: - output += event.content - return output - - async def _stream(self, query: str, session_id: str) -> AsyncIterator[dict]: - """ - Simplest streaming stub: synchronously invoke and emit once. - """ - result = self._invoke(query, session_id) - yield {"updates": result, "is_task_complete": True, "content": result} diff --git a/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb b/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb index b8bee55..fc5c207 100644 --- a/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb +++ b/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb @@ -72,97 +72,97 @@ "output_type": "stream", "text": [ "Collecting git+https://github.com/google/A2A.git#subdirectory=samples/python\n", - " Cloning https://github.com/google/A2A.git to /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-11e356th\n", - " Running command git clone --filter=blob:none --quiet https://github.com/google/A2A.git /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-11e356th\n", - " Resolved https://github.com/google/A2A.git to commit 081fa20bdfede24922c49e8e56fcdfbee0db0c28\n", + " Cloning https://github.com/google/A2A.git to /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-wbo7qrw9\n", + " Running command git clone --filter=blob:none --quiet https://github.com/google/A2A.git /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-wbo7qrw9\n", + " Resolved https://github.com/google/A2A.git to commit 09c36bd94f74ea1a1a4e0db5f65ba12b102171ba\n", " Installing build dependencies ... \u001b[?25ldone\n", "\u001b[?25h Getting requirements to build wheel ... \u001b[?25ldone\n", "\u001b[?25h Preparing metadata (pyproject.toml) ... \u001b[?25ldone\n", - "\u001b[?25hRequirement already satisfied: a2a-sdk>=0.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.2.1)\n", - "Requirement already satisfied: httpx-sse>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.4.0)\n", - "Requirement already satisfied: httpx>=0.28.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.28.1)\n", - "Requirement already satisfied: jwcrypto>=1.5.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (1.5.6)\n", - "Requirement already satisfied: pydantic>=2.10.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.11.4)\n", - "Requirement already satisfied: pyjwt>=2.10.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.10.1)\n", - "Requirement already satisfied: sse-starlette>=2.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.3.5)\n", - "Requirement already satisfied: starlette>=0.46.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.46.2)\n", - "Requirement already satisfied: typing-extensions>=4.12.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (4.13.2)\n", - "Requirement already satisfied: uvicorn>=0.34.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.34.2)\n", - "Requirement already satisfied: opentelemetry-api>=1.33.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.33.1)\n", - "Requirement already satisfied: opentelemetry-sdk>=1.33.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.33.1)\n", - "Requirement already satisfied: anyio in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (4.9.0)\n", - "Requirement already satisfied: certifi in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (2025.4.26)\n", - "Requirement already satisfied: httpcore==1.* in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (1.0.9)\n", - "Requirement already satisfied: idna in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (3.10)\n", - "Requirement already satisfied: h11>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpcore==1.*->httpx>=0.28.1->a2a-samples==0.1.0) (0.16.0)\n", - "Requirement already satisfied: cryptography>=3.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from jwcrypto>=1.5.6->a2a-samples==0.1.0) (45.0.2)\n", - "Requirement already satisfied: annotated-types>=0.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (0.7.0)\n", - "Requirement already satisfied: pydantic-core==2.33.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (2.33.2)\n", - "Requirement already satisfied: typing-inspection>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (0.4.0)\n", - "Requirement already satisfied: click>=7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from uvicorn>=0.34.0->a2a-samples==0.1.0) (8.2.0)\n", - "Requirement already satisfied: sniffio>=1.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from anyio->httpx>=0.28.1->a2a-samples==0.1.0) (1.3.1)\n", - "Requirement already satisfied: cffi>=1.14 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from cryptography>=3.4->jwcrypto>=1.5.6->a2a-samples==0.1.0) (1.17.1)\n", - "Requirement already satisfied: deprecated>=1.2.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.2.18)\n", - "Requirement already satisfied: importlib-metadata<8.7.0,>=6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (8.6.1)\n", - "Requirement already satisfied: opentelemetry-semantic-conventions==0.54b1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from opentelemetry-sdk>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (0.54b1)\n", - "Requirement already satisfied: pycparser in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from cffi>=1.14->cryptography>=3.4->jwcrypto>=1.5.6->a2a-samples==0.1.0) (2.22)\n", - "Requirement already satisfied: wrapt<2,>=1.10 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from deprecated>=1.2.6->opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.17.2)\n", - "Requirement already satisfied: zipp>=3.20 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from importlib-metadata<8.7.0,>=6.0->opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (3.21.0)\n", + "\u001b[?25hRequirement already satisfied: a2a-sdk>=0.2.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.2.4)\n", + "Requirement already satisfied: httpx-sse>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.4.0)\n", + "Requirement already satisfied: httpx>=0.28.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.28.1)\n", + "Requirement already satisfied: jwcrypto>=1.5.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (1.5.6)\n", + "Requirement already satisfied: pydantic>=2.10.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.11.5)\n", + "Requirement already satisfied: pyjwt>=2.10.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.10.1)\n", + "Requirement already satisfied: sse-starlette>=2.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.3.5)\n", + "Requirement already satisfied: starlette>=0.46.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.46.2)\n", + "Requirement already satisfied: typing-extensions>=4.12.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (4.13.2)\n", + "Requirement already satisfied: uvicorn>=0.34.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.34.2)\n", + "Requirement already satisfied: opentelemetry-api>=1.33.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-sdk>=0.2.4->a2a-samples==0.1.0) (1.33.1)\n", + "Requirement already satisfied: opentelemetry-sdk>=1.33.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-sdk>=0.2.4->a2a-samples==0.1.0) (1.33.1)\n", + "Requirement already satisfied: anyio in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (4.9.0)\n", + "Requirement already satisfied: certifi in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (2025.4.26)\n", + "Requirement already satisfied: httpcore==1.* in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (1.0.9)\n", + "Requirement already satisfied: idna in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (3.10)\n", + "Requirement already satisfied: h11>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpcore==1.*->httpx>=0.28.1->a2a-samples==0.1.0) (0.16.0)\n", + "Requirement already satisfied: cryptography>=3.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from jwcrypto>=1.5.6->a2a-samples==0.1.0) (45.0.3)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.33.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (2.33.2)\n", + "Requirement already satisfied: typing-inspection>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (0.4.1)\n", + "Requirement already satisfied: click>=7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from uvicorn>=0.34.0->a2a-samples==0.1.0) (8.2.1)\n", + "Requirement already satisfied: sniffio>=1.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from anyio->httpx>=0.28.1->a2a-samples==0.1.0) (1.3.1)\n", + "Requirement already satisfied: cffi>=1.14 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from cryptography>=3.4->jwcrypto>=1.5.6->a2a-samples==0.1.0) (1.17.1)\n", + "Requirement already satisfied: deprecated>=1.2.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from opentelemetry-api>=1.33.0->a2a-sdk>=0.2.4->a2a-samples==0.1.0) (1.2.18)\n", + "Requirement already satisfied: importlib-metadata<8.7.0,>=6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from opentelemetry-api>=1.33.0->a2a-sdk>=0.2.4->a2a-samples==0.1.0) (8.6.1)\n", + "Requirement already satisfied: opentelemetry-semantic-conventions==0.54b1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from opentelemetry-sdk>=1.33.0->a2a-sdk>=0.2.4->a2a-samples==0.1.0) (0.54b1)\n", + "Requirement already satisfied: pycparser in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from cffi>=1.14->cryptography>=3.4->jwcrypto>=1.5.6->a2a-samples==0.1.0) (2.22)\n", + "Requirement already satisfied: wrapt<2,>=1.10 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from deprecated>=1.2.6->opentelemetry-api>=1.33.0->a2a-sdk>=0.2.4->a2a-samples==0.1.0) (1.17.2)\n", + "Requirement already satisfied: zipp>=3.20 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from importlib-metadata<8.7.0,>=6.0->opentelemetry-api>=1.33.0->a2a-sdk>=0.2.4->a2a-samples==0.1.0) (3.22.0)\n", "\n", "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n", "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", - "Requirement already satisfied: llama_stack_client in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (0.2.7)\n", - "Requirement already satisfied: asyncclick in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (8.1.8.0)\n", - "Requirement already satisfied: nest_asyncio in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (1.6.0)\n", - "Requirement already satisfied: ipywidgets in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (8.1.7)\n", - "Requirement already satisfied: dotenv in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (0.9.9)\n", - "Requirement already satisfied: anyio<5,>=3.5.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (4.9.0)\n", - "Requirement already satisfied: click in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (8.2.0)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (1.9.0)\n", - "Requirement already satisfied: httpx<1,>=0.23.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (0.28.1)\n", - "Requirement already satisfied: pandas in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (2.2.3)\n", - "Requirement already satisfied: prompt-toolkit in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (3.0.51)\n", - "Requirement already satisfied: pyaml in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (25.1.0)\n", - "Requirement already satisfied: pydantic<3,>=1.9.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (2.11.4)\n", - "Requirement already satisfied: rich in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (14.0.0)\n", - "Requirement already satisfied: sniffio in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (1.3.1)\n", - "Requirement already satisfied: termcolor in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (3.1.0)\n", - "Requirement already satisfied: tqdm in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (4.67.1)\n", - "Requirement already satisfied: typing-extensions<5,>=4.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from llama_stack_client) (4.13.2)\n", - "Requirement already satisfied: comm>=0.1.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipywidgets) (0.2.2)\n", - "Requirement already satisfied: ipython>=6.1.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipywidgets) (9.2.0)\n", - "Requirement already satisfied: traitlets>=4.3.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipywidgets) (5.14.3)\n", - "Requirement already satisfied: widgetsnbextension~=4.0.14 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipywidgets) (4.0.14)\n", - "Requirement already satisfied: jupyterlab_widgets~=3.0.15 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipywidgets) (3.0.15)\n", - "Requirement already satisfied: python-dotenv in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from dotenv) (1.1.0)\n", - "Requirement already satisfied: idna>=2.8 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from anyio<5,>=3.5.0->llama_stack_client) (3.10)\n", - "Requirement already satisfied: certifi in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpx<1,>=0.23.0->llama_stack_client) (2025.4.26)\n", - "Requirement already satisfied: httpcore==1.* in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpx<1,>=0.23.0->llama_stack_client) (1.0.9)\n", - "Requirement already satisfied: h11>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->llama_stack_client) (0.16.0)\n", - "Requirement already satisfied: decorator in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (5.2.1)\n", - "Requirement already satisfied: ipython-pygments-lexers in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (1.1.1)\n", - "Requirement already satisfied: jedi>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (0.19.2)\n", - "Requirement already satisfied: matplotlib-inline in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (0.1.7)\n", - "Requirement already satisfied: pexpect>4.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (4.9.0)\n", - "Requirement already satisfied: pygments>=2.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (2.19.1)\n", - "Requirement already satisfied: stack_data in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (0.6.3)\n", - "Requirement already satisfied: wcwidth in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from prompt-toolkit->llama_stack_client) (0.2.13)\n", - "Requirement already satisfied: annotated-types>=0.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (0.7.0)\n", - "Requirement already satisfied: pydantic-core==2.33.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (2.33.2)\n", - "Requirement already satisfied: typing-inspection>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (0.4.0)\n", - "Requirement already satisfied: numpy>=1.26.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pandas->llama_stack_client) (2.2.6)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pandas->llama_stack_client) (2.9.0.post0)\n", - "Requirement already satisfied: pytz>=2020.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pandas->llama_stack_client) (2025.2)\n", - "Requirement already satisfied: tzdata>=2022.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pandas->llama_stack_client) (2025.2)\n", - "Requirement already satisfied: PyYAML in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pyaml->llama_stack_client) (6.0.2)\n", - "Requirement already satisfied: markdown-it-py>=2.2.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from rich->llama_stack_client) (3.0.0)\n", - "Requirement already satisfied: parso<0.9.0,>=0.8.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from jedi>=0.16->ipython>=6.1.0->ipywidgets) (0.8.4)\n", - "Requirement already satisfied: mdurl~=0.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich->llama_stack_client) (0.1.2)\n", - "Requirement already satisfied: ptyprocess>=0.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from pexpect>4.3->ipython>=6.1.0->ipywidgets) (0.7.0)\n", - "Requirement already satisfied: six>=1.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas->llama_stack_client) (1.17.0)\n", - "Requirement already satisfied: executing>=1.2.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from stack_data->ipython>=6.1.0->ipywidgets) (2.2.0)\n", - "Requirement already satisfied: asttokens>=2.1.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from stack_data->ipython>=6.1.0->ipywidgets) (3.0.0)\n", - "Requirement already satisfied: pure-eval in /Users/kcogan/Documents/llama-stack-on-ocp/venv8/lib/python3.13/site-packages (from stack_data->ipython>=6.1.0->ipywidgets) (0.2.3)\n", + "Requirement already satisfied: llama_stack_client in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (0.2.7)\n", + "Requirement already satisfied: asyncclick in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (8.1.8.0)\n", + "Requirement already satisfied: nest_asyncio in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (1.6.0)\n", + "Requirement already satisfied: ipywidgets in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (8.1.7)\n", + "Requirement already satisfied: dotenv in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (0.9.9)\n", + "Requirement already satisfied: anyio<5,>=3.5.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (4.9.0)\n", + "Requirement already satisfied: click in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (8.2.1)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (1.9.0)\n", + "Requirement already satisfied: httpx<1,>=0.23.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (0.28.1)\n", + "Requirement already satisfied: pandas in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (2.2.3)\n", + "Requirement already satisfied: prompt-toolkit in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (3.0.51)\n", + "Requirement already satisfied: pyaml in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (25.1.0)\n", + "Requirement already satisfied: pydantic<3,>=1.9.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (2.11.5)\n", + "Requirement already satisfied: rich in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (14.0.0)\n", + "Requirement already satisfied: sniffio in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (1.3.1)\n", + "Requirement already satisfied: termcolor in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (3.1.0)\n", + "Requirement already satisfied: tqdm in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (4.67.1)\n", + "Requirement already satisfied: typing-extensions<5,>=4.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (4.13.2)\n", + "Requirement already satisfied: comm>=0.1.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipywidgets) (0.2.2)\n", + "Requirement already satisfied: ipython>=6.1.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipywidgets) (9.2.0)\n", + "Requirement already satisfied: traitlets>=4.3.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipywidgets) (5.14.3)\n", + "Requirement already satisfied: widgetsnbextension~=4.0.14 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipywidgets) (4.0.14)\n", + "Requirement already satisfied: jupyterlab_widgets~=3.0.15 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipywidgets) (3.0.15)\n", + "Requirement already satisfied: python-dotenv in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from dotenv) (1.1.0)\n", + "Requirement already satisfied: idna>=2.8 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from anyio<5,>=3.5.0->llama_stack_client) (3.10)\n", + "Requirement already satisfied: certifi in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpx<1,>=0.23.0->llama_stack_client) (2025.4.26)\n", + "Requirement already satisfied: httpcore==1.* in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpx<1,>=0.23.0->llama_stack_client) (1.0.9)\n", + "Requirement already satisfied: h11>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->llama_stack_client) (0.16.0)\n", + "Requirement already satisfied: decorator in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (5.2.1)\n", + "Requirement already satisfied: ipython-pygments-lexers in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (1.1.1)\n", + "Requirement already satisfied: jedi>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (0.19.2)\n", + "Requirement already satisfied: matplotlib-inline in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (0.1.7)\n", + "Requirement already satisfied: pexpect>4.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (4.9.0)\n", + "Requirement already satisfied: pygments>=2.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (2.19.1)\n", + "Requirement already satisfied: stack_data in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (0.6.3)\n", + "Requirement already satisfied: wcwidth in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from prompt-toolkit->llama_stack_client) (0.2.13)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.33.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (2.33.2)\n", + "Requirement already satisfied: typing-inspection>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (0.4.1)\n", + "Requirement already satisfied: numpy>=1.26.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pandas->llama_stack_client) (2.2.6)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pandas->llama_stack_client) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pandas->llama_stack_client) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pandas->llama_stack_client) (2025.2)\n", + "Requirement already satisfied: PyYAML in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pyaml->llama_stack_client) (6.0.2)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from rich->llama_stack_client) (3.0.0)\n", + "Requirement already satisfied: parso<0.9.0,>=0.8.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from jedi>=0.16->ipython>=6.1.0->ipywidgets) (0.8.4)\n", + "Requirement already satisfied: mdurl~=0.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich->llama_stack_client) (0.1.2)\n", + "Requirement already satisfied: ptyprocess>=0.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pexpect>4.3->ipython>=6.1.0->ipywidgets) (0.7.0)\n", + "Requirement already satisfied: six>=1.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas->llama_stack_client) (1.17.0)\n", + "Requirement already satisfied: executing>=1.2.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from stack_data->ipython>=6.1.0->ipywidgets) (2.2.0)\n", + "Requirement already satisfied: asttokens>=2.1.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from stack_data->ipython>=6.1.0->ipywidgets) (3.0.0)\n", + "Requirement already satisfied: pure-eval in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from stack_data->ipython>=6.1.0->ipywidgets) (0.2.3)\n", "\n", "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n", "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n" @@ -213,8 +213,8 @@ "source": [ "from common.server import A2AServer\n", "from common.types import AgentCard, AgentSkill, AgentCapabilities\n", - "from a2a_llama_stack.A2ATool import A2ATool\n", - "from a2a_llama_stack.task_manager import AgentTaskManager\n", + "# from a2a_llama_stack.A2ATool import A2ATool\n", + "# from a2a_llama_stack.task_manager import AgentTaskManager\n", "\n", "# for asynchronously serving the A2A agent\n", "import os\n", @@ -423,23 +423,26 @@ "outputs": [], "source": [ "def _build_skill_meta(mgr):\n", - " \"\"\"Gather metadata for every skill in all executor cards.\"\"\"\n", - " return [\n", - " {\n", - " \"skill_id\": s.id, \"name\": s.name,\n", - " \"description\": getattr(s, \"description\", None),\n", - " \"inputSchema\": getattr(s, \"inputSchema\", None),\n", - " \"outputSchema\": getattr(s, \"outputSchema\", None),\n", - " }\n", - " for _, card, _, _ in mgr.skills.values()\n", - " for s in card.skills\n", - " ]\n", + " \"\"\"Gather unique metadata for every skill in all executor cards.\"\"\"\n", + " unique_skills = {} # Use a dictionary to store skills by their ID\n", + " for _, card, _, _ in mgr.skills.values():\n", + " for s in card.skills:\n", + " if s.id not in unique_skills:\n", + " unique_skills[s.id] = {\n", + " \"skill_id\": s.id,\n", + " \"name\": s.name,\n", + " \"description\": getattr(s, \"description\", None),\n", + " \"tags\": getattr(s, \"tags\", []),\n", + " \"examples\": getattr(s, \"examples\", None),\n", + "\n", + " }\n", + " return list(unique_skills.values()) # Convert the dictionary values back to a list\n", + "\n", "\n", "async def _send_task_to_agent(mgr, client, card, session, question, push=False, host=None, port=None) -> str:\n", " \"\"\"Build a card-driven payload (with optional push) and dispatch it.\"\"\"\n", - " # Skill metadata + input parts\n", - " skills = _build_skill_meta(mgr)\n", - " content = {\"skills\": skills, \"question\": question}\n", + " # Input parts\n", + " content = {\"question\": question}\n", " modes = getattr(card, \"acceptedInputModes\", [\"text\"])\n", " parts = ([{\"type\": \"json\", \"json\": content}]\n", " if \"json\" in modes\n", @@ -493,7 +496,23 @@ " \"\"\"Ask orchestrator for a plan, parse/fix JSON if necessary.\"\"\"\n", " _, card, client, sess = agent_manager.orchestrator\n", "\n", - " raw = await _send_task_to_agent(agent_manager, client, card, sess, question, push=push, host=host, port=port)\n", + " skills_meta = _build_skill_meta(agent_manager)\n", + "\n", + " plan_instructions = (\n", + " \"You are an orchestration assistant.\\n\"\n", + " \"Available skills (id & name & description & tags & examples):\\n\"\n", + " f\"{json.dumps(skills_meta, indent=2)}\\n\\n\"\n", + " \"When given a user question, respond _only_ with a JSON array of objects, \"\n", + " \"each with key `skill_id`, without any surrounding object. You may be asked to write single or multiple skills.\\n\"\n", + " \"For example for multiple tools:\\n\"\n", + " \"[\"\n", + " \"{\\\"skill_id\\\": \\\"tool_1\\\"}, \"\n", + " \"{\\\"skill_id\\\": \\\"tool_2\\\"}\"\n", + " \"]\"\n", + " )\n", + " combined = plan_instructions + \"\\n\\nUser question: \" + question\n", + "\n", + " raw = await _send_task_to_agent(agent_manager, client, card, sess, combined, push=push, host=host, port=port)\n", " print(f\"Raw plan ➡️ {raw}\")\n", "\n", " try:\n", @@ -532,7 +551,7 @@ " f\"write a clear and human-friendly response to the question: '{question}'. \"\n", " \"Keep it concise and easy to understand and respond like a human with character. \"\n", " \"Only use the information provided. If you cannot answer the question, say 'I don't know'. \"\n", - " \"Never show any code or JSON, just the answer.\\n\\n\"\n", + " \"Never show any code or JSON or Markdown, just the answer.\\n\\n\"\n", " )\n" ] }, @@ -632,10 +651,10 @@ "ORCHESTRATOR_URL = \"http://localhost:10010\"\n", "EXECUTOR_URLS = [\"http://localhost:10011\", \"http://localhost:10012\"]\n", "URLS = [ORCHESTRATOR_URL, *EXECUTOR_URLS]\n", - "MODULES = [\n", - " \"agents.a2a_planner\",\n", - " \"agents.a2a_custom_tools\",\n", - " \"agents.a2a_composer\",\n", + "AGENT_NAMES = [\n", + " \"a2a_planner\",\n", + " \"a2a_custom_tools\",\n", + " \"a2a_composer\",\n", "]" ] }, @@ -650,31 +669,57 @@ { "cell_type": "code", "execution_count": 12, - "id": "3083dd64", + "id": "5c5713c9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "✅ agents.a2a_planner ready on port 10010\n", - "✅ agents.a2a_custom_tools ready on port 10011\n", - "✅ agents.a2a_composer ready on port 10012\n" + "🗂 package_dir = /Users/kcogan/Documents/llama-stack-on-ocp/demos/a2a_llama_stack\n", + "🔗 python_path_root = /Users/kcogan/Documents/llama-stack-on-ocp/demos\n", + "\n", + "✅ a2a_planner ready on port 10010\n", + "✅ a2a_custom_tools ready on port 10011\n", + "✅ a2a_composer ready on port 10012\n" ] } ], "source": [ - "os.chdir('..') # change to the directory where the script is located\n", + "# move into the package directory and add the parent directory to the PYTHONPATH\n", + "package_dir = os.path.dirname(os.getcwd())\n", + "python_path_root = os.path.dirname(package_dir)\n", + "sys.path.insert(0, python_path_root)\n", + "\n", + "print(f\"🗂 package_dir = {package_dir}\")\n", + "print(f\"🔗 python_path_root = {python_path_root}\\n\")\n", + "\n", "\n", - "def _launch(mod, url):\n", - " port = int(url.split(\":\")[-1])\n", - " subprocess.Popen([sys.executable, \"-m\", mod, \"--port\", str(port)],\n", - " stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)\n", - " while socket.socket().connect_ex((\"127.0.0.1\", port)): time.sleep(.1)\n", - " return f\"✅ {mod} ready on port {port}\"\n", + "def _launch(url, agent_name):\n", + " port = int(url.rsplit(\":\", 1)[1])\n", "\n", + " # give the subprocess a PYTHONPATH so it sees your local package\n", + " env = os.environ.copy()\n", + " env[\"PYTHONPATH\"] = python_path_root + os.pathsep + env.get(\"PYTHONPATH\", \"\")\n", + "\n", + " subprocess.Popen(\n", + " [sys.executable, \"-m\", \"a2a_llama_stack\",\n", + " \"--agent-name\", agent_name, \"--port\", str(port)],\n", + " cwd=package_dir,\n", + " env=env,\n", + " stdout=subprocess.DEVNULL,\n", + " stderr=subprocess.DEVNULL,\n", + " )\n", + "\n", + " # wait until it's listening\n", + " while socket.socket().connect_ex((\"127.0.0.1\", port)) != 0:\n", + " time.sleep(0.1)\n", + "\n", + " return f\"✅ {agent_name} ready on port {port}\"\n", + "\n", + "# run the subprocesses in parallel\n", "with cf.ThreadPoolExecutor() as pool:\n", - " print(*pool.map(_launch, MODULES, URLS), sep=\"\\n\")" + " print(*pool.map(_launch, URLS, AGENT_NAMES), sep=\"\\n\")\n" ] }, { @@ -695,9 +740,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:02:19,356 INFO httpx: HTTP Request: GET http://localhost:10010/.well-known/agent.json \"HTTP/1.1 200 OK\"\n", - "2025-05-20 16:02:19,366 INFO httpx: HTTP Request: GET http://localhost:10011/.well-known/agent.json \"HTTP/1.1 200 OK\"\n", - "2025-05-20 16:02:19,376 INFO httpx: HTTP Request: GET http://localhost:10012/.well-known/agent.json \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:35:50,659 INFO httpx: HTTP Request: GET http://localhost:10010/.well-known/agent.json \"HTTP/1.1 200 OK\"\n", + "2025-05-27 11:35:50,668 INFO httpx: HTTP Request: GET http://localhost:10011/.well-known/agent.json \"HTTP/1.1 200 OK\"\n", + "2025-05-27 11:35:50,676 INFO httpx: HTTP Request: GET http://localhost:10012/.well-known/agent.json \"HTTP/1.1 200 OK\"\n" ] }, { @@ -755,7 +800,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:02:31,947 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:00,100 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -781,14 +826,18 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:02:37,550 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:07,624 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": {}}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-05-20\"The date today is 2023-05-20.\n", + " ✅ → {\n", + " \"type\": \"function\",\n", + " \"name\": \"date_tool\",\n", + " \"parameters\": {}\n", + "}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-05-27\"{\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": {}}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-05-27\"The output of the function call is today's date.\n", "➡️ Step 2: random_number_tool({})\n" ] }, @@ -796,14 +845,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:02:41,982 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:12,236 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:1The random number generated is 1.\n", + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:79The output of the function call is a random integer between 1 and 100.\n", "➡️ Step 3: random_number_tool({})\n" ] }, @@ -811,14 +860,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:02:46,690 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:16,885 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:86The random number generated is 86.\n", + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:63The output of the function call is a random integer between 1 and 100.\n", "➡️ Step 4: random_number_tool({})\n" ] }, @@ -826,14 +875,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:02:51,154 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:21,530 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:20The random number generated is 20.\n", + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:52The output of the function call is a random integer between 1 and 100.\n", "➡️ Step 5: random_number_tool({})\n" ] }, @@ -841,14 +890,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:02:55,637 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:26,209 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:5The random number generated is 5.\n", + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:93The output of the function call is a random integer between 1 and 100.\n", "➡️ Step 6: random_number_tool({})\n" ] }, @@ -856,14 +905,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:03:00,145 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:30,882 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:44The random number generated is 44.\n", + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:87The output of the function call is a random integer between 1 and 100.\n", "\n", "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n" ] @@ -872,7 +921,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:03:05,302 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:34,768 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -881,9 +930,11 @@ "text": [ "\n", "\u001b[1;36m🎉 FINAL ANSWER\u001b[0m\n", - "Here's your response:\n", + "Here's your date and five random numbers for today:\n", + "\n", + "Today is May 27th, 2025.\n", "\n", - "\"Today's date is May 20th, 2023. Here are five random numbers: 1, 86, 20, 5, and 44.\"\n", + "And here are five random numbers: 79, 63, 52, 93, and 87.\n", "\u001b[1;36m====================================\u001b[0m\n", "\n", "\u001b[1;33m=========== 🧠 Planning Phase ===========\u001b[0m\n" @@ -893,7 +944,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:03:11,257 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:39,162 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -914,14 +965,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:03:28,192 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:45,051 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": {}}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-05-20\"The current date is May 20, 2025.\n", + " ✅ → {\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": {}}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-05-27\"The output of the function call is today's date.\n", "\n", "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n" ] @@ -930,7 +981,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:03:30,939 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:46,240 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -939,7 +990,7 @@ "text": [ "\n", "\u001b[1;36m🎉 FINAL ANSWER\u001b[0m\n", - "\"Today's date is May 20, 2025.\"\n", + "Today's date is May 27th, 2025!\n", "\u001b[1;36m====================================\u001b[0m\n", "\n", "\u001b[1;33m=========== 🧠 Planning Phase ===========\u001b[0m\n" @@ -949,7 +1000,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:03:37,648 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:51,726 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -970,14 +1021,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:03:54,412 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:58,564 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:21The random number generated is 21.\n", + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:1The output of the function call is a random integer between 1 and 100.\n", "\n", "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n" ] @@ -986,7 +1037,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-20 16:03:57,080 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 11:36:59,600 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -995,7 +1046,7 @@ "text": [ "\n", "\u001b[1;36m🎉 FINAL ANSWER\u001b[0m\n", - "\"Here's a random number: 21.\"\n", + "Here's your random number: 1!\n", "\u001b[1;36m====================================\u001b[0m\n" ] } @@ -1025,7 +1076,7 @@ ], "metadata": { "kernelspec": { - "display_name": "venv8", + "display_name": "venv", "language": "python", "name": "python3" }, From 4488de03aa0ff639efb61961be619c0075942090 Mon Sep 17 00:00:00 2001 From: Kevin Cogan Date: Tue, 27 May 2025 13:14:47 +0100 Subject: [PATCH 06/13] docs: updated document to set up multi-agent code. --- demos/a2a_llama_stack/README.md | 261 ++++++++++++++++++++++++-------- 1 file changed, 200 insertions(+), 61 deletions(-) diff --git a/demos/a2a_llama_stack/README.md b/demos/a2a_llama_stack/README.md index e38079a..c78b5b2 100644 --- a/demos/a2a_llama_stack/README.md +++ b/demos/a2a_llama_stack/README.md @@ -1,127 +1,266 @@ -# Integrating a Custom Agent with Google A2A on **Llama Stack** +# Running Your Llama Stack Agent with Google A2A -This guide walks you through running a custom agent on **Llama Stack** using Google’s **Agent‑to‑Agent (A2A)** protocol. +Welcome! This guide provides comprehensive instructions for setting up and running a custom agent on **Llama Stack**, leveraging Google’s **Agent‑to‑Agent (A2A)** communication protocol. Follow these steps to make your agent operational. --- -## 🛠 Prerequisites +## Overview -- **Python 3.8 or newer** -- **`pip`** (Python package manager) -- A **Llama Stack** inference server running and reachable +By completing this guide, you will accomplish the following: +1. Setting up your development environment. +2. Download the requisite code repositories. +3. Install all necessary dependencies. +4. Configure connection details for your Llama Stack inference server. +5. Launch the A2A agent server(s). +6. Execute a client application to dispatch tasks to your agent(s). --- -## 1 — Download the Code +## Prerequisites + +Before commencing, please ensure the following components are installed and accessible: +* **Python 3.8 or newer** +* **`pip`** (Python package manager) +* A **Llama Stack** inference server, running and reachable from your machine. + +--- + +## Setup Instructions + +Follow these steps to prepare your environment and the application code. + +### 1. Download the Required Code + +Begin by cloning two Git repositories: the Llama Stack demos and the Google A2A examples. ```bash -# Clone the Llama Stack demos +# Clone the Llama Stack demos repository git clone https://github.com/opendatahub-io/llama-stack-demos.git -# Clone the Google A2A examples + +# Clone the Google A2A examples repository git clone https://github.com/google/A2A.git ``` +*These commands will create two new directories, `llama-stack-demos` and `A2A`, in your current working folder.* ---- +### 2. Create and Activate a Python Virtual Environment -## 2 — Create & Activate a Virtual Environment +Employing a virtual environment is strongly recommended to manage project-specific dependencies effectively and prevent conflicts with your global Python installation or other projects. ```bash +# Navigate to your main project directory (e.g. where you cloned the repositories). +# Create a virtual environment named 'venv' python3 -m venv venv -# macOS / Linux -source venv/bin/activate -# Windows -venv\Scripts\activate ``` ---- +Next, activate the virtual environment. The activation command varies by operating system: + +* **macOS / Linux:** + ```bash + source venv/bin/activate + ``` +* **Windows (Command Prompt or PowerShell):** + ```bash + venv\Scripts\activate + ``` +*Once activated, your terminal prompt should typically be prefixed with `(venv)`, indicating the virtual environment is active.* -## 3 — Prepare the Agent Package +### 3. Prepare the Custom Agent Package + +You will now copy the Llama Stack agent code from the `llama-stack-demos` repository into the appropriate directory within the `A2A` examples structure. ```bash -# Move the Llama Stack agent into the A2A samples +# Navigate to the target directory within the A2A examples. cd A2A/samples/python/agents + +# Copy the Llama Stack agent directory. cp -r ../../../../llama-stack-demos/demos/a2a_llama_stack . ``` -Verify that **`a2a_llama_stack/`** now contains: - -- `__init__.py` -- `__main__.py` -- `task_manager.py` -- `agent.py` +After the copy operation, verify that the `A2A/samples/python/agents/a2a_llama_stack/` directory has been created and contains the following files and folders: +* `__init__.py` +* `__main__.py` +* `task_manager.py` +* `A2AFleet.py` +* `A2ATool.py` +* `requirements.txt` +* `agents/` +* `cli/` +* `notebooks/` ---- +### 4. Install Python Dependencies -## 4 — Install Python Dependencies +Navigate into the `a2a_llama_stack` directory (which you just populated) and install its Python package dependencies. Ensure your virtual environment remains active. ```bash +# Navigate into the Llama Stack agent directory cd a2a_llama_stack + +# It is good practice to upgrade pip within the virtual environment python -m pip install --upgrade pip + +# Install the required packages specified in requirements.txt pip install -r requirements.txt ``` +*You should now be located in the `A2A/samples/python/agents/a2a_llama_stack` directory.* --- -## 5 — Set Environment Variables +## Configuration: Environment Variables -| Variable | Description | Default | -|-------------------|-------------------------------------|---------------------------| -| `LLAMA_STACK_URL` | Llama Stack server address | `http://localhost:8321` | -| `MODEL_ID` | Model identifier on Llama Stack | `llama3.2:3b-instruct-fp16` | +Your agent requires the network address of your Llama Stack server and the identifier of the AI model to be used. These are configured via environment variables. -**macOS / Linux** +| Variable | Description | Default Value | Example Custom Value | +|-------------------|-------------------------------------------------|-----------------------------|-----------------------------| +| `LLAMA_STACK_URL` | Address of your Llama Stack inference server. | `http://localhost:8321` | `http://your-llama-server` | +| `MODEL_ID` | Model identifier available on your Llama Stack. | `llama3.2:3b-instruct-fp16` | `your-custom-model-id` | -```bash -export LLAMA_STACK_URL=http://localhost:8321 -export MODEL_ID=llama3.2:3b-instruct-fp16 -``` +Set these variables in the terminal session where you plan to launch the agent server (detailed in the subsequent section). -**Windows (PowerShell)** +* **macOS / Linux:** + ```bash + export LLAMA_STACK_URL="http://localhost:8321" + export MODEL_ID="llama3.2:3b-instruct-fp16" + ``` + *(Adjust these values if your Llama Stack server URL or model ID differs from the defaults.)* -```powershell -setx LLAMA_STACK_URL "http://localhost:8321" -setx MODEL_ID "llama3.2:3b-instruct-fp16" -``` +* **Windows (PowerShell):** + ```powershell + setx LLAMA_STACK_URL "http://localhost:8321" + setx MODEL_ID "llama3.2:3b-instruct-fp16" + ``` + *(Modify the values as necessary. Note: After using `setx`, these variables are persistently set for the current user. However, you must open a **new** PowerShell window or restart your current one for these changes to become effective in that session.)* --- -## 6 — Launch the Agent +## Running the Application + +You are now prepared to launch your agent. This process involves two primary stages: +1. Launching the agent server(s). +2. Executing a client application to send tasks to these server(s). + +You can opt for a basic single-agent configuration or a more intricate multi-agent setup. + +### Part 1: Launch the Agent Server(s) + +The agent server is the core component that listens for and processes incoming A2A tasks. + +**Important Considerations:** +* Ensure your Python virtual environment (`venv`) is **active** in the terminal session used for this step. +* Confirm that the `LLAMA_STACK_URL` and `MODEL_ID` environment variables are **set** within this same terminal session. + +You should currently be in the `A2A/samples/python/agents/a2a_llama_stack` directory (upon completing Step 4 of the Setup Instructions). To launch the agent server module correctly, first navigate to the `A2A/samples/python/` directory: ```bash -# Navigate to A2A/samples/python -cd ../.. -# Run the following python command from A2A/samples/python -python -m agents.a2a_llama_stack --port 10010 +# If you are currently in A2A/samples/python/agents/a2a_llama_stack: +cd ../../ +# You should now be in the A2A/samples/python/ directory. ``` -A successful start prints something like: +Now, select **one** of the following server configurations: -``` -INFO | Agent listening on 0.0.0.0:10010 -``` +#### Option A: Basic Setup (Single Agent Server) ---- +This configuration runs a single agent, named `a2a_custom_tools`, which listens on port `10011`. This agent will interface with the Llama Stack for its operational tasks. -## 7 — Send Tasks from the CLI Host +```bash +# Ensure you are in the A2A/samples/python/ directory +python -m agents.a2a_llama_stack --agent-name a2a_custom_tools --port 10011 +``` -Open a **new terminal** and run: +#### Option B: Multi-Agent Setup (Multiple Agent Servers) + +This setup illustrates a more complex scenario involving three distinct agents: a planner, a tools agent, and a composer, each operating on a separate port. ```bash -source venv/bin/activate -cd A2A/samples/python/hosts/cli -uv run . --agent http://localhost:10010 +# Ensure you are in the A2A/samples/python/ directory + +# Terminal 1: Launch the planner agent +python -m agents.a2a_llama_stack --agent-name a2a_planner --port 10010 + +# Terminal 2: Launch the custom tools agent +# (Open a new terminal window/tab, activate venv, and set environment variables before running) +python -m agents.a2a_llama_stack --agent-name a2a_custom_tools --port 10011 + +# Terminal 3: Launch the composer agent +# (Open another new terminal window/tab, activate venv, and set environment variables before running) +python -m agents.a2a_llama_stack --agent-name a2a_composer --port 10012 +``` +*For the multi-agent setup (Option B), each `python -m ...` command initiates a server that will occupy its terminal. You will need to open multiple terminal windows/tabs or manage these processes in the background.* + +A successful agent server launch will display a confirmation message in the console, similar to: +``` +INFO | Agent listening on 0.0.0.0:XXXX ``` +(Where `XXXX` corresponds to the agent's port number, e.g., `10010`, `10011`, or `10012`). + +*Keep these agent server terminal(s) running while you proceed to the client setup.* + +### Part 2: Send Tasks from the Client + +With the agent server(s) operational, you can now use a client application to dispatch tasks. This requires opening a **new terminal window or tab**. + +1. **Activate the virtual environment** in this new terminal: + * **macOS / Linux:** + ```bash + # Navigate to your project root directory where 'venv' is located + source venv/bin/activate + ``` + * **Windows (Command Prompt or PowerShell):** + ```bash + # Navigate to your project root directory where 'venv' is located + venv\Scripts\activate + ``` + *(Recall that the `venv` directory was created in your main project folder, which houses the `A2A` and `llama-stack-demos` subdirectories.)* + +2. **Navigate to the client script directory:** + The client application is typically executed from the `cli` directory, located within the `a2a_llama_stack` agent's sample code. + ```bash + # Adjust this path according to your root project directory structure. + # Assuming your current directory is the project root (which contains the 'A2A' folder): + cd A2A/samples/python/agents/a2a_llama_stack/cli + ``` + +3. **Run the client application:** + The `uv run` command, followed by the specific client script (`basic_client.py` or `multi_agent_client.py`), executes the client. The arguments passed depend on your chosen server setup (Option A or B). + + #### If you used "Option A: Basic Setup" for the agent server: + Run the `basic_client.py` script, directing it to the `a2a_custom_tools` agent: + ```bash + uv run basic_client.py --agent http://localhost:10011 + ``` + + #### If you used "Option B: Multi-Agent Setup" for the agent servers: + Run the `multi_agent_client.py` script, providing the network addresses for all three agents. It is crucial that the `a2a_planner` agent (`http://localhost:10010`) is specified first. + ```bash + uv run multi_agent_client.py --agent http://localhost:10010 --agent http://localhost:10011 --agent http://localhost:10012 + ``` + +Upon executing the appropriate `uv run` command, the client will attempt to establish a connection with the agent server(s) and enable task interaction. -### Built‑in sample tools +--- + +### Built-in Sample Tools + +The custom Llama Stack agent you have deployed includes several sample tools for demonstration: | Tool | Description | |-----------------|--------------------------------| -| `random_number` | Returns a random integer | -| `get_date` | Returns today’s date | +| `random_number` | Returns a random integer. | +| `get_date` | Returns today’s date. | + +You can experiment with invoking these tools via the client interface once it is connected. --- -### 🎉 Done! +## 🎉 Congratulations! -Your custom agent is now running and ready to accept A2A tasks. +Your custom Llama Stack agent should now be running successfully using the Google A2A protocol and be prepared to accept tasks from the client. + +Should you encounter any difficulties, please review each step, paying particular attention to: +* Verification of prerequisites and their versions. +* Correct activation of the Python virtual environment. +* Accuracy of directory paths used in `cd`, `cp`, and script execution commands. +* Proper configuration of environment variables (`LLAMA_STACK_URL`, `MODEL_ID`). +* Alignment of client commands with the chosen server setup (Basic or Multi-Agent). +``` From d5eecdb80a6696bae9dad214673cbd9315325189 Mon Sep 17 00:00:00 2001 From: Kevin Cogan Date: Tue, 27 May 2025 13:16:31 +0100 Subject: [PATCH 07/13] feat: Added client code for basic and multi-agent set up. --- demos/a2a_llama_stack/__main__.py | 9 +- .../agents/a2a_composer/config.py | 8 +- .../agents/a2a_custom_tools/config.py | 6 +- .../agents/a2a_custom_tools/tools.py | 2 +- .../agents/a2a_planner/config.py | 6 +- demos/a2a_llama_stack/cli/__init__.py | 0 demos/a2a_llama_stack/cli/basic_client.py | 141 +++++++++++ .../a2a_llama_stack/cli/multi_agent_client.py | 237 ++++++++++++++++++ .../notebooks/A2A_Multi_Agent.ipynb | 82 +++--- 9 files changed, 431 insertions(+), 60 deletions(-) create mode 100644 demos/a2a_llama_stack/cli/__init__.py create mode 100644 demos/a2a_llama_stack/cli/basic_client.py create mode 100644 demos/a2a_llama_stack/cli/multi_agent_client.py diff --git a/demos/a2a_llama_stack/__main__.py b/demos/a2a_llama_stack/__main__.py index 0ddb39b..c131e2e 100644 --- a/demos/a2a_llama_stack/__main__.py +++ b/demos/a2a_llama_stack/__main__.py @@ -4,7 +4,6 @@ import click from llama_stack_client import LlamaStackClient, Agent -# Assuming these are accessible from your project structure from common.server import A2AServer from common.types import AgentCard, AgentCapabilities, AgentSkill @@ -22,7 +21,7 @@ def build_server(agent_name: str, host: str, port: int = None): logging.error(f"AGENT_CONFIG not found in {config_module_path}") raise - if port is None: # Use default from config if not overridden by CLI + if port is None: port = agent_config_data.get("default_port", 8000) agent_params_config = agent_config_data["agent_params"] @@ -67,8 +66,8 @@ def build_server(agent_name: str, host: str, port: int = None): @click.option("--host", default="0.0.0.0", help="Host to bind the server to.") @click.option("--port", type=int, default=None, help="Port to bind the server to (overrides agent's default).") def main(agent_name, host, port): - effective_port = port # Will be replaced by default from config if None in build_server - if port is None: # For logging purposes, find the default port if not specified + effective_port = port + if port is None: try: config_module_path = f"agents.a2a_llama_stack.agents.{agent_name.replace('-', '_')}.config" config_module = importlib.import_module(config_module_path) @@ -84,4 +83,4 @@ def main(agent_name, host, port): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/demos/a2a_llama_stack/agents/a2a_composer/config.py b/demos/a2a_llama_stack/agents/a2a_composer/config.py index f82058a..8d94146 100644 --- a/demos/a2a_llama_stack/agents/a2a_composer/config.py +++ b/demos/a2a_llama_stack/agents/a2a_composer/config.py @@ -1,6 +1,4 @@ -# Updated import to use AgentTaskManager from the root a2a_llama_stack directory from ...task_manager import AgentTaskManager, SUPPORTED_CONTENT_TYPES -# common.types.AgentSkill, AgentCapabilities are imported in generic_main AGENT_CONFIG = { "agent_params": { @@ -14,7 +12,7 @@ }, "tools": [] }, - # Updated to use AgentTaskManager + "task_manager_class": AgentTaskManager, "agent_card_params": { "name": "Writing Agent", @@ -35,9 +33,9 @@ "tags": ["writing"], "examples": ["Write human-friendly text based on the query and associated skills"], "inputModes": ["text/plain"], - "outputModes": ["application/json"], # As per original file + "outputModes": ["application/json"], } ] }, "default_port": 10012, -} \ No newline at end of file +} diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py index 31ab54b..7515f70 100644 --- a/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py +++ b/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py @@ -1,7 +1,5 @@ -# Updated import to use AgentTaskManager from the root a2a_llama_stack directory from ...task_manager import AgentTaskManager, SUPPORTED_CONTENT_TYPES from .tools import random_number_tool, date_tool # Import tools -# common.types.AgentSkill, AgentCapabilities are imported in generic_main AGENT_CONFIG = { "agent_params": { @@ -17,7 +15,7 @@ "max_infer_iters": 3, "sampling_params": None }, - # Updated to use AgentTaskManager + "task_manager_class": AgentTaskManager, "agent_card_params": { "name": "Custom Agent", @@ -52,4 +50,4 @@ ] }, "default_port": 10011, -} \ No newline at end of file +} diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/tools.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/tools.py index dcfda1e..2174ec4 100644 --- a/demos/a2a_llama_stack/agents/a2a_custom_tools/tools.py +++ b/demos/a2a_llama_stack/agents/a2a_custom_tools/tools.py @@ -13,4 +13,4 @@ def date_tool() -> str: """ Return today's date in YYYY-MM-DD format. """ - return datetime.utcnow().date().isoformat() \ No newline at end of file + return datetime.utcnow().date().isoformat() diff --git a/demos/a2a_llama_stack/agents/a2a_planner/config.py b/demos/a2a_llama_stack/agents/a2a_planner/config.py index a6a98e2..e35e3be 100644 --- a/demos/a2a_llama_stack/agents/a2a_planner/config.py +++ b/demos/a2a_llama_stack/agents/a2a_planner/config.py @@ -1,6 +1,4 @@ -# Updated import to use AgentTaskManager from the root a2a_llama_stack directory from ...task_manager import AgentTaskManager, SUPPORTED_CONTENT_TYPES -# common.types.AgentSkill, AgentCapabilities are imported in generic_main AGENT_CONFIG = { "agent_params": { @@ -14,7 +12,7 @@ }, "tools": [] }, - # Updated to use AgentTaskManager + "task_manager_class": AgentTaskManager, "agent_card_params": { "name": "Orchestration Agent", @@ -40,4 +38,4 @@ ] }, "default_port": 10010, -} \ No newline at end of file +} diff --git a/demos/a2a_llama_stack/cli/__init__.py b/demos/a2a_llama_stack/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/demos/a2a_llama_stack/cli/basic_client.py b/demos/a2a_llama_stack/cli/basic_client.py new file mode 100644 index 0000000..e771bdf --- /dev/null +++ b/demos/a2a_llama_stack/cli/basic_client.py @@ -0,0 +1,141 @@ +import asyncclick as click +import asyncio +import base64 +import os +import urllib +from uuid import uuid4 + +from common.client import A2AClient, A2ACardResolver +from common.types import TaskState, Task, TextPart, FilePart, FileContent +from common.utils.push_notification_auth import PushNotificationReceiverAuth + + +@click.command() +@click.option("--agent", default="http://localhost:10000") +@click.option("--session", default=0) +@click.option("--history", default=False) +@click.option("--use_push_notifications", default=False) +@click.option("--push_notification_receiver", default="http://localhost:5000") +async def cli(agent, session, history, use_push_notifications: bool, push_notification_receiver: str): + card_resolver = A2ACardResolver(agent) + card = card_resolver.get_agent_card() + + print("======= Agent Card ========") + print(card.model_dump_json(exclude_none=True)) + + notif_receiver_parsed = urllib.parse.urlparse(push_notification_receiver) + notification_receiver_host = notif_receiver_parsed.hostname + notification_receiver_port = notif_receiver_parsed.port + + if use_push_notifications: + from hosts.cli.push_notification_listener import PushNotificationListener + notification_receiver_auth = PushNotificationReceiverAuth() + await notification_receiver_auth.load_jwks(f"{agent}/.well-known/jwks.json") + + push_notification_listener = PushNotificationListener( + host = notification_receiver_host, + port = notification_receiver_port, + notification_receiver_auth=notification_receiver_auth, + ) + push_notification_listener.start() + + client = A2AClient(agent_card=card) + if session == 0: + sessionId = uuid4().hex + else: + sessionId = session + + continue_loop = True + streaming = card.capabilities.streaming + + while continue_loop: + taskId = uuid4().hex + print("========= starting a new task ======== ") + continue_loop = await completeTask(client, streaming, use_push_notifications, notification_receiver_host, notification_receiver_port, taskId, sessionId) + + if history and continue_loop: + print("========= history ======== ") + task_response = await client.get_task({"id": taskId, "historyLength": 10}) + print(task_response.model_dump_json(include={"result": {"history": True}})) + +async def completeTask(client: A2AClient, streaming, use_push_notifications: bool, notification_receiver_host: str, notification_receiver_port: int, taskId, sessionId): + prompt = click.prompt( + "\nWhat do you want to send to the agent? (:q or quit to exit)" + ) + if prompt == ":q" or prompt == "quit": + return False + + message = { + "role": "user", + "parts": [ + { + "type": "text", + "text": prompt, + } + ] + } + + file_path = click.prompt( + "Select a file path to attach? (press enter to skip)", + default="", + show_default=False, + ) + if file_path and file_path.strip() != "": + with open(file_path, "rb") as f: + file_content = base64.b64encode(f.read()).decode('utf-8') + file_name = os.path.basename(file_path) + + message["parts"].append( + { + "type": "file", + "file": { + "name": file_name, + "bytes": file_content, + } + } + ) + + payload = { + "id": taskId, + "sessionId": sessionId, + "acceptedOutputModes": ["text"], + "message": message, + } + + if use_push_notifications: + payload["pushNotification"] = { + "url": f"http://{notification_receiver_host}:{notification_receiver_port}/notify", + "authentication": { + "schemes": ["bearer"], + }, + } + + taskResult = None + if streaming: + response_stream = client.send_task_streaming(payload) + async for result in response_stream: + print(f"stream event => {result.model_dump_json(exclude_none=True)}") + taskResult = await client.get_task({"id": taskId}) + else: + taskResult = await client.send_task(payload) + print(f"\n{taskResult.model_dump_json(exclude_none=True)}") + + ## if the result is that more input is required, loop again. + state = TaskState(taskResult.result.status.state) + if state.name == TaskState.INPUT_REQUIRED.name: + return await completeTask( + client, + streaming, + use_push_notifications, + notification_receiver_host, + notification_receiver_port, + taskId, + sessionId + ) + else: + ## task is complete + return True + + +if __name__ == "__main__": + asyncio.run(cli()) diff --git a/demos/a2a_llama_stack/cli/multi_agent_client.py b/demos/a2a_llama_stack/cli/multi_agent_client.py new file mode 100644 index 0000000..eb382ba --- /dev/null +++ b/demos/a2a_llama_stack/cli/multi_agent_client.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 +import asyncio +import json +import logging +import urllib.parse +from uuid import uuid4 +from typing import Tuple, Dict, Any, Optional, List + +import asyncclick as click + +from common.client import A2AClient, A2ACardResolver +from hosts.cli.push_notification_listener import PushNotificationListener +from common.utils.push_notification_auth import PushNotificationReceiverAuth + +logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s") +logger = logging.getLogger(__name__) + +AgentInfo = Tuple[str, Any, A2AClient, str] + +def _build_skill_meta(agent_manager: 'AgentManager') -> List[Dict[str, Any]]: + unique_skills: Dict[str, Dict[str, Any]] = {} + if agent_manager.skills: + for _, card_obj, _, _ in agent_manager.skills.values(): + if hasattr(card_obj, 'skills') and isinstance(card_obj.skills, list): + for s in card_obj.skills: + if s.id not in unique_skills: + unique_skills[s.id] = { + "skill_id": s.id, + "name": s.name, + "description": getattr(s, "description", None), + "tags": getattr(s, "tags", []), + "examples": getattr(s, "examples", None), + } + return list(unique_skills.values()) + +class AgentManager: + def __init__(self, urls: List[str]): + if not urls: + raise ValueError("URLs list cannot be empty for AgentManager") + + self.orchestrator: AgentInfo = self._make_agent_info(urls[0]) + + self.skills: Dict[str, AgentInfo] = {} + if len(urls) > 1: + for skill_agent_url in urls[1:]: + agent_info_tuple = self._make_agent_info(skill_agent_url) + agent_card = agent_info_tuple[1] + if hasattr(agent_card, 'skills') and isinstance(agent_card.skills, list): + for skill_item in agent_card.skills: + self.skills[skill_item.id] = agent_info_tuple + + @staticmethod + def _make_agent_info(url: str) -> AgentInfo: + card: Any = A2ACardResolver(url).get_agent_card() + client = A2AClient(agent_card=card) + session_id = uuid4().hex + return url, card, client, session_id + +async def _send_payload(client: A2AClient, card: Any, session_id: str, payload: Dict[str, Any], streaming: bool) -> str: + response_text = "" + if streaming: + async for ev in client.send_task_streaming(payload): + part = ev.result.status.message.parts[0].text or "" + print(part, end="", flush=True) + response_text = part + print() + else: + res = await client.send_task(payload) + response_text = res.result.status.message.parts[0].text.strip() + return response_text + +async def _send_task_to_agent( + client: A2AClient, + card: Any, + session_id: str, + input_text: str, + push_enabled: bool, + push_host: Optional[str], + push_port: Optional[int] +) -> str: + payload: Dict[str, Any] = { + "id": uuid4().hex, + "sessionId": session_id, + "acceptedOutputModes": card.defaultOutputModes, + "message": {"role": "user", "parts": [{"type": "text", "text": input_text}]}, + } + + if push_enabled and push_host and push_port: + push_url = urllib.parse.urljoin(f"http://{push_host}:{push_port}", "/notify") + schemes = getattr(card.authentication, "supportedSchemes", ["bearer"]) + payload["pushNotification"] = { + "url": push_url, + "authentication": {"schemes": schemes}, + } + + streaming_capability = getattr(getattr(card, "capabilities", object()), "streaming", False) + return await _send_payload(client, card, session_id, payload, streaming_capability) + +@click.command(context_settings={"help_option_names": ["-h", "--help"]}) +@click.version_option(version="1.0.0") +@click.option("--agent", "cli_urls", multiple=True, required=True, help="Orchestrator + executor URLs.") +@click.option("--history/--no-history", "cli_history", default=False, help="Show history after each step.") +@click.option("--use-push-notifications/--no-push-notifications", "cli_use_push_notifications", default=False) +@click.option("--push-notification-receiver", "cli_push_notification_receiver", default="http://localhost:5000", show_default=True) +async def cli(cli_urls: List[str], cli_history: bool, cli_use_push_notifications: bool, cli_push_notification_receiver: str): + if len(cli_urls) < 2: + click.secho("Error: Provide at least orchestrator + executor URLs.", fg="red", err=True) + raise click.Abort() + + push_host_val: Optional[str] = None + push_port_val: Optional[int] = None + if cli_use_push_notifications: + parsed_url = urllib.parse.urlparse(cli_push_notification_receiver) + push_host_val, push_port_val = parsed_url.hostname, parsed_url.port + if not push_host_val or not push_port_val: + click.secho(f"Error: Invalid push notification receiver URL: {cli_push_notification_receiver}", fg="red", err=True) + raise click.Abort() + auth_handler = PushNotificationReceiverAuth() + orchestrator_jwks_url = f"{cli_urls[0]}/.well-known/jwks.json" + try: + await auth_handler.load_jwks(orchestrator_jwks_url) + except Exception as e: + click.secho(f"Error loading JWKS for push notifications from {orchestrator_jwks_url}: {e}", fg="red", err=True) + raise click.Abort() + + PushNotificationListener(host=push_host_val, port=push_port_val, notification_receiver_auth=auth_handler).start() + click.secho(f"Push notification listener started at http://{push_host_val}:{push_port_val}/notify", fg="blue") + + agent_manager = AgentManager(cli_urls) + + orch_url, orch_card, orch_client, orch_session_id = agent_manager.orchestrator + + click.secho("\n=========== 🛰️ Connected Agents ===========", fg="cyan") + click.echo(f"Orchestrator: {orch_url} ({orch_card.name})") + if agent_manager.skills: + click.echo("Executors:") + for skill_id, (skill_agent_url, skill_agent_card, _, _) in agent_manager.skills.items(): + click.echo(f" • {skill_id} -> {skill_agent_url} ({skill_agent_card.name})") + else: + click.echo("No skill executors configured.") + click.secho("========================================", fg="cyan") + + skills_meta = _build_skill_meta(agent_manager) + + while True: + try: + question = await asyncio.to_thread(click.prompt, "💬 Your question (:q to quit)", default="", type=str) + except RuntimeError: + question = click.prompt("💬 Your question (:q to quit)", default="", type=str) + + if question.strip().lower() in {":q", "quit"}: + click.secho("Goodbye! 👋", fg="green") + break + if not question.strip(): + continue + + _call_agent_lambda = lambda current_client, current_card, current_session_id, text_input: _send_task_to_agent( + current_client, current_card, current_session_id, text_input, cli_use_push_notifications, push_host_val, push_port_val + ) + + click.secho("\n=========== 🧠 Planning Phase ===========", fg="yellow") + + plan_instructions = ( + "You are an orchestration assistant.\n" + "Available skills (id & name & description & tags & examples):\n" + f"{json.dumps(skills_meta, indent=2)}\n\n" + "When given a user question, respond _only_ with a JSON array of objects, " + "each with key `skill_id`, without any surrounding object. You may be asked to write single or multiple skills.\n" + "For example for multiple tools:\n" + "[\n" + " {\"skill_id\": \"tool_1\"},\n" + " {\"skill_id\": \"tool_2\"}\n" + "]" + ) + combined_planner_input = plan_instructions + "\n\nUser question: " + question + + raw_plan = await _call_agent_lambda(orch_client, orch_card, orch_session_id, combined_planner_input) + click.echo(f"Raw plan ➡️ {raw_plan}") + + plan = [] + try: + plan = json.loads(raw_plan[: raw_plan.rfind("]") + 1]) + except Exception as e: + click.secho(f"Plan parse failed: {e}. Attempting to fix...", fg="red") + fix_json_input = "fix this json to be valid: " + raw_plan + fixed_plan_json = await _call_agent_lambda(orch_client, orch_card, orch_session_id, fix_json_input) + try: + plan = json.loads(fixed_plan_json) + click.secho(f"\nFixed Raw plan ➡️ {json.dumps(plan, indent=2)}\n", fg="green") + except Exception as fix_e: + click.secho(f"Failed to fix and parse plan: {fix_e}. Skipping execution.", fg="red", err=True) + continue + + if not isinstance(plan, list) or not all(isinstance(item, dict) and 'skill_id' in item for item in plan): + click.secho(f"Parsed plan is not a valid list of skill tasks: {plan}. Skipping execution.", fg="red", err=True) + continue + + click.secho(f"\nFinal plan ➡️ {json.dumps(plan, indent=2)}", fg="green") + + click.secho("\n=========== ⚡️ Execution Phase ===========", fg="yellow") + execution_results = [] + for i, step_details in enumerate(plan, 1): + skill_id_to_execute = step_details.get("skill_id") + skill_input_params_json = json.dumps(step_details.get("input", {})) + skill_invocation_text = f"{skill_id_to_execute}({skill_input_params_json})" + + click.echo(f"➡️ Step {i}: {skill_invocation_text}") + + skill_agent_info_tuple = agent_manager.skills.get(skill_id_to_execute) + + if not skill_agent_info_tuple: + click.secho(f"No executor for '{skill_id_to_execute}', skipping.", fg="red") + execution_results.append({"skill_id": skill_id_to_execute, "output": None, "error": "Skill agent not found"}) + continue + + _, skill_card, skill_client, skill_session_id = skill_agent_info_tuple + + skill_output = await _call_agent_lambda(skill_client, skill_card, skill_session_id, skill_invocation_text) + click.secho(f" ✅ → {skill_output}", fg="green") + execution_results.append({"skill_id": skill_id_to_execute, "output": skill_output}) + + click.secho("\n=========== 🛠️ Composing Answer ===========", fg="yellow") + + composition_prompt = ( + f"Using the following information: {json.dumps(execution_results)}, write a clear and human-friendly response to the question: '{question}'. " + "Keep it concise and easy to understand and respond like a human with character. Only use the information provided in the Response: \n" + "If you cannot answer the question, say 'I don't know'. \n" + "Never show any code or JSON, just the answer.\n\n" + ) + + final_answer = await _call_agent_lambda(orch_client, orch_card, orch_session_id, composition_prompt) + click.secho("\n🎉 FINAL ANSWER", fg="cyan") + click.echo(final_answer) + click.secho("====================================", fg="cyan") + +if __name__ == "__main__": + asyncio.run(cli()) diff --git a/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb b/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb index fc5c207..699d7b3 100644 --- a/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb +++ b/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb @@ -63,7 +63,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 15, "id": "eb5bce08", "metadata": {}, "outputs": [ @@ -72,8 +72,8 @@ "output_type": "stream", "text": [ "Collecting git+https://github.com/google/A2A.git#subdirectory=samples/python\n", - " Cloning https://github.com/google/A2A.git to /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-wbo7qrw9\n", - " Running command git clone --filter=blob:none --quiet https://github.com/google/A2A.git /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-wbo7qrw9\n", + " Cloning https://github.com/google/A2A.git to /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-td4brfvf\n", + " Running command git clone --filter=blob:none --quiet https://github.com/google/A2A.git /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-td4brfvf\n", " Resolved https://github.com/google/A2A.git to commit 09c36bd94f74ea1a1a4e0db5f65ba12b102171ba\n", " Installing build dependencies ... \u001b[?25ldone\n", "\u001b[?25h Getting requirements to build wheel ... \u001b[?25ldone\n", @@ -184,7 +184,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 16, "id": "c49b3fcf", "metadata": {}, "outputs": [], @@ -206,7 +206,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 17, "id": "95a70138", "metadata": {}, "outputs": [], @@ -250,7 +250,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 18, "id": "c5253d5c", "metadata": {}, "outputs": [ @@ -313,7 +313,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 19, "id": "86bde581", "metadata": {}, "outputs": [], @@ -334,7 +334,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 20, "id": "62b7e70c", "metadata": {}, "outputs": [], @@ -364,7 +364,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 21, "id": "e7cea371", "metadata": {}, "outputs": [], @@ -417,7 +417,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 22, "id": "a2f9f439", "metadata": {}, "outputs": [], @@ -487,7 +487,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 23, "id": "b86bd386", "metadata": {}, "outputs": [], @@ -566,7 +566,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 24, "id": "86d7612a", "metadata": {}, "outputs": [], @@ -643,7 +643,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 25, "id": "ceb43397", "metadata": {}, "outputs": [], @@ -668,7 +668,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 26, "id": "5c5713c9", "metadata": {}, "outputs": [ @@ -732,7 +732,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 27, "id": "0ea51ded", "metadata": {}, "outputs": [ @@ -740,9 +740,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:35:50,659 INFO httpx: HTTP Request: GET http://localhost:10010/.well-known/agent.json \"HTTP/1.1 200 OK\"\n", - "2025-05-27 11:35:50,668 INFO httpx: HTTP Request: GET http://localhost:10011/.well-known/agent.json \"HTTP/1.1 200 OK\"\n", - "2025-05-27 11:35:50,676 INFO httpx: HTTP Request: GET http://localhost:10012/.well-known/agent.json \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:35:51,276 INFO httpx: HTTP Request: GET http://localhost:10010/.well-known/agent.json \"HTTP/1.1 200 OK\"\n", + "2025-05-27 13:35:51,284 INFO httpx: HTTP Request: GET http://localhost:10011/.well-known/agent.json \"HTTP/1.1 200 OK\"\n", + "2025-05-27 13:35:51,290 INFO httpx: HTTP Request: GET http://localhost:10012/.well-known/agent.json \"HTTP/1.1 200 OK\"\n" ] }, { @@ -784,7 +784,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 28, "id": "6476a816", "metadata": {}, "outputs": [ @@ -800,7 +800,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:00,100 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:36:11,043 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -826,7 +826,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:07,624 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:36:18,598 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -845,14 +845,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:12,236 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:36:23,209 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:79The output of the function call is a random integer between 1 and 100.\n", + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:34The output of the function call is a random integer between 1 and 100.\n", "➡️ Step 3: random_number_tool({})\n" ] }, @@ -860,14 +860,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:16,885 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:36:27,805 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:63The output of the function call is a random integer between 1 and 100.\n", + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:26The output of the function call is a random integer between 1 and 100.\n", "➡️ Step 4: random_number_tool({})\n" ] }, @@ -875,14 +875,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:21,530 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:36:32,435 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:52The output of the function call is a random integer between 1 and 100.\n", + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:91The output of the function call is a random integer between 1 and 100.\n", "➡️ Step 5: random_number_tool({})\n" ] }, @@ -890,14 +890,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:26,209 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:36:37,079 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:93The output of the function call is a random integer between 1 and 100.\n", + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:57The output of the function call is a random integer between 1 and 100.\n", "➡️ Step 6: random_number_tool({})\n" ] }, @@ -905,14 +905,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:30,882 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:36:41,721 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:87The output of the function call is a random integer between 1 and 100.\n", + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:44The output of the function call is a random integer between 1 and 100.\n", "\n", "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n" ] @@ -921,7 +921,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:34,768 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:36:46,891 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -934,7 +934,7 @@ "\n", "Today is May 27th, 2025.\n", "\n", - "And here are five random numbers: 79, 63, 52, 93, and 87.\n", + "And here are five random numbers: 34, 26, 91, 57, and 44.\n", "\u001b[1;36m====================================\u001b[0m\n", "\n", "\u001b[1;33m=========== 🧠 Planning Phase ===========\u001b[0m\n" @@ -944,7 +944,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:39,162 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:36:52,971 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -965,7 +965,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:45,051 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:36:58,806 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -981,7 +981,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:46,240 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:37:01,191 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -1000,7 +1000,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:51,726 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:37:06,871 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -1021,14 +1021,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:58,564 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:37:13,798 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:1The output of the function call is a random integer between 1 and 100.\n", + " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:48The output of the function call is a random integer between 1 and 100.\n", "\n", "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n" ] @@ -1037,7 +1037,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2025-05-27 11:36:59,600 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" + "2025-05-27 13:37:14,941 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" ] }, { @@ -1046,7 +1046,7 @@ "text": [ "\n", "\u001b[1;36m🎉 FINAL ANSWER\u001b[0m\n", - "Here's your random number: 1!\n", + "Here's your random number: 48!\n", "\u001b[1;36m====================================\u001b[0m\n" ] } From c362530c35cfe64ca9c63826eaf1f8663f29d12f Mon Sep 17 00:00:00 2001 From: Kevin Cogan <44865890+kevincogan@users.noreply.github.com> Date: Tue, 3 Jun 2025 10:22:32 +0100 Subject: [PATCH 08/13] Update demos/a2a_llama_stack/cli/basic_client.py Co-authored-by: Shrey --- demos/a2a_llama_stack/cli/basic_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/a2a_llama_stack/cli/basic_client.py b/demos/a2a_llama_stack/cli/basic_client.py index e771bdf..de22f82 100644 --- a/demos/a2a_llama_stack/cli/basic_client.py +++ b/demos/a2a_llama_stack/cli/basic_client.py @@ -11,7 +11,7 @@ @click.command() -@click.option("--agent", default="http://localhost:10000") +@click.option("--agent", default="http://localhost:10011") @click.option("--session", default=0) @click.option("--history", default=False) @click.option("--use_push_notifications", default=False) From 2ac3f4f19a3051694f59863cc5e6d34ffc4bc23b Mon Sep 17 00:00:00 2001 From: Kevin Cogan <44865890+kevincogan@users.noreply.github.com> Date: Tue, 3 Jun 2025 10:23:31 +0100 Subject: [PATCH 09/13] Update demos/a2a_llama_stack/agents/a2a_custom_tools/config.py Co-authored-by: Shrey --- demos/a2a_llama_stack/agents/a2a_custom_tools/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py index 7515f70..b7efb2c 100644 --- a/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py +++ b/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py @@ -13,7 +13,7 @@ ), "tools": [random_number_tool, date_tool], "max_infer_iters": 3, - "sampling_params": None + "sampling_params": {"max_tokens": 4096} }, "task_manager_class": AgentTaskManager, From dc5384d714dd34e1021ba42519e4908647420585 Mon Sep 17 00:00:00 2001 From: Kevin Cogan Date: Tue, 3 Jun 2025 12:12:00 +0100 Subject: [PATCH 10/13] feat: Created simple and advanced A2A notebooks and deleted older multi-agent notebook. --- .../notebooks/A2A_Advanced_Multi_Agent.ipynb | 1187 +++++++++++++++++ .../notebooks/A2A_Multi_Agent.ipynb | 1098 --------------- .../notebooks/A2A_Quickstart_Guide.ipynb | 603 +++++++++ 3 files changed, 1790 insertions(+), 1098 deletions(-) create mode 100644 demos/a2a_llama_stack/notebooks/A2A_Advanced_Multi_Agent.ipynb delete mode 100644 demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb create mode 100644 demos/a2a_llama_stack/notebooks/A2A_Quickstart_Guide.ipynb diff --git a/demos/a2a_llama_stack/notebooks/A2A_Advanced_Multi_Agent.ipynb b/demos/a2a_llama_stack/notebooks/A2A_Advanced_Multi_Agent.ipynb new file mode 100644 index 0000000..a651bf3 --- /dev/null +++ b/demos/a2a_llama_stack/notebooks/A2A_Advanced_Multi_Agent.ipynb @@ -0,0 +1,1187 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "fef1ba28", + "metadata": {}, + "source": [ + "# Advanced Multi-Agent Orchestration with A2A and Llama Stack\n", + "\n", + "This notebook demonstrates how to construct and orchestrate a multi-agent system using the Agent-to-Agent (A2A) communication protocol. We will build individual agents using Llama Stack and then enable them to collaborate on complex tasks by exposing their functionalities via A2A servers. \n", + "\n", + "This demo focuses on an orchestration pattern where a planner agent determines which specialized agent (skill) to call, and a composer agent formats the final response.\n", + "\n", + "## Overview\n", + "\n", + "This notebook covers the following steps:\n", + "\n", + "1. **Setting up the Llama Stack Environment**: Initializing the Llama Stack client and configuring model parameters.\n", + "\n", + "2. **Defining Llama Stack Agents**: Creating three distinct Llama Stack agents:\n", + "\n", + " * `Planner Agent`: Responsible for interpreting user queries and creating a plan to use other agents' skills.\n", + "\n", + " * `Custom Tool Agent`: Equipped with tools for random number generation and date retrieval.\n", + "\n", + " * `Composer Agent`: Skilled at generating human-friendly text from structured data.\n", + "\n", + "3. **Serving Llama Stack Agents via A2A**: Exposing each Llama Stack agent over an individual A2A server, making their `AgentCard` skills accessible via the A2A protocol.\n", + "\n", + "4. **Orchestrating the A2A Agents**: Setting up an `AgentManager` to manage communication with the A2A-enabled agents and implementing an `orchestrate` function to coordinate them.\n", + "\n", + "5. **Running the Orchestration**: Launching the multi-agent system to answer user queries, leveraging the planner to select tools/skills and the composer to generate a final, human-readable response.\n", + "\n", + "\n", + "## Prerequisites\n", + "\n", + "Before starting, ensure you have the following:\n", + "- `python_requires >= 3.13`\n", + "\n", + "- Followed the instructions in the [Setup Guide](../../rag_agentic/notebooks/Level0_getting_started_with_Llama_Stack.ipynb) notebook.\n", + "\n", + "## Additional environment variables\n", + "This demo requires the following environment variables in addition to those defined in the [Setup Guide](../../rag_agentic/notebooks/Level0_getting_started_with_Llama_Stack.ipynb):\n", + "\n", + "- `PLANNER_AGENT_LOCAL_PORT`: The port for the A2A agent responsible for planning (e.g. 10020).\n", + "\n", + "- `CUSTOM_TOOL_AGENT_LOCAL_PORT`: The port for the A2A agent with custom tool capabilities (e.g. 10021).\n", + "\n", + "- `COMPOSER_AGENT_LOCAL_PORT`: The port for the A2A agent responsible for composing final answers (e.g. 10022)." + ] + }, + { + "cell_type": "markdown", + "id": "b4c88e09", + "metadata": {}, + "source": [ + "## 1. Setting Up this Notebook\n", + "To provide A2A communication capabilities, we will use the [sample implementation by Google](https://github.com/google/A2A/tree/main/samples/python). Please make sure that the content of the referenced directory is available on your Python path. This can be done, for example, by running the following command:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "01c195db", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fatal: destination path 'a2a-samples' already exists and is not an empty directory.\n", + "Collecting annotated-types==0.7.0 (from -r ../requirements.txt (line 1))\n", + " Using cached annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)\n", + "Collecting anyio==4.9.0 (from -r ../requirements.txt (line 2))\n", + " Using cached anyio-4.9.0-py3-none-any.whl.metadata (4.7 kB)\n", + "Requirement already satisfied: appnope==0.1.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 3)) (0.1.4)\n", + "Requirement already satisfied: asttokens==3.0.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 4)) (3.0.0)\n", + "Collecting certifi==2025.1.31 (from -r ../requirements.txt (line 5))\n", + " Using cached certifi-2025.1.31-py3-none-any.whl.metadata (2.5 kB)\n", + "Collecting cffi==1.17.1 (from -r ../requirements.txt (line 6))\n", + " Using cached cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl.metadata (1.5 kB)\n", + "Collecting charset-normalizer==3.4.2 (from -r ../requirements.txt (line 7))\n", + " Using cached charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl.metadata (35 kB)\n", + "Collecting click==8.1.8 (from -r ../requirements.txt (line 8))\n", + " Using cached click-8.1.8-py3-none-any.whl.metadata (2.3 kB)\n", + "Requirement already satisfied: comm==0.2.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 9)) (0.2.2)\n", + "Collecting cryptography==45.0.3 (from -r ../requirements.txt (line 10))\n", + " Using cached cryptography-45.0.3-cp311-abi3-macosx_10_9_universal2.whl.metadata (5.7 kB)\n", + "Requirement already satisfied: debugpy==1.8.14 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 11)) (1.8.14)\n", + "Requirement already satisfied: decorator==5.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 12)) (5.2.1)\n", + "Collecting distro==1.9.0 (from -r ../requirements.txt (line 13))\n", + " Using cached distro-1.9.0-py3-none-any.whl.metadata (6.8 kB)\n", + "Collecting dotenv==0.9.9 (from -r ../requirements.txt (line 14))\n", + " Using cached dotenv-0.9.9-py2.py3-none-any.whl.metadata (279 bytes)\n", + "Requirement already satisfied: executing==2.2.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 15)) (2.2.0)\n", + "Collecting fire==0.7.0 (from -r ../requirements.txt (line 16))\n", + " Using cached fire-0.7.0-py3-none-any.whl\n", + "Collecting h11==0.16.0 (from -r ../requirements.txt (line 17))\n", + " Using cached h11-0.16.0-py3-none-any.whl.metadata (8.3 kB)\n", + "Collecting httpcore==1.0.9 (from -r ../requirements.txt (line 18))\n", + " Using cached httpcore-1.0.9-py3-none-any.whl.metadata (21 kB)\n", + "Collecting httpx==0.28.1 (from -r ../requirements.txt (line 19))\n", + " Using cached httpx-0.28.1-py3-none-any.whl.metadata (7.1 kB)\n", + "Collecting httpx-sse==0.4.0 (from -r ../requirements.txt (line 20))\n", + " Using cached httpx_sse-0.4.0-py3-none-any.whl.metadata (9.0 kB)\n", + "Collecting idna==3.10 (from -r ../requirements.txt (line 21))\n", + " Using cached idna-3.10-py3-none-any.whl.metadata (10 kB)\n", + "Requirement already satisfied: ipykernel==6.29.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 22)) (6.29.5)\n", + "Requirement already satisfied: ipython==9.3.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 23)) (9.3.0)\n", + "Requirement already satisfied: ipython_pygments_lexers==1.1.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 24)) (1.1.1)\n", + "Requirement already satisfied: jedi==0.19.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 25)) (0.19.2)\n", + "Requirement already satisfied: jupyter_client==8.6.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 26)) (8.6.3)\n", + "Requirement already satisfied: jupyter_core==5.8.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 27)) (5.8.1)\n", + "Collecting jwcrypto==1.5.6 (from -r ../requirements.txt (line 28))\n", + " Using cached jwcrypto-1.5.6-py3-none-any.whl.metadata (3.1 kB)\n", + "Collecting llama_stack_client==0.2.2 (from -r ../requirements.txt (line 29))\n", + " Using cached llama_stack_client-0.2.2-py3-none-any.whl.metadata (15 kB)\n", + "Collecting markdown-it-py==3.0.0 (from -r ../requirements.txt (line 30))\n", + " Using cached markdown_it_py-3.0.0-py3-none-any.whl.metadata (6.9 kB)\n", + "Requirement already satisfied: matplotlib-inline==0.1.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 31)) (0.1.7)\n", + "Collecting mdurl==0.1.2 (from -r ../requirements.txt (line 32))\n", + " Using cached mdurl-0.1.2-py3-none-any.whl.metadata (1.6 kB)\n", + "Requirement already satisfied: nest-asyncio==1.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 33)) (1.6.0)\n", + "Collecting numpy==2.2.5 (from -r ../requirements.txt (line 34))\n", + " Using cached numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl.metadata (62 kB)\n", + "Requirement already satisfied: packaging==25.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 35)) (25.0)\n", + "Collecting pandas==2.2.3 (from -r ../requirements.txt (line 36))\n", + " Using cached pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl.metadata (89 kB)\n", + "Requirement already satisfied: parso==0.8.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 37)) (0.8.4)\n", + "Requirement already satisfied: pexpect==4.9.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 38)) (4.9.0)\n", + "Requirement already satisfied: platformdirs==4.3.8 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 39)) (4.3.8)\n", + "Requirement already satisfied: prompt_toolkit==3.0.51 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 40)) (3.0.51)\n", + "Requirement already satisfied: psutil==7.0.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 41)) (7.0.0)\n", + "Requirement already satisfied: ptyprocess==0.7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 42)) (0.7.0)\n", + "Requirement already satisfied: pure_eval==0.2.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 43)) (0.2.3)\n", + "Collecting pyaml==25.1.0 (from -r ../requirements.txt (line 44))\n", + " Using cached pyaml-25.1.0-py3-none-any.whl.metadata (12 kB)\n", + "Collecting pycparser==2.22 (from -r ../requirements.txt (line 45))\n", + " Using cached pycparser-2.22-py3-none-any.whl.metadata (943 bytes)\n", + "Collecting pydantic==2.11.3 (from -r ../requirements.txt (line 46))\n", + " Using cached pydantic-2.11.3-py3-none-any.whl.metadata (65 kB)\n", + "Collecting pydantic_core==2.33.1 (from -r ../requirements.txt (line 47))\n", + " Using cached pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl.metadata (6.8 kB)\n", + "Requirement already satisfied: Pygments==2.19.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 48)) (2.19.1)\n", + "Collecting PyJWT==2.10.1 (from -r ../requirements.txt (line 49))\n", + " Using cached PyJWT-2.10.1-py3-none-any.whl.metadata (4.0 kB)\n", + "Requirement already satisfied: python-dateutil==2.9.0.post0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 50)) (2.9.0.post0)\n", + "Collecting python-dotenv==1.1.0 (from -r ../requirements.txt (line 51))\n", + " Using cached python_dotenv-1.1.0-py3-none-any.whl.metadata (24 kB)\n", + "Collecting pytz==2025.2 (from -r ../requirements.txt (line 52))\n", + " Using cached pytz-2025.2-py2.py3-none-any.whl.metadata (22 kB)\n", + "Collecting PyYAML==6.0.2 (from -r ../requirements.txt (line 53))\n", + " Using cached PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl.metadata (2.1 kB)\n", + "Requirement already satisfied: pyzmq==26.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 54)) (26.4.0)\n", + "Collecting requests==2.32.3 (from -r ../requirements.txt (line 55))\n", + " Using cached requests-2.32.3-py3-none-any.whl.metadata (4.6 kB)\n", + "Collecting rich==14.0.0 (from -r ../requirements.txt (line 56))\n", + " Using cached rich-14.0.0-py3-none-any.whl.metadata (18 kB)\n", + "Requirement already satisfied: six==1.17.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 57)) (1.17.0)\n", + "Collecting sniffio==1.3.1 (from -r ../requirements.txt (line 58))\n", + " Using cached sniffio-1.3.1-py3-none-any.whl.metadata (3.9 kB)\n", + "Collecting sse-starlette==2.2.1 (from -r ../requirements.txt (line 59))\n", + " Using cached sse_starlette-2.2.1-py3-none-any.whl.metadata (7.8 kB)\n", + "Requirement already satisfied: stack-data==0.6.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 60)) (0.6.3)\n", + "Collecting starlette==0.46.2 (from -r ../requirements.txt (line 61))\n", + " Using cached starlette-0.46.2-py3-none-any.whl.metadata (6.2 kB)\n", + "Collecting termcolor==3.0.1 (from -r ../requirements.txt (line 62))\n", + " Using cached termcolor-3.0.1-py3-none-any.whl.metadata (6.1 kB)\n", + "Requirement already satisfied: tornado==6.5.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 63)) (6.5.1)\n", + "Collecting tqdm==4.67.1 (from -r ../requirements.txt (line 64))\n", + " Using cached tqdm-4.67.1-py3-none-any.whl.metadata (57 kB)\n", + "Requirement already satisfied: traitlets==5.14.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 65)) (5.14.3)\n", + "Collecting typing-inspection==0.4.0 (from -r ../requirements.txt (line 66))\n", + " Using cached typing_inspection-0.4.0-py3-none-any.whl.metadata (2.6 kB)\n", + "Collecting typing_extensions==4.13.2 (from -r ../requirements.txt (line 67))\n", + " Using cached typing_extensions-4.13.2-py3-none-any.whl.metadata (3.0 kB)\n", + "Collecting tzdata==2025.2 (from -r ../requirements.txt (line 68))\n", + " Using cached tzdata-2025.2-py2.py3-none-any.whl.metadata (1.4 kB)\n", + "Collecting urllib3==2.4.0 (from -r ../requirements.txt (line 69))\n", + " Using cached urllib3-2.4.0-py3-none-any.whl.metadata (6.5 kB)\n", + "Collecting uvicorn==0.34.2 (from -r ../requirements.txt (line 70))\n", + " Using cached uvicorn-0.34.2-py3-none-any.whl.metadata (6.5 kB)\n", + "Requirement already satisfied: wcwidth==0.2.13 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from -r ../requirements.txt (line 71)) (0.2.13)\n", + "Using cached annotated_types-0.7.0-py3-none-any.whl (13 kB)\n", + "Using cached anyio-4.9.0-py3-none-any.whl (100 kB)\n", + "Using cached certifi-2025.1.31-py3-none-any.whl (166 kB)\n", + "Using cached cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl (178 kB)\n", + "Using cached charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl (199 kB)\n", + "Using cached click-8.1.8-py3-none-any.whl (98 kB)\n", + "Using cached cryptography-45.0.3-cp311-abi3-macosx_10_9_universal2.whl (7.1 MB)\n", + "Using cached distro-1.9.0-py3-none-any.whl (20 kB)\n", + "Using cached dotenv-0.9.9-py2.py3-none-any.whl (1.9 kB)\n", + "Using cached h11-0.16.0-py3-none-any.whl (37 kB)\n", + "Using cached httpcore-1.0.9-py3-none-any.whl (78 kB)\n", + "Using cached httpx-0.28.1-py3-none-any.whl (73 kB)\n", + "Using cached httpx_sse-0.4.0-py3-none-any.whl (7.8 kB)\n", + "Using cached idna-3.10-py3-none-any.whl (70 kB)\n", + "Using cached jwcrypto-1.5.6-py3-none-any.whl (92 kB)\n", + "Using cached llama_stack_client-0.2.2-py3-none-any.whl (273 kB)\n", + "Using cached markdown_it_py-3.0.0-py3-none-any.whl (87 kB)\n", + "Using cached mdurl-0.1.2-py3-none-any.whl (10.0 kB)\n", + "Using cached numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl (5.1 MB)\n", + "Using cached pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl (11.3 MB)\n", + "Using cached pyaml-25.1.0-py3-none-any.whl (26 kB)\n", + "Using cached pycparser-2.22-py3-none-any.whl (117 kB)\n", + "Using cached pydantic-2.11.3-py3-none-any.whl (443 kB)\n", + "Using cached pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl (1.9 MB)\n", + "Using cached PyJWT-2.10.1-py3-none-any.whl (22 kB)\n", + "Using cached python_dotenv-1.1.0-py3-none-any.whl (20 kB)\n", + "Using cached pytz-2025.2-py2.py3-none-any.whl (509 kB)\n", + "Using cached PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl (171 kB)\n", + "Using cached requests-2.32.3-py3-none-any.whl (64 kB)\n", + "Using cached rich-14.0.0-py3-none-any.whl (243 kB)\n", + "Using cached sniffio-1.3.1-py3-none-any.whl (10 kB)\n", + "Using cached sse_starlette-2.2.1-py3-none-any.whl (10 kB)\n", + "Using cached starlette-0.46.2-py3-none-any.whl (72 kB)\n", + "Using cached termcolor-3.0.1-py3-none-any.whl (7.2 kB)\n", + "Using cached tqdm-4.67.1-py3-none-any.whl (78 kB)\n", + "Using cached typing_inspection-0.4.0-py3-none-any.whl (14 kB)\n", + "Using cached typing_extensions-4.13.2-py3-none-any.whl (45 kB)\n", + "Using cached tzdata-2025.2-py2.py3-none-any.whl (347 kB)\n", + "Using cached urllib3-2.4.0-py3-none-any.whl (128 kB)\n", + "Using cached uvicorn-0.34.2-py3-none-any.whl (62 kB)\n", + "Installing collected packages: pytz, urllib3, tzdata, typing_extensions, tqdm, termcolor, sniffio, PyYAML, python-dotenv, PyJWT, pycparser, numpy, mdurl, idna, httpx-sse, h11, distro, click, charset-normalizer, certifi, annotated-types, uvicorn, typing-inspection, requests, pydantic_core, pyaml, pandas, markdown-it-py, httpcore, fire, dotenv, cffi, anyio, starlette, rich, pydantic, httpx, cryptography, sse-starlette, llama_stack_client, jwcrypto\n", + "Successfully installed PyJWT-2.10.1 PyYAML-6.0.2 annotated-types-0.7.0 anyio-4.9.0 certifi-2025.1.31 cffi-1.17.1 charset-normalizer-3.4.2 click-8.1.8 cryptography-45.0.3 distro-1.9.0 dotenv-0.9.9 fire-0.7.0 h11-0.16.0 httpcore-1.0.9 httpx-0.28.1 httpx-sse-0.4.0 idna-3.10 jwcrypto-1.5.6 llama_stack_client-0.2.2 markdown-it-py-3.0.0 mdurl-0.1.2 numpy-2.2.5 pandas-2.2.3 pyaml-25.1.0 pycparser-2.22 pydantic-2.11.3 pydantic_core-2.33.1 python-dotenv-1.1.0 pytz-2025.2 requests-2.32.3 rich-14.0.0 sniffio-1.3.1 sse-starlette-2.2.1 starlette-0.46.2 termcolor-3.0.1 tqdm-4.67.1 typing-inspection-0.4.0 typing_extensions-4.13.2 tzdata-2025.2 urllib3-2.4.0 uvicorn-0.34.2\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n" + ] + } + ], + "source": [ + "! git clone https://github.com/google-a2a/a2a-samples.git\n", + "! pip install -r \"../requirements.txt\"" + ] + }, + { + "cell_type": "markdown", + "id": "d192c7e7", + "metadata": {}, + "source": [ + "Now, we will add the paths to the A2A library and our own tools to `sys.path`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2a99e55d", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "# the path of the A2A library\n", + "sys.path.append('./a2a-samples/samples/python')\n", + "# the path to our own utils\n", + "sys.path.append('../..')" + ] + }, + { + "cell_type": "markdown", + "id": "73be7200", + "metadata": {}, + "source": [ + "We will now proceed with the necessary imports." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "26570e4c", + "metadata": {}, + "outputs": [], + "source": [ + "from common.server import A2AServer\n", + "from common.types import AgentCard, AgentSkill, AgentCapabilities\n", + "from common.client import A2AClient, A2ACardResolver\n", + "from common.utils.push_notification_auth import PushNotificationReceiverAuth\n", + "from hosts.cli.push_notification_listener import PushNotificationListener\n", + "\n", + "from a2a_llama_stack.A2ATool import A2ATool\n", + "from a2a_llama_stack.task_manager import AgentTaskManager\n", + "\n", + "# for asynchronously serving the A2A agent\n", + "import threading\n", + "\n", + "\n", + "import json\n", + "import urllib.parse\n", + "from uuid import uuid4\n", + "from typing import Any, Dict, List, Tuple" + ] + }, + { + "cell_type": "markdown", + "id": "bd2f018c", + "metadata": {}, + "source": [ + "Next, we will initialize our environment as described in detail in our [\"Getting Started\" notebook](../../rag_agentic/notebooks/Level0_getting_started_with_Llama_Stack.ipynb). Please refer to it for additional explanations." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2de3ee44", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connected to Llama Stack server\n", + "Inference Parameters:\n", + "\tModel: llama3.1:8b-instruct-fp16\n", + "\tSampling Parameters: {'strategy': {'type': 'greedy'}, 'max_tokens': 4096}\n", + "\tstream: False\n" + ] + } + ], + "source": [ + "# for accessing the environment variables\n", + "import os\n", + "from dotenv import load_dotenv\n", + "load_dotenv()\n", + "\n", + "# for communication with Llama Stack\n", + "from llama_stack_client import LlamaStackClient\n", + "\n", + "# agent related imports\n", + "import uuid\n", + "from llama_stack_client import Agent\n", + "# from llama_stack_client.lib.agents.event_logger import EventLogger\n", + "\n", + "\n", + "base_url = os.getenv(\"REMOTE_BASE_URL\")\n", + "\n", + "\n", + "# Tavily search API key is required for some of our demos and must be provided to the client upon initialization.\n", + "# We will cover it in the agentic demos that use the respective tool. Please ignore this parameter for all other demos.\n", + "tavily_search_api_key = os.getenv(\"TAVILY_SEARCH_API_KEY\")\n", + "if tavily_search_api_key is None:\n", + " provider_data = None\n", + "else:\n", + " provider_data = {\"tavily_search_api_key\": tavily_search_api_key}\n", + "\n", + "\n", + "client = LlamaStackClient(\n", + " base_url=base_url,\n", + " provider_data=provider_data\n", + ")\n", + " \n", + "print(f\"Connected to Llama Stack server\")\n", + "\n", + "# model_id for the model you wish to use that is configured with the Llama Stack server\n", + "model_id = os.getenv(\"INFERENCE_MODEL_ID\")\n", + "\n", + "temperature = float(os.getenv(\"TEMPERATURE\", 0.0))\n", + "if temperature > 0.0:\n", + " top_p = float(os.getenv(\"TOP_P\", 0.95))\n", + " strategy = {\"type\": \"top_p\", \"temperature\": temperature, \"top_p\": top_p}\n", + "else:\n", + " strategy = {\"type\": \"greedy\"}\n", + "\n", + "max_tokens = int(os.getenv(\"MAX_TOKENS\", 4096))\n", + "\n", + "# sampling_params will later be used to pass the parameters to Llama Stack Agents/Inference APIs\n", + "sampling_params = {\n", + " \"strategy\": strategy,\n", + " \"max_tokens\": max_tokens,\n", + "}\n", + "\n", + "stream_env = os.getenv(\"STREAM\", \"False\")\n", + "# the Boolean 'stream' parameter will later be passed to Llama Stack Agents/Inference APIs\n", + "# any value non equal to 'False' will be considered as 'True'\n", + "stream = (stream_env != \"False\")\n", + "\n", + "print(f\"Inference Parameters:\\n\\tModel: {model_id}\\n\\tSampling Parameters: {sampling_params}\\n\\tstream: {stream}\")" + ] + }, + { + "cell_type": "markdown", + "id": "f6631ae7", + "metadata": {}, + "source": [ + "## 2. Setting Up and Serving A2A Agents\n", + "Now, we will define the core Llama Stack agents that will form our multi-agent system. These agents are created using the Llama Stack `Agent` class. Later, we will expose their functionalities via A2A servers.\n", + "\n", + "We will initialize three distinct Llama Stack agents:\n", + "\n", + "1. **Planner Agent**: an agent that acts as an orchestrator, determining which skills (other agents) are needed to answer a user's query.\n", + "\n", + "2. **Custom Tool Agent**: an agent equipped with tools to generate random numbers and provide the current date.\n", + "\n", + "3. **Composer Agent**: an agent skilled at writing human-friendly text based on provided information." + ] + }, + { + "cell_type": "markdown", + "id": "dd57e65d", + "metadata": {}, + "source": [ + "#### 2.1. Planner Agent\n", + "The Planner Agent is responsible for understanding the user's query and determining which skills (exposed by other agents) are needed to fulfill the request. It outputs a plan, typically a list of skill IDs to be invoked." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a64c0996", + "metadata": {}, + "outputs": [], + "source": [ + "planner_agent = Agent(\n", + " client,\n", + " model=model_id,\n", + " instructions=(\"You are an orchestration assistant. Ensure you count correctly the number of skills needed.\"),\n", + " sampling_params=sampling_params,\n", + " tools=[],\n", + " max_infer_iters=10,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "4eb66f6a", + "metadata": {}, + "source": [ + "#### 2.2. Custom Tool Agent\n", + "First, we define the Python functions that will serve as custom tools for our second agent." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "972b1ee0", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "from datetime import datetime\n", + "\n", + "\n", + "def random_number_tool() -> int:\n", + " \"\"\"\n", + " Generate a random integer between 1 and 100.\n", + " \"\"\"\n", + " print(\"\\n\\nGenerating a random number...\\n\\n\")\n", + " return random.randint(1, 100)\n", + "\n", + "\n", + "def date_tool() -> str:\n", + " \"\"\"\n", + " Return today's date in YYYY-MM-DD format.\n", + " \"\"\"\n", + " return datetime.utcnow().date().isoformat()" + ] + }, + { + "cell_type": "markdown", + "id": "6934c92d", + "metadata": {}, + "source": [ + "Next, initialize the Llama Stack agent, providing it with these tools and instructions on how to use them." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e6bef0d1", + "metadata": {}, + "outputs": [], + "source": [ + "custom_tool_agent = Agent(\n", + " client,\n", + " model=model_id,\n", + " instructions=(\n", + " \"You have access to two tools:\\n\"\n", + " \"- random_number_tool: generates one random integer between 1 and 100\\n\"\n", + " \"- date_tool: returns today's date in YYYY-MM-DD format\\n\"\n", + " \"Always use the appropriate tool to answer user queries.\"\n", + " ), \n", + " sampling_params=sampling_params,\n", + " tools=[random_number_tool, date_tool],\n", + " max_infer_iters=3,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "bc992470", + "metadata": {}, + "source": [ + "#### 2.3. Composer Agent\n", + "Finally, we initialize the Composer Agent. This agent's role is to take structured data (e.g. results from other tools or agents) and formulate a coherent, human-friendly response." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1b71d907", + "metadata": {}, + "outputs": [], + "source": [ + "composer_agent = Agent(\n", + " client,\n", + " model=model_id,\n", + " instructions=(\"You are skilled at writing human-friendly text based on the query and associated skills.\"), \n", + " sampling_params=sampling_params,\n", + " tools=[],\n", + " max_infer_iters=3,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "ba981a33", + "metadata": {}, + "source": [ + "## 3. Serving Llama Stack Agents via A2A\n", + "Now that we have our Llama Stack agents, we need to make their functionalities accessible via the A2A protocol. This involves:\n", + "\n", + "- Creating an `AgentCard`: An object containing metadata about the agent, including its URL and exposed capabilities (`AgentSkill`).\n", + "\n", + "- Wrapping the Llama Stack agent with an `AgentTaskManager`: An adapter that allows the A2A server to forward requests to the Llama Stack agent.\n", + "\n", + "- Creating and launching an `A2AServer`: A REST API server that handles A2A protocol communication for this agent." + ] + }, + { + "cell_type": "markdown", + "id": "850d78d6", + "metadata": {}, + "source": [ + "#### 3.1. Serving the Planner Agent\n", + "First, we serve the Planner Agent via its own A2A server. Its `AgentCard` will highlight its orchestration planning skill." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "12c7e150", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO: Started server process [85772]\n", + "INFO: Waiting for application startup.\n", + "INFO: Application startup complete.\n", + "ERROR: [Errno 48] error while attempting to bind on address ('::1', 10020, 0, 0): [errno 48] address already in use\n", + "INFO: Waiting for application shutdown.\n", + "INFO: Application shutdown complete.\n" + ] + } + ], + "source": [ + "planner_agent_local_port = int(os.getenv(\"planner_agent_LOCAL_PORT\", \"10020\"))\n", + "planner_agent_url = f\"http://localhost:{planner_agent_local_port}\"\n", + "\n", + "agent_card = AgentCard(\n", + " name=\"Orchestration Agent\",\n", + " description=\"Plans which tool to call for each user question\",\n", + " url=planner_agent_url,\n", + " version=\"0.1.0\",\n", + " defaultInputModes=[\"text/plain\"],\n", + " defaultOutputModes=[\"text/plain\"],\n", + " capabilities=AgentCapabilities(\n", + " streaming=False,\n", + " pushNotifications=False,\n", + " stateTransitionHistory=False,\n", + " ),\n", + " skills=[\n", + " AgentSkill(\n", + " id=\"orchestrate\",\n", + " name=\"Orchestration Planner\",\n", + " description=\"Plan user questions into JSON steps of {skill_id}\",\n", + " tags=[\"orchestration\"],\n", + " examples=[\"Plan: What's today's date and a random number?\"],\n", + " inputModes=[\"text/plain\"],\n", + " outputModes=[\"application/json\"],\n", + " ),\n", + " ],\n", + ")\n", + "task_manager = AgentTaskManager(agent=planner_agent)\n", + "server = A2AServer(\n", + " agent_card=agent_card,\n", + " task_manager=task_manager,\n", + " host='localhost',\n", + " port=planner_agent_local_port\n", + ")\n", + "thread = threading.Thread(target=server.start, daemon=True)\n", + "thread.start()" + ] + }, + { + "cell_type": "markdown", + "id": "d3292fc9", + "metadata": {}, + "source": [ + "#### 3.2. Serving the Custom Tool Agent\n", + "We create an `AgentCard` for the Custom Tool Agent, detailing its skills (random number generation and date retrieval). Then, we wrap our `custom_tool_agent` (the Llama Stack Agent) in an `AgentTaskManager` and start an A2AServer for it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fab56668", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO: Started server process [85772]\n", + "INFO: Waiting for application startup.\n", + "INFO: Application startup complete.\n", + "ERROR: [Errno 48] error while attempting to bind on address ('127.0.0.1', 10021): [errno 48] address already in use\n", + "INFO: Waiting for application shutdown.\n", + "INFO: Application shutdown complete.\n" + ] + } + ], + "source": [ + "custom_tool_agent_local_port = int(os.getenv(\"CUSTOM_TOOL_AGENT_LOCAL_PORT\", \"10021\"))\n", + "custom_tool_agent_url = f\"http://localhost:{custom_tool_agent_local_port}\"\n", + "\n", + "agent_card = AgentCard(\n", + " name=\"Custom Agent\",\n", + " description=\"Generates random numbers or retrieve today's dates\",\n", + " url=custom_tool_agent_url,\n", + " version=\"0.1.0\",\n", + " defaultInputModes=[\"text/plain\"],\n", + " defaultOutputModes=[\"text/plain\"],\n", + " capabilities=AgentCapabilities(\n", + " streaming=False,\n", + " pushNotifications=False,\n", + " stateTransitionHistory=False,\n", + " ),\n", + " skills=[\n", + " AgentSkill(\n", + " id=\"random_number_tool\", \n", + " name=\"Random Number Generator\",\n", + " description=\"Generates a random number between 1 and 100\",\n", + " tags=[\"random\"],\n", + " examples=[\"Give me a random number between 1 and 100\"],\n", + " inputModes=[\"text/plain\"],\n", + " outputModes=[\"text/plain\"],\n", + " ),\n", + " AgentSkill(\n", + " id=\"date_tool\",\n", + " name=\"Date Provider\",\n", + " description=\"Returns today's date in YYYY-MM-DD format\",\n", + " tags=[\"date\"],\n", + " examples=[\"What's the date today?\"],\n", + " inputModes=[\"text/plain\"],\n", + " outputModes=[\"text/plain\"],\n", + " ),\n", + " ],\n", + ")\n", + "task_manager = AgentTaskManager(agent=custom_tool_agent)\n", + "server = A2AServer(\n", + " agent_card=agent_card,\n", + " task_manager=task_manager,\n", + " host='localhost',\n", + " port=custom_tool_agent_local_port\n", + ")\n", + "thread = threading.Thread(target=server.start, daemon=True)\n", + "thread.start()" + ] + }, + { + "cell_type": "markdown", + "id": "5f18ef4f", + "metadata": {}, + "source": [ + "#### 3.3. Serving the Composer Agent\n", + "Similarly, we set up an A2A server for the Composer Agent. Its `AgentCard` will define its writing skill." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ee06174c", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO: Started server process [85772]\n", + "INFO: Waiting for application startup.\n", + "INFO: Application startup complete.\n", + "ERROR: [Errno 48] error while attempting to bind on address ('::1', 10022, 0, 0): [errno 48] address already in use\n", + "INFO: Waiting for application shutdown.\n", + "INFO: Application shutdown complete.\n" + ] + } + ], + "source": [ + "composer_agent_local_port = int(os.getenv(\"COMPOSER_AGENT_LOCAL_PORT\", \"10022\"))\n", + "composer_agent_url = f\"http://localhost:{composer_agent_local_port}\"\n", + "\n", + "agent_card = AgentCard(\n", + " name=\"Writing Agent\",\n", + " description=\"Generate human-friendly text based on the query and associated skills\",\n", + " url=composer_agent_url,\n", + " version=\"0.1.0\",\n", + " defaultInputModes=[\"text/plain\"],\n", + " defaultOutputModes=[\"text/plain\"],\n", + " capabilities=AgentCapabilities(\n", + " streaming=False,\n", + " pushNotifications=False,\n", + " stateTransitionHistory=False,\n", + " ),\n", + " skills=[\n", + " AgentSkill(\n", + " id=\"writing_agent\", \n", + " name=\"Writing Agent\",\n", + " description=\"Write human-friendly text based on the query and associated skills\",\n", + " tags=[\"writing\"],\n", + " examples=[\"Write human-friendly text based on the query and associated skills\"],\n", + " inputModes=[\"text/plain\"],\n", + " outputModes=[\"application/json\"],\n", + " ),\n", + " ],\n", + ")\n", + "task_manager = AgentTaskManager(agent=composer_agent)\n", + "server = A2AServer(\n", + " agent_card=agent_card,\n", + " task_manager=task_manager,\n", + " host='localhost',\n", + " port=composer_agent_local_port\n", + ")\n", + "thread = threading.Thread(target=server.start, daemon=True)\n", + "thread.start()" + ] + }, + { + "cell_type": "markdown", + "id": "a5b60a60", + "metadata": {}, + "source": [ + "## 4. Orchestrating the A2A Agents\n", + "With all Llama Stack agents defined and served via A2A, we now set up the client-side logic to interact with them. This involves managing connections and coordinating the flow of information between the Planner Agent, Skill Executor Agents (Custom Tool Agent), and the Composer Agent." + ] + }, + { + "cell_type": "markdown", + "id": "f75663ca", + "metadata": {}, + "source": [ + "#### 4.1. Agent Manager\n", + "The `AgentManager` class helps manage connections and agent cards for the orchestrator (Planner Agent) and the skill executor agents (Custom Tool Agent, Composer Agent). It simplifies accessing their A2A clients and metadata." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "2c328fbf", + "metadata": {}, + "outputs": [], + "source": [ + "AgentInfo = Tuple[str, Any, A2AClient, str]\n", + "\n", + "class AgentManager:\n", + " def __init__(self, urls: List[str]):\n", + " # first URL is your orchestrator…\n", + " self.orchestrator: AgentInfo = self._make_agent_info(urls[0])\n", + " # …the rest are skill agents, each keyed by skill.id\n", + " self.skills: Dict[str, AgentInfo] = {\n", + " skill.id: info\n", + " for url in urls[1:]\n", + " for info in (self._make_agent_info(url),)\n", + " for skill in info[1].skills\n", + " }\n", + "\n", + " @staticmethod\n", + " def _make_agent_info(url: str) -> AgentInfo:\n", + " card = A2ACardResolver(url).get_agent_card()\n", + " client = A2AClient(agent_card=card)\n", + " session = uuid4().hex\n", + " return url, card, client, session" + ] + }, + { + "cell_type": "markdown", + "id": "1d1260a5", + "metadata": {}, + "source": [ + "#### 4.2. Orchestration Helper Functions\n", + "These asynchronous helper functions are used by the main orchestration logic to send tasks to the A2A agents and process their responses.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "f76784de", + "metadata": {}, + "outputs": [], + "source": [ + "async def _send_payload(client, card, session, payload, streaming: bool) -> str:\n", + " if not streaming:\n", + " res = await client.send_task(payload)\n", + " return res.result.status.message.parts[0].text.strip()\n", + "\n", + " text = \"\"\n", + " async for ev in client.send_task_streaming(payload):\n", + " part = ev.result.status.message.parts[0].text or \"\"\n", + " print(part, end=\"\", flush=True)\n", + " text = part\n", + " print()\n", + " return text\n", + "\n", + "def _build_skill_meta(mgr):\n", + " \"\"\"Gather unique metadata for every skill in all executor cards.\"\"\"\n", + " unique_skills = {} # Use a dictionary to store skills by their ID\n", + " for _, card, _, _ in mgr.skills.values():\n", + " for s in card.skills:\n", + " if s.id not in unique_skills:\n", + " unique_skills[s.id] = {\n", + " \"skill_id\": s.id,\n", + " \"name\": s.name,\n", + " \"description\": getattr(s, \"description\", None),\n", + " \"tags\": getattr(s, \"tags\", []),\n", + " \"examples\": getattr(s, \"examples\", None),\n", + "\n", + " }\n", + " return list(unique_skills.values()) # Convert the dictionary values back to a list\n", + "\n", + "\n", + "async def _send_task(mgr, client, card, session, question, push=False, host=None, port=None) -> str:\n", + " \"\"\"Build a card-driven payload (with optional push) and dispatch it.\"\"\"\n", + " # Input parts\n", + " content = {\"question\": question}\n", + " modes = getattr(card, \"acceptedInputModes\", [\"text\"])\n", + " parts = ([{\"type\": \"json\", \"json\": content}]\n", + " if \"json\" in modes\n", + " else [{\"type\": \"text\", \"text\": json.dumps(content)}])\n", + "\n", + " # Optional push URL & auth\n", + " can_push = push and getattr(card.capabilities, \"pushNotifications\", False)\n", + " push_url = (urllib.parse.urljoin(f\"http://{host}:{port}\", \"/notify\")\n", + " if can_push and host and port else None)\n", + " schemes = getattr(card.authentication, \"supportedSchemes\", [\"bearer\"])\n", + "\n", + " # Assemble payload\n", + " payload = {\n", + " \"id\": uuid4().hex,\n", + " \"sessionId\": session,\n", + " \"acceptedOutputModes\": card.defaultOutputModes,\n", + " \"message\": {\"role\": \"user\", \"parts\": parts},\n", + " **({\"pushNotification\": {\"url\": push_url,\n", + " \"authentication\": {\"schemes\": schemes}}}\n", + " if push_url else {})\n", + " }\n", + "\n", + " # Dispatch, letting the card decide streaming vs one-shot\n", + " stream = getattr(card.capabilities, \"streaming\", False)\n", + " return await _send_payload(client, card, session, payload, stream)" + ] + }, + { + "cell_type": "markdown", + "id": "617d27e5", + "metadata": {}, + "source": [ + "#### 4.3. Orchestration Logic\n", + "\n", + "The `orchestrate` function coordinates the multi-agent interaction:\n", + "\n", + "1. **Planning Phase**: It queries the Planner Agent (via A2A) with the user's question and metadata about available skills from other agents. The Planner Agent returns a JSON plan (a list of `skill_id`s).\n", + "\n", + "2. **Execution Phase**: It iterates through the plan, calling the appropriate Skill Executor A2A Agents (e.g., Custom Tool Agent's skills) for each step.\n", + "\n", + "3. **Composition Phase**: Finally, it sends the original question and the collected results from the execution phase to the Composer A2A Agent to generate a polished, human-readable response." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "2e5e4ec3", + "metadata": {}, + "outputs": [], + "source": [ + "async def orchestrate(\n", + " agent_manager: AgentManager,\n", + " question: str,\n", + " push: bool = False,\n", + " push_receiver: str = \"http://localhost:5000\",\n", + ") -> str:\n", + " # Unpack orchestrator info\n", + " orch_url, orch_card, orch_client, orch_session = agent_manager.orchestrator\n", + "\n", + " # Optionally start push listener\n", + " host = port = None\n", + " if push:\n", + " parsed = urllib.parse.urlparse(push_receiver)\n", + " host, port = parsed.hostname, parsed.port\n", + " auth = PushNotificationReceiverAuth()\n", + " await auth.load_jwks(f\"{orch_url}/.well-known/jwks.json\")\n", + " PushNotificationListener(host, port, auth).start()\n", + "\n", + " # --- Planning Phase ---\n", + " print(\"\\n\\033[1;33m=========== 🧠 Planning Phase ===========\\033[0m\")\n", + " # Build skill metadata\n", + " skills_meta = _build_skill_meta(agent_manager)\n", + " plan_instructions = (\n", + " \"You are an orchestration assistant.\\n\"\n", + " \"Available skills (id & name & description & tags & examples):\\n\"\n", + " f\"{json.dumps(skills_meta, indent=2)}\\n\\n\"\n", + " \"When given a user question, respond _only_ with a JSON array of objects, \"\n", + " \"each with key `skill_id`, without any surrounding object. You may be asked to write single or multiple skills.\\n\"\n", + " \"For example for multiple tools:\\n\"\n", + " \"[\"\n", + " \"{\\\"skill_id\\\": \\\"tool_1\\\"}, \"\n", + " \"{\\\"skill_id\\\": \\\"tool_2\\\"}\"\n", + " \"]\"\n", + " )\n", + " combined = plan_instructions + \"\\n\\nUser question: \" + question\n", + " raw = await _send_task(agent_manager, orch_client, orch_card, orch_session, combined, push=push, host=host, port=port)\n", + " print(f\"Raw plan ➡️ {raw}\")\n", + " try:\n", + " plan = json.loads(raw[: raw.rfind(\"]\") + 1])\n", + " except ValueError:\n", + " print(\"\\033[31mPlan parse failed, fixing invalid JSON...\\033[0m\")\n", + " fixer = \"Fix this json to be valid: \" + raw\n", + " fixed = await _send_task(agent_manager, orch_client, orch_card, orch_session, fixer, push=push, host=host, port=port)\n", + " plan = json.loads(fixed)\n", + " print(f\"\\n\\033[1;32mFinal plan ➡️ {plan}\\033[0m\")\n", + "\n", + " # --- Execution Phase ---\n", + " print(\"\\n\\033[1;33m=========== ⚡️ Execution Phase ===========\\033[0m\")\n", + " parts = []\n", + " for i, step in enumerate(plan, 1):\n", + " sid = step[\"skill_id\"]\n", + " inp = json.dumps(step.get(\"input\", {}))\n", + " print(f\"➡️ Step {i}: {sid}({inp})\")\n", + "\n", + " info = agent_manager.skills.get(sid)\n", + " if not info:\n", + " print(f\"\\033[31mNo executor for '{sid}', skipping.\\033[0m\")\n", + " parts.append({\"skill_id\": sid, \"output\": None})\n", + " continue\n", + "\n", + " _, skill_card, skill_client, skill_sess = info\n", + " out = await _send_task(agent_manager, skill_client, skill_card, skill_sess, f\"{sid}({inp})\", push=push, host=host, port=port)\n", + " print(f\" ✅ → {out}\")\n", + " parts.append({\"skill_id\": sid, \"output\": out})\n", + "\n", + " # --- Composing Answer ---\n", + " print(\"\\n\\033[1;33m=========== 🛠️ Composing Answer ===========\\033[0m\")\n", + " comp_prompt = (\n", + " f\"Using the following information: {json.dumps(parts)}, \"\n", + " f\"write a clear and human-friendly response to the question: '{question}'. \"\n", + " \"Keep it concise and easy to understand and respond like a human with character. \"\n", + " \"Only use the information provided. If you cannot answer the question, say 'I don't know'. \"\n", + " \"Never show any code or JSON or Markdown, just the answer.\\n\\n\"\n", + " )\n", + " _, write_card, write_client, write_sess = agent_manager.skills[\"writing_agent\"]\n", + " final = await _send_task(agent_manager, write_client, write_card, write_sess, comp_prompt, push=push, host=host, port=port)\n", + "\n", + " print(\"\\n\\033[1;36m🎉 FINAL ANSWER\\033[0m\")\n", + " print(final)\n", + " print(\"\\033[1;36m====================================\\033[0m\")\n", + " return final" + ] + }, + { + "cell_type": "markdown", + "id": "937dbee6", + "metadata": {}, + "source": [ + "### 5. Running the Orchestration\n", + "Now we define the URLs for our orchestrator (Planner Agent) and skill executor agents (Custom Tool Agent, Composer Agent). We then initialize the `AgentManager` and call the `orchestrate` function with sample questions.\n", + "\n", + "The `AgentManager` uses the first URL for the orchestrator/planner and the rest for skill executors. The `orchestrate` function will then:\n", + "\n", + "1. Query the Planner Agent with the user's question and the list of available skills.\n", + "\n", + "2. The Planner Agent returns a plan (e.g. `[{'skill_id': 'random_number_tool'}]`).\n", + "\n", + "3. The `orchestrate` function executes the plan by calling the specified skill agents.\n", + "\n", + "4. Finally, it sends the original question and the skill outputs to the Composer Agent to generate a polished, human-readable response." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "19d1fa85", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\u001b[1;36m===================== 🛰️ Connected Agents =====================\u001b[0m\n", + "Orchestrator: http://localhost:10020 (Orchestration Agent)\n", + "Executors:\n", + " • random_number_tool -> http://localhost:10021 (Custom Agent)\n", + " • date_tool -> http://localhost:10021 (Custom Agent)\n", + " • writing_agent -> http://localhost:10022 (Writing Agent)\n", + "\u001b[1;36m===============================================================\u001b[0m\n", + "\n", + "\u001b[1;33m=========== 🧠 Planning Phase ===========\u001b[0m\n", + "Raw plan ➡️ [\n", + " {\"skill_id\": \"date_tool\"},\n", + " {\"skill_id\": \"random_number_tool\"},\n", + " {\"skill_id\": \"random_number_tool\"},\n", + " {\"skill_id\": \"random_number_tool\"},\n", + " {\"skill_id\": \"random_number_tool\"},\n", + " {\"skill_id\": \"random_number_tool\"}\n", + "]\n", + "\n", + "\u001b[1;32mFinal plan ➡️ [{'skill_id': 'date_tool'}, {'skill_id': 'random_number_tool'}, {'skill_id': 'random_number_tool'}, {'skill_id': 'random_number_tool'}, {'skill_id': 'random_number_tool'}, {'skill_id': 'random_number_tool'}]\u001b[0m\n", + "\n", + "\u001b[1;33m=========== ⚡️ Execution Phase ===========\u001b[0m\n", + "➡️ Step 1: date_tool({})\n", + " ✅ → {\n", + " \"type\": \"function\",\n", + " \"name\": \"date_tool\",\n", + " \"parameters\": {}\n", + "}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-06-03\"{\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": {}}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-06-03\"The date today is 2025-06-03.\n", + "➡️ Step 2: random_number_tool({})\n", + " ✅ → {\n", + " \"type\": \"function\",\n", + " \"name\": \"random_number_tool\",\n", + " \"parameters\": {}\n", + "}Tool:random_number_tool Args:{}Tool:random_number_tool Response:26{\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:4{\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}\n", + "➡️ Step 3: random_number_tool({})\n", + " ✅ → {\n", + " \"type\": \"function\",\n", + " \"name\": \"random_number_tool\",\n", + " \"parameters\": {}\n", + "}Tool:random_number_tool Args:{}Tool:random_number_tool Response:80{\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:43```\n", + "{\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}} \n", + "```\n", + "➡️ Step 4: random_number_tool({})\n", + " ✅ → {\n", + " \"type\": \"function\",\n", + " \"name\": \"random_number_tool\",\n", + " \"parameters\": {}\n", + "}Tool:random_number_tool Args:{}Tool:random_number_tool Response:88{\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:16{\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}\n", + "➡️ Step 5: random_number_tool({})\n", + " ✅ → {\n", + " \"type\": \"function\",\n", + " \"name\": \"random_number_tool\",\n", + " \"parameters\": {}\n", + "}Tool:random_number_tool Args:{}Tool:random_number_tool Response:81{\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:48```\n", + "{\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}\n", + "➡️ Step 6: random_number_tool({})\n", + " ✅ → {\n", + " \"type\": \"function\",\n", + " \"name\": \"random_number_tool\",\n", + " \"parameters\": {}\n", + "}Tool:random_number_tool Args:{}Tool:random_number_tool Response:92{\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:80{\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}\n", + "\n", + "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n", + "\n", + "\u001b[1;36m🎉 FINAL ANSWER\u001b[0m\n", + "Today's date is June 3rd, 2025.\n", + "\n", + "Here are five random numbers for you: 26, 4, 80, 43, and 16.\n", + "\u001b[1;36m====================================\u001b[0m\n", + "\n", + "\u001b[1;33m=========== 🧠 Planning Phase ===========\u001b[0m\n", + "Raw plan ➡️ [{\"skill_id\": \"date_tool\"}]\n", + "\n", + "\u001b[1;32mFinal plan ➡️ [{'skill_id': 'date_tool'}]\u001b[0m\n", + "\n", + "\u001b[1;33m=========== ⚡️ Execution Phase ===========\u001b[0m\n", + "➡️ Step 1: date_tool({})\n", + " ✅ → {\n", + " \"type\": \"function\",\n", + " \"name\": \"date_tool\",\n", + " \"parameters\": {}\n", + "}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-06-03\"{\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": {}}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-06-03\"The date today is 2025-06-03.\n", + "\n", + "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n", + "\n", + "\u001b[1;36m🎉 FINAL ANSWER\u001b[0m\n", + "The current date is 2025-06-03.\n", + "\u001b[1;36m====================================\u001b[0m\n", + "\n", + "\u001b[1;33m=========== 🧠 Planning Phase ===========\u001b[0m\n", + "Raw plan ➡️ [\n", + " {\"skill_id\": \"random_number_tool\"}\n", + "]\n", + "\n", + "\u001b[1;32mFinal plan ➡️ [{'skill_id': 'random_number_tool'}]\u001b[0m\n", + "\n", + "\u001b[1;33m=========== ⚡️ Execution Phase ===========\u001b[0m\n", + "➡️ Step 1: random_number_tool({})\n", + " ✅ → {\n", + " \"type\": \"function\",\n", + " \"name\": \"random_number_tool\",\n", + " \"parameters\": {}\n", + "}Tool:random_number_tool Args:{}Tool:random_number_tool Response:38{\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:85```\n", + "{\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}\n", + "\n", + "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n", + "\n", + "\u001b[1;36m🎉 FINAL ANSWER\u001b[0m\n", + "Here's your randomly generated number: 38.\n", + "\u001b[1;36m====================================\u001b[0m\n" + ] + } + ], + "source": [ + "ORCHESTRATOR_URL = \"http://localhost:10020\"\n", + "EXECUTOR_URLS = [\"http://localhost:10021\", \"http://localhost:10022\"]\n", + "URLS = [ORCHESTRATOR_URL, *EXECUTOR_URLS]\n", + "\n", + "_agent_manager = AgentManager(URLS)\n", + "orch_url, orch_card, *_ = _agent_manager.orchestrator\n", + "\n", + "print(\"\\n\\033[1;36m===================== 🛰️ Connected Agents =====================\\033[0m\")\n", + "print(f\"Orchestrator: {orch_url} ({orch_card.name})\")\n", + "print(\"Executors:\")\n", + "for sid, (u, card, *_) in _agent_manager.skills.items():\n", + " print(f\" • {sid} -> {u} ({card.name})\")\n", + "print(\"\\033[1;36m===============================================================\\033[0m\")\n", + "\n", + "questions = [ \n", + " \"Get todays date then generate five random numbers\",\n", + " \"Get todays date?\",\n", + " \"I want one random number\",\n", + " ]\n", + "\n", + "for question in questions:\n", + " await orchestrate(_agent_manager, question)" + ] + }, + { + "cell_type": "markdown", + "id": "32d32564", + "metadata": {}, + "source": [ + "## 6. Wrapping Up & Future Directions\n", + "\n", + "We've successfully orchestrated a team of specialized Llama Stack agents, showcasing how they can collaborate via the A2A protocol to tackle complex queries.\n", + "\n", + "**What We Achieved:**\n", + "\n", + "* We configured the Llama Stack environment and designed three distinct agents: a `Planner`, a `Custom Tool Agent` (with date/random number skills), and a `Composer`.\n", + "\n", + "* Each agent was made accessible through A2A, with an `AgentManager` and an `orchestrate` function guiding their interaction to deliver user-friendly answers.\n", + "\n", + "The key insight is the power of modular, specialized agents communicating through a standard protocol, allowing for flexible and scalable AI system development.\n", + "\n", + "### Future Directions:\n", + "\n", + "Inspired? Here are a few ways to build on this foundation:\n", + "\n", + "* **Refine & Expand**: Experiment with agent instructions or add new tools and specialized agents to the team.\n", + "\n", + "* **Boost Orchestration**: Explore more dynamic planning, such as conditional logic or inter-agent feedback loops.\n", + "\n", + "* **Challenge the System**: Test with increasingly complex queries to push the boundaries of the current setup.\n", + "\n", + "This notebook serves as a stepping stone into the exciting world of multi-agent AI. We hope it empowers you to build even more sophisticated applications. Happy coding!" + ] + } + ], + "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.13.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb b/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb deleted file mode 100644 index 699d7b3..0000000 --- a/demos/a2a_llama_stack/notebooks/A2A_Multi_Agent.ipynb +++ /dev/null @@ -1,1098 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "c55ab371", - "metadata": {}, - "source": [ - "# A2A Multi Agent - Quick-Start Notebook\n", - "\n", - "Welcome to the A2A Multi-Agent Quick-Start notebook! This guide will show you how to set up and interact with a multi-agent system using the Agent-to-Agent (A2A) protocol within the Llama Stack environment.\n", - "\n", - "## Overview\n", - "\n", - "This notebook guides you through the following key steps to set up and interact with a multi-agent system:\n", - "\n", - "1. **Setting up the Environment**: Preparing your Python environment by installing necessary libraries and configuring asynchronous operations.\n", - "\n", - "2. **Agent Management**: Understanding how to connect to and manage the different agents within the system.\n", - "\n", - "3. **Defining Agent Task Flow**: Exploring the multi-phase process (planning, execution, and composition) by which agents collaboratively solve a query.\n", - "\n", - "4. **Launching Agent Servers**: Starting the Agent-to-Agent (A2A) orchestrator and skill agent servers.\n", - "\n", - "5. **Interacting with Agents**: Sending questions to the agent team and observing their orchestrated responses.\n", - "\n", - "## Prerequisites\n", - "\n", - "Before you begin, ensure that you have:\n", - "\n", - "* `python_requires >= 3.13`.\n", - "\n", - "* Completed the initial setup as outlined in the [Setup Guide](../../rag_agentic/notebooks/Level0_getting_started_with_Llama_Stack.ipynb) notebook.\n", - "\n", - "## Environment Variables\n", - "\n", - "This notebook is designed to be flexible, allowing you to connect to either a local or a remote Llama Stack instance, and to specify the inference models used by the agents. You can configure these aspects using the following environment variables:\n", - "\n", - "* `REMOTE_BASE_URL`: Set this variable if you intend to connect to a **remote Llama Stack instance**. If this variable is not set, the notebook will default to running with a local instance.\n", - "\n", - "* `INFERENCE_MODEL_ID`: Define this variable to specify the default Large Language Model (LLM) that agents should use for inference. We recommend using `llama3.1:8b-instruct-fp16` for optimal performance.\n", - "\n", - "**Note on Agent-Specific Models:**\n", - "If you require different inference models for individual agents, you can achieve this by directly opening and modifying the `__main__.py` file within each respective agent's folder (e.g. `demos/a2a_llama_stack/agents/a2a_custom_tools/__main__.py`)." - ] - }, - { - "cell_type": "markdown", - "id": "b853d0ba", - "metadata": {}, - "source": [ - "## 1. Setting Up the Notebook Environment\n", - "\n", - "We'll start by setting up the necessary environment and installing the required packages to enable A2A communication." - ] - }, - { - "cell_type": "markdown", - "id": "2d1a1075", - "metadata": {}, - "source": [ - "We'll install the official [A2A sample implementation by Google](https://github.com/google/A2A/tree/main/samples/python), the Llama Stack client, and other essential packages for asynchronous operations in Jupyter. Run the following command:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "eb5bce08", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Collecting git+https://github.com/google/A2A.git#subdirectory=samples/python\n", - " Cloning https://github.com/google/A2A.git to /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-td4brfvf\n", - " Running command git clone --filter=blob:none --quiet https://github.com/google/A2A.git /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-td4brfvf\n", - " Resolved https://github.com/google/A2A.git to commit 09c36bd94f74ea1a1a4e0db5f65ba12b102171ba\n", - " Installing build dependencies ... \u001b[?25ldone\n", - "\u001b[?25h Getting requirements to build wheel ... \u001b[?25ldone\n", - "\u001b[?25h Preparing metadata (pyproject.toml) ... \u001b[?25ldone\n", - "\u001b[?25hRequirement already satisfied: a2a-sdk>=0.2.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.2.4)\n", - "Requirement already satisfied: httpx-sse>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.4.0)\n", - "Requirement already satisfied: httpx>=0.28.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.28.1)\n", - "Requirement already satisfied: jwcrypto>=1.5.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (1.5.6)\n", - "Requirement already satisfied: pydantic>=2.10.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.11.5)\n", - "Requirement already satisfied: pyjwt>=2.10.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.10.1)\n", - "Requirement already satisfied: sse-starlette>=2.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.3.5)\n", - "Requirement already satisfied: starlette>=0.46.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.46.2)\n", - "Requirement already satisfied: typing-extensions>=4.12.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (4.13.2)\n", - "Requirement already satisfied: uvicorn>=0.34.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.34.2)\n", - "Requirement already satisfied: opentelemetry-api>=1.33.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-sdk>=0.2.4->a2a-samples==0.1.0) (1.33.1)\n", - "Requirement already satisfied: opentelemetry-sdk>=1.33.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from a2a-sdk>=0.2.4->a2a-samples==0.1.0) (1.33.1)\n", - "Requirement already satisfied: anyio in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (4.9.0)\n", - "Requirement already satisfied: certifi in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (2025.4.26)\n", - "Requirement already satisfied: httpcore==1.* in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (1.0.9)\n", - "Requirement already satisfied: idna in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (3.10)\n", - "Requirement already satisfied: h11>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpcore==1.*->httpx>=0.28.1->a2a-samples==0.1.0) (0.16.0)\n", - "Requirement already satisfied: cryptography>=3.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from jwcrypto>=1.5.6->a2a-samples==0.1.0) (45.0.3)\n", - "Requirement already satisfied: annotated-types>=0.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (0.7.0)\n", - "Requirement already satisfied: pydantic-core==2.33.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (2.33.2)\n", - "Requirement already satisfied: typing-inspection>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (0.4.1)\n", - "Requirement already satisfied: click>=7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from uvicorn>=0.34.0->a2a-samples==0.1.0) (8.2.1)\n", - "Requirement already satisfied: sniffio>=1.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from anyio->httpx>=0.28.1->a2a-samples==0.1.0) (1.3.1)\n", - "Requirement already satisfied: cffi>=1.14 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from cryptography>=3.4->jwcrypto>=1.5.6->a2a-samples==0.1.0) (1.17.1)\n", - "Requirement already satisfied: deprecated>=1.2.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from opentelemetry-api>=1.33.0->a2a-sdk>=0.2.4->a2a-samples==0.1.0) (1.2.18)\n", - "Requirement already satisfied: importlib-metadata<8.7.0,>=6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from opentelemetry-api>=1.33.0->a2a-sdk>=0.2.4->a2a-samples==0.1.0) (8.6.1)\n", - "Requirement already satisfied: opentelemetry-semantic-conventions==0.54b1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from opentelemetry-sdk>=1.33.0->a2a-sdk>=0.2.4->a2a-samples==0.1.0) (0.54b1)\n", - "Requirement already satisfied: pycparser in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from cffi>=1.14->cryptography>=3.4->jwcrypto>=1.5.6->a2a-samples==0.1.0) (2.22)\n", - "Requirement already satisfied: wrapt<2,>=1.10 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from deprecated>=1.2.6->opentelemetry-api>=1.33.0->a2a-sdk>=0.2.4->a2a-samples==0.1.0) (1.17.2)\n", - "Requirement already satisfied: zipp>=3.20 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from importlib-metadata<8.7.0,>=6.0->opentelemetry-api>=1.33.0->a2a-sdk>=0.2.4->a2a-samples==0.1.0) (3.22.0)\n", - "\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", - "Requirement already satisfied: llama_stack_client in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (0.2.7)\n", - "Requirement already satisfied: asyncclick in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (8.1.8.0)\n", - "Requirement already satisfied: nest_asyncio in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (1.6.0)\n", - "Requirement already satisfied: ipywidgets in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (8.1.7)\n", - "Requirement already satisfied: dotenv in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (0.9.9)\n", - "Requirement already satisfied: anyio<5,>=3.5.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (4.9.0)\n", - "Requirement already satisfied: click in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (8.2.1)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (1.9.0)\n", - "Requirement already satisfied: httpx<1,>=0.23.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (0.28.1)\n", - "Requirement already satisfied: pandas in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (2.2.3)\n", - "Requirement already satisfied: prompt-toolkit in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (3.0.51)\n", - "Requirement already satisfied: pyaml in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (25.1.0)\n", - "Requirement already satisfied: pydantic<3,>=1.9.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (2.11.5)\n", - "Requirement already satisfied: rich in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (14.0.0)\n", - "Requirement already satisfied: sniffio in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (1.3.1)\n", - "Requirement already satisfied: termcolor in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (3.1.0)\n", - "Requirement already satisfied: tqdm in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (4.67.1)\n", - "Requirement already satisfied: typing-extensions<5,>=4.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from llama_stack_client) (4.13.2)\n", - "Requirement already satisfied: comm>=0.1.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipywidgets) (0.2.2)\n", - "Requirement already satisfied: ipython>=6.1.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipywidgets) (9.2.0)\n", - "Requirement already satisfied: traitlets>=4.3.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipywidgets) (5.14.3)\n", - "Requirement already satisfied: widgetsnbextension~=4.0.14 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipywidgets) (4.0.14)\n", - "Requirement already satisfied: jupyterlab_widgets~=3.0.15 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipywidgets) (3.0.15)\n", - "Requirement already satisfied: python-dotenv in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from dotenv) (1.1.0)\n", - "Requirement already satisfied: idna>=2.8 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from anyio<5,>=3.5.0->llama_stack_client) (3.10)\n", - "Requirement already satisfied: certifi in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpx<1,>=0.23.0->llama_stack_client) (2025.4.26)\n", - "Requirement already satisfied: httpcore==1.* in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpx<1,>=0.23.0->llama_stack_client) (1.0.9)\n", - "Requirement already satisfied: h11>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->llama_stack_client) (0.16.0)\n", - "Requirement already satisfied: decorator in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (5.2.1)\n", - "Requirement already satisfied: ipython-pygments-lexers in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (1.1.1)\n", - "Requirement already satisfied: jedi>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (0.19.2)\n", - "Requirement already satisfied: matplotlib-inline in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (0.1.7)\n", - "Requirement already satisfied: pexpect>4.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (4.9.0)\n", - "Requirement already satisfied: pygments>=2.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (2.19.1)\n", - "Requirement already satisfied: stack_data in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from ipython>=6.1.0->ipywidgets) (0.6.3)\n", - "Requirement already satisfied: wcwidth in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from prompt-toolkit->llama_stack_client) (0.2.13)\n", - "Requirement already satisfied: annotated-types>=0.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (0.7.0)\n", - "Requirement already satisfied: pydantic-core==2.33.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (2.33.2)\n", - "Requirement already satisfied: typing-inspection>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (0.4.1)\n", - "Requirement already satisfied: numpy>=1.26.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pandas->llama_stack_client) (2.2.6)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pandas->llama_stack_client) (2.9.0.post0)\n", - "Requirement already satisfied: pytz>=2020.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pandas->llama_stack_client) (2025.2)\n", - "Requirement already satisfied: tzdata>=2022.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pandas->llama_stack_client) (2025.2)\n", - "Requirement already satisfied: PyYAML in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pyaml->llama_stack_client) (6.0.2)\n", - "Requirement already satisfied: markdown-it-py>=2.2.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from rich->llama_stack_client) (3.0.0)\n", - "Requirement already satisfied: parso<0.9.0,>=0.8.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from jedi>=0.16->ipython>=6.1.0->ipywidgets) (0.8.4)\n", - "Requirement already satisfied: mdurl~=0.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich->llama_stack_client) (0.1.2)\n", - "Requirement already satisfied: ptyprocess>=0.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from pexpect>4.3->ipython>=6.1.0->ipywidgets) (0.7.0)\n", - "Requirement already satisfied: six>=1.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas->llama_stack_client) (1.17.0)\n", - "Requirement already satisfied: executing>=1.2.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from stack_data->ipython>=6.1.0->ipywidgets) (2.2.0)\n", - "Requirement already satisfied: asttokens>=2.1.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from stack_data->ipython>=6.1.0->ipywidgets) (3.0.0)\n", - "Requirement already satisfied: pure-eval in /Users/kcogan/Documents/llama-stack-on-ocp/venv/lib/python3.13/site-packages (from stack_data->ipython>=6.1.0->ipywidgets) (0.2.3)\n", - "\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n" - ] - } - ], - "source": [ - "! pip install \"git+https://github.com/google/A2A.git#subdirectory=samples/python\"\n", - "! pip install llama_stack_client asyncclick nest_asyncio ipywidgets dotenv" - ] - }, - { - "cell_type": "markdown", - "id": "3f49992c", - "metadata": {}, - "source": [ - "Next, we'll add the necessary paths to `sys.path` to ensure we can import the required libraries later in the notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "c49b3fcf", - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "# the path of the A2A library\n", - "sys.path.append('./A2A/samples/python')\n", - "# the path to our own utils\n", - "sys.path.append('../..')" - ] - }, - { - "cell_type": "markdown", - "id": "36e1216b", - "metadata": {}, - "source": [ - "We will now proceed with importing all the necessary Python libraries and modules for the notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "95a70138", - "metadata": {}, - "outputs": [], - "source": [ - "from common.server import A2AServer\n", - "from common.types import AgentCard, AgentSkill, AgentCapabilities\n", - "# from a2a_llama_stack.A2ATool import A2ATool\n", - "# from a2a_llama_stack.task_manager import AgentTaskManager\n", - "\n", - "# for asynchronously serving the A2A agent\n", - "import os\n", - "import time\n", - "import json\n", - "import logging\n", - "from dotenv import load_dotenv\n", - "import urllib.parse\n", - "from uuid import uuid4\n", - "from typing import Any, Dict, List, Tuple\n", - "import subprocess\n", - "import socket\n", - "import asyncio\n", - "import nest_asyncio\n", - "import threading\n", - "import concurrent.futures as cf\n", - "\n", - "\n", - "\n", - "# Importing custom modules from the common package\n", - "from common.client import A2AClient, A2ACardResolver\n", - "from common.utils.push_notification_auth import PushNotificationReceiverAuth\n", - "from hosts.cli.push_notification_listener import PushNotificationListener" - ] - }, - { - "cell_type": "markdown", - "id": "4943937a", - "metadata": {}, - "source": [ - "Next, we will initialize our environment as described in detail in our [\"Getting Started\" notebook](../../rag_agentic/notebooks/Level0_getting_started_with_Llama_Stack.ipynb). Please refer to it for additional explanations." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "c5253d5c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connected to Llama Stack server\n", - "Inference Parameters:\n", - "\tModel: llama3.1:8b-instruct-fp16\n", - "\tSampling Parameters: {'strategy': {'type': 'greedy'}, 'max_tokens': 4096}\n", - "\tstream: False\n" - ] - } - ], - "source": [ - "# for accessing the environment variables\n", - "import os\n", - "from dotenv import load_dotenv\n", - "load_dotenv()\n", - "\n", - "\n", - "base_url = os.getenv(\"REMOTE_BASE_URL\", \"http://localhost:8321\")\n", - " \n", - "print(f\"Connected to Llama Stack server\")\n", - "\n", - "# the model you wish to use that is configured with the Llama Stack server\n", - "model_id = os.getenv(\"INFERENCE_MODEL_ID\", \"llama3.1:8b-instruct-fp16\")\n", - "\n", - "temperature = float(os.getenv(\"TEMPERATURE\", 0.0))\n", - "if temperature > 0.0:\n", - " top_p = float(os.getenv(\"TOP_P\", 0.95))\n", - " strategy = {\"type\": \"top_p\", \"temperature\": temperature, \"top_p\": top_p}\n", - "else:\n", - " strategy = {\"type\": \"greedy\"}\n", - "\n", - "max_tokens = int(os.getenv(\"MAX_TOKENS\", 4096))\n", - "\n", - "# sampling_params will later be used to pass the parameters to Llama Stack Agents/Inference APIs\n", - "sampling_params = {\n", - " \"strategy\": strategy,\n", - " \"max_tokens\": max_tokens,\n", - "}\n", - "\n", - "stream_env = os.getenv(\"STREAM\", \"False\")\n", - "# the Boolean 'stream' parameter will later be passed to Llama Stack Agents/Inference APIs\n", - "# any value non equal to 'False' will be considered as 'True'\n", - "stream = (stream_env != \"False\")\n", - "\n", - "print(f\"Inference Parameters:\\n\\tModel: {model_id}\\n\\tSampling Parameters: {sampling_params}\\n\\tstream: {stream}\")" - ] - }, - { - "cell_type": "markdown", - "id": "5fb3a22f", - "metadata": {}, - "source": [ - "We'll configure basic logging to provide informative output from the agents as they run, which can be very helpful for debugging and understanding the flow." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "86bde581", - "metadata": {}, - "outputs": [], - "source": [ - "# Configuring basic logging for the application\n", - "logging.basicConfig(level=logging.INFO,\n", - " format=\"%(asctime)s %(levelname)s %(name)s: %(message)s\")\n", - "logger = logging.getLogger(__name__)" - ] - }, - { - "cell_type": "markdown", - "id": "91f8fbb5", - "metadata": {}, - "source": [ - "We need to use `nest_asyncio` to make sure things run smoothly when your Python code tries to do multiple tasks at the same time in Jupyter." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "62b7e70c", - "metadata": {}, - "outputs": [], - "source": [ - "nest_asyncio.apply()" - ] - }, - { - "cell_type": "markdown", - "id": "1b8ac79e", - "metadata": {}, - "source": [ - "## 2. Understanding the `AgentManager`\n", - "\n", - "The `AgentManager` class is key to connecting with and managing the different agents in our system.\n", - "\n", - "It handles:\n", - "\n", - "* Connecting to your orchestrator agent.\n", - "\n", - "* Connecting to individual skill agents (the ones that perform specific tasks).\n", - "\n", - "* Managing unique session IDs for each connection.\n", - "\n", - "* Using a helper function (`_send_payload`) to send tasks to the agents." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "e7cea371", - "metadata": {}, - "outputs": [], - "source": [ - "AgentInfo = Tuple[str, Any, A2AClient, str]\n", - "\n", - "class AgentManager:\n", - " def __init__(self, urls: List[str]):\n", - " # first URL is your orchestrator…\n", - " self.orchestrator: AgentInfo = self._make_agent_info(urls[0])\n", - " # …the rest are skill agents, each keyed by skill.id\n", - " self.skills: Dict[str, AgentInfo] = {\n", - " skill.id: info\n", - " for url in urls[1:]\n", - " for info in (self._make_agent_info(url),)\n", - " for skill in info[1].skills\n", - " }\n", - "\n", - " @staticmethod\n", - " def _make_agent_info(url: str) -> AgentInfo:\n", - " card = A2ACardResolver(url).get_agent_card()\n", - " client = A2AClient(agent_card=card)\n", - " session = uuid4().hex\n", - " return url, card, client, session\n", - "\n", - "\n", - "async def _send_payload(client, card, session, payload, streaming: bool) -> str:\n", - " if not streaming:\n", - " res = await client.send_task(payload)\n", - " return res.result.status.message.parts[0].text.strip()\n", - "\n", - " text = \"\"\n", - " async for ev in client.send_task_streaming(payload):\n", - " part = ev.result.status.message.parts[0].text or \"\"\n", - " print(part, end=\"\", flush=True)\n", - " text = part\n", - " print()\n", - " return text" - ] - }, - { - "cell_type": "markdown", - "id": "4ee4d979", - "metadata": {}, - "source": [ - "## 3. Preparing and Sending Tasks to Agents\n", - "\n", - "This section defines functions that format the user's question into a structured message (a JSON payload) that the agents can understand and process. It then uses the `_send_payload` helper from the `AgentManager` to send this task to the appropriate agent." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "a2f9f439", - "metadata": {}, - "outputs": [], - "source": [ - "def _build_skill_meta(mgr):\n", - " \"\"\"Gather unique metadata for every skill in all executor cards.\"\"\"\n", - " unique_skills = {} # Use a dictionary to store skills by their ID\n", - " for _, card, _, _ in mgr.skills.values():\n", - " for s in card.skills:\n", - " if s.id not in unique_skills:\n", - " unique_skills[s.id] = {\n", - " \"skill_id\": s.id,\n", - " \"name\": s.name,\n", - " \"description\": getattr(s, \"description\", None),\n", - " \"tags\": getattr(s, \"tags\", []),\n", - " \"examples\": getattr(s, \"examples\", None),\n", - "\n", - " }\n", - " return list(unique_skills.values()) # Convert the dictionary values back to a list\n", - "\n", - "\n", - "async def _send_task_to_agent(mgr, client, card, session, question, push=False, host=None, port=None) -> str:\n", - " \"\"\"Build a card-driven payload (with optional push) and dispatch it.\"\"\"\n", - " # Input parts\n", - " content = {\"question\": question}\n", - " modes = getattr(card, \"acceptedInputModes\", [\"text\"])\n", - " parts = ([{\"type\": \"json\", \"json\": content}]\n", - " if \"json\" in modes\n", - " else [{\"type\": \"text\", \"text\": json.dumps(content)}])\n", - "\n", - " # Optional push URL & auth\n", - " can_push = push and getattr(card.capabilities, \"pushNotifications\", False)\n", - " push_url = (urllib.parse.urljoin(f\"http://{host}:{port}\", \"/notify\")\n", - " if can_push and host and port else None)\n", - " schemes = getattr(card.authentication, \"supportedSchemes\", [\"bearer\"])\n", - "\n", - " # Assemble payload\n", - " payload = {\n", - " \"id\": uuid4().hex,\n", - " \"sessionId\": session,\n", - " \"acceptedOutputModes\": card.defaultOutputModes,\n", - " \"message\": {\"role\": \"user\", \"parts\": parts},\n", - " **({\"pushNotification\": {\"url\": push_url,\n", - " \"authentication\": {\"schemes\": schemes}}}\n", - " if push_url else {})\n", - " }\n", - "\n", - " # Dispatch, letting the card decide streaming vs one-shot\n", - " stream = getattr(card.capabilities, \"streaming\", False)\n", - " return await _send_payload(client, card, session, payload, stream)\n" - ] - }, - { - "cell_type": "markdown", - "id": "a5862103", - "metadata": {}, - "source": [ - "## 4. Defining the Agent Task Flow: Planning, Execution, and Composition\n", - "This section contains the core logic for how the agents work together to answer a question. It's broken down into three distinct phases:\n", - "\n", - "* **Planning:** The orchestrator agent figures out the steps needed to answer the question and which skill agents to use.\n", - "\n", - "* **Execution:** The notebook code calls the necessary skill agents based on the plan and collects their results.\n", - "\n", - "* **Composition:** A final agent combines the results from the skill agents into a human-friendly answer." - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "b86bd386", - "metadata": {}, - "outputs": [], - "source": [ - "async def _planning_phase(agent_manager, question, push, host, port):\n", - " \"\"\"Ask orchestrator for a plan, parse/fix JSON if necessary.\"\"\"\n", - " _, card, client, sess = agent_manager.orchestrator\n", - "\n", - " skills_meta = _build_skill_meta(agent_manager)\n", - "\n", - " plan_instructions = (\n", - " \"You are an orchestration assistant.\\n\"\n", - " \"Available skills (id & name & description & tags & examples):\\n\"\n", - " f\"{json.dumps(skills_meta, indent=2)}\\n\\n\"\n", - " \"When given a user question, respond _only_ with a JSON array of objects, \"\n", - " \"each with key `skill_id`, without any surrounding object. You may be asked to write single or multiple skills.\\n\"\n", - " \"For example for multiple tools:\\n\"\n", - " \"[\"\n", - " \"{\\\"skill_id\\\": \\\"tool_1\\\"}, \"\n", - " \"{\\\"skill_id\\\": \\\"tool_2\\\"}\"\n", - " \"]\"\n", - " )\n", - " combined = plan_instructions + \"\\n\\nUser question: \" + question\n", - "\n", - " raw = await _send_task_to_agent(agent_manager, client, card, sess, combined, push=push, host=host, port=port)\n", - " print(f\"Raw plan ➡️ {raw}\")\n", - "\n", - " try:\n", - " return json.loads(raw[: raw.rfind(\"]\") + 1])\n", - " except ValueError:\n", - " print(\"\\033[31mPlan parse failed, fixing invalid JSON...\\033[0m\")\n", - " fixer = \"Fix this json to be valid: \" + raw\n", - " fixed = await _send_task_to_agent(agent_manager, client, card, sess, fixer, push=push, host=host, port=port)\n", - " return json.loads(fixed)\n", - "\n", - "\n", - "async def _execution_phase(agent_manager, plan, push, host, port):\n", - " \"\"\"Run each step in the plan via its skill and collect outputs.\"\"\"\n", - " results = []\n", - " for i, step in enumerate(plan, 1):\n", - " sid, inp = step[\"skill_id\"], json.dumps(step.get(\"input\", {}))\n", - " print(f\"➡️ Step {i}: {sid}({inp})\")\n", - "\n", - " info = agent_manager.skills.get(sid)\n", - " if not info:\n", - " print(f\"\\033[31mNo executor for '{sid}', skipping.\\033[0m\")\n", - " results.append({\"skill_id\": sid, \"output\": None})\n", - " continue\n", - "\n", - " _, skill_card, skill_client, skill_sess = info\n", - " out = await _send_task_to_agent(agent_manager, skill_client, skill_card, skill_sess, f\"{sid}({inp})\", push=push, host=host, port=port)\n", - " print(f\" ✅ → {out}\")\n", - " results.append({\"skill_id\": sid, \"output\": out})\n", - "\n", - " return results\n", - "\n", - "def _compose_prompt(parts, question):\n", - " \"\"\"Create the final composition prompt for the orchestrator.\"\"\"\n", - " return (\n", - " f\"Using the following information: {json.dumps(parts)}, \"\n", - " f\"write a clear and human-friendly response to the question: '{question}'. \"\n", - " \"Keep it concise and easy to understand and respond like a human with character. \"\n", - " \"Only use the information provided. If you cannot answer the question, say 'I don't know'. \"\n", - " \"Never show any code or JSON or Markdown, just the answer.\\n\\n\"\n", - " )\n" - ] - }, - { - "cell_type": "markdown", - "id": "6c6d3fe2", - "metadata": {}, - "source": [ - "## 5. Orchestrating the Agent Interaction\n", - "This is the main function that ties together the planning, execution, and composition phases to answer a user's question using the agent team." - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "86d7612a", - "metadata": {}, - "outputs": [], - "source": [ - "async def ask_question(\n", - " agent_manager: AgentManager,\n", - " question: str,\n", - " push: bool = False,\n", - " push_receiver: str = \"http://localhost:5000\",\n", - ") -> str:\n", - " # Unpack orchestrator info\n", - " orch_url, orch_card, orch_client, orch_session = agent_manager.orchestrator\n", - "\n", - " # Optionally start push listener\n", - " host = port = None\n", - " if push:\n", - " parsed = urllib.parse.urlparse(push_receiver)\n", - " host, port = parsed.hostname, parsed.port\n", - " auth = PushNotificationReceiverAuth()\n", - " await auth.load_jwks(f\"{orch_url}/.well-known/jwks.json\")\n", - " PushNotificationListener(\n", - " host=host,\n", - " port=port,\n", - " notification_receiver_auth=auth\n", - " ).start()\n", - "\n", - " # --- Planning Phase ---\n", - " print(\"\\n\\033[1;33m=========== 🧠 Planning Phase ===========\\033[0m\")\n", - " plan = await _planning_phase(agent_manager, question, push=push, host=host, port=port)\n", - " print(f\"\\n\\033[1;32mFinal plan ➡️ {plan}\\033[0m\")\n", - "\n", - " # --- Execution Phase ---\n", - " print(\"\\n\\033[1;33m=========== ⚡️ Execution Phase ===========\\033[0m\")\n", - " parts = await _execution_phase(agent_manager, plan, push=push, host=host, port=port)\n", - "\n", - " # --- Composing Answer ---\n", - " print(\"\\n\\033[1;33m=========== 🛠️ Composing Answer ===========\\033[0m\")\n", - " comp_prompt = _compose_prompt(parts, question)\n", - " WRITING_AGENT_ID = \"writing_agent\"\n", - "\n", - " _, skill_card, skill_client, skill_sess = agent_manager.skills.get(WRITING_AGENT_ID)\n", - " final = await _send_task_to_agent(agent_manager, skill_client, skill_card, skill_sess, comp_prompt, push=push, host=host, port=port)\n", - "\n", - " print(\"\\n\\033[1;36m🎉 FINAL ANSWER\\033[0m\")\n", - " print(final)\n", - " print(\"\\033[1;36m====================================\\033[0m\")\n", - " return final\n" - ] - }, - { - "cell_type": "markdown", - "id": "4d580d4f", - "metadata": {}, - "source": [ - "## 6. Launching the A2A Agent Servers\n", - "Before we can interact with the agents, we need to start their servers. This bootstrap script handles bringing the complete A2A stack online.\n", - "\n", - "It performs three key actions in sequence:\n", - "\n", - "1. Defines connection details (ports and modules).\n", - "\n", - "2. Starts each agent in parallel and waits for them to be ready.\n", - "\n", - "3. Connects to the running agents and summarizes their status." - ] - }, - { - "cell_type": "markdown", - "id": "60bd2d20", - "metadata": {}, - "source": [ - "Below, we set up the network addresses (`URLS`) for our orchestrator and skill agents, and specify the Python modules that implement their functionality. These definitions are crucial for starting and connecting to the agents in the next steps." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "ceb43397", - "metadata": {}, - "outputs": [], - "source": [ - "ORCHESTRATOR_URL = \"http://localhost:10010\"\n", - "EXECUTOR_URLS = [\"http://localhost:10011\", \"http://localhost:10012\"]\n", - "URLS = [ORCHESTRATOR_URL, *EXECUTOR_URLS]\n", - "AGENT_NAMES = [\n", - " \"a2a_planner\",\n", - " \"a2a_custom_tools\",\n", - " \"a2a_composer\",\n", - "]" - ] - }, - { - "cell_type": "markdown", - "id": "9580373e", - "metadata": {}, - "source": [ - "This launches the agent processes and wait until each server is ready and listening on its assigned port." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "5c5713c9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "🗂 package_dir = /Users/kcogan/Documents/llama-stack-on-ocp/demos/a2a_llama_stack\n", - "🔗 python_path_root = /Users/kcogan/Documents/llama-stack-on-ocp/demos\n", - "\n", - "✅ a2a_planner ready on port 10010\n", - "✅ a2a_custom_tools ready on port 10011\n", - "✅ a2a_composer ready on port 10012\n" - ] - } - ], - "source": [ - "# move into the package directory and add the parent directory to the PYTHONPATH\n", - "package_dir = os.path.dirname(os.getcwd())\n", - "python_path_root = os.path.dirname(package_dir)\n", - "sys.path.insert(0, python_path_root)\n", - "\n", - "print(f\"🗂 package_dir = {package_dir}\")\n", - "print(f\"🔗 python_path_root = {python_path_root}\\n\")\n", - "\n", - "\n", - "def _launch(url, agent_name):\n", - " port = int(url.rsplit(\":\", 1)[1])\n", - "\n", - " # give the subprocess a PYTHONPATH so it sees your local package\n", - " env = os.environ.copy()\n", - " env[\"PYTHONPATH\"] = python_path_root + os.pathsep + env.get(\"PYTHONPATH\", \"\")\n", - "\n", - " subprocess.Popen(\n", - " [sys.executable, \"-m\", \"a2a_llama_stack\",\n", - " \"--agent-name\", agent_name, \"--port\", str(port)],\n", - " cwd=package_dir,\n", - " env=env,\n", - " stdout=subprocess.DEVNULL,\n", - " stderr=subprocess.DEVNULL,\n", - " )\n", - "\n", - " # wait until it's listening\n", - " while socket.socket().connect_ex((\"127.0.0.1\", port)) != 0:\n", - " time.sleep(0.1)\n", - "\n", - " return f\"✅ {agent_name} ready on port {port}\"\n", - "\n", - "# run the subprocesses in parallel\n", - "with cf.ThreadPoolExecutor() as pool:\n", - " print(*pool.map(_launch, URLS, AGENT_NAMES), sep=\"\\n\")\n" - ] - }, - { - "cell_type": "markdown", - "id": "96c1a74a", - "metadata": {}, - "source": [ - "Now that the agents should be running, we'll use our `AgentManager` to connect to them, confirm they are online, and see what skills they offer." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "0ea51ded", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:35:51,276 INFO httpx: HTTP Request: GET http://localhost:10010/.well-known/agent.json \"HTTP/1.1 200 OK\"\n", - "2025-05-27 13:35:51,284 INFO httpx: HTTP Request: GET http://localhost:10011/.well-known/agent.json \"HTTP/1.1 200 OK\"\n", - "2025-05-27 13:35:51,290 INFO httpx: HTTP Request: GET http://localhost:10012/.well-known/agent.json \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\u001b[1;36m===================== 🛰️ Connected Agents =====================\u001b[0m\n", - "Orchestrator: http://localhost:10010 (Orchestration Agent)\n", - "Executors:\n", - " • random_number_tool -> http://localhost:10011 (Custom Agent)\n", - " • date_tool -> http://localhost:10011 (Custom Agent)\n", - " • writing_agent -> http://localhost:10012 (Writing Agent)\n", - "\u001b[1;36m===============================================================\u001b[0m\n" - ] - } - ], - "source": [ - "_agent_manager = AgentManager(URLS)\n", - "orch_url, orch_card, *_ = _agent_manager.orchestrator\n", - "\n", - "print(\"\\n\\033[1;36m===================== 🛰️ Connected Agents =====================\\033[0m\")\n", - "print(f\"Orchestrator: {orch_url} ({orch_card.name})\")\n", - "print(\"Executors:\")\n", - "for sid, (u, card, *_) in _agent_manager.skills.items():\n", - " print(f\" • {sid} -> {u} ({card.name})\")\n", - "print(\"\\033[1;36m===============================================================\\033[0m\")" - ] - }, - { - "cell_type": "markdown", - "id": "f32892ab", - "metadata": {}, - "source": [ - "## 7. Asking the Agent Team a Question!\n", - "\n", - "Finally, it's time to put our agent team to work! We'll use the `ask_question` function we defined earlier to send our queries and see the multi-agent system in action." - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "6476a816", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\u001b[1;33m=========== 🧠 Planning Phase ===========\u001b[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:36:11,043 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Raw plan ➡️ [\n", - " {\"skill_id\": \"date_tool\"},\n", - " {\"skill_id\": \"random_number_tool\"},\n", - " {\"skill_id\": \"random_number_tool\"},\n", - " {\"skill_id\": \"random_number_tool\"},\n", - " {\"skill_id\": \"random_number_tool\"},\n", - " {\"skill_id\": \"random_number_tool\"}\n", - "]\n", - "\n", - "\u001b[1;32mFinal plan ➡️ [{'skill_id': 'date_tool'}, {'skill_id': 'random_number_tool'}, {'skill_id': 'random_number_tool'}, {'skill_id': 'random_number_tool'}, {'skill_id': 'random_number_tool'}, {'skill_id': 'random_number_tool'}]\u001b[0m\n", - "\n", - "\u001b[1;33m=========== ⚡️ Execution Phase ===========\u001b[0m\n", - "➡️ Step 1: date_tool({})\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:36:18,598 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " ✅ → {\n", - " \"type\": \"function\",\n", - " \"name\": \"date_tool\",\n", - " \"parameters\": {}\n", - "}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-05-27\"{\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": {}}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-05-27\"The output of the function call is today's date.\n", - "➡️ Step 2: random_number_tool({})\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:36:23,209 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:34The output of the function call is a random integer between 1 and 100.\n", - "➡️ Step 3: random_number_tool({})\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:36:27,805 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:26The output of the function call is a random integer between 1 and 100.\n", - "➡️ Step 4: random_number_tool({})\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:36:32,435 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:91The output of the function call is a random integer between 1 and 100.\n", - "➡️ Step 5: random_number_tool({})\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:36:37,079 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:57The output of the function call is a random integer between 1 and 100.\n", - "➡️ Step 6: random_number_tool({})\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:36:41,721 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:44The output of the function call is a random integer between 1 and 100.\n", - "\n", - "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:36:46,891 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\u001b[1;36m🎉 FINAL ANSWER\u001b[0m\n", - "Here's your date and five random numbers for today:\n", - "\n", - "Today is May 27th, 2025.\n", - "\n", - "And here are five random numbers: 34, 26, 91, 57, and 44.\n", - "\u001b[1;36m====================================\u001b[0m\n", - "\n", - "\u001b[1;33m=========== 🧠 Planning Phase ===========\u001b[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:36:52,971 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Raw plan ➡️ [\n", - " {\"skill_id\": \"date_tool\"}\n", - "]\n", - "\n", - "\u001b[1;32mFinal plan ➡️ [{'skill_id': 'date_tool'}]\u001b[0m\n", - "\n", - "\u001b[1;33m=========== ⚡️ Execution Phase ===========\u001b[0m\n", - "➡️ Step 1: date_tool({})\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:36:58,806 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": {}}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-05-27\"The output of the function call is today's date.\n", - "\n", - "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:37:01,191 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\u001b[1;36m🎉 FINAL ANSWER\u001b[0m\n", - "Today's date is May 27th, 2025!\n", - "\u001b[1;36m====================================\u001b[0m\n", - "\n", - "\u001b[1;33m=========== 🧠 Planning Phase ===========\u001b[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:37:06,871 INFO httpx: HTTP Request: POST http://0.0.0.0:10010/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Raw plan ➡️ [\n", - " {\"skill_id\": \"random_number_tool\"}\n", - "]\n", - "\n", - "\u001b[1;32mFinal plan ➡️ [{'skill_id': 'random_number_tool'}]\u001b[0m\n", - "\n", - "\u001b[1;33m=========== ⚡️ Execution Phase ===========\u001b[0m\n", - "➡️ Step 1: random_number_tool({})\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:37:13,798 INFO httpx: HTTP Request: POST http://0.0.0.0:10011/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " ✅ → {\"type\": \"function\", \"name\": \"random_number_tool\", \"parameters\": {}}Tool:random_number_tool Args:{}Tool:random_number_tool Response:48The output of the function call is a random integer between 1 and 100.\n", - "\n", - "\u001b[1;33m=========== 🛠️ Composing Answer ===========\u001b[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-05-27 13:37:14,941 INFO httpx: HTTP Request: POST http://0.0.0.0:10012/ \"HTTP/1.1 200 OK\"\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\u001b[1;36m🎉 FINAL ANSWER\u001b[0m\n", - "Here's your random number: 48!\n", - "\u001b[1;36m====================================\u001b[0m\n" - ] - } - ], - "source": [ - "questions = [ \n", - " \"Get todays date then generate five random numbers\",\n", - " \"Get todays date?\",\n", - " \"generate a random number\",\n", - " ]\n", - "\n", - "for question in questions:\n", - " await ask_question(_agent_manager, question)" - ] - }, - { - "cell_type": "markdown", - "id": "3d0c00af", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "Congratulations! You've successfully set up and interacted with a multi-agent system using the A2A protocol. You saw how an orchestrator agent planned the task, how skill agents executed the steps, and how a composition agent put it all together for you.\n", - "\n", - "Future demos will cover more advanced aspects of agent-to-agent communication." - ] - } - ], - "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.13.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/demos/a2a_llama_stack/notebooks/A2A_Quickstart_Guide.ipynb b/demos/a2a_llama_stack/notebooks/A2A_Quickstart_Guide.ipynb new file mode 100644 index 0000000..6bd9e48 --- /dev/null +++ b/demos/a2a_llama_stack/notebooks/A2A_Quickstart_Guide.ipynb @@ -0,0 +1,603 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "fef1ba28", + "metadata": {}, + "source": [ + "# A simple agent A2A Application with Custom Tools\n", + "\n", + "This notebook presents a simple scenario where an agent uses the A2A protocol to query another agent for information using custom tools. We show how to initialize an agent in Llama Stack and grant it access to communicating with another, external agent.\n", + "\n", + "This demo demonstrates core A2A communication principles.\n", + "\n", + "## Overview\n", + "\n", + "This notebook covers the following steps:\n", + "\n", + "1. Setting up a Llama Stack agent with custom tool capabilities (e.g., random number generation, date retrieval).\n", + "2. Serving this agent over an A2A server.\n", + "3. Initializing another Llama Stack agent capable of communicating with the custom tool agent.\n", + "4. Launching the second agent and using it to answer user queries by leveraging the first agent's tools.\n", + "\n", + "## Prerequisites\n", + "\n", + "Before starting, ensure you have the following:\n", + "- `python_requires >= 3.13`\n", + "\n", + "- Followed the instructions in the [Setup Guide](../../rag_agentic/notebooks/Level0_getting_started_with_Llama_Stack.ipynb) notebook.\n", + "\n", + "## Additional environment variables\n", + "This demo requires the following environment variables in addition to those defined in the [Setup Guide](../../rag_agentic/notebooks//Level0_getting_started_with_Llama_Stack.ipynb):\n", + "- `CUSTOM_TOOL_AGENT_LOCAL_PORT`: the port over which we will serve the exported A2A agent with custom tool capabilities." + ] + }, + { + "cell_type": "markdown", + "id": "b4c88e09", + "metadata": {}, + "source": [ + "## 1. Setting Up this Notebook\n", + "To provide A2A communication capabilities, we will use the [sample implementation by Google](https://github.com/google/A2A/tree/main/samples/python). Please make sure that the content of the referenced directory is available on your Python path. This can be done, for example, by running the following command:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "01c195db", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fatal: destination path 'a2a-samples' already exists and is not an empty directory.\n", + "Requirement already satisfied: annotated-types==0.7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 1)) (0.7.0)\n", + "Requirement already satisfied: anyio==4.9.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 2)) (4.9.0)\n", + "Requirement already satisfied: appnope==0.1.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 3)) (0.1.4)\n", + "Requirement already satisfied: asttokens==3.0.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 4)) (3.0.0)\n", + "Requirement already satisfied: certifi==2025.1.31 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 5)) (2025.1.31)\n", + "Requirement already satisfied: cffi==1.17.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 6)) (1.17.1)\n", + "Requirement already satisfied: charset-normalizer==3.4.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 7)) (3.4.2)\n", + "Requirement already satisfied: click==8.1.8 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 8)) (8.1.8)\n", + "Requirement already satisfied: comm==0.2.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 9)) (0.2.2)\n", + "Requirement already satisfied: cryptography==45.0.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 10)) (45.0.3)\n", + "Requirement already satisfied: debugpy==1.8.14 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 11)) (1.8.14)\n", + "Requirement already satisfied: decorator==5.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 12)) (5.2.1)\n", + "Requirement already satisfied: distro==1.9.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 13)) (1.9.0)\n", + "Requirement already satisfied: dotenv==0.9.9 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 14)) (0.9.9)\n", + "Requirement already satisfied: executing==2.2.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 15)) (2.2.0)\n", + "Requirement already satisfied: fire==0.7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 16)) (0.7.0)\n", + "Requirement already satisfied: h11==0.16.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 17)) (0.16.0)\n", + "Requirement already satisfied: httpcore==1.0.9 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 18)) (1.0.9)\n", + "Requirement already satisfied: httpx==0.28.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 19)) (0.28.1)\n", + "Requirement already satisfied: httpx-sse==0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 20)) (0.4.0)\n", + "Requirement already satisfied: idna==3.10 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 21)) (3.10)\n", + "Requirement already satisfied: ipykernel==6.29.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 22)) (6.29.5)\n", + "Requirement already satisfied: ipython==9.3.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 23)) (9.3.0)\n", + "Requirement already satisfied: ipython_pygments_lexers==1.1.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 24)) (1.1.1)\n", + "Requirement already satisfied: jedi==0.19.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 25)) (0.19.2)\n", + "Requirement already satisfied: jupyter_client==8.6.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 26)) (8.6.3)\n", + "Requirement already satisfied: jupyter_core==5.8.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 27)) (5.8.1)\n", + "Requirement already satisfied: jwcrypto==1.5.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 28)) (1.5.6)\n", + "Requirement already satisfied: llama_stack_client==0.2.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 29)) (0.2.2)\n", + "Requirement already satisfied: markdown-it-py==3.0.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 30)) (3.0.0)\n", + "Requirement already satisfied: matplotlib-inline==0.1.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 31)) (0.1.7)\n", + "Requirement already satisfied: mdurl==0.1.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 32)) (0.1.2)\n", + "Requirement already satisfied: nest-asyncio==1.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 33)) (1.6.0)\n", + "Requirement already satisfied: numpy==2.2.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 34)) (2.2.5)\n", + "Requirement already satisfied: packaging==25.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 35)) (25.0)\n", + "Requirement already satisfied: pandas==2.2.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 36)) (2.2.3)\n", + "Requirement already satisfied: parso==0.8.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 37)) (0.8.4)\n", + "Requirement already satisfied: pexpect==4.9.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 38)) (4.9.0)\n", + "Requirement already satisfied: platformdirs==4.3.8 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 39)) (4.3.8)\n", + "Requirement already satisfied: prompt_toolkit==3.0.51 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 40)) (3.0.51)\n", + "Requirement already satisfied: psutil==7.0.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 41)) (7.0.0)\n", + "Requirement already satisfied: ptyprocess==0.7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 42)) (0.7.0)\n", + "Requirement already satisfied: pure_eval==0.2.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 43)) (0.2.3)\n", + "Requirement already satisfied: pyaml==25.1.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 44)) (25.1.0)\n", + "Requirement already satisfied: pycparser==2.22 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 45)) (2.22)\n", + "Requirement already satisfied: pydantic==2.11.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 46)) (2.11.3)\n", + "Requirement already satisfied: pydantic_core==2.33.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 47)) (2.33.1)\n", + "Requirement already satisfied: Pygments==2.19.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 48)) (2.19.1)\n", + "Requirement already satisfied: PyJWT==2.10.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 49)) (2.10.1)\n", + "Requirement already satisfied: python-dateutil==2.9.0.post0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 50)) (2.9.0.post0)\n", + "Requirement already satisfied: python-dotenv==1.1.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 51)) (1.1.0)\n", + "Requirement already satisfied: pytz==2025.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 52)) (2025.2)\n", + "Requirement already satisfied: PyYAML==6.0.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 53)) (6.0.2)\n", + "Requirement already satisfied: pyzmq==26.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 54)) (26.4.0)\n", + "Requirement already satisfied: requests==2.32.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 55)) (2.32.3)\n", + "Requirement already satisfied: rich==14.0.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 56)) (14.0.0)\n", + "Requirement already satisfied: six==1.17.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 57)) (1.17.0)\n", + "Requirement already satisfied: sniffio==1.3.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 58)) (1.3.1)\n", + "Requirement already satisfied: sse-starlette==2.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 59)) (2.2.1)\n", + "Requirement already satisfied: stack-data==0.6.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 60)) (0.6.3)\n", + "Requirement already satisfied: starlette==0.46.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 61)) (0.46.2)\n", + "Requirement already satisfied: termcolor==3.0.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 62)) (3.0.1)\n", + "Requirement already satisfied: tornado==6.5.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 63)) (6.5.1)\n", + "Requirement already satisfied: tqdm==4.67.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 64)) (4.67.1)\n", + "Requirement already satisfied: traitlets==5.14.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 65)) (5.14.3)\n", + "Requirement already satisfied: typing-inspection==0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 66)) (0.4.0)\n", + "Requirement already satisfied: typing_extensions==4.13.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 67)) (4.13.2)\n", + "Requirement already satisfied: tzdata==2025.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 68)) (2025.2)\n", + "Requirement already satisfied: urllib3==2.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 69)) (2.4.0)\n", + "Requirement already satisfied: uvicorn==0.34.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 70)) (0.34.2)\n", + "Requirement already satisfied: wcwidth==0.2.13 in /Users/kcogan/Documents/llama-stack-on-ocp/venv5/lib/python3.13/site-packages (from -r ../requirements.txt (line 71)) (0.2.13)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n" + ] + } + ], + "source": [ + "! git clone https://github.com/google-a2a/a2a-samples.git\n", + "! pip install -r \"../requirements.txt\"" + ] + }, + { + "cell_type": "markdown", + "id": "d192c7e7", + "metadata": {}, + "source": [ + "Now, we will add the paths to the A2A library and our own tools to `sys.path`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2a99e55d", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "# the path of the A2A library\n", + "sys.path.append('./a2a-samples/samples/python')\n", + "# the path to our own utils\n", + "sys.path.append('../..')" + ] + }, + { + "cell_type": "markdown", + "id": "73be7200", + "metadata": {}, + "source": [ + "We will now proceed with the necessary imports." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "26570e4c", + "metadata": {}, + "outputs": [], + "source": [ + "from common.server import A2AServer\n", + "from common.types import AgentCard, AgentSkill, AgentCapabilities\n", + "from a2a_llama_stack.A2ATool import A2ATool\n", + "from a2a_llama_stack.task_manager import AgentTaskManager\n", + "\n", + "# for asynchronously serving the A2A agent\n", + "import threading" + ] + }, + { + "cell_type": "markdown", + "id": "bd2f018c", + "metadata": {}, + "source": [ + "Next, we will initialize our environment as described in detail in our [\"Getting Started\" notebook](../../rag_agentic/notebooks/Level0_getting_started_with_Llama_Stack.ipynb). Please refer to it for additional explanations." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2de3ee44", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connected to Llama Stack server\n", + "Inference Parameters:\n", + "\tModel: llama3.1:8b-instruct-fp16\n", + "\tSampling Parameters: {'strategy': {'type': 'greedy'}, 'max_tokens': 4096}\n", + "\tstream: False\n" + ] + } + ], + "source": [ + "# for accessing the environment variables\n", + "import os\n", + "from dotenv import load_dotenv\n", + "load_dotenv()\n", + "\n", + "# for communication with Llama Stack\n", + "from llama_stack_client import LlamaStackClient\n", + "\n", + "# agent related imports\n", + "import uuid\n", + "from llama_stack_client import Agent\n", + "from llama_stack_client.lib.agents.event_logger import EventLogger\n", + "\n", + "# pretty print of the results returned from the model/agent - import from the rag_agentic demo subdirectory\n", + "import sys\n", + "sys.path.append('../../rag_agentic') \n", + "from src.utils import step_printer\n", + "from termcolor import cprint\n", + "\n", + "\n", + "base_url = os.getenv(\"REMOTE_BASE_URL\")\n", + "\n", + "\n", + "# Tavily search API key is required for some of our demos and must be provided to the client upon initialization.\n", + "# We will cover it in the agentic demos that use the respective tool. Please ignore this parameter for all other demos.\n", + "tavily_search_api_key = os.getenv(\"TAVILY_SEARCH_API_KEY\")\n", + "if tavily_search_api_key is None:\n", + " provider_data = None\n", + "else:\n", + " provider_data = {\"tavily_search_api_key\": tavily_search_api_key}\n", + "\n", + "\n", + "client = LlamaStackClient(\n", + " base_url=base_url,\n", + " provider_data=provider_data\n", + ")\n", + " \n", + "print(f\"Connected to Llama Stack server\")\n", + "\n", + "# model_id for the model you wish to use that is configured with the Llama Stack server\n", + "model_id = os.getenv(\"INFERENCE_MODEL_ID\")\n", + "\n", + "temperature = float(os.getenv(\"TEMPERATURE\", 0.0))\n", + "if temperature > 0.0:\n", + " top_p = float(os.getenv(\"TOP_P\", 0.95))\n", + " strategy = {\"type\": \"top_p\", \"temperature\": temperature, \"top_p\": top_p}\n", + "else:\n", + " strategy = {\"type\": \"greedy\"}\n", + "\n", + "max_tokens = int(os.getenv(\"MAX_TOKENS\", 4096))\n", + "\n", + "# sampling_params will later be used to pass the parameters to Llama Stack Agents/Inference APIs\n", + "sampling_params = {\n", + " \"strategy\": strategy,\n", + " \"max_tokens\": max_tokens,\n", + "}\n", + "\n", + "stream_env = os.getenv(\"STREAM\", \"False\")\n", + "# the Boolean 'stream' parameter will later be passed to Llama Stack Agents/Inference APIs\n", + "# any value non equal to 'False' will be considered as 'True'\n", + "stream = (stream_env != \"False\")\n", + "\n", + "print(f\"Inference Parameters:\\n\\tModel: {model_id}\\n\\tSampling Parameters: {sampling_params}\\n\\tstream: {stream}\")" + ] + }, + { + "cell_type": "markdown", + "id": "f6631ae7", + "metadata": {}, + "source": [ + "## 2. Setting Up and Serving a Simple A2A Agent with Custom Tools\n", + "We will now initialize an agent with custom tools (random number generator and date tool) and make it available via an A2A server.\n", + "\n", + "Our first steps involve defining these custom tools and then creating an agent that knows how to use them.\n", + "- Define Python functions that will serve as our tools.\n", + "- Initialize a Llama Stack agent, providing it with these tools and instructions on how to use them." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "972b1ee0", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "from datetime import datetime\n", + "\n", + "\n", + "def random_number_tool() -> int:\n", + " \"\"\"\n", + " Generate a random integer between 1 and 100.\n", + " \"\"\"\n", + " print(\"\\n\\nGenerating a random number...\\n\\n\")\n", + " return random.randint(1, 100)\n", + "\n", + "\n", + "def date_tool() -> str:\n", + " \"\"\"\n", + " Return today's date in YYYY-MM-DD format.\n", + " \"\"\"\n", + " return datetime.utcnow().date().isoformat()" + ] + }, + { + "cell_type": "markdown", + "id": "6934c92d", + "metadata": {}, + "source": [ + "- Initialize a Llama Stack agent with a list of tools including the built-in RAG tool. The RAG tool specification must include a list of document collection IDs to retrieve from." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "1b71d907", + "metadata": {}, + "outputs": [], + "source": [ + "custom_tool_agent = Agent(\n", + " client,\n", + " model=model_id,\n", + " instructions=(\n", + " \"You have access to two tools:\\n\"\n", + " \"- random_number_tool: generates one random integer between 1 and 100\\n\"\n", + " \"- date_tool: returns today's date in YYYY-MM-DD format\\n\"\n", + " \"Always use the appropriate tool to answer user queries.\"\n", + " ), \n", + " sampling_params=sampling_params,\n", + " tools=[random_number_tool, date_tool],\n", + " max_infer_iters=3,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "b73064c0", + "metadata": {}, + "source": [ + "Now, our Llama Stack agent is ready to be served as an A2A agent. This includes the following steps:\n", + " - Create an `AgentCard` - an object containing all the details about the agent we are about to serve, including its URL and exposed capabilities.\n", + " - Wrap the Llama Stack agent with an `AgentTaskManager` object - a wrapper/adapter making it possible for the A2A server to redirect incoming request to the Llama Stack agent.\n", + " - Create and launch an `A2AServer` - a Rest API server capable of communicating via the A2A protocol." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fab56668", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO: Started server process [4581]\n", + "INFO: Waiting for application startup.\n", + "INFO: Application startup complete.\n", + "INFO: Uvicorn running on http://localhost:10020 (Press CTRL+C to quit)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INFO: 127.0.0.1:49654 - \"GET /.well-known/agent.json HTTP/1.1\" 200 OK\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/ipykernel_4581/2943416845.py:17: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).\n", + " return datetime.utcnow().date().isoformat()\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INFO: ::1:49667 - \"POST / HTTP/1.1\" 200 OK\n", + "INFO: ::1:49696 - \"POST / HTTP/1.1\" 200 OK\n" + ] + } + ], + "source": [ + "custom_tool_agent_local_port = int(os.getenv(\"CUSTOM_TOOL_AGENT_LOCAL_PORT\", \"10020\"))\n", + "custom_tool_agent_url = f\"http://localhost:{custom_tool_agent_local_port}\"\n", + "\n", + "agent_card = AgentCard(\n", + " name=\"Custom Agent\",\n", + " description=\"Generates random numbers or retrieve today's dates\",\n", + " url=custom_tool_agent_url,\n", + " version=\"0.1.0\",\n", + " defaultInputModes=[\"text/plain\"],\n", + " defaultOutputModes=[\"text/plain\"],\n", + " capabilities=AgentCapabilities(\n", + " streaming=True,\n", + " pushNotifications=False,\n", + " stateTransitionHistory=False,\n", + " ),\n", + " skills=[\n", + " AgentSkill(\n", + " id=\"random_number_tool\", \n", + " name=\"Random Number Generator\",\n", + " description=\"Generates a random number between 1 and 100\",\n", + " tags=[\"random\"],\n", + " examples=[\"Give me a random number between 1 and 100\"],\n", + " inputModes=[\"text/plain\"],\n", + " outputModes=[\"text/plain\"],\n", + " ),\n", + " AgentSkill(\n", + " id=\"date_tool\",\n", + " name=\"Date Provider\",\n", + " description=\"Returns today's date in YYYY-MM-DD format\",\n", + " tags=[\"date\"],\n", + " examples=[\"What's the date today?\"],\n", + " inputModes=[\"text/plain\"],\n", + " outputModes=[\"text/plain\"],\n", + " ),\n", + " ],\n", + ")\n", + "task_manager = AgentTaskManager(agent=custom_tool_agent)\n", + "server = A2AServer(\n", + " agent_card=agent_card,\n", + " task_manager=task_manager,\n", + " host='localhost',\n", + " port=custom_tool_agent_local_port\n", + ")\n", + "thread = threading.Thread(target=server.start, daemon=True)\n", + "thread.start()" + ] + }, + { + "cell_type": "markdown", + "id": "937dbee6", + "metadata": {}, + "source": [ + "## 3. Setting up an agent capable of A2A communication with the CUSTOM_TOOL agent\n", + "This includes the following steps:\n", + " - Create a Llama Stack client tool that wraps A2A communication with the CUSTOM_TOOL agent.\n", + " - Initialize a client agent with access to the above client tool." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "ece2521b", + "metadata": {}, + "outputs": [], + "source": [ + "custom_tool_agent_tool = A2ATool(custom_tool_agent_url)\n", + "a2a_client_agent = Agent(\n", + " client,\n", + " model=model_id,\n", + " instructions=\"You are a helpful assistant. When a tool is used, only print its output without adding more content.\",\n", + " sampling_params=sampling_params,\n", + " tools=[custom_tool_agent_tool],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "bf46ddf5", + "metadata": {}, + "source": [ + "Now, let's use our client agent for serving user requests." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "4da019ad", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34m\n", + "User> What is today's date?\u001b[0m\n", + "\n", + "---------- 📍 Step 1: InferenceStep ----------\n", + "🛠️ Tool call Generated:\n", + "\u001b[35mTool call: Custom Agent, Arguments: {'query': \"today's date\"}\u001b[0m\n", + "\n", + "---------- 📍 Step 2: ToolExecutionStep ----------\n", + "🔧 Executing tool...\n" + ] + }, + { + "data": { + "text/html": [ + "
'{\\n    \"type\": \"function\",\\n    \"name\": \"date_tool\",\\n    \"parameters\": {}\\n}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-06-03\"{\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": {}}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-06-03\"I used the date_tool function to get today\\'s date.'\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[32m'\u001b[0m\u001b[32m{\u001b[0m\u001b[32m\\n \"type\": \"function\",\\n \"name\": \"date_tool\",\\n \"parameters\": \u001b[0m\u001b[32m{\u001b[0m\u001b[32m}\u001b[0m\u001b[32m\\n\u001b[0m\u001b[32m}\u001b[0m\u001b[32mTool:date_tool Args:\u001b[0m\u001b[32m{\u001b[0m\u001b[32m}\u001b[0m\u001b[32mTool:date_tool Response:\"2025-06-03\"\u001b[0m\u001b[32m{\u001b[0m\u001b[32m\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": \u001b[0m\u001b[32m{\u001b[0m\u001b[32m}\u001b[0m\u001b[32m}\u001b[0m\u001b[32mTool:date_tool Args:\u001b[0m\u001b[32m{\u001b[0m\u001b[32m}\u001b[0m\u001b[32mTool:date_tool Response:\"2025-06-03\"I used the date_tool function to get today\\'s date.'\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "---------- 📍 Step 3: InferenceStep ----------\n", + "🛠️ Tool call Generated:\n", + "\u001b[35mTool call: Custom Agent, Arguments: {'query': \"today's date\"}\u001b[0m\n", + "\n", + "---------- 📍 Step 4: ToolExecutionStep ----------\n", + "🔧 Executing tool...\n" + ] + }, + { + "data": { + "text/html": [ + "
'{\\n    \"type\": \"function\",\\n    \"name\": \"date_tool\",\\n    \"parameters\": {}\\n}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-06-03\"{\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": {}}Tool:date_tool Args:{}Tool:date_tool Response:\"2025-06-03\"I used the date_tool function to get today\\'s date.'\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[32m'\u001b[0m\u001b[32m{\u001b[0m\u001b[32m\\n \"type\": \"function\",\\n \"name\": \"date_tool\",\\n \"parameters\": \u001b[0m\u001b[32m{\u001b[0m\u001b[32m}\u001b[0m\u001b[32m\\n\u001b[0m\u001b[32m}\u001b[0m\u001b[32mTool:date_tool Args:\u001b[0m\u001b[32m{\u001b[0m\u001b[32m}\u001b[0m\u001b[32mTool:date_tool Response:\"2025-06-03\"\u001b[0m\u001b[32m{\u001b[0m\u001b[32m\"type\": \"function\", \"name\": \"date_tool\", \"parameters\": \u001b[0m\u001b[32m{\u001b[0m\u001b[32m}\u001b[0m\u001b[32m}\u001b[0m\u001b[32mTool:date_tool Args:\u001b[0m\u001b[32m{\u001b[0m\u001b[32m}\u001b[0m\u001b[32mTool:date_tool Response:\"2025-06-03\"I used the date_tool function to get today\\'s date.'\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "---------- 📍 Step 5: InferenceStep ----------\n", + "🤖 Model Response:\n", + "\u001b[35mThe current date is 2025-06-03.\n", + "\u001b[0m\n", + "========== Query processing completed ========== \n", + "\n" + ] + } + ], + "source": [ + "queries = [\n", + " \"What is today's date?\",\n", + "]\n", + "\n", + "for prompt in queries:\n", + " cprint(f\"\\nUser> {prompt}\", \"blue\")\n", + " \n", + " # create a new turn with a new session ID for each prompt\n", + " response = a2a_client_agent.create_turn(\n", + " messages=[\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": prompt,\n", + " }\n", + " ],\n", + " session_id=a2a_client_agent.create_session(f\"agent-session_{uuid.uuid4()}\"),\n", + " stream=stream,\n", + " )\n", + " \n", + " # print the response, including tool calls output\n", + " if stream:\n", + " for log in EventLogger().log(response):\n", + " log.print()\n", + " else:\n", + " step_printer(response.steps)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv5", + "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.13.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From d5115ae73bf42e5d026d6043a1e888e650f18af9 Mon Sep 17 00:00:00 2001 From: Kevin Cogan Date: Tue, 3 Jun 2025 12:15:34 +0100 Subject: [PATCH 11/13] docs: Updated docs to use UV package manager only and docs updates. --- demos/a2a_llama_stack/README.md | 126 +++++++++++++++----------------- 1 file changed, 60 insertions(+), 66 deletions(-) diff --git a/demos/a2a_llama_stack/README.md b/demos/a2a_llama_stack/README.md index c78b5b2..aebbb32 100644 --- a/demos/a2a_llama_stack/README.md +++ b/demos/a2a_llama_stack/README.md @@ -39,39 +39,17 @@ git clone https://github.com/opendatahub-io/llama-stack-demos.git # Clone the Google A2A examples repository -git clone https://github.com/google/A2A.git +git clone https://github.com/google-a2a/a2a-samples.git ``` *These commands will create two new directories, `llama-stack-demos` and `A2A`, in your current working folder.* -### 2. Create and Activate a Python Virtual Environment +### 2. Prepare the Custom Agent Package -Employing a virtual environment is strongly recommended to manage project-specific dependencies effectively and prevent conflicts with your global Python installation or other projects. - -```bash -# Navigate to your main project directory (e.g. where you cloned the repositories). -# Create a virtual environment named 'venv' -python3 -m venv venv -``` - -Next, activate the virtual environment. The activation command varies by operating system: - -* **macOS / Linux:** - ```bash - source venv/bin/activate - ``` -* **Windows (Command Prompt or PowerShell):** - ```bash - venv\Scripts\activate - ``` -*Once activated, your terminal prompt should typically be prefixed with `(venv)`, indicating the virtual environment is active.* - -### 3. Prepare the Custom Agent Package - -You will now copy the Llama Stack agent code from the `llama-stack-demos` repository into the appropriate directory within the `A2A` examples structure. +You will now copy the Llama Stack agent code from the `llama-stack-demos` repository into the appropriate directory within the `a2a-samples` examples structure. ```bash # Navigate to the target directory within the A2A examples. -cd A2A/samples/python/agents +cd a2a-samples/samples/python/agents # Copy the Llama Stack agent directory. cp -r ../../../../llama-stack-demos/demos/a2a_llama_stack . @@ -88,21 +66,42 @@ After the copy operation, verify that the `A2A/samples/python/agents/a2a_llama_s * `cli/` * `notebooks/` +### 3. Create and Activate a Python Virtual Environment + +Employing a virtual environment is strongly recommended to manage project-specific dependencies effectively and prevent conflicts with your global Python installation or other projects. + +```bash +# Navigate to the `python` directory. +cd .. + +# Create a virtual environment named 'venv' using uv +uv venv +``` + +Next, activate the virtual environment. The activation command varies by operating system: + +* **macOS / Linux:** + ```bash + source .venv/bin/activate + ``` +* **Windows (Command Prompt or PowerShell):** + ```bash + .venv\Scripts\activate + ``` +*Once activated, your terminal prompt should typically be prefixed with `(venv)`, indicating the virtual environment is active.* + ### 4. Install Python Dependencies Navigate into the `a2a_llama_stack` directory (which you just populated) and install its Python package dependencies. Ensure your virtual environment remains active. ```bash # Navigate into the Llama Stack agent directory -cd a2a_llama_stack - -# It is good practice to upgrade pip within the virtual environment -python -m pip install --upgrade pip +cd agents/a2a_llama_stack -# Install the required packages specified in requirements.txt -pip install -r requirements.txt +# Install the required packages specified in requirements.txt using uv +uv pip install -r requirements.txt ``` -*You should now be located in the `A2A/samples/python/agents/a2a_llama_stack` directory.* +*You should now be located in the `a2a-samples/samples/python/agents/a2a_llama_stack` directory.* --- @@ -112,22 +111,22 @@ Your agent requires the network address of your Llama Stack server and the ident | Variable | Description | Default Value | Example Custom Value | |-------------------|-------------------------------------------------|-----------------------------|-----------------------------| -| `LLAMA_STACK_URL` | Address of your Llama Stack inference server. | `http://localhost:8321` | `http://your-llama-server` | -| `MODEL_ID` | Model identifier available on your Llama Stack. | `llama3.2:3b-instruct-fp16` | `your-custom-model-id` | +| `REMOTE_BASE_URL` | Address of your Llama Stack inference server. | `http://localhost:8321` | `http://your-llama-server` | +| `INFERENCE_MODEL_ID` | Model identifier available on your Llama Stack. | `llama3.2:3b-instruct-fp16` | `your-custom-model-id` | Set these variables in the terminal session where you plan to launch the agent server (detailed in the subsequent section). * **macOS / Linux:** ```bash - export LLAMA_STACK_URL="http://localhost:8321" - export MODEL_ID="llama3.2:3b-instruct-fp16" + export REMOTE_BASE_URL="http://localhost:8321" + export INFERENCE_MODEL_ID="llama3.2:3b-instruct-fp16" ``` - *(Adjust these values if your Llama Stack server URL or model ID differs from the defaults.)* + *(Adjust these values if your `REMOTE_BASE_URL` or `INFERENCE_MODEL_ID` differs from the defaults.)* * **Windows (PowerShell):** ```powershell - setx LLAMA_STACK_URL "http://localhost:8321" - setx MODEL_ID "llama3.2:3b-instruct-fp16" + setx REMOTE_BASE_URL "http://localhost:8321" + setx INFERENCE_MODEL_ID "llama3.2:3b-instruct-fp16" ``` *(Modify the values as necessary. Note: After using `setx`, these variables are persistently set for the current user. However, you must open a **new** PowerShell window or restart your current one for these changes to become effective in that session.)* @@ -147,14 +146,14 @@ The agent server is the core component that listens for and processes incoming A **Important Considerations:** * Ensure your Python virtual environment (`venv`) is **active** in the terminal session used for this step. -* Confirm that the `LLAMA_STACK_URL` and `MODEL_ID` environment variables are **set** within this same terminal session. +* Confirm that the `REMOTE_BASE_URL` and `INFERENCE_MODEL_ID` environment variables are **set** within this same terminal session. -You should currently be in the `A2A/samples/python/agents/a2a_llama_stack` directory (upon completing Step 4 of the Setup Instructions). To launch the agent server module correctly, first navigate to the `A2A/samples/python/` directory: +You should currently be in the `a2a-samples/samples/python/agents/a2a_llama_stack` directory (upon completing Step 4 of the Setup Instructions). To launch the agent server module correctly, first navigate to the `a2a-samples/samples/python/` directory: ```bash -# If you are currently in A2A/samples/python/agents/a2a_llama_stack: +# If you are currently in a2a-samples/samples/python/agents/a2a_llama_stack: cd ../../ -# You should now be in the A2A/samples/python/ directory. +# You should now be in the a2a-samples/samples/python/ directory. ``` Now, select **one** of the following server configurations: @@ -164,8 +163,9 @@ Now, select **one** of the following server configurations: This configuration runs a single agent, named `a2a_custom_tools`, which listens on port `10011`. This agent will interface with the Llama Stack for its operational tasks. ```bash -# Ensure you are in the A2A/samples/python/ directory -python -m agents.a2a_llama_stack --agent-name a2a_custom_tools --port 10011 +# Ensure you are in the a2a-samples/samples/python/ directory +# And your virtual environment is active. +uv run --active python -m agents.a2a_llama_stack --agent-name a2a_custom_tools --port 10011 ``` #### Option B: Multi-Agent Setup (Multiple Agent Servers) @@ -176,15 +176,15 @@ This setup illustrates a more complex scenario involving three distinct agents: # Ensure you are in the A2A/samples/python/ directory # Terminal 1: Launch the planner agent -python -m agents.a2a_llama_stack --agent-name a2a_planner --port 10010 +uv run --active python -m agents.a2a_llama_stack --agent-name a2a_planner --port 10010 # Terminal 2: Launch the custom tools agent # (Open a new terminal window/tab, activate venv, and set environment variables before running) -python -m agents.a2a_llama_stack --agent-name a2a_custom_tools --port 10011 +uv run --active python -m agents.a2a_llama_stack --agent-name a2a_custom_tools --port 10011 # Terminal 3: Launch the composer agent # (Open another new terminal window/tab, activate venv, and set environment variables before running) -python -m agents.a2a_llama_stack --agent-name a2a_composer --port 10012 +uv run --active python -m agents.a2a_llama_stack --agent-name a2a_composer --port 10012 ``` *For the multi-agent setup (Option B), each `python -m ...` command initiates a server that will occupy its terminal. You will need to open multiple terminal windows/tabs or manage these processes in the background.* @@ -200,25 +200,19 @@ INFO | Agent listening on 0.0.0.0:XXXX With the agent server(s) operational, you can now use a client application to dispatch tasks. This requires opening a **new terminal window or tab**. -1. **Activate the virtual environment** in this new terminal: - * **macOS / Linux:** - ```bash - # Navigate to your project root directory where 'venv' is located - source venv/bin/activate - ``` - * **Windows (Command Prompt or PowerShell):** - ```bash - # Navigate to your project root directory where 'venv' is located - venv\Scripts\activate - ``` - *(Recall that the `venv` directory was created in your main project folder, which houses the `A2A` and `llama-stack-demos` subdirectories.)* +1. **Setting up the Client Script Environment:** + ```bash + # Navigate to the client script directory: + cd a2a-samples/samples/python + + # Install required packages: + uv pip install asyncclick + ``` 2. **Navigate to the client script directory:** The client application is typically executed from the `cli` directory, located within the `a2a_llama_stack` agent's sample code. ```bash - # Adjust this path according to your root project directory structure. - # Assuming your current directory is the project root (which contains the 'A2A' folder): - cd A2A/samples/python/agents/a2a_llama_stack/cli + cd agents/a2a_llama_stack/cli ``` 3. **Run the client application:** @@ -227,13 +221,13 @@ With the agent server(s) operational, you can now use a client application to di #### If you used "Option A: Basic Setup" for the agent server: Run the `basic_client.py` script, directing it to the `a2a_custom_tools` agent: ```bash - uv run basic_client.py --agent http://localhost:10011 + uv run --active basic_client.py --agent http://localhost:10011 ``` #### If you used "Option B: Multi-Agent Setup" for the agent servers: Run the `multi_agent_client.py` script, providing the network addresses for all three agents. It is crucial that the `a2a_planner` agent (`http://localhost:10010`) is specified first. ```bash - uv run multi_agent_client.py --agent http://localhost:10010 --agent http://localhost:10011 --agent http://localhost:10012 + uv run --active multi_agent_client.py --agent http://localhost:10010 --agent http://localhost:10011 --agent http://localhost:10012 ``` Upon executing the appropriate `uv run` command, the client will attempt to establish a connection with the agent server(s) and enable task interaction. From 0a0793e922b145da50f34c357ca30da08cc6a613 Mon Sep 17 00:00:00 2001 From: Kevin Cogan Date: Tue, 3 Jun 2025 12:27:48 +0100 Subject: [PATCH 12/13] fix: rebased commits with newer updates. --- .../agents/a2a_custom_tools/config.py | 5 +- .../notebooks/A2A_Agentic_RAG.ipynb | 214 +++++++++--------- demos/a2a_llama_stack/requirements.txt | 37 +++ 3 files changed, 148 insertions(+), 108 deletions(-) diff --git a/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py b/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py index b7efb2c..453670e 100644 --- a/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py +++ b/demos/a2a_llama_stack/agents/a2a_custom_tools/config.py @@ -13,7 +13,10 @@ ), "tools": [random_number_tool, date_tool], "max_infer_iters": 3, - "sampling_params": {"max_tokens": 4096} + "sampling_params": { + "strategy": {"type": "greedy"}, + "max_tokens": 4096, + }, }, "task_manager_class": AgentTaskManager, diff --git a/demos/a2a_llama_stack/notebooks/A2A_Agentic_RAG.ipynb b/demos/a2a_llama_stack/notebooks/A2A_Agentic_RAG.ipynb index bf40476..af8fc14 100644 --- a/demos/a2a_llama_stack/notebooks/A2A_Agentic_RAG.ipynb +++ b/demos/a2a_llama_stack/notebooks/A2A_Agentic_RAG.ipynb @@ -45,7 +45,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 1, "id": "5e50535c", "metadata": {}, "outputs": [ @@ -53,79 +53,78 @@ "name": "stdout", "output_type": "stream", "text": [ - "Collecting git+https://github.com/google/A2A.git#subdirectory=samples/python\n", - " Cloning https://github.com/google/A2A.git to /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-aswa9mq1\n", - " Running command git clone --filter=blob:none --quiet https://github.com/google/A2A.git /private/var/folders/p4/635191ns4599kwjkqt12kwd80000gn/T/pip-req-build-aswa9mq1\n", - " Resolved https://github.com/google/A2A.git to commit 081fa20bdfede24922c49e8e56fcdfbee0db0c28\n", - " Installing build dependencies ... \u001b[?25ldone\n", - "\u001b[?25h Getting requirements to build wheel ... \u001b[?25ldone\n", - "\u001b[?25h Preparing metadata (pyproject.toml) ... \u001b[?25ldone\n", - "\u001b[?25hRequirement already satisfied: a2a-sdk>=0.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.2.1)\n", - "Requirement already satisfied: httpx-sse>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.4.0)\n", - "Requirement already satisfied: httpx>=0.28.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.28.1)\n", - "Requirement already satisfied: jwcrypto>=1.5.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (1.5.6)\n", - "Requirement already satisfied: pydantic>=2.10.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.11.4)\n", - "Requirement already satisfied: pyjwt>=2.10.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.10.1)\n", - "Requirement already satisfied: sse-starlette>=2.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (2.3.5)\n", - "Requirement already satisfied: starlette>=0.46.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.46.2)\n", - "Requirement already satisfied: typing-extensions>=4.12.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (4.13.2)\n", - "Requirement already satisfied: uvicorn>=0.34.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-samples==0.1.0) (0.34.2)\n", - "Requirement already satisfied: opentelemetry-api>=1.33.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.33.1)\n", - "Requirement already satisfied: opentelemetry-sdk>=1.33.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.33.1)\n", - "Requirement already satisfied: anyio in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (4.9.0)\n", - "Requirement already satisfied: certifi in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (2025.4.26)\n", - "Requirement already satisfied: httpcore==1.* in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (1.0.9)\n", - "Requirement already satisfied: idna in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpx>=0.28.1->a2a-samples==0.1.0) (3.10)\n", - "Requirement already satisfied: h11>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpcore==1.*->httpx>=0.28.1->a2a-samples==0.1.0) (0.16.0)\n", - "Requirement already satisfied: cryptography>=3.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from jwcrypto>=1.5.6->a2a-samples==0.1.0) (45.0.2)\n", - "Requirement already satisfied: annotated-types>=0.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (0.7.0)\n", - "Requirement already satisfied: pydantic-core==2.33.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (2.33.2)\n", - "Requirement already satisfied: typing-inspection>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pydantic>=2.10.6->a2a-samples==0.1.0) (0.4.0)\n", - "Requirement already satisfied: click>=7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from uvicorn>=0.34.0->a2a-samples==0.1.0) (8.2.0)\n", - "Requirement already satisfied: sniffio>=1.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from anyio->httpx>=0.28.1->a2a-samples==0.1.0) (1.3.1)\n", - "Requirement already satisfied: cffi>=1.14 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from cryptography>=3.4->jwcrypto>=1.5.6->a2a-samples==0.1.0) (1.17.1)\n", - "Requirement already satisfied: deprecated>=1.2.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.2.18)\n", - "Requirement already satisfied: importlib-metadata<8.7.0,>=6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (8.6.1)\n", - "Requirement already satisfied: opentelemetry-semantic-conventions==0.54b1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from opentelemetry-sdk>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (0.54b1)\n", - "Requirement already satisfied: pycparser in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from cffi>=1.14->cryptography>=3.4->jwcrypto>=1.5.6->a2a-samples==0.1.0) (2.22)\n", - "Requirement already satisfied: wrapt<2,>=1.10 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from deprecated>=1.2.6->opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (1.17.2)\n", - "Requirement already satisfied: zipp>=3.20 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from importlib-metadata<8.7.0,>=6.0->opentelemetry-api>=1.33.0->a2a-sdk>=0.2.1->a2a-samples==0.1.0) (3.21.0)\n", - "\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", - "Requirement already satisfied: llama_stack_client in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (0.2.7)\n", - "Requirement already satisfied: dotenv in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (0.9.9)\n", - "Requirement already satisfied: anyio<5,>=3.5.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (4.9.0)\n", - "Requirement already satisfied: click in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (8.2.0)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (1.9.0)\n", - "Requirement already satisfied: httpx<1,>=0.23.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (0.28.1)\n", - "Requirement already satisfied: pandas in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (2.2.3)\n", - "Requirement already satisfied: prompt-toolkit in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (3.0.51)\n", - "Requirement already satisfied: pyaml in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (25.1.0)\n", - "Requirement already satisfied: pydantic<3,>=1.9.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (2.11.4)\n", - "Requirement already satisfied: rich in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (14.0.0)\n", - "Requirement already satisfied: sniffio in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (1.3.1)\n", - "Requirement already satisfied: termcolor in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (3.1.0)\n", - "Requirement already satisfied: tqdm in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (4.67.1)\n", - "Requirement already satisfied: typing-extensions<5,>=4.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from llama_stack_client) (4.13.2)\n", - "Requirement already satisfied: python-dotenv in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from dotenv) (1.1.0)\n", - "Requirement already satisfied: idna>=2.8 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from anyio<5,>=3.5.0->llama_stack_client) (3.10)\n", - "Requirement already satisfied: certifi in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpx<1,>=0.23.0->llama_stack_client) (2025.4.26)\n", - "Requirement already satisfied: httpcore==1.* in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpx<1,>=0.23.0->llama_stack_client) (1.0.9)\n", - "Requirement already satisfied: h11>=0.16 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->llama_stack_client) (0.16.0)\n", - "Requirement already satisfied: annotated-types>=0.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (0.7.0)\n", - "Requirement already satisfied: pydantic-core==2.33.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (2.33.2)\n", - "Requirement already satisfied: typing-inspection>=0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->llama_stack_client) (0.4.0)\n", - "Requirement already satisfied: numpy>=1.26.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pandas->llama_stack_client) (2.2.6)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pandas->llama_stack_client) (2.9.0.post0)\n", - "Requirement already satisfied: pytz>=2020.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pandas->llama_stack_client) (2025.2)\n", - "Requirement already satisfied: tzdata>=2022.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pandas->llama_stack_client) (2025.2)\n", - "Requirement already satisfied: wcwidth in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from prompt-toolkit->llama_stack_client) (0.2.13)\n", - "Requirement already satisfied: PyYAML in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from pyaml->llama_stack_client) (6.0.2)\n", - "Requirement already satisfied: markdown-it-py>=2.2.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from rich->llama_stack_client) (3.0.0)\n", - "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from rich->llama_stack_client) (2.19.1)\n", - "Requirement already satisfied: mdurl~=0.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich->llama_stack_client) (0.1.2)\n", - "Requirement already satisfied: six>=1.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv7/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas->llama_stack_client) (1.17.0)\n", + "fatal: destination path 'a2a-samples' already exists and is not an empty directory.\n", + "Requirement already satisfied: annotated-types==0.7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 1)) (0.7.0)\n", + "Requirement already satisfied: anyio==4.9.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 2)) (4.9.0)\n", + "Requirement already satisfied: appnope==0.1.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 3)) (0.1.4)\n", + "Requirement already satisfied: asttokens==3.0.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 4)) (3.0.0)\n", + "Requirement already satisfied: certifi==2025.1.31 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 5)) (2025.1.31)\n", + "Requirement already satisfied: cffi==1.17.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 6)) (1.17.1)\n", + "Requirement already satisfied: charset-normalizer==3.4.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 7)) (3.4.2)\n", + "Requirement already satisfied: click==8.1.8 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 8)) (8.1.8)\n", + "Requirement already satisfied: comm==0.2.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 9)) (0.2.2)\n", + "Requirement already satisfied: cryptography==45.0.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 10)) (45.0.3)\n", + "Requirement already satisfied: debugpy==1.8.14 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 11)) (1.8.14)\n", + "Requirement already satisfied: decorator==5.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 12)) (5.2.1)\n", + "Requirement already satisfied: distro==1.9.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 13)) (1.9.0)\n", + "Requirement already satisfied: dotenv==0.9.9 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 14)) (0.9.9)\n", + "Requirement already satisfied: executing==2.2.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 15)) (2.2.0)\n", + "Requirement already satisfied: fire==0.7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 16)) (0.7.0)\n", + "Requirement already satisfied: h11==0.16.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 17)) (0.16.0)\n", + "Requirement already satisfied: httpcore==1.0.9 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 18)) (1.0.9)\n", + "Requirement already satisfied: httpx==0.28.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 19)) (0.28.1)\n", + "Requirement already satisfied: httpx-sse==0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 20)) (0.4.0)\n", + "Requirement already satisfied: idna==3.10 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 21)) (3.10)\n", + "Requirement already satisfied: ipykernel==6.29.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 22)) (6.29.5)\n", + "Requirement already satisfied: ipython==9.3.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 23)) (9.3.0)\n", + "Requirement already satisfied: ipython_pygments_lexers==1.1.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 24)) (1.1.1)\n", + "Requirement already satisfied: jedi==0.19.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 25)) (0.19.2)\n", + "Requirement already satisfied: jupyter_client==8.6.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 26)) (8.6.3)\n", + "Requirement already satisfied: jupyter_core==5.8.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 27)) (5.8.1)\n", + "Requirement already satisfied: jwcrypto==1.5.6 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 28)) (1.5.6)\n", + "Requirement already satisfied: llama_stack_client==0.2.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 29)) (0.2.2)\n", + "Requirement already satisfied: markdown-it-py==3.0.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 30)) (3.0.0)\n", + "Requirement already satisfied: matplotlib-inline==0.1.7 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 31)) (0.1.7)\n", + "Requirement already satisfied: mdurl==0.1.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 32)) (0.1.2)\n", + "Requirement already satisfied: nest-asyncio==1.6.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 33)) (1.6.0)\n", + "Requirement already satisfied: numpy==2.2.5 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 34)) (2.2.5)\n", + "Requirement already satisfied: packaging==25.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 35)) (25.0)\n", + "Requirement already satisfied: pandas==2.2.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 36)) (2.2.3)\n", + "Requirement already satisfied: parso==0.8.4 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 37)) (0.8.4)\n", + "Requirement already satisfied: pexpect==4.9.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 38)) (4.9.0)\n", + "Requirement already satisfied: platformdirs==4.3.8 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 39)) (4.3.8)\n", + "Requirement already satisfied: prompt_toolkit==3.0.51 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 40)) (3.0.51)\n", + "Requirement already satisfied: psutil==7.0.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 41)) (7.0.0)\n", + "Requirement already satisfied: ptyprocess==0.7.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 42)) (0.7.0)\n", + "Requirement already satisfied: pure_eval==0.2.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 43)) (0.2.3)\n", + "Requirement already satisfied: pyaml==25.1.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 44)) (25.1.0)\n", + "Requirement already satisfied: pycparser==2.22 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 45)) (2.22)\n", + "Requirement already satisfied: pydantic==2.11.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 46)) (2.11.3)\n", + "Requirement already satisfied: pydantic_core==2.33.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 47)) (2.33.1)\n", + "Requirement already satisfied: Pygments==2.19.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 48)) (2.19.1)\n", + "Requirement already satisfied: PyJWT==2.10.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 49)) (2.10.1)\n", + "Requirement already satisfied: python-dateutil==2.9.0.post0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 50)) (2.9.0.post0)\n", + "Requirement already satisfied: python-dotenv==1.1.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 51)) (1.1.0)\n", + "Requirement already satisfied: pytz==2025.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 52)) (2025.2)\n", + "Requirement already satisfied: PyYAML==6.0.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 53)) (6.0.2)\n", + "Requirement already satisfied: pyzmq==26.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 54)) (26.4.0)\n", + "Requirement already satisfied: requests==2.32.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 55)) (2.32.3)\n", + "Requirement already satisfied: rich==14.0.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 56)) (14.0.0)\n", + "Requirement already satisfied: six==1.17.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 57)) (1.17.0)\n", + "Requirement already satisfied: sniffio==1.3.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 58)) (1.3.1)\n", + "Requirement already satisfied: sse-starlette==2.2.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 59)) (2.2.1)\n", + "Requirement already satisfied: stack-data==0.6.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 60)) (0.6.3)\n", + "Requirement already satisfied: starlette==0.46.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 61)) (0.46.2)\n", + "Requirement already satisfied: termcolor==3.0.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 62)) (3.0.1)\n", + "Requirement already satisfied: tornado==6.5.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 63)) (6.5.1)\n", + "Requirement already satisfied: tqdm==4.67.1 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 64)) (4.67.1)\n", + "Requirement already satisfied: traitlets==5.14.3 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 65)) (5.14.3)\n", + "Requirement already satisfied: typing-inspection==0.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 66)) (0.4.0)\n", + "Requirement already satisfied: typing_extensions==4.13.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 67)) (4.13.2)\n", + "Requirement already satisfied: tzdata==2025.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 68)) (2025.2)\n", + "Requirement already satisfied: urllib3==2.4.0 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 69)) (2.4.0)\n", + "Requirement already satisfied: uvicorn==0.34.2 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 70)) (0.34.2)\n", + "Requirement already satisfied: wcwidth==0.2.13 in /Users/kcogan/Documents/llama-stack-on-ocp/venv2/lib/python3.13/site-packages (from -r ../requirements.txt (line 71)) (0.2.13)\n", "\n", "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n", "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n" @@ -133,8 +132,8 @@ } ], "source": [ - "! pip install \"git+https://github.com/google/A2A.git#subdirectory=samples/python\"\n", - "! pip install llama_stack_client dotenv" + "! git clone https://github.com/google-a2a/a2a-samples.git\n", + "! pip install -r \"../requirements.txt\"" ] }, { @@ -147,14 +146,14 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 2, "id": "92301f97-17f4-4f48-a3da-a5288b8b02dd", "metadata": {}, "outputs": [], "source": [ "import sys\n", "# the path of the A2A library\n", - "sys.path.append('./A2A/samples/python')\n", + "sys.path.append('./a2a-samples/samples/python')\n", "# the path to our own utils\n", "sys.path.append('../..')" ] @@ -169,7 +168,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 3, "id": "839cf607-0301-41dc-ab13-d57b9ac20bd8", "metadata": {}, "outputs": [], @@ -193,7 +192,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 4, "id": "9b87b139-bd18-47b2-889a-1b8ed3018655", "metadata": {}, "outputs": [ @@ -203,8 +202,8 @@ "text": [ "Connected to Llama Stack server\n", "Inference Parameters:\n", - "\tModel: llama32-3b\n", - "\tSampling Parameters: {'strategy': {'type': 'greedy'}, 'max_tokens': 512}\n", + "\tModel: llama3.1:8b-instruct-fp16\n", + "\tSampling Parameters: {'strategy': {'type': 'greedy'}, 'max_tokens': 4096}\n", "\tstream: False\n" ] } @@ -289,17 +288,17 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 5, "id": "3b8ee65a-5925-44ed-a81b-f0df2c52465e", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "VectorDBRegisterResponse(embedding_dimension=384, embedding_model='all-MiniLM-L6-v2', identifier='test_vector_db_0efe2f59-9ec0-48bc-b673-4d4559a7e882', provider_id='milvus', provider_resource_id='test_vector_db_0efe2f59-9ec0-48bc-b673-4d4559a7e882', type='vector_db', access_attributes=None)" + "VectorDBRegisterResponse(embedding_dimension=384, embedding_model='all-MiniLM-L6-v2', identifier='test_vector_db_981e088d-881d-4ce7-89ce-54248f1d65d0', provider_id='faiss', provider_resource_id='test_vector_db_981e088d-881d-4ce7-89ce-54248f1d65d0', type='vector_db', access_attributes=None)" ] }, - "execution_count": 10, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -325,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 6, "id": "93e86b69-7b8e-4418-976c-2a694a99a55b", "metadata": {}, "outputs": [], @@ -359,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 7, "id": "2dd4664a-ff7f-4474-b6af-3a4ad3f73052", "metadata": { "tags": [] @@ -403,23 +402,25 @@ "name": "stderr", "output_type": "stream", "text": [ - "INFO: Started server process [14257]\n", + "INFO: Started server process [91522]\n", "INFO: Waiting for application startup.\n", "INFO: Application startup complete.\n", - "INFO: Uvicorn running on http://localhost:10020 (Press CTRL+C to quit)\n" + "INFO: Uvicorn running on http://localhost:10030 (Press CTRL+C to quit)\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "INFO: ::1:58564 - \"GET /.well-known/agent.json HTTP/1.1\" 200 OK\n", - "INFO: ::1:58565 - \"POST / HTTP/1.1\" 200 OK\n" + "INFO: ::1:55296 - \"GET /.well-known/agent.json HTTP/1.1\" 200 OK\n", + "INFO: ::1:55350 - \"POST / HTTP/1.1\" 200 OK\n", + "INFO: ::1:56094 - \"GET /.well-known/agent.json HTTP/1.1\" 200 OK\n", + "INFO: ::1:56098 - \"POST / HTTP/1.1\" 200 OK\n" ] } ], "source": [ - "rag_agent_local_port = int(os.getenv(\"RAG_AGENT_LOCAL_PORT\", \"10010\"))\n", + "rag_agent_local_port = int(os.getenv(\"RAG_AGENT_LOCAL_PORT\", \"10030\"))\n", "rag_agent_url = f\"http://localhost:{rag_agent_local_port}\"\n", "\n", "agent_card = AgentCard(\n", @@ -458,7 +459,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 9, "id": "7414837e-b04e-432d-82c3-6719a8f62132", "metadata": {}, "outputs": [], @@ -483,7 +484,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 10, "id": "95b9baa2-4739-426a-b79a-2ff90f44c023", "metadata": { "tags": [] @@ -498,7 +499,7 @@ "\n", "---------- 📍 Step 1: InferenceStep ----------\n", "🛠️ Tool call Generated:\n", - "\u001b[35mTool call: OpenShift Knowledge Source Agent, Arguments: {'query': 'install OpenShift'}\u001b[0m\n", + "\u001b[35mTool call: OpenShift Knowledge Source Agent, Arguments: {'query': 'installing OpenShift'}\u001b[0m\n", "\n", "---------- 📍 Step 2: ToolExecutionStep ----------\n", "🔧 Executing tool...\n" @@ -507,11 +508,11 @@ { "data": { "text/html": [ - "
'Tool:knowledge_search Args:{\\'query\\': \\'OpenShift installation\\'}Tool:knowledge_search Response:[TextContentItem(text=\\'knowledge_search tool found 5 chunks:\\\\nBEGIN of knowledge_search tool results.\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 1:\\\\nDocument_id:num-0\\\\nContent: 23\\\\ninstall OpenShift Local, 16\\\\nJ\\\\nJAR file, 23\\\\nJava, 14, 24, 44\\\\nJavaScript, 24, 44\\\\nJenkins, 28\\\\nK\\\\nKiali, 36\\\\nKibana, 40, 40\\\\nKnative, 34\\\\nKubernetes, 7\\\\nL\\\\nlogs, 40\\\\nM\\\\nMicroservices, 36\\\\nmonitor, 40\\\\nN\\\\nNode.js, 14\\\\nnon-root accounts, 20\\\\nO\\\\nOpenShift 4.12, 33\\\\nOpenShift Kubernetes Distribution, 8\\\\nOpenShift Service Mesh, 36\\\\noperator, 28, 36\\\\nOperatorHub, 33, 36\\\\nOperators, 33\\\\n48\\\\nP\\\\nperspectives, 22\\\\nPHP, 14, 24\\\\nPlatform-as-a-Service, 8\\\\nprivilege escalation, 19\\\\nprivileged ports, 20\\\\nProject, 9\\\\nPrometheus, 40, 44\\\\nPromQL, 45\\\\nPython, 14, 24, 44\\\\nQ\\\\nQuarkus, 14, 44\\\\nR\\\\nRed Hat developer account, 13\\\\nRed Hat OpenShift, 7\\\\nRed Hat OpenShift Dev Spaces, 14\\\\nRed Hat OpenShift Local, 8, 15\\\\nRed Hat OpenShift Pipelines, 28\\\\nRed Hat Quay, 20\\\\nRed Hat Universal Base Images, 19\\\\nrole, 19\\\\nRoute, 9\\\\nRust, 14\\\\nS\\\\nScala, 14\\\\nScaling, 42\\\\nsecure by default, 19\\\\nSecurity Context Constraints, 19\\\\nServerless, 34\\\\nservice mesh, 36\\\\nsource code project, 22\\\\nstateful applications, 33\\\\nT\\\\nTekton, 28\\\\ntemplates, 32\\\\nTopology, 27\\\\nTwelve-Factor App, 21, 40\\\\nTypeScript, 24\\\\nU\\\\nUBI, 19\\\\nV\\\\nVertical scaling, 42\\\\nVisual Studio Code, 14\\\\nW\\\\nWeb Console, 22, 40\\\\n49\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 2:\\\\nDocument_id:num-0\\\\nContent:  We\\\\nrecommend you to check the official Red Hat OpenShift Local documentation for an updated list of\\\\nrequirements at the official documentation website.\\\\n\\\\uf05a\\\\nRegarding Linux, even if Red Hat does not officially support them, OpenShift Local\\\\ncan run on other distributions, such as Ubuntu or Debian, with minor caveats.\\\\nRunning OpenShift Local on any Linux distribution requires a few additional\\\\nsoftware packages to be installed through your default package manager. The\\\\n15\\\\ndocumentation at crc.dev/crc has more information about this subject.\\\\n7.2. Hardware Requirements\\\\nIn terms of hardware, OpenShift Local has some strict requirements. Your system must use a recent\\\\nIntel CPU (except for Macs, where Apple Silicon machines are supported) with at least four physical\\\\ncores and have at least 16 GB of RAM. Be aware that the base installation of OpenShift Local\\\\nrequires at least 9 GB free to start. Of course, to run other applications on OpenShift Local, you will\\\\nneed more RAM, so using a computer with at least 32 GB of RAM is strongly recommended.\\\\nOpenShift Local also requires at least 35 GB of free disk space for its installation. The memory\\\\nrequirements are likely to increase in the future, so please check the documentation at crc.dev for\\\\nmore up-to-date information.\\\\n7.3. Installation\\\\nTo install OpenShift Local, open your web browser and navigate to console.redhat.com/openshift/\\\\ncreate/local . Download the latest release of OpenShift Local and the \"pull secret\" file. The latter is a\\\\nfile containing a key identifying your copy of OpenShift Local to your Red Hat Developer account.\\\\nUnzip the file containing the OpenShift Local executable, and using your terminal, run the\\\\ncommand crc setup . This command will prepare your copy of OpenShift Local, verifying\\\\nrequirements and setting the required configuration values.\\\\nOnce the crc setup command is ready, launch crc start. Running crc start can take a long time,\\\\naround 20 minutes, on a recent PC.\\\\nOnce started, access the OpenShift Web Console with the crc console command, which will open\\\\nyour default browser. OpenShift Local uses the developer username and password to log in as a\\\\nlow-privilege user, while the kubeadmin user uses a random-generated password. Use the crc\\\\nconsole --credentials command to find the credentials required to log in as the kubeadmin user.\\\\nOpenShift Local allows developers to perform various everyday tasks as if it were a standard\\\\nOpenShift cluster, like deploying applications\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 3:\\\\nDocument_id:num-0\\\\nContent:  \"Import from Git\" entry. Click on it, and paste the URL of a project, for example,\\\\ngitlab.com/akosma/simple-deno-api.git.\\\\nAs soon as you paste the URL, OpenShift will immediately analyze the structure and programming\\\\nlanguage of the project and automatically recommend options for its build process. In our case, it’s\\\\na small application built with the Go programming language, and as such, it will advise the options\\\\nshown on the screen.\\\\nFigure 5. Deploying a project directly from its Git repository\\\\n25\\\\nThis particular example doesn’t require more configurations than the ones shown on the screen;\\\\nclick the [\\\\u2009Create\\\\u2009] button.\\\\nAfter a few seconds, you will see your application running on the \"Topology\" screen. OpenShift will\\\\ndownload the source code and trigger your project’s build. Click on the Topology screen icon to see\\\\nthe \"Build\" section, indicating that a build is running. The compilation and deployment of your\\\\napplication can take some time, depending on the complexity of the source code and the\\\\nprogramming language used.\\\\nOnce the build has finished, on the same pane, you will see a route available under the \"Routes\"\\\\nsection. Click on it, and you will see your application in action.\\\\n10.2. Container Registry\\\\nOpenShift has built your application source code, and the product of this build process is a\\\\ncontainer. You can see the container that OpenShift made for you on the \"Administrator\"\\\\nperspective, selecting the \"Builds\" menu and then the \"ImageStreams\" menu entry.\\\\nOpenShift includes a container registry; developers can use it as any other registry from outside the\\\\ncluster. Let us use \"podman\" to access the container registry and run the container locally on your\\\\nworkstation.\\\\nUsers must have the \"registry-editor\" and the \"system:image-builder\" roles to access the container\\\\nregistry. Since we’re connected to the Web Console using the \"kubeadmin\" user, we can provide\\\\nthose roles directly from the user interface without using the command line.\\\\nNavigate to the \"User Management\" section and select \"RoleBindings.\" Click on the [\\\\u2009Create\\\\nbinding\\\\u2009] button, and fill the form using the following values:\\\\n• Name: developer-sourcecode-registry-editor\\\\n• Namespace: sourcecode\\\\n• Role name: registry-editor\\\\n• Subject: User\\\\n• Subject name: developer\\\\nDo the same for the \"system:image-builder\" role, using a different \"Name\" field\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 4:\\\\nDocument_id:num-0\\\\nContent: .\\\\nThese characteristics set OpenShift apart as an excellent Kubernetes platform for enterprise users.\\\\nThe latest version of OpenShift available at the time of this writing is 4.12.\\\\n3.2. Is Red Hat OpenShift Open Source?\\\\nRed Hat OpenShift is a commercial product based on an open-source project called OKD. This\\\\nacronym means \" OpenShift Kubernetes Distribution\" and is publicly available for everyone to\\\\ninspect and contribute. Like the upstream Kubernetes project, OKD developers use the Go\\\\nprogramming language.\\\\n3.3. How can I run OpenShift?\\\\nToday, Red Hat OpenShift is available through various mechanisms and formats:\\\\n• DevOps teams can install it in their data centers \"on-premise.\"\\\\n• Major hyperscalers such as AWS, Azure, Google Cloud Platform, and IBM Cloud offer managed\\\\nRed Hat OpenShift installations.\\\\n• Developers can either run OpenShift locally on their workstations using Red Hat OpenShift\\\\nLocal, also known as CRC or \"Code-Ready Containers\"\\\\n• They can also request a 30-day trial OpenShift cluster, offered by Red Hat, at no charge, for\\\\ntesting and evaluation purposes.\\\\nRed Hat OpenShift is an integrated Platform-as-a-Service for enterprise users based on Kubernetes.\\\\nIt is tightly integrated with advanced security settings, developer tooling, and monitoring\\\\nmechanisms, allowing DevOps teams to be more productive.\\\\n8\\\\nChapter 4. OpenShift-only Custom Resource\\\\nDefinitions\\\\nRed Hat OpenShift is a complete DevOps platform extending Kubernetes in various ways. It bundles\\\\na constellation of Custom Resource Definitions (CRDs) to make the life of developers and cluster\\\\nadministrators easier.\\\\nLet us talk first about the CRDs only available on OpenShift.\\\\n4.1. Project\\\\nAn OpenShift Project is similar to a Kubernetes namespace, but more tightly integrated into the\\\\nsecurity system of OpenShift through additional annotations.\\\\napiVersion: project.openshift.io/v1\\\\nkind: Project\\\\nmetadata:\\\\n\\\\xa0 name: linkedin-learning-project\\\\n\\\\xa0 annotations:\\\\n\\\\xa0   openshift.io/description: \"Project description\"\\\\n\\\\xa0   openshift.io/display-name: \"Display name\"\\\\n4.2. Route\\\\nThe OpenShift Route object was one of the primary inspirations during the development of the\\\\nIngress object. In OpenShift, Ingress and Route objects work together to ensure your applications\\\\nare available outside the cluster.\\\\napiVersion: route.openshift.io/v1\\\\nkind: Route\\\\nmetadata:\\\\n\\\\xa0 name: my-route\\\\nspec:\\\\n\\\\xa0 host:\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 5:\\\\nDocument_id:num-0\\\\nContent: _02 branch of the GitHub\\\\nrepository for this course.\\\\nThe whole point of OpenShift is to be able to deploy, run, and monitor containerized applications.\\\\nDevOps engineers can deploy containers in OpenShift clusters through various means, for example:\\\\n• Using a YAML manifest.\\\\n• Deploying a single container using the web console.\\\\n• Building a project stored in a Git repository anywhere on the Internet and deploying the\\\\nresulting container.\\\\n• Using the integrated CI/CD pipelines.\\\\n• Using the odo tool together with \"Devfiles.\"\\\\nEach approach has pros and cons; in this chapter, we will review how to use the web console and\\\\nhow to use the odo tool.\\\\n9.1. Deploying Applications with the Web Console\\\\nDevOps engineers can deploy applications immediately using the Web Console. Launch your CRC\\\\ninstance and open the web console in your preferred web browser. The URL of the OpenShift web\\\\nconsole is \"https://console-openshift-console.apps-crc.testing.\"\\\\nThe OpenShift Web Console offers two major perspectives:\\\\n• The \"Administrator\" perspective.\\\\n• And the \"Developer\" perspective.\\\\nFor this explanation, select the \"Developer\" perspective.\\\\nThe first time you open the Developer perspective, a popup invites you to follow a user interface\\\\ntour.\\\\nOn the left-hand side, the perspective menu shows an entry titled \"Add,\" which, as the name\\\\nimplies, provides various mechanisms to deploy applications on an OpenShift cluster.\\\\nThe \"Add\" screen shows the various ways DevOps engineers can deploy applications on a cluster:\\\\n• Using the Developer Catalog, browsing and choosing among a long list of available databases,\\\\nmessage queues, and other valuable components to build applications, or entering your\\\\npreferred Helm chart repository to extend the catalog.\\\\n• Specifying the URL to a specific container on any standard container registry.\\\\n• Specifying the URL of a source code project stored on a Git repository, for example, but not\\\\n22\\\\nlimited to GitHub, GitLab, Gitea, or other locations.\\\\n• Importing YAML directly or even a JAR file with a Java application.\\\\nLet us select the \"Container Image\" option, where we can specify the URL of a ready-to-use\\\\ncontainer.\\\\nEnter the URL of the container on the field, and click on the [\\\\u2009Create\\\\u2009] button at the bottom of the\\\\npage. You do not need to change any other value on the form.\\\\nA few seconds later, depending on the size of the container and the speed of your Internet\\\\nconnection,\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'END of knowledge_search tool results.\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'The above results were retrieved to help answer the user\\\\\\'s query: \"OpenShift installation\". Use them as supporting information only in answering this query.\\\\n\\', type=\\'text\\')]To install OpenShift, you can follow these steps:\\n\\n1. Check the system requirements for OpenShift Local, which is a version of OpenShift that can be installed on a local machine. The requirements include a recent Intel CPU with at least four physical cores, 16 GB of RAM, and 35 GB of free disk space.\\n2. Download the latest version of OpenShift Local from the official Red Hat website.\\n3. Unzip the downloaded file and run the `crc setup` command to prepare the installation.\\n4. Launch the `crc start` command to start the installation process, which may take around 20 minutes.\\n5. Once the installation is complete, access the OpenShift Web Console using the `crc console` command, which will open your default browser.\\n6. Log in to the Web Console using the developer username and password, or use the `crc console --credentials` command to find the credentials required to log in as the kubeadmin user.\\n7. You can then deploy applications using the web console or using the odo tool.\\n\\nNote that OpenShift is also available through various mechanisms and formats, including installation in data centers, managed installations by hyperscalers, and trial clusters offered by Red Hat.'\n",
+       "
'{\"type\": \"function\", \"name\": \"knowledge_search\", \"parameters\": {\"query\": \"OpenShift installation\"}}Tool:knowledge_search Args:{\\'query\\': \\'OpenShift installation\\'}Tool:knowledge_search Response:[TextContentItem(text=\\'knowledge_search tool found 5 chunks:\\\\nBEGIN of knowledge_search tool results.\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 1:\\\\nDocument_id:num-0\\\\nContent:  We\\\\nrecommend you to check the official Red Hat OpenShift Local documentation for an updated list of\\\\nrequirements at the official documentation website.\\\\n\\\\uf05a\\\\nRegarding Linux, even if Red Hat does not officially support them, OpenShift Local\\\\ncan run on other distributions, such as Ubuntu or Debian, with minor caveats.\\\\nRunning OpenShift Local on any Linux distribution requires a few additional\\\\nsoftware packages to be installed through your default package manager. The\\\\n15\\\\ndocumentation at crc.dev/crc has more information about this subject.\\\\n7.2. Hardware Requirements\\\\nIn terms of hardware, OpenShift Local has some strict requirements. Your system must use a recent\\\\nIntel CPU (except for Macs, where Apple Silicon machines are supported) with at least four physical\\\\ncores and have at least 16 GB of RAM. Be aware that the base installation of OpenShift Local\\\\nrequires at least 9 GB free to start. Of course, to run other applications on OpenShift Local, you will\\\\nneed more RAM, so using a computer with at least 32 GB of RAM is strongly recommended.\\\\nOpenShift Local also requires at least 35 GB of free disk space for its installation. The memory\\\\nrequirements are likely to increase in the future, so please check the documentation at crc.dev for\\\\nmore up-to-date information.\\\\n7.3. Installation\\\\nTo install OpenShift Local, open your web browser and navigate to console.redhat.com/openshift/\\\\ncreate/local . Download the latest release of OpenShift Local and the \"pull secret\" file. The latter is a\\\\nfile containing a key identifying your copy of OpenShift Local to your Red Hat Developer account.\\\\nUnzip the file containing the OpenShift Local executable, and using your terminal, run the\\\\ncommand crc setup . This command will prepare your copy of OpenShift Local, verifying\\\\nrequirements and setting the required configuration values.\\\\nOnce the crc setup command is ready, launch crc start. Running crc start can take a long time,\\\\naround 20 minutes, on a recent PC.\\\\nOnce started, access the OpenShift Web Console with the crc console command, which will open\\\\nyour default browser. OpenShift Local uses the developer username and password to log in as a\\\\nlow-privilege user, while the kubeadmin user uses a random-generated password. Use the crc\\\\nconsole --credentials command to find the credentials required to log in as the kubeadmin user.\\\\nOpenShift Local allows developers to perform various everyday tasks as if it were a standard\\\\nOpenShift cluster, like deploying applications\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 2:\\\\nDocument_id:num-0\\\\nContent: .\\\\nThese characteristics set OpenShift apart as an excellent Kubernetes platform for enterprise users.\\\\nThe latest version of OpenShift available at the time of this writing is 4.12.\\\\n3.2. Is Red Hat OpenShift Open Source?\\\\nRed Hat OpenShift is a commercial product based on an open-source project called OKD. This\\\\nacronym means \" OpenShift Kubernetes Distribution\" and is publicly available for everyone to\\\\ninspect and contribute. Like the upstream Kubernetes project, OKD developers use the Go\\\\nprogramming language.\\\\n3.3. How can I run OpenShift?\\\\nToday, Red Hat OpenShift is available through various mechanisms and formats:\\\\n• DevOps teams can install it in their data centers \"on-premise.\"\\\\n• Major hyperscalers such as AWS, Azure, Google Cloud Platform, and IBM Cloud offer managed\\\\nRed Hat OpenShift installations.\\\\n• Developers can either run OpenShift locally on their workstations using Red Hat OpenShift\\\\nLocal, also known as CRC or \"Code-Ready Containers\"\\\\n• They can also request a 30-day trial OpenShift cluster, offered by Red Hat, at no charge, for\\\\ntesting and evaluation purposes.\\\\nRed Hat OpenShift is an integrated Platform-as-a-Service for enterprise users based on Kubernetes.\\\\nIt is tightly integrated with advanced security settings, developer tooling, and monitoring\\\\nmechanisms, allowing DevOps teams to be more productive.\\\\n8\\\\nChapter 4. OpenShift-only Custom Resource\\\\nDefinitions\\\\nRed Hat OpenShift is a complete DevOps platform extending Kubernetes in various ways. It bundles\\\\na constellation of Custom Resource Definitions (CRDs) to make the life of developers and cluster\\\\nadministrators easier.\\\\nLet us talk first about the CRDs only available on OpenShift.\\\\n4.1. Project\\\\nAn OpenShift Project is similar to a Kubernetes namespace, but more tightly integrated into the\\\\nsecurity system of OpenShift through additional annotations.\\\\napiVersion: project.openshift.io/v1\\\\nkind: Project\\\\nmetadata:\\\\n\\\\xa0 name: linkedin-learning-project\\\\n\\\\xa0 annotations:\\\\n\\\\xa0   openshift.io/description: \"Project description\"\\\\n\\\\xa0   openshift.io/display-name: \"Display name\"\\\\n4.2. Route\\\\nThe OpenShift Route object was one of the primary inspirations during the development of the\\\\nIngress object. In OpenShift, Ingress and Route objects work together to ensure your applications\\\\nare available outside the cluster.\\\\napiVersion: route.openshift.io/v1\\\\nkind: Route\\\\nmetadata:\\\\n\\\\xa0 name: my-route\\\\nspec:\\\\n\\\\xa0 host:\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 3:\\\\nDocument_id:num-0\\\\nContent:  \"Import from Git\" entry. Click on it, and paste the URL of a project, for example,\\\\ngitlab.com/akosma/simple-deno-api.git.\\\\nAs soon as you paste the URL, OpenShift will immediately analyze the structure and programming\\\\nlanguage of the project and automatically recommend options for its build process. In our case, it’s\\\\na small application built with the Go programming language, and as such, it will advise the options\\\\nshown on the screen.\\\\nFigure 5. Deploying a project directly from its Git repository\\\\n25\\\\nThis particular example doesn’t require more configurations than the ones shown on the screen;\\\\nclick the [\\\\u2009Create\\\\u2009] button.\\\\nAfter a few seconds, you will see your application running on the \"Topology\" screen. OpenShift will\\\\ndownload the source code and trigger your project’s build. Click on the Topology screen icon to see\\\\nthe \"Build\" section, indicating that a build is running. The compilation and deployment of your\\\\napplication can take some time, depending on the complexity of the source code and the\\\\nprogramming language used.\\\\nOnce the build has finished, on the same pane, you will see a route available under the \"Routes\"\\\\nsection. Click on it, and you will see your application in action.\\\\n10.2. Container Registry\\\\nOpenShift has built your application source code, and the product of this build process is a\\\\ncontainer. You can see the container that OpenShift made for you on the \"Administrator\"\\\\nperspective, selecting the \"Builds\" menu and then the \"ImageStreams\" menu entry.\\\\nOpenShift includes a container registry; developers can use it as any other registry from outside the\\\\ncluster. Let us use \"podman\" to access the container registry and run the container locally on your\\\\nworkstation.\\\\nUsers must have the \"registry-editor\" and the \"system:image-builder\" roles to access the container\\\\nregistry. Since we’re connected to the Web Console using the \"kubeadmin\" user, we can provide\\\\nthose roles directly from the user interface without using the command line.\\\\nNavigate to the \"User Management\" section and select \"RoleBindings.\" Click on the [\\\\u2009Create\\\\nbinding\\\\u2009] button, and fill the form using the following values:\\\\n• Name: developer-sourcecode-registry-editor\\\\n• Namespace: sourcecode\\\\n• Role name: registry-editor\\\\n• Subject: User\\\\n• Subject name: developer\\\\nDo the same for the \"system:image-builder\" role, using a different \"Name\" field\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 4:\\\\nDocument_id:num-0\\\\nContent:  Git repository, for example, but not\\\\n22\\\\nlimited to GitHub, GitLab, Gitea, or other locations.\\\\n• Importing YAML directly or even a JAR file with a Java application.\\\\nLet us select the \"Container Image\" option, where we can specify the URL of a ready-to-use\\\\ncontainer.\\\\nEnter the URL of the container on the field, and click on the [\\\\u2009Create\\\\u2009] button at the bottom of the\\\\npage. You do not need to change any other value on the form.\\\\nA few seconds later, depending on the size of the container and the speed of your Internet\\\\nconnection, OpenShift will have pulled the container and deployed it onto your cluster. This\\\\ndeployment will include the usual standard elements: a \"Deployment\" object, a \"Service\" object, and\\\\na \"Route.\"\\\\nOpenShift offers a visual representation of the applications running on your project: click on the\\\\nicon of your container, and you will see a panel opening on the right side of the screen. This panel\\\\nwill include the URL automatically assigned to your deployment, and clicking it will show the\\\\napplication in action in another browser tab.\\\\nFigure 4. Topology screen on Red Hat OpenShift\\\\n9.2. Creating and Debugging Applications with the odo\\\\nTool\\\\nWith the oc tool, Red Hat provides another one geared toward software developers: the odo tool.\\\\nDevelopers can use the odo tool to create applications using \"Devfiles,\" particular files named\\\\n\"devfile.yaml\" based on an open standard available at the Devfiles website. Devfiles contain\\\\ninformation about your application’s programming language, dependencies, and other essential\\\\ndetails.\\\\n23\\\\nThe odo tool is not available by default on your command line, but you can download it from the\\\\n\"Help\" menu on the OpenShift Web Console through the \"Command line tools\" entry. Click on the\\\\n\"Download odo\" link at the bottom, and select the version of odo that corresponds to your system.\\\\nThe \"odo catalog list components was\" command shows the various programming languages and\\\\nframeworks supported off-the-box by \"odo.\"\\\\nThe odo init  command prompts the user for a new application using many programming\\\\nlanguages: .NET, Go, Java, JavaScript, PHP, Python, and TypeScript. The last command generates a\\\\nscaffold ready to be populated with the required logic. Finally, the odo push command builds and\\\\npushes the container to the OpenShift container registry, deploying\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'Result 5:\\\\nDocument_id:num-0\\\\nContent: _02 branch of the GitHub\\\\nrepository for this course.\\\\nThe whole point of OpenShift is to be able to deploy, run, and monitor containerized applications.\\\\nDevOps engineers can deploy containers in OpenShift clusters through various means, for example:\\\\n• Using a YAML manifest.\\\\n• Deploying a single container using the web console.\\\\n• Building a project stored in a Git repository anywhere on the Internet and deploying the\\\\nresulting container.\\\\n• Using the integrated CI/CD pipelines.\\\\n• Using the odo tool together with \"Devfiles.\"\\\\nEach approach has pros and cons; in this chapter, we will review how to use the web console and\\\\nhow to use the odo tool.\\\\n9.1. Deploying Applications with the Web Console\\\\nDevOps engineers can deploy applications immediately using the Web Console. Launch your CRC\\\\ninstance and open the web console in your preferred web browser. The URL of the OpenShift web\\\\nconsole is \"https://console-openshift-console.apps-crc.testing.\"\\\\nThe OpenShift Web Console offers two major perspectives:\\\\n• The \"Administrator\" perspective.\\\\n• And the \"Developer\" perspective.\\\\nFor this explanation, select the \"Developer\" perspective.\\\\nThe first time you open the Developer perspective, a popup invites you to follow a user interface\\\\ntour.\\\\nOn the left-hand side, the perspective menu shows an entry titled \"Add,\" which, as the name\\\\nimplies, provides various mechanisms to deploy applications on an OpenShift cluster.\\\\nThe \"Add\" screen shows the various ways DevOps engineers can deploy applications on a cluster:\\\\n• Using the Developer Catalog, browsing and choosing among a long list of available databases,\\\\nmessage queues, and other valuable components to build applications, or entering your\\\\npreferred Helm chart repository to extend the catalog.\\\\n• Specifying the URL to a specific container on any standard container registry.\\\\n• Specifying the URL of a source code project stored on a Git repository, for example, but not\\\\n22\\\\nlimited to GitHub, GitLab, Gitea, or other locations.\\\\n• Importing YAML directly or even a JAR file with a Java application.\\\\nLet us select the \"Container Image\" option, where we can specify the URL of a ready-to-use\\\\ncontainer.\\\\nEnter the URL of the container on the field, and click on the [\\\\u2009Create\\\\u2009] button at the bottom of the\\\\npage. You do not need to change any other value on the form.\\\\nA few seconds later, depending on the size of the container and the speed of your Internet\\\\nconnection,\\\\n\\', type=\\'text\\'), TextContentItem(text=\\'END of knowledge_search tool results.\\\\n\\', type=\\'text\\')]To install OpenShift, you can follow these steps:\\n\\n1. Check the official Red Hat OpenShift Local documentation for an updated list of requirements at the official documentation website.\\n2. Ensure your system meets the hardware requirements: a recent Intel CPU (except for Macs, where Apple Silicon machines are supported) with at least four physical cores and 16 GB of RAM.\\n3. Download the latest release of OpenShift Local and the \"pull secret\" file from console.redhat.com/openshift/create/local.\\n4. Unzip the file containing the OpenShift Local executable and run the command `crc setup` to prepare your copy of OpenShift Local, verifying requirements and setting the required configuration values.\\n5. Launch crc start, which can take around 20 minutes on a recent PC.\\n6. Access the OpenShift Web Console with the crc console command, which will open your default browser. Log in as a low-privilege user using the developer username and password, while the kubeadmin user uses a random-generated password. Use the crc console --credentials command to find the credentials required to log in as the kubeadmin user.\\n\\nAlternatively, you can also install OpenShift using the odo tool by downloading it from the \"Help\" menu on the OpenShift Web Console through the \"Command line tools\" entry and following the prompts to create a new application using Devfiles.'\n",
        "
\n" ], "text/plain": [ - "\u001b[32m'Tool:knowledge_search Args:\u001b[0m\u001b[32m{\u001b[0m\u001b[32m\\'query\\': \\'OpenShift installation\\'\u001b[0m\u001b[32m}\u001b[0m\u001b[32mTool:knowledge_search Response:\u001b[0m\u001b[32m[\u001b[0m\u001b[32mTextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'knowledge_search tool found 5 chunks:\\\\nBEGIN of knowledge_search tool results.\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 1:\\\\nDocument_id:num-0\\\\nContent: 23\\\\ninstall OpenShift Local, 16\\\\nJ\\\\nJAR file, 23\\\\nJava, 14, 24, 44\\\\nJavaScript, 24, 44\\\\nJenkins, 28\\\\nK\\\\nKiali, 36\\\\nKibana, 40, 40\\\\nKnative, 34\\\\nKubernetes, 7\\\\nL\\\\nlogs, 40\\\\nM\\\\nMicroservices, 36\\\\nmonitor, 40\\\\nN\\\\nNode.js, 14\\\\nnon-root accounts, 20\\\\nO\\\\nOpenShift 4.12, 33\\\\nOpenShift Kubernetes Distribution, 8\\\\nOpenShift Service Mesh, 36\\\\noperator, 28, 36\\\\nOperatorHub, 33, 36\\\\nOperators, 33\\\\n48\\\\nP\\\\nperspectives, 22\\\\nPHP, 14, 24\\\\nPlatform-as-a-Service, 8\\\\nprivilege escalation, 19\\\\nprivileged ports, 20\\\\nProject, 9\\\\nPrometheus, 40, 44\\\\nPromQL, 45\\\\nPython, 14, 24, 44\\\\nQ\\\\nQuarkus, 14, 44\\\\nR\\\\nRed Hat developer account, 13\\\\nRed Hat OpenShift, 7\\\\nRed Hat OpenShift Dev Spaces, 14\\\\nRed Hat OpenShift Local, 8, 15\\\\nRed Hat OpenShift Pipelines, 28\\\\nRed Hat Quay, 20\\\\nRed Hat Universal Base Images, 19\\\\nrole, 19\\\\nRoute, 9\\\\nRust, 14\\\\nS\\\\nScala, 14\\\\nScaling, 42\\\\nsecure by default, 19\\\\nSecurity Context Constraints, 19\\\\nServerless, 34\\\\nservice mesh, 36\\\\nsource code project, 22\\\\nstateful applications, 33\\\\nT\\\\nTekton, 28\\\\ntemplates, 32\\\\nTopology, 27\\\\nTwelve-Factor App, 21, 40\\\\nTypeScript, 24\\\\nU\\\\nUBI, 19\\\\nV\\\\nVertical scaling, 42\\\\nVisual Studio Code, 14\\\\nW\\\\nWeb Console, 22, 40\\\\n49\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 2:\\\\nDocument_id:num-0\\\\nContent: We\\\\nrecommend you to check the official Red Hat OpenShift Local documentation for an updated list of\\\\nrequirements at the official documentation website.\\\\n\\\\uf05a\\\\nRegarding Linux, even if Red Hat does not officially support them, OpenShift Local\\\\ncan run on other distributions, such as Ubuntu or Debian, with minor caveats.\\\\nRunning OpenShift Local on any Linux distribution requires a few additional\\\\nsoftware packages to be installed through your default package manager. The\\\\n15\\\\ndocumentation at crc.dev/crc has more information about this subject.\\\\n7.2. Hardware Requirements\\\\nIn terms of hardware, OpenShift Local has some strict requirements. Your system must use a recent\\\\nIntel CPU \u001b[0m\u001b[32m(\u001b[0m\u001b[32mexcept for Macs, where Apple Silicon machines are supported\u001b[0m\u001b[32m)\u001b[0m\u001b[32m with at least four physical\\\\ncores and have at least 16 GB of RAM. Be aware that the base installation of OpenShift Local\\\\nrequires at least 9 GB free to start. Of course, to run other applications on OpenShift Local, you will\\\\nneed more RAM, so using a computer with at least 32 GB of RAM is strongly recommended.\\\\nOpenShift Local also requires at least 35 GB of free disk space for its installation. The memory\\\\nrequirements are likely to increase in the future, so please check the documentation at crc.dev for\\\\nmore up-to-date information.\\\\n7.3. Installation\\\\nTo install OpenShift Local, open your web browser and navigate to console.redhat.com/openshift/\\\\ncreate/local . Download the latest release of OpenShift Local and the \"pull secret\" file. The latter is a\\\\nfile containing a key identifying your copy of OpenShift Local to your Red Hat Developer account.\\\\nUnzip the file containing the OpenShift Local executable, and using your terminal, run the\\\\ncommand crc setup . This command will prepare your copy of OpenShift Local, verifying\\\\nrequirements and setting the required configuration values.\\\\nOnce the crc setup command is ready, launch crc start. Running crc start can take a long time,\\\\naround 20 minutes, on a recent PC.\\\\nOnce started, access the OpenShift Web Console with the crc console command, which will open\\\\nyour default browser. OpenShift Local uses the developer username and password to log in as a\\\\nlow-privilege user, while the kubeadmin user uses a random-generated password. Use the crc\\\\nconsole --credentials command to find the credentials required to log in as the kubeadmin user.\\\\nOpenShift Local allows developers to perform various everyday tasks as if it were a standard\\\\nOpenShift cluster, like deploying applications\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 3:\\\\nDocument_id:num-0\\\\nContent: \"Import from Git\" entry. Click on it, and paste the URL of a project, for example,\\\\ngitlab.com/akosma/simple-deno-api.git.\\\\nAs soon as you paste the URL, OpenShift will immediately analyze the structure and programming\\\\nlanguage of the project and automatically recommend options for its build process. In our case, it’s\\\\na small application built with the Go programming language, and as such, it will advise the options\\\\nshown on the screen.\\\\nFigure 5. Deploying a project directly from its Git repository\\\\n25\\\\nThis particular example doesn’t require more configurations than the ones shown on the screen;\\\\nclick the \u001b[0m\u001b[32m[\u001b[0m\u001b[32m\\\\u2009Create\\\\u2009\u001b[0m\u001b[32m]\u001b[0m\u001b[32m button.\\\\nAfter a few seconds, you will see your application running on the \"Topology\" screen. OpenShift will\\\\ndownload the source code and trigger your project’s build. Click on the Topology screen icon to see\\\\nthe \"Build\" section, indicating that a build is running. The compilation and deployment of your\\\\napplication can take some time, depending on the complexity of the source code and the\\\\nprogramming language used.\\\\nOnce the build has finished, on the same pane, you will see a route available under the \"Routes\"\\\\nsection. Click on it, and you will see your application in action.\\\\n10.2. Container Registry\\\\nOpenShift has built your application source code, and the product of this build process is a\\\\ncontainer. You can see the container that OpenShift made for you on the \"Administrator\"\\\\nperspective, selecting the \"Builds\" menu and then the \"ImageStreams\" menu entry.\\\\nOpenShift includes a container registry; developers can use it as any other registry from outside the\\\\ncluster. Let us use \"podman\" to access the container registry and run the container locally on your\\\\nworkstation.\\\\nUsers must have the \"registry-editor\" and the \"system:image-builder\" roles to access the container\\\\nregistry. Since we’re connected to the Web Console using the \"kubeadmin\" user, we can provide\\\\nthose roles directly from the user interface without using the command line.\\\\nNavigate to the \"User Management\" section and select \"RoleBindings.\" Click on the \u001b[0m\u001b[32m[\u001b[0m\u001b[32m\\\\u2009Create\\\\nbinding\\\\u2009\u001b[0m\u001b[32m]\u001b[0m\u001b[32m button, and fill the form using the following values:\\\\n• Name: developer-sourcecode-registry-editor\\\\n• Namespace: sourcecode\\\\n• Role name: registry-editor\\\\n• Subject: User\\\\n• Subject name: developer\\\\nDo the same for the \"system:image-builder\" role, using a different \"Name\" field\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 4:\\\\nDocument_id:num-0\\\\nContent: .\\\\nThese characteristics set OpenShift apart as an excellent Kubernetes platform for enterprise users.\\\\nThe latest version of OpenShift available at the time of this writing is 4.12.\\\\n3.2. Is Red Hat OpenShift Open Source?\\\\nRed Hat OpenShift is a commercial product based on an open-source project called OKD. This\\\\nacronym means \" OpenShift Kubernetes Distribution\" and is publicly available for everyone to\\\\ninspect and contribute. Like the upstream Kubernetes project, OKD developers use the Go\\\\nprogramming language.\\\\n3.3. How can I run OpenShift?\\\\nToday, Red Hat OpenShift is available through various mechanisms and formats:\\\\n• DevOps teams can install it in their data centers \"on-premise.\"\\\\n• Major hyperscalers such as AWS, Azure, Google Cloud Platform, and IBM Cloud offer managed\\\\nRed Hat OpenShift installations.\\\\n• Developers can either run OpenShift locally on their workstations using Red Hat OpenShift\\\\nLocal, also known as CRC or \"Code-Ready Containers\"\\\\n• They can also request a 30-day trial OpenShift cluster, offered by Red Hat, at no charge, for\\\\ntesting and evaluation purposes.\\\\nRed Hat OpenShift is an integrated Platform-as-a-Service for enterprise users based on Kubernetes.\\\\nIt is tightly integrated with advanced security settings, developer tooling, and monitoring\\\\nmechanisms, allowing DevOps teams to be more productive.\\\\n8\\\\nChapter 4. OpenShift-only Custom Resource\\\\nDefinitions\\\\nRed Hat OpenShift is a complete DevOps platform extending Kubernetes in various ways. It bundles\\\\na constellation of Custom Resource Definitions \u001b[0m\u001b[32m(\u001b[0m\u001b[32mCRDs\u001b[0m\u001b[32m)\u001b[0m\u001b[32m to make the life of developers and cluster\\\\nadministrators easier.\\\\nLet us talk first about the CRDs only available on OpenShift.\\\\n4.1. Project\\\\nAn OpenShift Project is similar to a Kubernetes namespace, but more tightly integrated into the\\\\nsecurity system of OpenShift through additional annotations.\\\\napiVersion: project.openshift.io/v1\\\\nkind: Project\\\\nmetadata:\\\\n\\\\xa0 name: linkedin-learning-project\\\\n\\\\xa0 annotations:\\\\n\\\\xa0 openshift.io/description: \"Project description\"\\\\n\\\\xa0 openshift.io/display-name: \"Display name\"\\\\n4.2. Route\\\\nThe OpenShift Route object was one of the primary inspirations during the development of the\\\\nIngress object. In OpenShift, Ingress and Route objects work together to ensure your applications\\\\nare available outside the cluster.\\\\napiVersion: route.openshift.io/v1\\\\nkind: Route\\\\nmetadata:\\\\n\\\\xa0 name: my-route\\\\nspec:\\\\n\\\\xa0 host:\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 5:\\\\nDocument_id:num-0\\\\nContent: _02 branch of the GitHub\\\\nrepository for this course.\\\\nThe whole point of OpenShift is to be able to deploy, run, and monitor containerized applications.\\\\nDevOps engineers can deploy containers in OpenShift clusters through various means, for example:\\\\n• Using a YAML manifest.\\\\n• Deploying a single container using the web console.\\\\n• Building a project stored in a Git repository anywhere on the Internet and deploying the\\\\nresulting container.\\\\n• Using the integrated CI/CD pipelines.\\\\n• Using the odo tool together with \"Devfiles.\"\\\\nEach approach has pros and cons; in this chapter, we will review how to use the web console and\\\\nhow to use the odo tool.\\\\n9.1. Deploying Applications with the Web Console\\\\nDevOps engineers can deploy applications immediately using the Web Console. Launch your CRC\\\\ninstance and open the web console in your preferred web browser. The URL of the OpenShift web\\\\nconsole is \"https://console-openshift-console.apps-crc.testing.\"\\\\nThe OpenShift Web Console offers two major perspectives:\\\\n• The \"Administrator\" perspective.\\\\n• And the \"Developer\" perspective.\\\\nFor this explanation, select the \"Developer\" perspective.\\\\nThe first time you open the Developer perspective, a popup invites you to follow a user interface\\\\ntour.\\\\nOn the left-hand side, the perspective menu shows an entry titled \"Add,\" which, as the name\\\\nimplies, provides various mechanisms to deploy applications on an OpenShift cluster.\\\\nThe \"Add\" screen shows the various ways DevOps engineers can deploy applications on a cluster:\\\\n• Using the Developer Catalog, browsing and choosing among a long list of available databases,\\\\nmessage queues, and other valuable components to build applications, or entering your\\\\npreferred Helm chart repository to extend the catalog.\\\\n• Specifying the URL to a specific container on any standard container registry.\\\\n• Specifying the URL of a source code project stored on a Git repository, for example, but not\\\\n22\\\\nlimited to GitHub, GitLab, Gitea, or other locations.\\\\n• Importing YAML directly or even a JAR file with a Java application.\\\\nLet us select the \"Container Image\" option, where we can specify the URL of a ready-to-use\\\\ncontainer.\\\\nEnter the URL of the container on the field, and click on the \u001b[0m\u001b[32m[\u001b[0m\u001b[32m\\\\u2009Create\\\\u2009\u001b[0m\u001b[32m]\u001b[0m\u001b[32m button at the bottom of the\\\\npage. You do not need to change any other value on the form.\\\\nA few seconds later, depending on the size of the container and the speed of your Internet\\\\nconnection,\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'END of knowledge_search tool results.\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'The above results were retrieved to help answer the user\\\\\\'s query: \"OpenShift installation\". Use them as supporting information only in answering this query.\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m]\u001b[0m\u001b[32mTo install OpenShift, you can follow these steps:\\n\\n1. Check the system requirements for OpenShift Local, which is a version of OpenShift that can be installed on a local machine. The requirements include a recent Intel CPU with at least four physical cores, 16 GB of RAM, and 35 GB of free disk space.\\n2. Download the latest version of OpenShift Local from the official Red Hat website.\\n3. Unzip the downloaded file and run the `crc setup` command to prepare the installation.\\n4. Launch the `crc start` command to start the installation process, which may take around 20 minutes.\\n5. Once the installation is complete, access the OpenShift Web Console using the `crc console` command, which will open your default browser.\\n6. Log in to the Web Console using the developer username and password, or use the `crc console --credentials` command to find the credentials required to log in as the kubeadmin user.\\n7. You can then deploy applications using the web console or using the odo tool.\\n\\nNote that OpenShift is also available through various mechanisms and formats, including installation in data centers, managed installations by hyperscalers, and trial clusters offered by Red Hat.'\u001b[0m\n" + "\u001b[32m'\u001b[0m\u001b[32m{\u001b[0m\u001b[32m\"type\": \"function\", \"name\": \"knowledge_search\", \"parameters\": \u001b[0m\u001b[32m{\u001b[0m\u001b[32m\"query\": \"OpenShift installation\"\u001b[0m\u001b[32m}\u001b[0m\u001b[32m}\u001b[0m\u001b[32mTool:knowledge_search Args:\u001b[0m\u001b[32m{\u001b[0m\u001b[32m\\'query\\': \\'OpenShift installation\\'\u001b[0m\u001b[32m}\u001b[0m\u001b[32mTool:knowledge_search Response:\u001b[0m\u001b[32m[\u001b[0m\u001b[32mTextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'knowledge_search tool found 5 chunks:\\\\nBEGIN of knowledge_search tool results.\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 1:\\\\nDocument_id:num-0\\\\nContent: We\\\\nrecommend you to check the official Red Hat OpenShift Local documentation for an updated list of\\\\nrequirements at the official documentation website.\\\\n\\\\uf05a\\\\nRegarding Linux, even if Red Hat does not officially support them, OpenShift Local\\\\ncan run on other distributions, such as Ubuntu or Debian, with minor caveats.\\\\nRunning OpenShift Local on any Linux distribution requires a few additional\\\\nsoftware packages to be installed through your default package manager. The\\\\n15\\\\ndocumentation at crc.dev/crc has more information about this subject.\\\\n7.2. Hardware Requirements\\\\nIn terms of hardware, OpenShift Local has some strict requirements. Your system must use a recent\\\\nIntel CPU \u001b[0m\u001b[32m(\u001b[0m\u001b[32mexcept for Macs, where Apple Silicon machines are supported\u001b[0m\u001b[32m)\u001b[0m\u001b[32m with at least four physical\\\\ncores and have at least 16 GB of RAM. Be aware that the base installation of OpenShift Local\\\\nrequires at least 9 GB free to start. Of course, to run other applications on OpenShift Local, you will\\\\nneed more RAM, so using a computer with at least 32 GB of RAM is strongly recommended.\\\\nOpenShift Local also requires at least 35 GB of free disk space for its installation. The memory\\\\nrequirements are likely to increase in the future, so please check the documentation at crc.dev for\\\\nmore up-to-date information.\\\\n7.3. Installation\\\\nTo install OpenShift Local, open your web browser and navigate to console.redhat.com/openshift/\\\\ncreate/local . Download the latest release of OpenShift Local and the \"pull secret\" file. The latter is a\\\\nfile containing a key identifying your copy of OpenShift Local to your Red Hat Developer account.\\\\nUnzip the file containing the OpenShift Local executable, and using your terminal, run the\\\\ncommand crc setup . This command will prepare your copy of OpenShift Local, verifying\\\\nrequirements and setting the required configuration values.\\\\nOnce the crc setup command is ready, launch crc start. Running crc start can take a long time,\\\\naround 20 minutes, on a recent PC.\\\\nOnce started, access the OpenShift Web Console with the crc console command, which will open\\\\nyour default browser. OpenShift Local uses the developer username and password to log in as a\\\\nlow-privilege user, while the kubeadmin user uses a random-generated password. Use the crc\\\\nconsole --credentials command to find the credentials required to log in as the kubeadmin user.\\\\nOpenShift Local allows developers to perform various everyday tasks as if it were a standard\\\\nOpenShift cluster, like deploying applications\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 2:\\\\nDocument_id:num-0\\\\nContent: .\\\\nThese characteristics set OpenShift apart as an excellent Kubernetes platform for enterprise users.\\\\nThe latest version of OpenShift available at the time of this writing is 4.12.\\\\n3.2. Is Red Hat OpenShift Open Source?\\\\nRed Hat OpenShift is a commercial product based on an open-source project called OKD. This\\\\nacronym means \" OpenShift Kubernetes Distribution\" and is publicly available for everyone to\\\\ninspect and contribute. Like the upstream Kubernetes project, OKD developers use the Go\\\\nprogramming language.\\\\n3.3. How can I run OpenShift?\\\\nToday, Red Hat OpenShift is available through various mechanisms and formats:\\\\n• DevOps teams can install it in their data centers \"on-premise.\"\\\\n• Major hyperscalers such as AWS, Azure, Google Cloud Platform, and IBM Cloud offer managed\\\\nRed Hat OpenShift installations.\\\\n• Developers can either run OpenShift locally on their workstations using Red Hat OpenShift\\\\nLocal, also known as CRC or \"Code-Ready Containers\"\\\\n• They can also request a 30-day trial OpenShift cluster, offered by Red Hat, at no charge, for\\\\ntesting and evaluation purposes.\\\\nRed Hat OpenShift is an integrated Platform-as-a-Service for enterprise users based on Kubernetes.\\\\nIt is tightly integrated with advanced security settings, developer tooling, and monitoring\\\\nmechanisms, allowing DevOps teams to be more productive.\\\\n8\\\\nChapter 4. OpenShift-only Custom Resource\\\\nDefinitions\\\\nRed Hat OpenShift is a complete DevOps platform extending Kubernetes in various ways. It bundles\\\\na constellation of Custom Resource Definitions \u001b[0m\u001b[32m(\u001b[0m\u001b[32mCRDs\u001b[0m\u001b[32m)\u001b[0m\u001b[32m to make the life of developers and cluster\\\\nadministrators easier.\\\\nLet us talk first about the CRDs only available on OpenShift.\\\\n4.1. Project\\\\nAn OpenShift Project is similar to a Kubernetes namespace, but more tightly integrated into the\\\\nsecurity system of OpenShift through additional annotations.\\\\napiVersion: project.openshift.io/v1\\\\nkind: Project\\\\nmetadata:\\\\n\\\\xa0 name: linkedin-learning-project\\\\n\\\\xa0 annotations:\\\\n\\\\xa0 openshift.io/description: \"Project description\"\\\\n\\\\xa0 openshift.io/display-name: \"Display name\"\\\\n4.2. Route\\\\nThe OpenShift Route object was one of the primary inspirations during the development of the\\\\nIngress object. In OpenShift, Ingress and Route objects work together to ensure your applications\\\\nare available outside the cluster.\\\\napiVersion: route.openshift.io/v1\\\\nkind: Route\\\\nmetadata:\\\\n\\\\xa0 name: my-route\\\\nspec:\\\\n\\\\xa0 host:\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 3:\\\\nDocument_id:num-0\\\\nContent: \"Import from Git\" entry. Click on it, and paste the URL of a project, for example,\\\\ngitlab.com/akosma/simple-deno-api.git.\\\\nAs soon as you paste the URL, OpenShift will immediately analyze the structure and programming\\\\nlanguage of the project and automatically recommend options for its build process. In our case, it’s\\\\na small application built with the Go programming language, and as such, it will advise the options\\\\nshown on the screen.\\\\nFigure 5. Deploying a project directly from its Git repository\\\\n25\\\\nThis particular example doesn’t require more configurations than the ones shown on the screen;\\\\nclick the \u001b[0m\u001b[32m[\u001b[0m\u001b[32m\\\\u2009Create\\\\u2009\u001b[0m\u001b[32m]\u001b[0m\u001b[32m button.\\\\nAfter a few seconds, you will see your application running on the \"Topology\" screen. OpenShift will\\\\ndownload the source code and trigger your project’s build. Click on the Topology screen icon to see\\\\nthe \"Build\" section, indicating that a build is running. The compilation and deployment of your\\\\napplication can take some time, depending on the complexity of the source code and the\\\\nprogramming language used.\\\\nOnce the build has finished, on the same pane, you will see a route available under the \"Routes\"\\\\nsection. Click on it, and you will see your application in action.\\\\n10.2. Container Registry\\\\nOpenShift has built your application source code, and the product of this build process is a\\\\ncontainer. You can see the container that OpenShift made for you on the \"Administrator\"\\\\nperspective, selecting the \"Builds\" menu and then the \"ImageStreams\" menu entry.\\\\nOpenShift includes a container registry; developers can use it as any other registry from outside the\\\\ncluster. Let us use \"podman\" to access the container registry and run the container locally on your\\\\nworkstation.\\\\nUsers must have the \"registry-editor\" and the \"system:image-builder\" roles to access the container\\\\nregistry. Since we’re connected to the Web Console using the \"kubeadmin\" user, we can provide\\\\nthose roles directly from the user interface without using the command line.\\\\nNavigate to the \"User Management\" section and select \"RoleBindings.\" Click on the \u001b[0m\u001b[32m[\u001b[0m\u001b[32m\\\\u2009Create\\\\nbinding\\\\u2009\u001b[0m\u001b[32m]\u001b[0m\u001b[32m button, and fill the form using the following values:\\\\n• Name: developer-sourcecode-registry-editor\\\\n• Namespace: sourcecode\\\\n• Role name: registry-editor\\\\n• Subject: User\\\\n• Subject name: developer\\\\nDo the same for the \"system:image-builder\" role, using a different \"Name\" field\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 4:\\\\nDocument_id:num-0\\\\nContent: Git repository, for example, but not\\\\n22\\\\nlimited to GitHub, GitLab, Gitea, or other locations.\\\\n• Importing YAML directly or even a JAR file with a Java application.\\\\nLet us select the \"Container Image\" option, where we can specify the URL of a ready-to-use\\\\ncontainer.\\\\nEnter the URL of the container on the field, and click on the \u001b[0m\u001b[32m[\u001b[0m\u001b[32m\\\\u2009Create\\\\u2009\u001b[0m\u001b[32m]\u001b[0m\u001b[32m button at the bottom of the\\\\npage. You do not need to change any other value on the form.\\\\nA few seconds later, depending on the size of the container and the speed of your Internet\\\\nconnection, OpenShift will have pulled the container and deployed it onto your cluster. This\\\\ndeployment will include the usual standard elements: a \"Deployment\" object, a \"Service\" object, and\\\\na \"Route.\"\\\\nOpenShift offers a visual representation of the applications running on your project: click on the\\\\nicon of your container, and you will see a panel opening on the right side of the screen. This panel\\\\nwill include the URL automatically assigned to your deployment, and clicking it will show the\\\\napplication in action in another browser tab.\\\\nFigure 4. Topology screen on Red Hat OpenShift\\\\n9.2. Creating and Debugging Applications with the odo\\\\nTool\\\\nWith the oc tool, Red Hat provides another one geared toward software developers: the odo tool.\\\\nDevelopers can use the odo tool to create applications using \"Devfiles,\" particular files named\\\\n\"devfile.yaml\" based on an open standard available at the Devfiles website. Devfiles contain\\\\ninformation about your application’s programming language, dependencies, and other essential\\\\ndetails.\\\\n23\\\\nThe odo tool is not available by default on your command line, but you can download it from the\\\\n\"Help\" menu on the OpenShift Web Console through the \"Command line tools\" entry. Click on the\\\\n\"Download odo\" link at the bottom, and select the version of odo that corresponds to your system.\\\\nThe \"odo catalog list components was\" command shows the various programming languages and\\\\nframeworks supported off-the-box by \"odo.\"\\\\nThe odo init command prompts the user for a new application using many programming\\\\nlanguages: .NET, Go, Java, JavaScript, PHP, Python, and TypeScript. The last command generates a\\\\nscaffold ready to be populated with the required logic. Finally, the odo push command builds and\\\\npushes the container to the OpenShift container registry, deploying\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'Result 5:\\\\nDocument_id:num-0\\\\nContent: _02 branch of the GitHub\\\\nrepository for this course.\\\\nThe whole point of OpenShift is to be able to deploy, run, and monitor containerized applications.\\\\nDevOps engineers can deploy containers in OpenShift clusters through various means, for example:\\\\n• Using a YAML manifest.\\\\n• Deploying a single container using the web console.\\\\n• Building a project stored in a Git repository anywhere on the Internet and deploying the\\\\nresulting container.\\\\n• Using the integrated CI/CD pipelines.\\\\n• Using the odo tool together with \"Devfiles.\"\\\\nEach approach has pros and cons; in this chapter, we will review how to use the web console and\\\\nhow to use the odo tool.\\\\n9.1. Deploying Applications with the Web Console\\\\nDevOps engineers can deploy applications immediately using the Web Console. Launch your CRC\\\\ninstance and open the web console in your preferred web browser. The URL of the OpenShift web\\\\nconsole is \"https://console-openshift-console.apps-crc.testing.\"\\\\nThe OpenShift Web Console offers two major perspectives:\\\\n• The \"Administrator\" perspective.\\\\n• And the \"Developer\" perspective.\\\\nFor this explanation, select the \"Developer\" perspective.\\\\nThe first time you open the Developer perspective, a popup invites you to follow a user interface\\\\ntour.\\\\nOn the left-hand side, the perspective menu shows an entry titled \"Add,\" which, as the name\\\\nimplies, provides various mechanisms to deploy applications on an OpenShift cluster.\\\\nThe \"Add\" screen shows the various ways DevOps engineers can deploy applications on a cluster:\\\\n• Using the Developer Catalog, browsing and choosing among a long list of available databases,\\\\nmessage queues, and other valuable components to build applications, or entering your\\\\npreferred Helm chart repository to extend the catalog.\\\\n• Specifying the URL to a specific container on any standard container registry.\\\\n• Specifying the URL of a source code project stored on a Git repository, for example, but not\\\\n22\\\\nlimited to GitHub, GitLab, Gitea, or other locations.\\\\n• Importing YAML directly or even a JAR file with a Java application.\\\\nLet us select the \"Container Image\" option, where we can specify the URL of a ready-to-use\\\\ncontainer.\\\\nEnter the URL of the container on the field, and click on the \u001b[0m\u001b[32m[\u001b[0m\u001b[32m\\\\u2009Create\\\\u2009\u001b[0m\u001b[32m]\u001b[0m\u001b[32m button at the bottom of the\\\\npage. You do not need to change any other value on the form.\\\\nA few seconds later, depending on the size of the container and the speed of your Internet\\\\nconnection,\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m, TextContentItem\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtext\u001b[0m\u001b[32m=\\'END of knowledge_search tool results.\\\\n\\', \u001b[0m\u001b[32mtype\u001b[0m\u001b[32m=\\'text\\'\u001b[0m\u001b[32m)\u001b[0m\u001b[32m]\u001b[0m\u001b[32mTo install OpenShift, you can follow these steps:\\n\\n1. Check the official Red Hat OpenShift Local documentation for an updated list of requirements at the official documentation website.\\n2. Ensure your system meets the hardware requirements: a recent Intel CPU \u001b[0m\u001b[32m(\u001b[0m\u001b[32mexcept for Macs, where Apple Silicon machines are supported\u001b[0m\u001b[32m)\u001b[0m\u001b[32m with at least four physical cores and 16 GB of RAM.\\n3. Download the latest release of OpenShift Local and the \"pull secret\" file from console.redhat.com/openshift/create/local.\\n4. Unzip the file containing the OpenShift Local executable and run the command `crc setup` to prepare your copy of OpenShift Local, verifying requirements and setting the required configuration values.\\n5. Launch crc start, which can take around 20 minutes on a recent PC.\\n6. Access the OpenShift Web Console with the crc console command, which will open your default browser. Log in as a low-privilege user using the developer username and password, while the kubeadmin user uses a random-generated password. Use the crc console --credentials command to find the credentials required to log in as the kubeadmin user.\\n\\nAlternatively, you can also install OpenShift using the odo tool by downloading it from the \"Help\" menu on the OpenShift Web Console through the \"Command line tools\" entry and following the prompts to create a new application using Devfiles.'\u001b[0m\n" ] }, "metadata": {}, @@ -524,17 +525,16 @@ "\n", "---------- 📍 Step 3: InferenceStep ----------\n", "🤖 Model Response:\n", - "\u001b[35mTo install OpenShift, you can follow these steps:\n", + "\u001b[35mTo install OpenShift, follow these steps:\n", "\n", - "1. Check the system requirements for OpenShift Local, which is a version of OpenShift that can be installed on a local machine. The requirements include a recent Intel CPU with at least four physical cores, 16 GB of RAM, and 35 GB of free disk space.\n", - "2. Download the latest version of OpenShift Local from the official Red Hat website.\n", - "3. Unzip the downloaded file and run the `crc setup` command to prepare the installation.\n", - "4. Launch the `crc start` command to start the installation process, which may take around 20 minutes.\n", - "5. Once the installation is complete, access the OpenShift Web Console using the `crc console` command, which will open your default browser.\n", - "6. Log in to the Web Console using the developer username and password, or use the `crc console --credentials` command to find the credentials required to log in as the kubeadmin user.\n", - "7. You can then deploy applications using the web console or using the odo tool.\n", + "1. Check the official Red Hat OpenShift Local documentation for an updated list of requirements at the official documentation website.\n", + "2. Ensure your system meets the hardware requirements: a recent Intel CPU (except for Macs, where Apple Silicon machines are supported) with at least four physical cores and 16 GB of RAM.\n", + "3. Download the latest release of OpenShift Local and the \"pull secret\" file from console.redhat.com/openshift/create/local.\n", + "4. Unzip the file containing the OpenShift Local executable and run the command `crc setup` to prepare your copy of OpenShift Local, verifying requirements and setting the required configuration values.\n", + "5. Launch crc start, which can take around 20 minutes on a recent PC.\n", + "6. Access the OpenShift Web Console with the crc console command, which will open your default browser. Log in as a low-privilege user using the developer username and password, while the kubeadmin user uses a random-generated password. Use the crc console --credentials command to find the credentials required to log in as the kubeadmin user.\n", "\n", - "Note that OpenShift is also available through various mechanisms and formats, including installation in data centers, managed installations by hyperscalers, and trial clusters offered by Red Hat.\n", + "Alternatively, you can also install OpenShift using the odo tool by downloading it from the \"Help\" menu on the OpenShift Web Console through the \"Command line tools\" entry and following the prompts to create a new application using Devfiles.\n", "\u001b[0m\n", "========== Query processing completed ========== \n", "\n" @@ -583,7 +583,7 @@ ], "metadata": { "kernelspec": { - "display_name": "venv7", + "display_name": "venv2", "language": "python", "name": "python3" }, diff --git a/demos/a2a_llama_stack/requirements.txt b/demos/a2a_llama_stack/requirements.txt index 7843071..9acb9e0 100644 --- a/demos/a2a_llama_stack/requirements.txt +++ b/demos/a2a_llama_stack/requirements.txt @@ -1,34 +1,71 @@ annotated-types==0.7.0 anyio==4.9.0 +appnope==0.1.4 +asttokens==3.0.0 certifi==2025.1.31 +cffi==1.17.1 +charset-normalizer==3.4.2 click==8.1.8 +comm==0.2.2 +cryptography==45.0.3 +debugpy==1.8.14 +decorator==5.2.1 distro==1.9.0 +dotenv==0.9.9 +executing==2.2.0 +fire==0.7.0 h11==0.16.0 httpcore==1.0.9 httpx==0.28.1 +httpx-sse==0.4.0 idna==3.10 +ipykernel==6.29.5 +ipython==9.3.0 +ipython_pygments_lexers==1.1.1 +jedi==0.19.2 +jupyter_client==8.6.3 +jupyter_core==5.8.1 +jwcrypto==1.5.6 llama_stack_client==0.2.2 markdown-it-py==3.0.0 +matplotlib-inline==0.1.7 mdurl==0.1.2 +nest-asyncio==1.6.0 numpy==2.2.5 +packaging==25.0 pandas==2.2.3 +parso==0.8.4 +pexpect==4.9.0 +platformdirs==4.3.8 prompt_toolkit==3.0.51 +psutil==7.0.0 +ptyprocess==0.7.0 +pure_eval==0.2.3 pyaml==25.1.0 +pycparser==2.22 pydantic==2.11.3 pydantic_core==2.33.1 Pygments==2.19.1 +PyJWT==2.10.1 python-dateutil==2.9.0.post0 +python-dotenv==1.1.0 pytz==2025.2 PyYAML==6.0.2 +pyzmq==26.4.0 +requests==2.32.3 rich==14.0.0 six==1.17.0 sniffio==1.3.1 sse-starlette==2.2.1 +stack-data==0.6.3 starlette==0.46.2 termcolor==3.0.1 +tornado==6.5.1 tqdm==4.67.1 +traitlets==5.14.3 typing-inspection==0.4.0 typing_extensions==4.13.2 tzdata==2025.2 +urllib3==2.4.0 uvicorn==0.34.2 wcwidth==0.2.13 From a1816ecb9b69e7be0239221b78f4d46484b8d94c Mon Sep 17 00:00:00 2001 From: Kevin Cogan Date: Tue, 3 Jun 2025 12:52:13 +0100 Subject: [PATCH 13/13] fix: udpated asyncclick error by adding to requirements.txt. --- demos/a2a_llama_stack/README.md | 3 --- demos/a2a_llama_stack/requirements.txt | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/demos/a2a_llama_stack/README.md b/demos/a2a_llama_stack/README.md index aebbb32..17708cf 100644 --- a/demos/a2a_llama_stack/README.md +++ b/demos/a2a_llama_stack/README.md @@ -204,9 +204,6 @@ With the agent server(s) operational, you can now use a client application to di ```bash # Navigate to the client script directory: cd a2a-samples/samples/python - - # Install required packages: - uv pip install asyncclick ``` 2. **Navigate to the client script directory:** diff --git a/demos/a2a_llama_stack/requirements.txt b/demos/a2a_llama_stack/requirements.txt index 9acb9e0..6aa34fc 100644 --- a/demos/a2a_llama_stack/requirements.txt +++ b/demos/a2a_llama_stack/requirements.txt @@ -2,6 +2,7 @@ annotated-types==0.7.0 anyio==4.9.0 appnope==0.1.4 asttokens==3.0.0 +asyncclick==8.1.8 certifi==2025.1.31 cffi==1.17.1 charset-normalizer==3.4.2