-
Notifications
You must be signed in to change notification settings - Fork 253
Add Agent Builder A2A with Agent Framework example app #508
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4d6ca5b
3c5e56c
a120ed3
08ae566
5e2a42a
6c5aa71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| FROM python:3.11-slim | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| COPY elastic_agent_builder_a2a.py test_elastic_agent_builder_a2a.py ./ | ||
|
|
||
| RUN pip install agent-framework | ||
| RUN pip install python-dotenv | ||
|
|
||
| CMD ["python", "-m", "unittest", "test_elastic_agent_builder_a2a.py"] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your example is in the wrong folder. Given it is a supporting piece for a blog it should be moved to |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| # 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 deployment running in [Elastic Cloud](https://cloud.elastic.co/registration?utm_source=github&utm_content=elasticsearch-labs-example-apps). | ||
| * Requires Elasticsearch Serverless (or for hosted deployments at least Elasticsearch version 9.2.0). | ||
| 2. 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. | ||
|
|
||
| ## Setup your Elasticsearch deployment | ||
|
|
||
| 1. Create an index named `my-docs` in your Elasticsearch deployment 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.` | ||
|
|
||
|
|
||
|
|
||
| ## Running the example app | ||
|
|
||
| 1. Open Visual Studio Code and open a new terminal within the Visual Studio Code editor. | ||
| 2. In the open terminal, clone the Search Labs source code repository which contains the Elastic Agent Builder A2A App example. | ||
|
|
||
| git clone https://github.com/elastic/elasticsearch-labs | ||
|
|
||
| 3. `cd` to change directory to the example code located in the `example-apps/agent-builder-a2a-agent-framework` subdirectory. | ||
|
|
||
| cd elasticsearch-labs/example-apps/agent-builder-a2a-agent-framework | ||
|
|
||
| 4. Replace placeholder values in `elastic_agent_builder_a2a.py` with values copied from your Elastic deployment. | ||
| 1. Open the file `elastic_agent_builder_a2a.py` in the Visual Studio editor. | ||
| 2. Replace <YOUR-ELASTIC-AGENT-BUILDER-URL\> | ||
| 1. In your Elastic deployment, 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. In Visual Studio add the **MCP Server URL** value to the `elastic-agent-builder-a2a.py` file. | ||
| * Find where the placeholder text “**<YOUR-ELASTIC-AGENT-BUILDER-URL\>**” 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` | ||
|
|
||
| 3. Replace <YOUR-ELASTIC-API-KEY\> | ||
| 1. In your Elastic deployment, click **Elasticsearch** in the navigation menu to go to your deployment’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. In Visual Studio add the API Key value to the `elastic-agent-builder-a2a.py` file. | ||
| * Find where the placeholder text “**<YOUR-ELASTIC-API-KEY\>**” appears and paste in the copied API Key value to replace the placeholder text. | ||
|
|
||
| 4. Confirm the **relative_card_path** is set correctly in the `elastic-agent-builder-a2a.py` file by finding the code line that starts with the text “agent_card”. Confirm that the **relative_card_path** matches the Agent ID you specified when you created the agent in Elastic Agent Builder. If your Agent ID is “helloworld_agent” then the **relative_card_path** should be set to `/helloworld_agent.json` | ||
| 5. Save the `elastic_agent_builder_a2a.py` file in the Visual Studio editor. | ||
|
|
||
| 5. Create a Python virtual environment by running the following code in the Visual Studio Code terminal. | ||
|
|
||
| python -m venv .venv | ||
|
|
||
| 6. 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 | ||
|
|
||
| 7. Install the Microsoft Agent Framework with the following `pip` command: | ||
|
|
||
| pip install agent-framework | ||
|
|
||
| 8. Run the example code by entering the following command into the terminal: | ||
|
|
||
| python elastic_agent_builder_a2a.py | ||
|
|
||
| ## Running the example test | ||
|
|
||
| 1. Setup the environment variables. | ||
| 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 deployment. See instructions on where to get these values in the [Running the example app](#running-the-example-app) section of this `README.md` file. | ||
| * Set the value of **ES_AGENT_URL** to be the value of **YOUR-ELASTIC-AGENT-BUILDER-URL** | ||
| * Set the value of **ES_API_KEY** to be value of **YOUR-ELASTIC-API-KEY** | ||
| 2. Run the test directly with Python. | ||
|
|
||
| python test_elastic_agent_builder_a2a.py | ||
|
|
||
| 3. Run the test with Docker. | ||
|
|
||
| docker compose up |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| version: '3.8' | ||
|
|
||
| services: | ||
| elastic-agent-builder-a2a-test: | ||
| build: | ||
| context: . | ||
| dockerfile: Dockerfile | ||
| container_name: elastic-agent-builder-a2a-test | ||
| environment: | ||
| - ES_AGENT_URL=${ES_AGENT_URL} | ||
| - ES_API_KEY=${ES_API_KEY} | ||
|
|
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a particular reason why this is a script rather than a notebook that can be run inline? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import asyncio | ||
| import httpx | ||
| from a2a.client import A2ACardResolver | ||
| from agent_framework.a2a import A2AAgent | ||
|
|
||
|
|
||
| async def main(): | ||
| a2a_agent_host = "<YOUR-ELASTIC-AGENT-BUILDER-URL>" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I woudl change this to using env variables that are loaded from the .env file (same for API key as well) |
||
|
|
||
| print(f"Connection to Elastic A2A agent at: {a2a_agent_host}") | ||
|
|
||
| custom_headers = {"Authorization": "ApiKey <YOUR-ELASTIC-API-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()) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add example values |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| ES_AGENT_URL= | ||
| ES_API_KEY= |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find it strange that you have this test in here, but it's not mentioned in the article anywhere. Is there a particular reason for that? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import unittest | ||
| from unittest.mock import patch | ||
| from dotenv import load_dotenv | ||
| import os | ||
| import asyncio | ||
| from pathlib import Path | ||
| import importlib.util | ||
|
|
||
|
|
||
| class test_main_function(unittest.TestCase): | ||
| def setUp(self): | ||
| # Replace placeholder values with .env values | ||
| load_dotenv() | ||
|
|
||
| self.test_file = Path("elastic_agent_builder_a2a.py") | ||
| self.backup_file = Path("elastic_agent_builder_a2a.py.backup") | ||
|
|
||
| if self.test_file.exists(): | ||
| self.original_content = self.test_file.read_text() | ||
| self.backup_file.write_text(self.original_content) | ||
|
|
||
| self.es_agent_url = os.getenv("ES_AGENT_URL") | ||
| self.es_api_key = os.getenv("ES_API_KEY") | ||
| content = self.test_file.read_text() | ||
| modified_content = content.replace( | ||
| "<YOUR-ELASTIC-AGENT-BUILDER-URL>", self.es_agent_url | ||
| ) | ||
| modified_content = modified_content.replace( | ||
| "<YOUR-ELASTIC-API-KEY>", self.es_api_key | ||
| ) | ||
| self.test_file.write_text(modified_content) | ||
|
|
||
| # Import the modified module | ||
| spec = importlib.util.spec_from_file_location( | ||
| "elastic_agent_builder_a2a", self.test_file | ||
| ) | ||
| self.user_input_module = importlib.util.module_from_spec(spec) | ||
| spec.loader.exec_module(self.user_input_module) | ||
|
|
||
| def tearDown(self): | ||
| if self.backup_file.exists(): | ||
| self.test_file.write_text(self.backup_file.read_text()) | ||
| self.backup_file.unlink() | ||
|
|
||
| @patch("builtins.input", return_value="hello world") | ||
| @patch("builtins.print") | ||
| def test_main_input(self, mock_print, mock_input): | ||
| # Run test | ||
| asyncio.run(self.user_input_module.main()) | ||
| mock_print.assert_called_with("Hello World! 🌎") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use virtual env and a requirements.txt file to ensure people don't have issues with it breaking for subsequent versions or configuraiton specific to their environment.