Skip to content

Commit 61443fd

Browse files
committed
Add Agent Builder A2A with Strands Agents SDK example app : RPS+
1 parent e13191a commit 61443fd

File tree

6 files changed

+377
-0
lines changed

6 files changed

+377
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM python:3.11-slim
2+
3+
WORKDIR /app
4+
5+
RUN python -m venv /opt/venv
6+
7+
ENV PATH="/opt/venv/bin:$PATH"
8+
9+
COPY requirements.txt .
10+
11+
RUN pip install -r requirements.txt
12+
13+
COPY elastic_agent_builder_a2a_rps+.py .
14+
15+
CMD ["python", "elastic_agent_builder_a2a_rps+.py"]
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# Elastic Agent Builder A2A Example App : RTG+ (Rock Paper Scissors +)
2+
3+
**Getting started with Agent Builder and A2A using Strands Agents SDK**
4+
5+
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 [Strands Agents SDK](https://strandsagents.com).
6+
7+
## Prerequisites
8+
9+
1. An Elasticsearch project/deployment running in [Elastic Cloud](https://cloud.elastic.co/registration?utm_source=github&utm_content=elasticsearch-labs-example-apps).
10+
* Requires Elasticsearch serverless project (or for hosted deployments at least Elasticsearch version 9.2.0).
11+
2. A text editor or an integrated development environment (IDE) like [Visual Studio Code](https://code.visualstudio.com/download) running on your local computer.
12+
3. [Python version 3.10 or greater](https://www.python.org/downloads/) installed on your local computer.
13+
14+
## Set up your Elasticsearch project
15+
16+
1. Create an index named `game-docs` in your Elasticsearch project by running the following command in Elastic Developer Tools:
17+
18+
PUT /game-docs
19+
{
20+
"mappings": {
21+
"properties": {
22+
"title": { "type": "text" },
23+
"content": {
24+
"type": "semantic_text"
25+
},
26+
"filename": { "type": "keyword" },
27+
"last_modified": { "type": "date" }
28+
}
29+
}
30+
}
31+
2. Insert a document into your index named `RPS+.md` by running the following command in Elastic Developer Tools:
32+
33+
PUT /game-docs/_doc/rps+-md
34+
{
35+
"title": "Rock Paper Scissors +",
36+
"content": "
37+
# Game Name
38+
RPS+
39+
40+
# Starting Prompt
41+
Let's play RPS+ !
42+
---
43+
What do you choose?
44+
45+
# Game Objects
46+
1. Rock 🪨 👊
47+
2. Paper 📜 🖐
48+
3. Scissors ✄ ✌️
49+
4. Light ☼ 👍
50+
5. Dark Energy ☄ 🫱
51+
52+
# Judgement of Victory
53+
* Rock beats Scissors
54+
* because rocks break scissors
55+
* Paper beats Rock
56+
* because paper covers rock
57+
* Scissors beat Paper
58+
* because scissors cut paper
59+
* Rock beats Light
60+
* because you can build a rock structure to block out light
61+
* Paper beats Light
62+
* because knowledge stored in files and paper books helps us understand light
63+
* Light beats Dark Energy
64+
* because light enables humans to lighten up and laugh in the face of dark energy as it causes the eventual heat death of the universe
65+
* Light beats Scissors
66+
* because light is needed to use scissors safely
67+
* Dark Energy beats Rock
68+
* because dark energy rocks more than rocks. It rocks rocks and everything else in its expansion of the universe
69+
* Dark Energy beats Paper
70+
* because humans, with their knowledge stored in files and paper books, can't explain dark energy
71+
* Scissors beat Dark Energy
72+
* because a human running with scissors is darker than dark energy
73+
74+
# Invalid Input
75+
I was hoping for an worthy opponent
76+
- but alas it appears that time has past
77+
- but alas there's little time for your todo list when [todo:fix this] is so vast
78+
79+
# Cancel Game
80+
The future belongs to the bold. Goodbye..
81+
82+
",
83+
"filename": "RPS+.md",
84+
"last_modified": "2025-11-25T12:00:00Z"
85+
}
86+
87+
3. In Elastic Agent Builder, create a **tool** with the following values:
88+
* **Type**: `ES|QL`
89+
* **Tool ID**: `example.get_game_docs`
90+
* **Description**: `Get RPS+ doc from Elasticsearch game-docs index.`
91+
* **ES|QL**:
92+
93+
FROM game-docs | WHERE filename == "RPS+.md"
94+
95+
1. In Elastic Agent Builder, create an **agent** with the following values:
96+
* **Agent ID**: `rps_plus_agent`
97+
* **Custom Instructions**:
98+
99+
When prompted, if the prompt contains an integer, then select the corresponding numbered item in the list of "Game Objects" from your documents. Otherwise select a random game object. This is your chosen game object for a single round of the game.
100+
101+
# General Game Rules
102+
* 2 players
103+
- the user: the person playing the game
104+
- you: the agent playing the game and serving as the game master
105+
* Each player chooses a game object which will be compared and cause them to tie, win or lose.
106+
107+
# Start the game
108+
1. This is the way each new game always starts. You make the first line of your response only the name of your chosen game object.
109+
110+
2. The remainder of your response should be the "Starting Prompt" text from your documents and generate a list of "Game Objects" for the person playing the game to choose a game object from.
111+
112+
# End of Game: The game ends in one of the following three outcomes:
113+
3. Invalid Input: If the player responds with an invalid game object choice, respond with variations of the "Invalid Input" text from your documents and then end the game.
114+
115+
4. Tie: The game ends in a tie if the user chooses the same game object as your game object choice.
116+
117+
5. Win or Lose: The game winner is decided based on the "Judgement of Victory" conditions from your documents. Compare the user's game object choice and your game object choice and determine who chose the winning game object.
118+
119+
# Game conclusion
120+
Respond with a declaration of the winner of the game by outputting the corresponding text in the "Judgement of Victory" section of your documents.
121+
122+
* **Display Name**: `RPS+ Agent`
123+
* **Display Description**: `An agent that plays the game RPS+`
124+
125+
126+
127+
## Clone the example app
128+
129+
1. Open a terminal and clone the Search Labs source code repository which contains the Elastic Agent Builder A2A App example: RPS+. Run the following command to clone the example app:
130+
131+
git clone https://github.com/elastic/elasticsearch-labs
132+
133+
3. `cd` to change directory to the example code located in the `supporting-blog-content/agent-builder-a2a-strands-agents` subdirectory.
134+
135+
cd elasticsearch-labs/supporting-blog-content/agent-builder-a2a-strands-agents
136+
137+
## Set up the environment variables
138+
139+
1. Set up the environment variables with values copied from your Elastic project.
140+
1. Make a copy of the file `env.example` and name the new file `.env `
141+
2. Edit the `.env` file to set the values of the environment variables to use the values copied from your Elastic project.
142+
* Replace <YOUR-ELASTIC-AGENT-BUILDER-URL\>
143+
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.**
144+
2. Add the **MCP Server URL** value to the `.env` file.
145+
* 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
146+
147+
`https://rps-game-project-12345a.kb.us-east-1.aws.elastic.cloud/api/agent_builder/a2a`
148+
149+
* Replace <YOUR-ELASTIC-API-KEY\>
150+
1. In your Elastic project, click **Elasticsearch** in the navigation menu to go to your project’s home page.
151+
2. Click **Create API key** to create a new API key.
152+
3. After the API key is created, copy the API Key value.
153+
4. Add the API Key value to the `.env` file.
154+
* Find where the placeholder text “**<YOUR-ELASTIC-API-KEY\>**” appears and paste in the copied API Key value to replace the placeholder text.
155+
156+
3. Save the changes to the `.env` file.
157+
158+
## Running the example app with Python
159+
160+
1. Create a Python virtual environment by running the following code in the terminal.
161+
162+
python -m venv .venv
163+
164+
2. Activate the Python virtual environment.
165+
* If you’re running MacOS, the command to activate the virtual environment is:
166+
167+
source .venv/bin/activate
168+
169+
* If you’re on Windows, the command to activate the virtual environment is:
170+
171+
.venv\Scripts\activate
172+
173+
3. Install the [Strands Agents SDK](https://strandsagents.com) along with its necessary Python packages by running the following `pip` command:
174+
175+
pip install -r requirements.txt
176+
177+
4. Run the example app by entering the following command into the terminal:
178+
179+
python elastic_agent_builder_a2a_rps+.py
180+
181+
## Running the example app with Docker
182+
183+
1. Run the example app with Docker by entering the following command into the terminal:
184+
185+
docker compose run elastic-agent-builder-a2a-strands-agents
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
services:
2+
elastic-agent-builder-a2a-strands-agents:
3+
build: .
4+
container_name: elastic-agent-builder-a2a-strands-agents
5+
stdin_open: true
6+
tty: true
7+
environment:
8+
- ES_AGENT_URL=${ES_AGENT_URL}
9+
- ES_API_KEY=${ES_API_KEY}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import asyncio
2+
from dotenv import load_dotenv
3+
from uuid import uuid4
4+
import httpx
5+
import os
6+
import random
7+
from a2a.client import A2ACardResolver, ClientConfig, ClientFactory
8+
from a2a.types import Message, Part, Role, TextPart
9+
10+
DEFAULT_TIMEOUT = 60 # set request timeout to 1 minute
11+
12+
13+
def create_message(*, role: Role = Role.user, text: str, context_id=None) -> Message:
14+
return Message(
15+
kind="message",
16+
role="user",
17+
parts=[Part(TextPart(kind="text", text=text))],
18+
message_id=uuid4().hex,
19+
context_id=context_id,
20+
)
21+
22+
23+
async def main():
24+
load_dotenv()
25+
a2a_agent_host = os.getenv("ES_AGENT_URL")
26+
a2a_agent_key = os.getenv("ES_API_KEY")
27+
custom_headers = {"Authorization": f"ApiKey {a2a_agent_key}"}
28+
29+
async with httpx.AsyncClient(
30+
timeout=DEFAULT_TIMEOUT, headers=custom_headers
31+
) as httpx_client:
32+
# Get agent card
33+
resolver = A2ACardResolver(httpx_client=httpx_client, base_url=a2a_agent_host)
34+
agent_card = await resolver.get_agent_card(
35+
relative_card_path="/rps_plus_agent.json"
36+
)
37+
# Create client using factory
38+
config = ClientConfig(
39+
httpx_client=httpx_client,
40+
streaming=True,
41+
)
42+
factory = ClientFactory(config)
43+
client = factory.create(agent_card)
44+
# Use the client to communicate with the agent
45+
print("\nSending 'start game' message to Elastic A2A agent...")
46+
random_game_object = random.randint(1, 5)
47+
msg = create_message(text=f"start with game object {random_game_object}")
48+
async for event in client.send_message(msg):
49+
if isinstance(event, Message):
50+
context_id = event.context_id
51+
response_complete = event.parts[0].root.text
52+
# Get agent choice from the first line of the response
53+
parsed_response = response_complete.split("\n", 1)
54+
agent_choice = parsed_response[0]
55+
print(parsed_response[1])
56+
# User choice sent for game results from the agent
57+
prompt = input("Your Choice : ")
58+
msg = create_message(text=prompt, context_id=context_id)
59+
async for event in client.send_message(msg):
60+
if isinstance(event, Message):
61+
print(f"Agent Choice : {agent_choice}")
62+
print(event.parts[0].root.text)
63+
64+
65+
if __name__ == "__main__":
66+
asyncio.run(main())
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ES_AGENT_URL=<YOUR-ELASTIC-AGENT-BUILDER-URL>
2+
ES_API_KEY=<YOUR-ELASTIC-API-KEY>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
a2a-sdk==0.3.20
2+
aiohappyeyeballs==2.6.1
3+
aiohttp==3.13.2
4+
aiomysql==0.3.2
5+
aiosignal==1.4.0
6+
aiosqlite==0.21.0
7+
annotated-doc==0.0.4
8+
annotated-types==0.7.0
9+
anyio==4.12.0
10+
asyncpg==0.31.0
11+
attrs==25.4.0
12+
aws-requests-auth==0.4.3
13+
beautifulsoup4==4.14.3
14+
boto3==1.42.4
15+
botocore==1.42.4
16+
cachetools==6.2.2
17+
certifi==2025.11.12
18+
cffi==2.0.0
19+
charset-normalizer==3.4.4
20+
click==8.3.1
21+
colorama==0.4.6
22+
cryptography==46.0.3
23+
dill==0.4.0
24+
docstring_parser==0.17.0
25+
fastapi==0.123.10
26+
frozenlist==1.8.0
27+
google-api-core==2.28.1
28+
google-auth==2.43.0
29+
googleapis-common-protos==1.72.0
30+
greenlet==3.3.0
31+
h11==0.16.0
32+
halo==0.0.31
33+
httpcore==1.0.9
34+
httpx==0.28.1
35+
httpx-sse==0.4.3
36+
idna==3.11
37+
importlib_metadata==8.7.0
38+
jmespath==1.0.1
39+
jsonschema==4.25.1
40+
jsonschema-specifications==2025.9.1
41+
log-symbols==0.0.14
42+
markdown-it-py==4.0.0
43+
markdownify==1.2.2
44+
mcp==1.23.1
45+
mdurl==0.1.2
46+
mpmath==1.3.0
47+
multidict==6.7.0
48+
ollama==0.6.1
49+
opentelemetry-api==1.39.0
50+
opentelemetry-instrumentation==0.60b0
51+
opentelemetry-instrumentation-threading==0.60b0
52+
opentelemetry-sdk==1.39.0
53+
opentelemetry-semantic-conventions==0.60b0
54+
packaging==25.0
55+
pillow==11.3.0
56+
prompt_toolkit==3.0.52
57+
propcache==0.4.1
58+
proto-plus==1.26.1
59+
protobuf==6.33.1
60+
pyasn1==0.6.1
61+
pyasn1_modules==0.4.2
62+
pycparser==2.23
63+
pydantic==2.12.5
64+
pydantic-settings==2.12.0
65+
pydantic_core==2.41.5
66+
Pygments==2.19.2
67+
PyJWT==2.10.1
68+
PyMySQL==1.1.2
69+
python-dateutil==2.9.0.post0
70+
python-dotenv==1.2.1
71+
python-multipart==0.0.20
72+
referencing==0.37.0
73+
requests==2.32.5
74+
rich==14.2.0
75+
rpds-py==0.30.0
76+
rsa==4.9.1
77+
s3transfer==0.16.0
78+
six==1.17.0
79+
slack_bolt==1.27.0
80+
slack_sdk==3.39.0
81+
soupsieve==2.8
82+
spinners==0.0.24
83+
SQLAlchemy==2.0.44
84+
sse-starlette==3.0.3
85+
starlette==0.50.0
86+
strands-agents==1.19.0
87+
strands-agents-builder==0.1.10
88+
strands-agents-tools==0.2.17
89+
sympy==1.14.0
90+
tenacity==9.1.2
91+
termcolor==3.2.0
92+
typing-inspection==0.4.2
93+
typing_extensions==4.15.0
94+
urllib3==2.6.0
95+
uvicorn==0.38.0
96+
watchdog==6.0.0
97+
wcwidth==0.2.14
98+
wrapt==1.17.3
99+
yarl==1.22.0
100+
zipp==3.23.0

0 commit comments

Comments
 (0)