diff --git a/supporting-blog-content/agent-builder-a2a-agent-framework/Dockerfile b/supporting-blog-content/agent-builder-a2a-agent-framework/Dockerfile new file mode 100644 index 00000000..b8f80f9a --- /dev/null +++ b/supporting-blog-content/agent-builder-a2a-agent-framework/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.11-slim + +WORKDIR /app + +RUN python -m venv /opt/venv + +ENV PATH="/opt/venv/bin:$PATH" + +COPY requirements.txt . + +RUN pip install -r requirements.txt + +COPY elastic_agent_builder_a2a.py . + +CMD ["python", "elastic_agent_builder_a2a.py"] \ No newline at end of file diff --git a/supporting-blog-content/agent-builder-a2a-agent-framework/README.md b/supporting-blog-content/agent-builder-a2a-agent-framework/README.md new file mode 100644 index 00000000..c2515564 --- /dev/null +++ b/supporting-blog-content/agent-builder-a2a-agent-framework/README.md @@ -0,0 +1,133 @@ +# Elastic Agent Builder A2A App + +**Getting started with Agent Builder and A2A using Microsoft Agent Framework** + +This is an example Python console app that demonstrates how to connect and utilize an [Elastic Agent Builder](https://www.elastic.co/elasticsearch/agent-builder) agent via the Agent2Agent (A2A) Protocol orchestrated with the [Microsoft Agent Framework](https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview). + +## Prerequisites + +1. An Elasticsearch project/deployment running in [Elastic Cloud](https://cloud.elastic.co/registration?utm_source=github&utm_content=elasticsearch-labs-example-apps). + * Requires Elasticsearch serverless project (or for hosted deployments at least Elasticsearch version 9.2.0). +2. A text editor or an integrated development environment (IDE) like [Visual Studio Code](https://code.visualstudio.com/download) running on your local computer. +3. [Python version 3.10 or greater](https://www.python.org/downloads/) installed on your local computer. + +## Set up your Elasticsearch project + +1. Create an index named `my-docs` in your Elasticsearch project by running the following command in Elastic Developer Tools: + + PUT /my-docs + { + "mappings": { + "properties": { + "title": { "type": "text" }, + "content": { + "type": "semantic_text" + }, + "filename": { "type": "keyword" }, + "last_modified": { "type": "date" } + } + } + } +2. Insert a document into your index named `greetings.md` by running the following command in Elastic Developer Tools: + + PUT /my-docs/_doc/greetings-md + { + "title": "Greetings", + "content": " + # Greetings + ## Basic Greeting + Hello! + + ## Helloworld Greeting + Hello World! 🌎 + + ## Not Greeting + I'm only a greeting agent. 🤷 + + ", + "filename": "greetings.md", + "last_modified": "2025-11-04T12:00:00Z" + } + +3. In Elastic Agent Builder, create a **tool** with the following values: +* **Type**: `ES|QL` +* **Tool ID**: `example.get_greetings` +* **Description**: `Get greetings doc from Elasticsearch my-docs index.` +* **ES|QL**: + + FROM my-docs | WHERE filename == "greetings.md" + +4. In Elastic Agent Builder, create an **agent** with the following values: +* **Agent ID**: `helloworld_agent` +* **Custom Instructions**: + + If the prompt contains greeting text like "Hi" or "Hello" then respond with only the Basic Hello text from your documents. + + If the prompt contains the text “Hello World” then respond with only the Hello World text from your documents. + + In all other cases where the prompt does not contain greeting words, then respond with only the Not Greeting text from your documents. + +* **Display Name**: `HelloWorld Agent` +* **Display Description**: `An agent that responds to greetings.` + + + +## Clone the example app + +1. Open a terminal and clone the Search Labs source code repository which contains the Elastic Agent Builder A2A App example. Run the following command to clone the example app: + + git clone https://github.com/elastic/elasticsearch-labs + +3. `cd` to change directory to the example code located in the `supporting-blog-content/agent-builder-a2a-agent-framework` subdirectory. + + cd elasticsearch-labs/supporting-blog-content/agent-builder-a2a-agent-framework + +## Set up the environment variables + +1. Set up the environment variables with values copied from your Elastic project. + 1. Make a copy of the file `env.example` and name the new file `.env ` + 2. Edit the `.env` file to set the values of the environment variables to use the values copied from your Elastic project. + * Replace + 1. In your Elastic project, go to the Elastic Agent Builder - Tools page. Click the **MCP Server** dropdown at the top of the Tools page. Select **Copy MCP Server URL.** + 2. Add the **MCP Server URL** value to the `.env` file. + * Find where the placeholder text “****” appears and paste in the copied **MCP Server URL** to replace the placeholder text. Now edit the pasted **MCP Server URL**. Delete the text “mcp” at the end of the URL and replace it with the text “a2a”. The edited URL should look something like this + + `https://example-project-a123.kb.westus2.azure.elastic.cloud/api/agent_builder/a2a` + + * Replace + 1. In your Elastic project, click **Elasticsearch** in the navigation menu to go to your project’s home page. + 2. Click **Create API key** to create a new API key. + 3. After the API key is created, copy the API Key value. + 4. Add the API Key value to the `.env` file. + * Find where the placeholder text “****” appears and paste in the copied API Key value to replace the placeholder text. + + 3. Save the changes to the `.env` file. + +## Running the example app with Python + +1. Create a Python virtual environment by running the following code in the terminal. + + python -m venv .venv + +2. Activate the Python virtual environment. + * If you’re running MacOS, the command to activate the virtual environment is: + + source .venv/bin/activate + + * If you’re on Windows, the command to activate the virtual environment is: + + .venv\Scripts\activate + +3. Install the Microsoft Agent Framework along with its necessary Python packages by running the following `pip` command: + + pip install -r requirements.txt + +4. Run the example app by entering the following command into the terminal: + + python elastic_agent_builder_a2a.py + +## Running the example app with Docker + +1. Run the example app with Docker by entering the following command into the terminal: + + docker compose run elastic-agent-builder-a2a diff --git a/supporting-blog-content/agent-builder-a2a-agent-framework/docker-compose.yml b/supporting-blog-content/agent-builder-a2a-agent-framework/docker-compose.yml new file mode 100644 index 00000000..8a2834df --- /dev/null +++ b/supporting-blog-content/agent-builder-a2a-agent-framework/docker-compose.yml @@ -0,0 +1,9 @@ +services: + elastic-agent-builder-a2a: + build: . + container_name: elastic-agent-builder-a2a + stdin_open: true + tty: true + environment: + - ES_AGENT_URL=${ES_AGENT_URL} + - ES_API_KEY=${ES_API_KEY} \ No newline at end of file diff --git a/supporting-blog-content/agent-builder-a2a-agent-framework/elastic_agent_builder_a2a.py b/supporting-blog-content/agent-builder-a2a-agent-framework/elastic_agent_builder_a2a.py new file mode 100644 index 00000000..c6132bee --- /dev/null +++ b/supporting-blog-content/agent-builder-a2a-agent-framework/elastic_agent_builder_a2a.py @@ -0,0 +1,43 @@ +import asyncio +from dotenv import load_dotenv +import httpx +import os +from a2a.client import A2ACardResolver +from agent_framework.a2a import A2AAgent + + +async def main(): + load_dotenv() + a2a_agent_host = os.getenv("ES_AGENT_URL") + a2a_agent_key = os.getenv("ES_API_KEY") + + print(f"Connection to Elastic A2A agent at: {a2a_agent_host}") + + custom_headers = {"Authorization": f"ApiKey {a2a_agent_key}"} + + async with httpx.AsyncClient(timeout=60.0, headers=custom_headers) as http_client: + # Resolve the A2A Agent Card + resolver = A2ACardResolver(httpx_client=http_client, base_url=a2a_agent_host) + agent_card = await resolver.get_agent_card( + relative_card_path="/helloworld_agent.json" + ) + print(f"Found Agent: {agent_card.name} - {agent_card.description}") + + # Use the Agent + agent = A2AAgent( + name=agent_card.name, + description=agent_card.description, + agent_card=agent_card, + url=a2a_agent_host, + http_client=http_client, + ) + prompt = input("Enter Greeting >>> ") + print("\nSending message to Elastic A2A agent...") + response = await agent.run(prompt) + print("\nAgent Response:") + for message in response.messages: + print(message.text) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/supporting-blog-content/agent-builder-a2a-agent-framework/env.example b/supporting-blog-content/agent-builder-a2a-agent-framework/env.example new file mode 100644 index 00000000..a857c0fd --- /dev/null +++ b/supporting-blog-content/agent-builder-a2a-agent-framework/env.example @@ -0,0 +1,2 @@ +ES_AGENT_URL= +ES_API_KEY= \ No newline at end of file diff --git a/supporting-blog-content/agent-builder-a2a-agent-framework/requirements.txt b/supporting-blog-content/agent-builder-a2a-agent-framework/requirements.txt new file mode 100644 index 00000000..f54e6fd9 --- /dev/null +++ b/supporting-blog-content/agent-builder-a2a-agent-framework/requirements.txt @@ -0,0 +1,131 @@ +a2a-sdk~=0.3.14 +ag-ui-protocol~=0.1.10 +agent-framework~=1.0.0b251114 +agent-framework-a2a~=1.0.0b251114 +agent-framework-ag-ui~=1.0.0b251117 +agent-framework-anthropic~=1.0.0b251114 +agent-framework-azure-ai~=1.0.0b251114 +agent-framework-azurefunctions~=1.0.0b251114 +agent-framework-chatkit~=1.0.0b251114 +agent-framework-copilotstudio~=1.0.0b251114 +agent-framework-core~=1.0.0b251114 +agent-framework-devui~=1.0.0b251114 +agent-framework-lab~=1.0.0b251024 +agent-framework-mem0~=1.0.0b251114 +agent-framework-purview~=1.0.0b251114 +agent-framework-redis~=1.0.0b251114 +aiohappyeyeballs~=2.6.1 +aiohttp~=3.13.2 +aiosignal~=1.4.0 +annotated-doc~=0.0.4 +annotated-types~=0.7.0 +anthropic~=0.74.0 +anyio~=4.11.0 +attrs~=25.4.0 +azure-ai-agents~=1.2.0b5 +azure-ai-projects~=2.0.0b2 +azure-core~=1.36.0 +azure-functions~=1.24.0 +azure-functions-durable~=1.4.0 +azure-identity~=1.25.1 +azure-storage-blob~=12.27.1 +backoff~=2.2.1 +cachetools~=6.2.2 +certifi~=2025.11.12 +cffi~=2.0.0 +charset-normalizer~=3.4.4 +click~=8.3.1 +colorama~=0.4.6 +cryptography~=46.0.3 +distro~=1.9.0 +docstring_parser~=0.17.0 +fastapi~=0.121.2 +frozenlist~=1.8.0 +furl~=2.1.4 +google-api-core~=2.28.1 +google-auth~=2.43.0 +googleapis-common-protos~=1.72.0 +griffe~=1.15.0 +grpcio~=1.76.0 +h11~=0.16.0 +h2~=4.3.0 +hpack~=4.1.0 +httpcore~=1.0.9 +httptools~=0.7.1 +httpx~=0.28.1 +httpx-sse~=0.4.3 +hyperframe~=6.1.0 +idna~=3.11 +importlib_metadata~=8.7.0 +isodate~=0.7.2 +jiter~=0.12.0 +jsonpath-ng~=1.7.0 +jsonschema~=4.25.1 +jsonschema-specifications~=2025.9.1 +MarkupSafe~=3.0.3 +mcp~=1.21.2 +mem0ai~=1.0.1 +microsoft-agents-activity~=0.6.0 +microsoft-agents-copilotstudio-client~=0.6.0 +microsoft-agents-hosting-core~=0.6.0 +ml_dtypes~=0.5.4 +msal~=1.34.0 +msal-extensions~=1.3.1 +multidict~=6.7.0 +numpy~=2.3.5 +openai~=2.8.1 +openai-agents~=0.6.0 +openai-chatkit~=1.3.0 +opentelemetry-api~=1.38.0 +opentelemetry-exporter-otlp-proto-common~=1.38.0 +opentelemetry-exporter-otlp-proto-grpc~=1.38.0 +opentelemetry-proto~=1.38.0 +opentelemetry-sdk~=1.38.0 +opentelemetry-semantic-conventions~=0.59b0 +opentelemetry-semantic-conventions-ai~=0.4.13 +orderedmultidict~=1.0.2 +packaging~=25.0 +ply~=3.11 +portalocker~=3.2.0 +posthog~=7.0.1 +propcache~=0.4.1 +proto-plus~=1.26.1 +protobuf~=5.29.5 +pyasn1~=0.6.1 +pyasn1_modules~=0.4.2 +pycparser~=2.23 +pydantic~=2.12.4 +pydantic-settings~=2.12.0 +pydantic_core~=2.41.5 +PyJWT~=2.10.1 +python-dateutil~=2.9.0.post0 +python-dotenv~=1.2.1 +python-multipart~=0.0.20 +python-ulid~=3.1.0 +pytz~=2025.2 +PyYAML~=6.0.3 +qdrant-client~=1.16.0 +redis~=6.4.0 +redisvl~=0.11.0 +referencing~=0.37.0 +requests~=2.32.5 +rpds-py~=0.29.0 +rsa~=4.9.1 +six~=1.17.0 +sniffio~=1.3.1 +SQLAlchemy~=2.0.44 +sse-starlette~=3.0.3 +starlette~=0.49.3 +tenacity~=9.1.2 +tqdm~=4.67.1 +types-requests~=2.32.4.20250913 +typing-inspection~=0.4.2 +typing_extensions~=4.15.0 +urllib3~=2.5.0 +uvicorn~=0.38.0 +uvloop~=0.22.1 +watchfiles~=1.1.1 +websockets~=15.0.1 +Werkzeug~=3.1.3 +yarl~=1.22.0 +zipp~=3.23.0 \ No newline at end of file