Spaces:
Sleeping
Sleeping
Sheyda Kiani Mehr
commited on
Commit
Β·
43d5db4
1
Parent(s):
71bf3a5
original a2a
Browse files- Containerfile.txt +42 -0
- README-2.md +59 -0
- __init__.py +0 -0
- __main__.py +77 -0
- agent_executor.py +42 -0
- pyproject.toml +26 -0
- Dockerfile β test/Dockerfile +0 -0
- Dockfile β test/Dockfile +0 -0
- README.md β test/README.md +0 -0
- main.py β test/main.py +0 -0
- requirements.txt β test/requirements.txt +0 -0
- simple_main.py β test/simple_main.py +0 -0
- test.py β test/test.py +0 -0
- test_docker β test/test_docker +0 -0
- uv.lock +0 -0
Containerfile.txt
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM registry.access.redhat.com/ubi8/python-312
|
| 2 |
+
|
| 3 |
+
# Set work directory
|
| 4 |
+
WORKDIR /opt/app-root/
|
| 5 |
+
|
| 6 |
+
# Copy Python Project Files (Container context must be the `python` directory)
|
| 7 |
+
COPY . /opt/app-root
|
| 8 |
+
|
| 9 |
+
USER root
|
| 10 |
+
|
| 11 |
+
# Install system build dependencies and UV package manager
|
| 12 |
+
RUN dnf -y update && dnf install -y gcc gcc-c++ \
|
| 13 |
+
&& pip install uv
|
| 14 |
+
|
| 15 |
+
# Set environment variables for uv:
|
| 16 |
+
# UV_COMPILE_BYTECODE=1: Compiles Python files to .pyc for faster startup
|
| 17 |
+
# UV_LINK_MODE=copy: Ensures files are copied, not symlinked, which can avoid issues
|
| 18 |
+
ENV UV_COMPILE_BYTECODE=1 \
|
| 19 |
+
UV_LINK_MODE=copy
|
| 20 |
+
|
| 21 |
+
# Install dependencies using uv sync.
|
| 22 |
+
# --frozen: Ensures uv respects the uv.lock file
|
| 23 |
+
# --no-install-project: Prevents installing the project itself in this stage
|
| 24 |
+
# --no-dev: Excludes development dependencies
|
| 25 |
+
# --mount=type=cache: Leverages Docker's build cache for uv, speeding up repeated builds
|
| 26 |
+
RUN --mount=type=cache,target=/.cache/uv \
|
| 27 |
+
uv sync --frozen --no-install-project --no-dev
|
| 28 |
+
|
| 29 |
+
# Install the project
|
| 30 |
+
RUN --mount=type=cache,target=/.cache/uv \
|
| 31 |
+
uv sync --frozen --no-dev
|
| 32 |
+
|
| 33 |
+
# Allow non-root user to access the everything in app-root
|
| 34 |
+
RUN chgrp -R root /opt/app-root/ && chmod -R g+rwx /opt/app-root/
|
| 35 |
+
|
| 36 |
+
# Expose default port (change if needed)
|
| 37 |
+
EXPOSE 9999
|
| 38 |
+
|
| 39 |
+
USER 1001
|
| 40 |
+
|
| 41 |
+
# Run the agent
|
| 42 |
+
CMD uv run . --host 0.0.0.0
|
README-2.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hello World Example
|
| 2 |
+
|
| 3 |
+
Hello World example agent that only returns Message events
|
| 4 |
+
|
| 5 |
+
## Getting started
|
| 6 |
+
|
| 7 |
+
1. Start the server
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
uv run .
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
2. Run the test client
|
| 14 |
+
|
| 15 |
+
```bash
|
| 16 |
+
uv run test_client.py
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
## Build Container Image
|
| 20 |
+
|
| 21 |
+
Agent can also be built using a container file.
|
| 22 |
+
|
| 23 |
+
1. Navigate to the directory `samples/python/agents/helloworld` directory:
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
cd samples/python/agents/helloworld
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
2. Build the container file
|
| 30 |
+
|
| 31 |
+
```bash
|
| 32 |
+
podman build . -t helloworld-a2a-server
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
> [!Tip]
|
| 36 |
+
> Podman is a drop-in replacement for `docker` which can also be used in these commands.
|
| 37 |
+
|
| 38 |
+
3. Run you container
|
| 39 |
+
|
| 40 |
+
```bash
|
| 41 |
+
podman run -p 9999:9999 helloworld-a2a-server
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
## Validate
|
| 45 |
+
|
| 46 |
+
To validate in a separate terminal, run the A2A client:
|
| 47 |
+
|
| 48 |
+
```bash
|
| 49 |
+
cd samples/python/hosts/cli
|
| 50 |
+
uv run . --agent http://localhost:9999
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
## Disclaimer
|
| 55 |
+
Important: The sample code provided is for demonstration purposes and illustrates the mechanics of the Agent-to-Agent (A2A) protocol. When building production applications, it is critical to treat any agent operating outside of your direct control as a potentially untrusted entity.
|
| 56 |
+
|
| 57 |
+
All data received from an external agentβincluding but not limited to its AgentCard, messages, artifacts, and task statusesβshould be handled as untrusted input. For example, a malicious agent could provide an AgentCard containing crafted data in its fields (e.g., description, name, skills.description). If this data is used without sanitization to construct prompts for a Large Language Model (LLM), it could expose your application to prompt injection attacks. Failure to properly validate and sanitize this data before use can introduce security vulnerabilities into your application.
|
| 58 |
+
|
| 59 |
+
Developers are responsible for implementing appropriate security measures, such as input validation and secure handling of credentials to protect their systems and users.
|
__init__.py
ADDED
|
File without changes
|
__main__.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import uvicorn
|
| 2 |
+
|
| 3 |
+
from a2a.server.apps import A2AStarletteApplication
|
| 4 |
+
from a2a.server.request_handlers import DefaultRequestHandler
|
| 5 |
+
from a2a.server.tasks import InMemoryTaskStore
|
| 6 |
+
from a2a.types import (
|
| 7 |
+
AgentCapabilities,
|
| 8 |
+
AgentCard,
|
| 9 |
+
AgentSkill,
|
| 10 |
+
)
|
| 11 |
+
from agent_executor import (
|
| 12 |
+
HelloWorldAgentExecutor, # type: ignore[import-untyped]
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
if __name__ == '__main__':
|
| 17 |
+
# --8<-- [start:AgentSkill]
|
| 18 |
+
skill = AgentSkill(
|
| 19 |
+
id='hello_world',
|
| 20 |
+
name='Returns hello world',
|
| 21 |
+
description='just returns hello world',
|
| 22 |
+
tags=['hello world'],
|
| 23 |
+
examples=['hi', 'hello world'],
|
| 24 |
+
)
|
| 25 |
+
# --8<-- [end:AgentSkill]
|
| 26 |
+
|
| 27 |
+
extended_skill = AgentSkill(
|
| 28 |
+
id='super_hello_world',
|
| 29 |
+
name='Returns a SUPER Hello World',
|
| 30 |
+
description='A more enthusiastic greeting, only for authenticated users.',
|
| 31 |
+
tags=['hello world', 'super', 'extended'],
|
| 32 |
+
examples=['super hi', 'give me a super hello'],
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
# --8<-- [start:AgentCard]
|
| 36 |
+
# This will be the public-facing agent card
|
| 37 |
+
public_agent_card = AgentCard(
|
| 38 |
+
name='Hello World Agent',
|
| 39 |
+
description='Just a hello world agent',
|
| 40 |
+
url='http://localhost:9999/',
|
| 41 |
+
version='1.0.0',
|
| 42 |
+
default_input_modes=['text'],
|
| 43 |
+
default_output_modes=['text'],
|
| 44 |
+
capabilities=AgentCapabilities(streaming=True),
|
| 45 |
+
skills=[skill], # Only the basic skill for the public card
|
| 46 |
+
supports_authenticated_extended_card=True,
|
| 47 |
+
)
|
| 48 |
+
# --8<-- [end:AgentCard]
|
| 49 |
+
|
| 50 |
+
# This will be the authenticated extended agent card
|
| 51 |
+
# It includes the additional 'extended_skill'
|
| 52 |
+
specific_extended_agent_card = public_agent_card.model_copy(
|
| 53 |
+
update={
|
| 54 |
+
'name': 'Hello World Agent - Extended Edition', # Different name for clarity
|
| 55 |
+
'description': 'The full-featured hello world agent for authenticated users.',
|
| 56 |
+
'version': '1.0.1', # Could even be a different version
|
| 57 |
+
# Capabilities and other fields like url, default_input_modes, default_output_modes,
|
| 58 |
+
# supports_authenticated_extended_card are inherited from public_agent_card unless specified here.
|
| 59 |
+
'skills': [
|
| 60 |
+
skill,
|
| 61 |
+
extended_skill,
|
| 62 |
+
], # Both skills for the extended card
|
| 63 |
+
}
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
request_handler = DefaultRequestHandler(
|
| 67 |
+
agent_executor=HelloWorldAgentExecutor(),
|
| 68 |
+
task_store=InMemoryTaskStore(),
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
server = A2AStarletteApplication(
|
| 72 |
+
agent_card=public_agent_card,
|
| 73 |
+
http_handler=request_handler,
|
| 74 |
+
extended_agent_card=specific_extended_agent_card,
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
uvicorn.run(server.build(), host='0.0.0.0', port=9999)
|
agent_executor.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from a2a.server.agent_execution import AgentExecutor, RequestContext
|
| 2 |
+
from a2a.server.events import EventQueue
|
| 3 |
+
from a2a.utils import new_agent_text_message
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# --8<-- [start:HelloWorldAgent]
|
| 7 |
+
class HelloWorldAgent:
|
| 8 |
+
"""Hello World Agent."""
|
| 9 |
+
|
| 10 |
+
async def invoke(self) -> str:
|
| 11 |
+
return 'Hello World'
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# --8<-- [end:HelloWorldAgent]
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
# --8<-- [start:HelloWorldAgentExecutor_init]
|
| 18 |
+
class HelloWorldAgentExecutor(AgentExecutor):
|
| 19 |
+
"""Test AgentProxy Implementation."""
|
| 20 |
+
|
| 21 |
+
def __init__(self):
|
| 22 |
+
self.agent = HelloWorldAgent()
|
| 23 |
+
|
| 24 |
+
# --8<-- [end:HelloWorldAgentExecutor_init]
|
| 25 |
+
# --8<-- [start:HelloWorldAgentExecutor_execute]
|
| 26 |
+
async def execute(
|
| 27 |
+
self,
|
| 28 |
+
context: RequestContext,
|
| 29 |
+
event_queue: EventQueue,
|
| 30 |
+
) -> None:
|
| 31 |
+
result = await self.agent.invoke()
|
| 32 |
+
await event_queue.enqueue_event(new_agent_text_message(result))
|
| 33 |
+
|
| 34 |
+
# --8<-- [end:HelloWorldAgentExecutor_execute]
|
| 35 |
+
|
| 36 |
+
# --8<-- [start:HelloWorldAgentExecutor_cancel]
|
| 37 |
+
async def cancel(
|
| 38 |
+
self, context: RequestContext, event_queue: EventQueue
|
| 39 |
+
) -> None:
|
| 40 |
+
raise Exception('cancel not supported')
|
| 41 |
+
|
| 42 |
+
# --8<-- [end:HelloWorldAgentExecutor_cancel]
|
pyproject.toml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "helloworld"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "HelloWorld agent example that only returns Messages"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
requires-python = ">=3.10"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"a2a-sdk>=0.3.0",
|
| 9 |
+
"click>=8.1.8",
|
| 10 |
+
"dotenv>=0.9.9",
|
| 11 |
+
"httpx>=0.28.1",
|
| 12 |
+
"langchain-google-genai>=2.1.4",
|
| 13 |
+
"langgraph>=0.4.1",
|
| 14 |
+
"pydantic>=2.11.4",
|
| 15 |
+
"python-dotenv>=1.1.0",
|
| 16 |
+
"sse-starlette>=2.3.5",
|
| 17 |
+
"starlette>=0.46.2",
|
| 18 |
+
"uvicorn>=0.34.2",
|
| 19 |
+
]
|
| 20 |
+
|
| 21 |
+
[tool.hatch.build.targets.wheel]
|
| 22 |
+
packages = ["."]
|
| 23 |
+
|
| 24 |
+
[build-system]
|
| 25 |
+
requires = ["hatchling"]
|
| 26 |
+
build-backend = "hatchling.build"
|
Dockerfile β test/Dockerfile
RENAMED
|
File without changes
|
Dockfile β test/Dockfile
RENAMED
|
File without changes
|
README.md β test/README.md
RENAMED
|
File without changes
|
main.py β test/main.py
RENAMED
|
File without changes
|
requirements.txt β test/requirements.txt
RENAMED
|
File without changes
|
simple_main.py β test/simple_main.py
RENAMED
|
File without changes
|
test.py β test/test.py
RENAMED
|
File without changes
|
test_docker β test/test_docker
RENAMED
|
File without changes
|
uv.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|