From 1b8b50947ae415f023c4ae50186fc84de2ff0456 Mon Sep 17 00:00:00 2001 From: ChenXAn <2277587992@qq.com> Date: Fri, 15 Aug 2025 13:57:26 +0800 Subject: [PATCH 1/6] enchancement: Shows how to support multiple A2AStarletteApplication instances or AgentExecutor implementations on a single port. --- .../a2a_multi_agent_on_single_port/README.md | 85 ++ .../pyproject.toml | 24 + .../response.xml | 486 ++++++++++++ .../src/no_llm_framework/client/__init__.py | 0 .../src/no_llm_framework/client/__main__.py | 50 ++ .../src/no_llm_framework/client/agent.py | 259 ++++++ .../client/agent_answer.jinja | 5 + .../src/no_llm_framework/client/agents.jinja | 11 + .../client/called_agent_history.jinja | 6 + .../src/no_llm_framework/client/constant.py | 3 + .../src/no_llm_framework/client/decide.jinja | 41 + .../src/no_llm_framework/server/__init__.py | 0 .../src/no_llm_framework/server/__main__.py | 220 ++++++ .../src/no_llm_framework/server/agent.py | 187 +++++ .../no_llm_framework/server/agent_executor.py | 104 +++ .../server/called_tools_history.jinja | 5 + .../src/no_llm_framework/server/constant.py | 3 + .../src/no_llm_framework/server/decide.jinja | 43 + .../src/no_llm_framework/server/mcp.py | 65 ++ .../src/no_llm_framework/server/tool.jinja | 4 + .../a2a_multi_agent_on_single_port/uv.lock | 736 ++++++++++++++++++ 21 files changed, 2337 insertions(+) create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/README.md create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/pyproject.toml create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/response.xml create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/client/__init__.py create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/client/__main__.py create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/client/agent.py create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/client/agent_answer.jinja create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/client/agents.jinja create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/client/called_agent_history.jinja create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/client/constant.py create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/client/decide.jinja create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/__init__.py create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/__main__.py create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent.py create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent_executor.py create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/called_tools_history.jinja create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/constant.py create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/decide.jinja create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/mcp.py create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/tool.jinja create mode 100644 samples/python/agents/a2a_multi_agent_on_single_port/uv.lock diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/README.md b/samples/python/agents/a2a_multi_agent_on_single_port/README.md new file mode 100644 index 00000000..703e4be6 --- /dev/null +++ b/samples/python/agents/a2a_multi_agent_on_single_port/README.md @@ -0,0 +1,85 @@ +# Example: Using a2a-python SDK Without an LLM Framework + +This repository demonstrates how to set up and use the [a2a-python SDK](https://github.com/google/a2a-python) to create a simple server and client, without relying on any agent framework. + +## Overview + +- **A2A (Agent-to-Agent):** A protocol and SDK for communication with AI Agents. +- **This Example:** Shows how to support multiple A2AStarletteApplication instances or AgentExecutor implementations on a single port. + +## Prerequisites + +- Python 3.13+ +- [uv](https://github.com/astral-sh/uv) (for fast dependency management and running) +- An API key for Gemini (set as `GEMINI_API_KEY`) + +## Installation + +1. **Clone the repository:** + + ```bash + git clone + cd + ``` + +2. **Install dependencies:** + + ```bash + uv pip install -e . + ``` + +3. **Set environment variables:** + + ```bash + export GEMINI_API_KEY=your-gemini-api-key + ``` + + Or create a `.env` file with: + + ```sh + GEMINI_API_KEY=your-gemini-api-key + ``` + +## Running the Example + +### 1. Start the Server + +```bash +uv run --env-file .env python -m src.no_llm_framework.server.__main__ +``` + +- The server will start on port `9999`. + +### 2. Run the Client + +In a new terminal: + +```bash +uv run --env-file .env python -m src.no_llm_framework.client --question "What is A2A protocol?" +``` + +- The client will connect to the server and send a request. + +### 3. View the Response + +- The response from the client will be saved to [`response.xml`](./response.xml). + +## File Structure + +- `src/no_llm_framework/server/`: Server implementation. +- `src/no_llm_framework/client/`: Client implementation. +- `response.xml`: Example response from the client. + +## Troubleshooting + +- **Missing dependencies:** Make sure you have `uv` installed. +- **API key errors:** Ensure `GEMINI_API_KEY` is set correctly. +- **Port conflicts:** Make sure port 9999 is free. + +## Disclaimer + +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. + +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. + +Developers are responsible for implementing appropriate security measures, such as input validation and secure handling of credentials to protect their systems and users. diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/pyproject.toml b/samples/python/agents/a2a_multi_agent_on_single_port/pyproject.toml new file mode 100644 index 00000000..297512e4 --- /dev/null +++ b/samples/python/agents/a2a_multi_agent_on_single_port/pyproject.toml @@ -0,0 +1,24 @@ +[project] +name = "no-llm-framework" +version = "0.1.0" +description = "Use A2A without any agent framework" +readme = "README.md" +authors = [{ name = "prem", email = "prem.ch@ku.th" }] +requires-python = ">=3.13" +dependencies = [ + "a2a-sdk>=0.3.0", + "asyncclick>=8.1.8", + "colorama>=0.4.6", + "fastmcp>=2.3.4", + "google-genai", + "jinja2>=3.1.6", + "rich>=14.0.0", + "starlette>=0.47.2" +] +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project.scripts] +a2a-server = "no_llm_framework.server.__main__:main" +a2a-client = "no_llm_framework.client.__main__:main" diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/response.xml b/samples/python/agents/a2a_multi_agent_on_single_port/response.xml new file mode 100644 index 00000000..685707ee --- /dev/null +++ b/samples/python/agents/a2a_multi_agent_on_single_port/response.xml @@ -0,0 +1,486 @@ + +Thoughts: +- The question directly asks about the A2A protocol. +- The A2A Protocol Agent is specifically designed to answer questions about the A2A protocol. +- No other agent is relevant. + + + +```json +[ + { + "name": "A2A Protocol Agent", + "prompt": "What is A2A protocol?" + } +] +``` + +Step 0 +Thoughts: +- The question asks for a definition of the A2A protocol. This is a general question about the Google A2A project. +- The `fetch_A2A_documentation` tool is designed to retrieve the entire documentation, which is likely to contain the definition. This should be the first tool used. +- If the definition isn't directly found in the fetched documentation, `search_A2A_documentation` could be used to search for relevant sections within the documentation. + + + +```json +[ + { + "name": "fetch_A2A_documentation", + "arguments": {} + }, + { + "name": "search_A2A_documentation", + "arguments": { + "query": "What is A2A protocol?" + } + } +] +``` + +Previous tools have been called. +- Tool: +- Arguments: {} +- Result: # A2A (Agent2Agent) Protocol High-Level Summary + +This project defines and demonstrates the **Agent2Agent (A2A) protocol**, an open standard initiated by Google designed to enable communication and interoperability between disparate AI agent systems. The core goal is to allow agents built on different frameworks (e.g., LangGraph, CrewAI, Google ADK, Genkit) or by different vendors to discover each other's capabilities, negotiate interaction modes (text, forms, files, potentially audio/video later), and collaborate on tasks. + +The repository provides: + +1. **Formal Specification:** A detailed JSON Schema (`specification/json/a2a.json`) defining the structure of A2A messages, including requests, responses, task states, artifacts, and agent metadata (Agent Cards). +2. **Core Concepts Documentation (Implied):** Links in the main README suggest documentation covering agent discovery, task lifecycle, artifact exchange, streaming updates, push notifications, and enterprise readiness. +3. **Sample Implementations:** + * **Common Libraries:** Reusable Python (`samples/python/common`) and JavaScript/TypeScript (`samples/js/src`) code for building A2A clients and servers, handling JSON-RPC communication, task management, and potentially authentication. + * **Example Agents:** Demonstrations of integrating A2A into various agent frameworks: + * **Python:** LangGraph (currency conversion, streaming), CrewAI (image generation, file artifacts), Google ADK (expense reports, form handling). + * **JavaScript/TypeScript:** Genkit (movie info via API, code generation with file artifacts). + * **Example Hosts:** Applications that *consume* A2A services: + * CLIs in both Python and JS for direct interaction. + * A Python-based multi-agent orchestrator (using Google ADK) that delegates tasks to other A2A agents. +4. **Demo Web Application:** A web UI (`demo/ui`, likely using Mesop) demonstrating multi-agent interactions facilitated by the A2A protocol, including visualization of conversations, tasks, events, and agent discovery. It features a backend service coordinating with the host agent. + +Key features of the A2A protocol highlighted by the specification and samples include: agent discovery via Agent Cards, standardized task management (send, get, cancel), support for different content types (text, files, structured data) via `Parts` and `Artifacts`, streaming updates for long-running tasks, and mechanisms for push notifications. The project is open source and encourages community contribution. + +# A2A (Agent2Agent) Protocol + +## 1. Overview + +* **Project Name:** Agent2Agent (A2A) Protocol +* **Purpose:** An open protocol by Google enabling communication and interoperability between AI agents built on different frameworks or by different vendors. +* **Core Goal:** Allow agents to discover capabilities, negotiate interaction, and collaborate securely on tasks. +* **Communication:** Uses JSON-RPC 2.0 over HTTP(S). Supports standard request/response and Server-Sent Events (SSE) for streaming. +* **Key Components:** Specification (JSON Schema), Common Libraries (Python, JS/TS), Sample Agents (LangGraph, CrewAI, ADK, Genkit), Sample Hosts (CLI, Orchestrator), Demo Web App (Mesop). + +## 2. Protocol Specification (`specification/json/a2a.json`) + +### 2.1. Core JSON-RPC Structures + +* **`JSONRPCMessage`:** Base for requests/responses. Contains `jsonrpc: "2.0"` and optional `id`. +* **`JSONRPCRequest`:** Represents a request. + * `method`: String identifying the operation (e.g., "message/send"). + * `params`: Object or Array containing parameters for the method. + * `id`: Unique identifier (string/number) for request/response correlation. Omitted/null for notifications. +* **`JSONRPCResponse`:** Represents a response. + * `result`: Contains the successful result data (can be `null`). Mutually exclusive with `error`. + * `error`: Contains an error object if the request failed. Mutually exclusive with `result`. + * `id`: Must match the corresponding request `id`. +* **`JSONRPCError`:** Represents an error. + * `code`: Integer error code. + * `message`: String description of the error. + * `data`: Optional additional error details. + +### 2.2. Key A2A Data Objects + +* **`AgentCard`:** Metadata describing an agent. Found typically at `/.well-known/agent.json`. + * `name`: (string) Human-readable name. + * `description`: (string | null) Agent description. + * `url`: (string) Base URL endpoint for the agent's A2A service. + * `provider`: (`AgentProvider` | null) Organization details. + * `version`: (string) Agent/API version. + * `documentationUrl`: (string | null) Link to documentation. + * `capabilities`: (`AgentCapabilities`) Features supported (streaming, push). + * `authentication`: (`AgentAuthentication` | null) Auth schemes/credentials needed. + * `defaultInputModes`: (string[]) Default supported input types (e.g., "text", "file"). + * `defaultOutputModes`: (string[]) Default supported output types. + * `skills`: (`AgentSkill[]`) List of specific capabilities. +* **`AgentCapabilities`:** + * `streaming`: (boolean) Supports `message/stream`. + * `pushNotifications`: (boolean) Supports `tasks/pushNotification/set|get`. + * `stateTransitionHistory`: (boolean) Supports providing detailed history. +* **`AgentSkill`:** + * `id`: (string) Unique skill ID. + * `name`: (string) Skill name. + * `description`: (string | null) Skill description. + * `tags`: (string[] | null) Keywords. + * `examples`: (string[] | null) Usage examples. + * `inputModes`: (string[] | null) Overrides default inputs for this skill. + * `outputModes`: (string[] | null) Overrides default outputs for this skill. +* **`Task`:** Represents a unit of work processed by an agent. + * `id`: (string) Unique task identifier. + * `sessionId`: (string | null) Groups related tasks. + * `status`: (`TaskStatus`) Current state and associated message. + * `artifacts`: (`Artifact[]` | null) Outputs generated by the task. + * `history`: (`Message[]` | null) (Optional) History of messages exchanged for this task (if requested via `historyLength`). + * `metadata`: (object | null) Additional task metadata. +* **`TaskStatus`:** + * `state`: (`TaskState`) Current lifecycle state (enum). + * `message`: (`Message` | null) Message associated with this status (e.g., progress update, final response text, input prompt). + * `timestamp`: (string) ISO 8601 timestamp of the status update. +* **`TaskState` (Enum):** + * `submitted`: Task received, not yet started. + * `working`: Task is actively being processed. + * `input-required`: Agent requires further input from the user/client. + * `completed`: Task finished successfully. + * `canceled`: Task was canceled. + * `failed`: Task failed due to an error. + * `unknown`: State cannot be determined. +* **`Message`:** Communication unit between user and agent. + * `role`: ("user" | "agent") Sender role. + * `parts`: (`Part[]`) Content parts (text, file, data). + * `metadata`: (object | null) Message-specific metadata. +* **`Part` (Union Type):** Represents a piece of content within a Message or Artifact. + * **`TextPart`:** + * `type`: "text" + * `text`: (string) The textual content. + * **`FilePart`:** + * `type`: "file" + * `file`: (`FileContent`) File details (bytes or URI). + * **`DataPart`:** + * `type`: "data" + * `data`: (object) Structured JSON data (e.g., for forms). + * `metadata`: (object | null) Optional metadata for the specific part. +* **`FileContent`:** Represents file data. + * `name`: (string | null) Filename. + * `mimeType`: (string | null) MIME type. + * `bytes`: (string | null) Base64 encoded file content. *Mutually exclusive with `uri`.* + * `uri`: (string | null) URI pointing to the file content. *Mutually exclusive with `bytes`.* +* **`Artifact`:** Output generated by a task. + * `name`: (string | null) Artifact name. + * `description`: (string | null) Artifact description. + * `parts`: (`Part[]`) Content parts. + * `index`: (integer, default 0) Order index, useful for streaming/updates. + * `append`: (boolean | null) For streaming, indicates if content should append to artifact at the same index. + * `lastChunk`: (boolean | null) For streaming, indicates the final chunk for this artifact. + * `metadata`: (object | null) Artifact metadata. +* **`PushNotificationConfig`:** Configuration for push notifications. + * `url`: (string) Endpoint URL for the agent to POST notifications to. + * `token`: (string | null) Optional token for the agent to include. + * `authentication`: (`AuthenticationInfo` | null) Auth details the agent needs to call the URL. +* **`TaskPushNotificationConfig`:** Associates a `PushNotificationConfig` with a task ID. + +### 2.3. A2A RPC Methods + +* **`message/send`:** (Request/Response) + * Sends a message to initiate or continue a task. + * `params`: `TaskSendParams` (includes `id`, `sessionId`, `message`, optionally `pushNotification`, `historyLength`, `metadata`). + * `result`: `Task` (final state after synchronous processing). +* **`message/stream`:** (Request/Stream) + * Sends a message and subscribes to real-time updates via SSE. + * `params`: `TaskSendParams`. + * `result` (stream events): `TaskStatusUpdateEvent` or `TaskArtifactUpdateEvent`. Final event has `final: true`. +* **`tasks/get`:** (Request/Response) + * Retrieves the current state of a task. + * `params`: `TaskQueryParams` (includes `id`, optionally `historyLength`). + * `result`: `Task`. +* **`tasks/cancel`:** (Request/Response) + * Requests cancellation of a running task. + * `params`: `TaskIdParams` (includes `id`). + * `result`: `Task` (updated state, likely 'canceled') or error if not cancelable. +* **`tasks/pushNotification/set`:** (Request/Response) + * Sets or updates the push notification configuration for a task. + * `params`: `TaskPushNotificationConfig`. + * `result`: `TaskPushNotificationConfig` (confirmed config). +* **`tasks/pushNotification/get`:** (Request/Response) + * Retrieves the current push notification configuration for a task. + * `params`: `TaskIdParams`. + * `result`: `TaskPushNotificationConfig`. +* **`tasks/resubscribe`:** (Request/Stream) + * Resubscribes to task updates after a connection interruption (SSE). + * `params`: `TaskQueryParams`. + * `result` (stream events): `TaskStatusUpdateEvent` or `TaskArtifactUpdateEvent`. + +### 2.4. Streaming Update Events (Result of `message/stream` or `tasks/resubscribe`) + +* **`TaskStatusUpdateEvent`:** Signals a change in task status. + * `id`: (string) Task ID. + * `status`: (`TaskStatus`) The new status object. + * `final`: (boolean) True if this is the terminal update for the task. + * `metadata`: (object | null) Event metadata. +* **`TaskArtifactUpdateEvent`:** Signals a new or updated artifact. + * `id`: (string) Task ID. + * `artifact`: (`Artifact`) The artifact data. + * `final`: (boolean, usually false for artifacts) Can signal end concurrently with status. + * `metadata`: (object | null) Event metadata. + +### 2.5. Standard Error Codes + +* `-32700`: `JSONParseError` - Invalid JSON payload. +* `-32600`: `InvalidRequestError` - Invalid JSON-RPC request object. +* `-32601`: `MethodNotFoundError` - Method does not exist. +* `-32602`: `InvalidParamsError` - Invalid method parameters. +* `-32603`: `InternalError` - Internal server error. + +### 2.6. A2A Specific Error Codes + +* `-32001`: `TaskNotFoundError` - Specified task ID not found. +* `-32002`: `TaskNotCancelableError` - Task is in a final state and cannot be canceled. +* `-32003`: `PushNotificationNotSupportedError` - Agent does not support push notifications. +* `-32004`: `UnsupportedOperationError` - The requested operation is not supported. +* `-32005`: `ContentTypeNotSupportedError` - Mismatch in supported content types. + +## 3. Core Concepts + +* **Agent Discovery:** Clients find agents and their capabilities by fetching the `AgentCard` JSON, typically from `/.well-known/agent.json`. +* **Task Lifecycle:** Tasks progress through states defined in `TaskState` (submitted -> working -> [input-required] -> completed/canceled/failed). +* **Communication:** Uses `Message` objects containing `Part`s (text, file, data). Task outputs are represented as `Artifact`s, also containing `Part`s. +* **Streaming:** Long-running tasks can provide real-time updates using SSE via `message/stream`. Updates are sent as `TaskStatusUpdateEvent` and `TaskArtifactUpdateEvent`. +* **Push Notifications:** Agents can proactively notify clients about task updates using webhook URLs provided via `tasks/pushNotification/set`. Authentication mechanisms (e.g., Bearer tokens via JWT signed with keys from agent's JWKS endpoint) are supported. +* **Authentication:** Defined in `AgentCard` and `PushNotificationConfig`. Can involve various schemes (e.g., API keys, OAuth, JWT). Samples use JWT for push notifications. +* **Forms:** Structured data can be requested and submitted using `DataPart` within Messages/Artifacts (demonstrated in ADK sample). + +## 4. Implementations & Samples + +### 4.1. Common Libraries + +* **Python (`samples/python/common`)**: + * `client/`: `A2AClient` for making requests, `A2ACardResolver` for discovery. + * `server/`: `A2AServer` (Starlette-based), `TaskManager` base class, `InMemoryTaskManager`. + * `types.py`: Pydantic models mirroring the JSON schema. + * `utils/`: Helpers for push notification auth (JWT signing/verification, JWKS endpoint). +* **JavaScript/TypeScript (`samples/js/src`)**: + * `client/`: `A2AClient` implementation using `fetch`. + * `server/`: `A2AServer` (Express-based), `TaskStore` interface, `InMemoryTaskStore`, `FileStore`. + * `schema.ts`: TypeScript interfaces matching the JSON schema. + * `handler.ts`, `error.ts`, `utils.ts`: Support code for the server. + +### 4.2. Python Samples + +* **Location:** `samples/python/agents/` & `samples/python/hosts/` +* **Setup:** Uses `uv` and `pyproject.toml`. Requires Python >= 3.12/3.13. API keys via `.env`. +* **Agents:** + * **LangGraph (`agents/langgraph`)**: Currency conversion agent. Demonstrates tool use, multi-turn (`input-required`), and **streaming** (`message/stream`). + * **CrewAI (`agents/crewai`)**: Image generation agent. Demonstrates multi-turn and handling **file artifacts** (images). + * **Google ADK (`agents/google_adk`)**: Expense reimbursement agent. Demonstrates multi-turn and handling **forms** using `DataPart`. +* **Hosts:** + * **CLI (`hosts/cli`)**: Simple command-line client to interact with any A2A agent. Supports streaming and optional push notification listening. + * **Multi-Agent Orchestrator (`hosts/multiagent`)**: An ADK-based "Host Agent" that manages connections (`RemoteAgentConnections`) to other A2A agents and delegates tasks based on instructions. + +### 4.3. JavaScript/TypeScript Samples + +* **Location:** `samples/js/` +* **Setup:** Uses `npm`/`pnpm`, `tsx`, `tsconfig.json`. Requires Node.js >= 18. API keys via environment variables. Framework: **Genkit**. +* **Agents (`src/agents/`)**: + * **Movie Agent (`movie-agent`)**: Uses TMDB API via Genkit tools to answer movie questions. Demonstrates tool use and multi-turn (`AWAITING_USER_INPUT` mapped to `input-required`). + * **Coder Agent (`coder`)**: Generates code files. Demonstrates producing multiple **file artifacts** via streaming updates. Uses custom Genkit format (`code-format.ts`). +* **Hosts:** + * **CLI (`src/cli.ts`)**: Command-line client for interacting with JS agents. + +### 4.4. Demo Application (`demo/`) + +* **UI (`demo/ui`)**: Web application built with **Mesop**. + * Visualizes conversations with multiple agents via the host orchestrator. + * Renders text, images, forms. + * Allows dynamic agent registration via URL. + * Provides views for task list and event logs. +* **Service (`demo/ui/service`)**: Backend service for the Mesop UI. + * `server/`: Manages conversations, routes messages, interfaces with the host agent (`ADKHostManager` or `InMemoryFakeAgentManager`). + * `client/`: Client used by the UI to talk to its *own* backend service. + +## 5. Development & Setup + +* **Prerequisites:** Python (>=3.12 or 3.13), Node.js (>=18), `uv` (for Python), `npm`/`pnpm` (for JS). +* **API Keys:** Required for LLM access (e.g., `GOOGLE_API_KEY`, `TMDB_API_KEY`), typically set via `.env` files or environment variables. +* **Running Samples:** Generally involves running an agent server (`uv run ...` or `npm run ...`) and then a host client/app (e.g., `uv run hosts/cli --agent `). + +## 6. Contribution + +* See `CONTRIBUTING.md`. +* GitHub discussion available as the primary means of communication. +* GitHub issues for bugs and feature requests. +* Google Form for private feedback. + + +- Tool: +- Arguments: {'query': 'What is A2A protocol?'} +- Result: ## Query + +What is A2A protocol?. + +## Response + +### Sources: +Important: you can fetch the full content of any source using the fetch_url_content tool + +#### (google/A2A/docs/index.md)[https://raw.githubusercontent.com/google/A2A/main/docs/index.md] (Score: 0.63) +- + +The **Agent2Agent (A2A) Protocol** is an open standard designed to enable seamless communication and collaboration between AI agents. In a world where agents are built using diverse frameworks and by different vendors, A2A provides a common language, breaking down silos and fostering interoperability. + +- [Blog Post: Announcing the Agent2Agent Protocol (A2A)](https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/) +- [Watch the A2A Demo Video](https://storage.googleapis.com/gweb-developer-goog-blog-assets/original_videos/A2A_demo_v4.mp4) + +![A2A Main Graphic](assets/a2a-main.png){width="50%"} +{style="text-align: center; margin-bottom:1em; margin-top:2em;"} + +--- + +# + +#### (google/A2A/docs/specification.md)[https://raw.githubusercontent.com/google/A2A/main/docs/specification.md] (Score: 0.63) +- + +The Agent2Agent (A2A) Protocol is an open standard designed to facilitate communication and interoperability between independent, potentially opaque AI agent systems. In an ecosystem where agents might be built using different frameworks, languages, or by different vendors, A2A provides a common language and interaction model. + +This document provides the detailed technical specification for the A2A protocol. Its primary goal is to enable agents to: + +- Discover each other's capabilities. +- Negotiate interaction modalities (text, files, structured data). +- Manage collaborative tasks. +- Securely exchange information to achieve user goals **without needing access to each other's internal state, memory, or tools.** + +# + +#### (google/A2A/llms.txt)[https://raw.githubusercontent.com/google/A2A/main/llms.txt] (Score: 0.62) +- operability +9. AI agents +10. Agent communication standard + + +- [index.md](https://raw.githubusercontent.com/google/A2A/main/docs/index.md): Agent2Agent (A2A) Protocol Documentation +The Agent2Agent (A2A) Protocol is an open standard that enables seamless communication and collaboration between AI agents built on diverse frameworks and by different vendors. It provides a common language, breaking down silos and fostering interoperability. A2A facilitates dynamic, multimodal communication between agents as peers, allowing them to delegate sub-tasks, exchange information, and coordinate actions to solve complex problems. The protocol ensures security and preserves intellectual property by enabling agents to interact without sharing internal memory, tools, or proprietary logic. To get started, users can read the introduction, dive into the specification, follow tutorials, or explore code samples. + +A code example is not explicitly provided in the document; however, the document references a Python quickstart tutorial: +```markdow + +#### (google/A2A/docs/specification/details.md)[https://raw.githubusercontent.com/google/A2A/main/docs/specification/details.md] (Score: 0.62) +- + +The Agent2Agent (A2A) Protocol is an open standard designed to facilitate communication and interoperability between independent, potentially opaque AI agent systems. In an ecosystem where agents might be built using different frameworks, languages, or vendors, A2A provides a common language and interaction model. This enables agents to discover each other's capabilities, negotiate interaction modalities (text, files, structured data), manage collaborative tasks, and securely exchange information to achieve user goals **without needing access to each other's internal state, memory, or tools.** + +**Key Goals:** + +- **Interoperability:** Bridge the gap between disparate agentic systems. +- **Collaboration:** Enable agents to delegate tasks, exchange context, and work together on complex user requests. +- **Discovery:** Allow agents to dynamically find and understand the capabilities of other agents. +- **Flexibility:** Support various interaction modes (text, files, forms, streaming). +- **Security:** Facilitate secure communication patterns suitable for enterprise environments. +- **Asynchronicity:** Support long-running tasks and interactions that may involve human-in-the-loop scenarios. + +This document provides the detailed technical specification for the A2A protocol, defining its data structures, communication methods, and core concepts. + +# + +#### (google/A2A/docs/community.md)[https://raw.githubusercontent.com/google/A2A/main/docs/community.md] (Score: 0.62) +- + +The **Agent2Agent (A2A) protocol** is generating significant buzz across the +tech world, and for good reason! This open interoperability protocol is designed +to enable **seamless collaboration between AI agents across diverse frameworks +and vendors**. By standardizing communication, A2A aims to unlock complex +workflows, enhance productivity, and foster a new era of **"Agent +Interoperability"**. Don't just take our word for it – see what the community is +saying! + + +#### (google/A2A/docs/topics/a2a-and-mcp.md)[https://raw.githubusercontent.com/google/A2A/main/docs/topics/a2a-and-mcp.md] (Score: 0.60) +- A2A) + +- **Focus:** A2A standardizes how independent, often opaque, **AI agents communicate and collaborate with each other as peers.** +- **Mechanism:** It provides an application-level protocol for agents to: + - Discover each other's high-level skills and capabilities (via Agent Cards). + - Negotiate interaction modalities (text, files, structured data). + - Manage shared, stateful, and potentially long-running tasks. + - Exchange conversational context, instructions, and complex, multi-part results. +- **Use Cases:** + - A customer service agent delegating a complex billing inquiry to a specialized billing agent, maintaining context of the customer interaction. + - A travel planning agent coordinating with separate flight, hotel, and activity booking agents, managing a multi-stage booking process. + - Agents exchanging information and status updates for a collaborative project that evolves over time. +- **Key Difference from Tool Interaction:** A2A allows for more dynamic, stateful, and potentially multi-modal interactions than typically seen with simple tool calls. Agents using A2A communicate *as agents* (or on behalf of users) rather than just invoking a discrete function + +#### (google/A2A/docs/topics/enterprise-ready.md)[https://raw.githubusercontent.com/google/A2A/main/docs/topics/enterprise-ready.md] (Score: 0.60) +- + +The Agent2Agent (A2A) protocol is designed with enterprise requirements at its core. Instead of inventing new, proprietary standards for security and operations, A2A aims to integrate seamlessly with existing enterprise infrastructure and widely adopted best practices. A2A treats remote agents as standard, HTTP-based enterprise applications. This approach allows organizations to leverage their existing investments and expertise in security, monitoring, governance, and identity management. + +A key principle of A2A is that agents are typically "opaque" – they do not share internal memory, tools, or direct resource access with each other. This opacity naturally aligns with standard client/server security paradigms. + + +#### (google/A2A/docs/topics/what-is-a2a.md)[https://raw.githubusercontent.com/google/A2A/main/docs/topics/what-is-a2a.md] (Score: 0.60) +- + +Adopting A2A can lead to significant advantages: + +- **Increased Interoperability:** Break down silos between different AI agent ecosystems, allowing agents from various vendors and frameworks to work together. +- **Enhanced Agent Capabilities:** Allow developers to create more sophisticated applications by composing the strengths of multiple specialized agents. +- **Reduced Integration Complexity:** Standardize the "how" of agent communication, allowing teams to focus on the "what" – the value their agents provide. +- **Fostering Innovation:** Encourage the development of a richer ecosystem of specialized agents that can readily plug into larger collaborative workflows. +- **Future-Proofing:** Provide a flexible framework that can adapt as agent technologies continue to evolve. + +By establishing common ground for agent-to-agent communication, A2A aims to accelerate the adoption and utility of AI agents across diverse industries and applications, paving the way for more powerful and collaborative AI systems. + +[Watch the A2A Demo Video](https://storage.googleapis.com/gweb-developer-goog-blog-assets/original_videos/A2A_demo_v4.mp4) + +Next, learn about the [Key Concepts](./key-concepts.md) that form the foundation of the A2A protoc +- + +A2A provides a standardized way for these independent, often "opaque" (black-box) agentic systems to interact. It defines: + +- **A common transport and format:** JSON-RPC 2.0 over HTTP(S) for how messages are structured and transmitted. +- **Discovery mechanisms (Agent Cards):** How agents can advertise their capabilities and be found by other agents. +- **Task management workflows:** How collaborative tasks are initiated, progressed, and completed. This includes support for tasks that may be long-running or require multiple turns of interaction. +- **Support for various data modalities:** How agents exchange not just text, but also files, structured data (like forms), and potentially other rich media. +- **Core principles for security and asynchronicity:** Guidelines for secure communication and handling tasks that might take significant time or involve human-in-the-loop processes. + +# + +#### (google/A2A/docs/llms.txt)[https://raw.githubusercontent.com/google/A2A/main/docs/llms.txt] (Score: 0.60) +- + +* **Project Name:** Agent2Agent (A2A) Protocol +* **Purpose:** An open protocol by Google enabling communication and interoperability between AI agents built on different frameworks or by different vendors. +* **Core Goal:** Allow agents to discover capabilities, negotiate interaction, and collaborate securely on tasks. +* **Communication:** Uses JSON-RPC 2.0 over HTTP(S). Supports standard request/response and Server-Sent Events (SSE) for streaming. +* **Key Components:** Specification (JSON Schema), Common Libraries (Python, JS/TS), Sample Agents (LangGraph, CrewAI, ADK, Genkit), Sample Hosts (CLI, Orchestrator), Demo Web App (Mesop). + +# + +Step 1 +Thoughts: +- The previous response provides a comprehensive explanation of the A2A protocol. It's highly detailed and seems to cover most aspects of the protocol. +- There's no need to call any additional tools as the information already obtained is sufficient to answer the question thoroughly. + + + +[] + + + +The A2A (Agent2Agent) protocol is an open standard initiated by Google to enable communication and interoperability between different AI agent systems. Its core goal is to allow agents built on various frameworks or by different vendors to discover each other's capabilities, negotiate interaction methods (text, files, structured data, potentially audio/video), and collaborate on tasks. + +Key features include: + +* **Agent Discovery:** Agents discover each other's capabilities via Agent Cards, typically located at `/.well-known/agent.json`. +* **Standardized Task Management:** A standardized workflow for managing tasks (send, get, cancel). +* **Support for Various Content Types:** Supports various content types through `Parts` and `Artifacts` (text, files, structured data). +* **Streaming Updates:** Supports streaming updates for long-running tasks using Server-Sent Events (SSE). +* **Push Notifications:** Provides mechanisms for push notifications to keep clients updated. +* **JSON-RPC 2.0 over HTTP(S):** Uses JSON-RPC 2.0 over HTTP(S) for communication, supporting both standard request/response and SSE for streaming. + +The protocol is defined by a JSON Schema, and supporting libraries and sample implementations are available in Python and JavaScript/TypeScript. These samples demonstrate integration with various agent frameworks (LangGraph, CrewAI, Google ADK, Genkit) and include example agents, hosts, and a demo web application. The protocol also incorporates considerations for security and enterprise readiness. + + + +Thoughts: +- The question "What is A2A protocol?" has already been answered comprehensively by the A2A Protocol Agent. +- No further agents are needed. The provided answer is sufficient. + + + +```json +[] +``` + + + +The A2A Protocol Agent's response provides a complete and accurate definition of the A2A protocol, including its key features and functionalities. No further information is required. + \ No newline at end of file diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/client/__init__.py b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/client/__main__.py b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/client/__main__.py new file mode 100644 index 00000000..b8763a45 --- /dev/null +++ b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/client/__main__.py @@ -0,0 +1,50 @@ +import asyncio +from typing import Literal + + +import asyncclick as click +import colorama +from no_llm_framework.client.agent import Agent + + +@click.command() +@click.option('--host', 'host', default='localhost') +@click.option('--port', 'port', default=9999) +@click.option('--mode', 'mode', default='streaming') +@click.option('--question', 'question', required=True) +async def a_main( + host: str, + port: int, + mode: Literal['completion', 'streaming'], + question: str, +): + """Main function to run the A2A Repo Agent client. + + Args: + host (str): The host address to run the server on. + port (int): The port number to run the server on. + mode (Literal['completion', 'streaming']): The mode to run the server on. + question (str): The question to ask the Agent. + """ # noqa: E501 + agent_index = 1 + agent = Agent( + mode='stream', + token_stream_callback=None, + agent_urls=[f'http://{host}:{port}/{agent_index}'], + ) + async for chunk in agent.stream(question): + if chunk.startswith(' Generator[str]: + """Stream LLM response. + + Args: + prompt (str): The prompt to send to the LLM. + + Returns: + Generator[str, None, None]: A generator of the LLM response. + """ + client = genai.Client(vertexai=False, api_key=GOOGLE_API_KEY) + for chunk in client.models.generate_content_stream( + model='gemini-2.5-flash-lite', + contents=prompt, + ): + yield chunk.text + + +class Agent: + """Agent for interacting with the Google Gemini LLM in different modes.""" + + def __init__( + self, + mode: Literal['complete', 'stream'] = 'stream', + token_stream_callback: Callable[[str], None] | None = None, + agent_urls: list[str] | None = None, + agent_prompt: str | None = None, + ): + self.mode = mode + self.token_stream_callback = token_stream_callback + self.agent_urls = agent_urls + self.agents_registry: dict[str, AgentCard] = {} + + async def get_agents(self) -> tuple[dict[str, AgentCard], str]: + """Retrieve agent cards from all agent URLs and render the agent prompt. + + Returns: + tuple[dict[str, AgentCard], str]: A dictionary mapping agent names to AgentCard objects, and the rendered agent prompt string. + """ + async with httpx.AsyncClient() as httpx_client: + card_resolvers = [ + A2ACardResolver(httpx_client, url) for url in self.agent_urls + ] + agent_cards = await asyncio.gather( + *[ + card_resolver.get_agent_card() + for card_resolver in card_resolvers + ] + ) + agents_registry = { + agent_card.name: agent_card for agent_card in agent_cards + } + agent_prompt = agents_template.render(agent_cards=agent_cards) + return agents_registry, agent_prompt + + def call_llm(self, prompt: str) -> str: + """Call the LLM with the given prompt and return the response as a string or generator. + + Args: + prompt (str): The prompt to send to the LLM. + + Returns: + str or Generator[str]: The LLM response as a string or generator, depending on mode. + """ + if self.mode == 'complete': + return stream_llm(prompt) + + result = '' + for chunk in stream_llm(prompt): + result += chunk + return result + + async def decide( + self, + question: str, + agents_prompt: str, + called_agents: list[dict] | None = None, + ) -> Generator[str, None]: + """Decide which agent(s) to use to answer the question. + + Args: + question (str): The question to answer. + agents_prompt (str): The prompt describing available agents. + called_agents (list[dict] | None): Previously called agents and their answers. + + Returns: + Generator[str, None]: The LLM's response as a generator of strings. + """ + if called_agents: + call_agent_prompt = agent_answer_template.render( + called_agents=called_agents + ) + else: + call_agent_prompt = '' + prompt = decide_template.render( + question=question, + agent_prompt=agents_prompt, + call_agent_prompt=call_agent_prompt, + ) + return self.call_llm(prompt) + + def extract_agents(self, response: str) -> list[dict]: + """Extract the agents from the response. + + Args: + response (str): The response from the LLM. + """ + pattern = r'```json\n(.*?)\n```' + match = re.search(pattern, response, re.DOTALL) + if match: + return json.loads(match.group(1)) + return [] + + async def send_message_to_an_agent( + self, agent_card: AgentCard, message: str + ): + """Send a message to a specific agent and yield the streaming response. + + Args: + agent_card (AgentCard): The agent to send the message to. + message (str): The message to send. + + Yields: + str: The streaming response from the agent. + """ + async with httpx.AsyncClient() as httpx_client: + client = A2AClient(httpx_client, agent_card=agent_card) + message = MessageSendParams( + message=Message( + role=Role.user, + parts=[Part(TextPart(text=message))], + message_id=uuid4().hex, + task_id=uuid4().hex, + ) + ) + + streaming_request = SendStreamingMessageRequest( + id=str(uuid4().hex), params=message + ) + async for chunk in client.send_message_streaming(streaming_request): + if isinstance( + chunk.root, SendStreamingMessageSuccessResponse + ) and isinstance(chunk.root.result, TaskStatusUpdateEvent): + message = chunk.root.result.status.message + if message: + yield message.parts[0].root.text + + async def stream(self, question: str): + """Stream the process of answering a question, possibly involving multiple agents. + + Args: + question (str): The question to answer. + + Yields: + str: Streaming output, including agent responses and intermediate steps. + """ + agent_answers: list[dict] = [] + for _ in range(3): + agents_registry, agent_prompt = await self.get_agents() + response = '' + for chunk in await self.decide( + question, agent_prompt, agent_answers + ): + response += chunk + if self.token_stream_callback: + self.token_stream_callback(chunk) + yield chunk + + agents = self.extract_agents(response) + if agents: + for agent in agents: + agent_response = '' + agent_card = agents_registry[agent['name']] + yield f'\n' + async for chunk in self.send_message_to_an_agent( + agent_card, agent['prompt'] + ): + agent_response += chunk + if self.token_stream_callback: + self.token_stream_callback(chunk) + yield chunk + yield '\n' + match = re.search( + r'(.*?)', agent_response, re.DOTALL + ) + answer = match.group(1).strip() if match else agent_response + agent_answers.append( + { + 'name': agent['name'], + 'prompt': agent['prompt'], + 'answer': answer, + } + ) + else: + return + + +if __name__ == '__main__': + import asyncio + + import colorama + + async def main(): + """Main function to run the A2A Repo Agent client.""" + agent = Agent( + mode='stream', + token_stream_callback=None, + agent_urls=['http://localhost:9999/'], + ) + + async for chunk in agent.stream('What is A2A protocol?'): + if chunk.startswith(' + +Note: +- You can leave the selected agents empty if you think none of the agents are relevant to the question or given contexts are enough to answer the question. +- You can select multiple agents if you think multiple agents are relevant to the question. +- You `agent_name` must be one of the agent names in the agents list, and you must spell it correctly. +- If there is no need to call any agent, Please give your answer by continuing the following format: + + + + + diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/__init__.py b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/__main__.py b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/__main__.py new file mode 100644 index 00000000..2736afab --- /dev/null +++ b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/__main__.py @@ -0,0 +1,220 @@ +import click +import uvicorn +from a2a.server.apps.jsonrpc import A2AStarletteApplication +from a2a.server.request_handlers.default_request_handler import ( + DefaultRequestHandler, +) +from a2a.server.tasks.inmemory_task_store import InMemoryTaskStore +from a2a.types import ( + AgentCapabilities, + AgentCard +) +from starlette.applications import Starlette +from starlette.routing import Route, Router +from starlette.requests import Request +from typing import Any, List +from a2a.server.request_handlers.jsonrpc_handler import JSONRPCHandler + +from no_llm_framework.server.agent_executor import CoreAgentExecutor +from starlette.responses import JSONResponse + + +class DynamicContextBuilder: + def __init__(self, original_builder, agent_index): + self.original_builder = original_builder + self.agent_index = agent_index + + async def build(self, request: Request): + if self.original_builder and hasattr(self.original_builder, "build"): + context = await self.original_builder.build(request) + else: + context = None + + if context: + setattr(context, "agent_index", self.agent_index) + return context + + +class DatabaseA2AStarletteApplication(A2AStarletteApplication): + def __init__(self, http_handler: DefaultRequestHandler): + temp_card = AgentCard( + name="Temporary Agent", + description="Initial agent card", + url="http://localhost:9999/a2a/default", + version="1.0.0", + defaultInputModes=["text"], + defaultOutputModes=["text"], + capabilities=AgentCapabilities( + inputModes=["text"], outputModes=["text"], streaming=True + ), + skills=[], + examples=[], + ) + + self.task_store = InMemoryTaskStore() + + self.agent_card = temp_card + self.http_handler = http_handler + self.handler = JSONRPCHandler( + agent_card=self.agent_card, request_handler=self.http_handler + ) + + self.agent_router = Router() + + self.agent_router.add_route( + "/.well-known/agent.json", self._handle_dynamic_agent_card, methods=["GET"] + ) + self.agent_router.add_route("/", self._handle_dynamic_request, methods=["POST"]) + + def _get_agent_card_from_config(self, agent_index: str, request: Request) -> AgentCard: + + agent_data = { + "name": "default", + "description": "default", + "version": "1.0.0", + "examples": "What is A2A protocol?" + } + + return AgentCard( + name=agent_data["name"], + description=agent_data["description"], + url=f"http://{request.url.hostname}:{request.url.port}/a2a/{agent_index}", + version=agent_data["version"], + defaultInputModes=["text"], + defaultOutputModes=["text"], + capabilities=AgentCapabilities( + inputModes=["text"], + outputModes=["text"], + streaming=True, + ), + skills=[], + examples=agent_data["examples"] or [], + ) + + def routes( + self, + rpc_url: str = "/", + extended_agent_card_url: str = "/agent/authenticatedExtendedCard", + **kwargs, + ) -> List[Route]: + routes = [ + Route(rpc_url, self._handle_requests, methods=["POST"]), + + Route( + "/a2a/{agent_index}/.well-known/agent.json", + self._handle_dynamic_agent_card, + methods=["GET"], + ), + + Route("/a2a/{agent_index}", self._handle_dynamic_request, methods=["POST"]), + ] + + if self.agent_card and self.agent_card.supportsAuthenticatedExtendedCard: + routes.append( + Route( + extended_agent_card_url, + self._handle_get_authenticated_extended_agent_card, + methods=["GET"], + ) + ) + + return routes + + async def _handle_dynamic_agent_card(self, request: Request) -> JSONResponse: + + agent_index = request.path_params["agent_index"] + try: + card = self._get_agent_card_from_config(agent_index, request) + return JSONResponse(card.dict()) + except Exception as e: + return JSONResponse( + {"error": f"Agent not found: {str(e)}"}, status_code=404 + ) + + async def _handle_dynamic_request(self, request: Request) -> JSONResponse: + + original_card = None + original_context_builder = None + + try: + agent_index = request.path_params.get("agent_index") + self.task_store = InMemoryTaskStore() + self.http_handler.agent_executor = CoreAgentExecutor( + agent_index=agent_index + ) + + if not agent_index: + return JSONResponse( + {"error": "Missing agent_index in path parameters"}, status_code=400 + ) + + original_card = self.agent_card + original_context_builder = getattr(self, "_context_builder", None) + + try: + self.agent_card = self._get_agent_card_from_config(agent_index, request) + self.handler.agent_card.name = self.agent_card.name + self.handler.agent_card.description = self.agent_card.description + except Exception as e: + return JSONResponse( + {"error": f"Failed to get agent card: {str(e)}"}, status_code=404 + ) + + self._context_builder = DynamicContextBuilder( + original_context_builder, agent_index + ) + + response = await self._handle_requests(request) + return response + except Exception as e: + import traceback + + traceback.print_exc() + return JSONResponse( + { + "error": "Internal server error", + "details": str(e), + "type": type(e).__name__, + }, + status_code=500, + ) + finally: + + if original_card is not None: + self.agent_card = original_card + if original_context_builder is not None: + self._context_builder = original_context_builder + + def build( + self, + agent_card_url: str = "/.well-known/agent.json", + rpc_url: str = "/", + extended_agent_card_url: str = "/agent/authenticatedExtendedCard", + **kwargs: Any, + ) -> Starlette: + app = Starlette(**kwargs) + self.add_routes_to_app( + app, + agent_card_url=agent_card_url, + rpc_url=rpc_url, + extended_agent_card_url=extended_agent_card_url, + ) + return app + + +@click.command() +@click.option("--host", default="localhost") +@click.option("--port", default=9999) +def main(host: str, port: int): + + task_store = InMemoryTaskStore() + request_handler = DefaultRequestHandler( + agent_executor=CoreAgentExecutor(1), + task_store=task_store, + ) + server = DatabaseA2AStarletteApplication(request_handler) + uvicorn.run(server.build(), host=host, port=port) + + +if __name__ == "__main__": + main() diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent.py b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent.py new file mode 100644 index 00000000..446ad649 --- /dev/null +++ b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent.py @@ -0,0 +1,187 @@ +import asyncio +import json +import re + +from collections.abc import AsyncGenerator, Callable, Generator +from pathlib import Path +from typing import Literal + +from google import genai +from jinja2 import Template +from mcp.types import CallToolResult + +from no_llm_framework.server.constant import GOOGLE_API_KEY +from no_llm_framework.server.mcp import call_mcp_tool, get_mcp_tool_prompt + + +dir_path = Path(__file__).parent + +with Path(dir_path / 'decide.jinja').open('r') as f: + decide_template = Template(f.read()) + +with Path(dir_path / 'tool.jinja').open('r') as f: + tool_template = Template(f.read()) + +with Path(dir_path / 'called_tools_history.jinja').open('r') as f: + called_tools_history_template = Template(f.read()) + + +def stream_llm(prompt: str) -> Generator[str, None]: + """Stream LLM response. + + Args: + prompt (str): The prompt to send to the LLM. + + Returns: + Generator[str, None, None]: A generator of the LLM response. + """ + client = genai.Client(vertexai=False, api_key=GOOGLE_API_KEY) + for chunk in client.models.generate_content_stream( + model='gemini-2.5-flash-lite', + contents=prompt, + ): + yield chunk.text + + +class Agent: + """Agent for interacting with the Google Gemini LLM in different modes.""" + + def __init__( + self, + mode: Literal['complete', 'stream'] = 'stream', + token_stream_callback: Callable[[str], None] | None = None, + mcp_url: str | None = None, + ): + self.mode = mode + self.token_stream_callback = token_stream_callback + self.mcp_url = mcp_url + + def call_llm(self, prompt: str) -> Generator[str, None]: + """Call the LLM with the given prompt and return a generator of responses. + + Args: + prompt (str): The prompt to send to the LLM. + + Returns: + Generator[str, None]: A generator yielding the LLM's response. + """ + return stream_llm(prompt) + + async def decide( + self, question: str, called_tools: list[dict] | None = None + ) -> Generator[str, None]: + """Decide which tool to use to answer the question. + + Args: + question (str): The question to answer. + called_tools (list[dict]): The tools that have been called. + """ + if self.mcp_url is None: + return self.call_llm(question) + tool_prompt = await get_mcp_tool_prompt(self.mcp_url) + if called_tools: + called_tools_prompt = called_tools_history_template.render( + called_tools=called_tools + ) + else: + called_tools_prompt = '' + + prompt = decide_template.render( + question=question, + tool_prompt=tool_prompt, + called_tools=called_tools_prompt, + ) + return self.call_llm(prompt) + + def extract_tools(self, response: str) -> list[dict]: + """Extract the tools from the response. + + Args: + response (str): The response from the LLM. + """ + pattern = r'```json\n(.*?)\n```' + match = re.search(pattern, response, re.DOTALL) + if match: + return json.loads(match.group(1)) + return [] + + async def call_tool(self, tools: list[dict]) -> list[CallToolResult]: + """Call the tool. + + Args: + tools (list[dict]): The tools to call. + """ + return await asyncio.gather( + *[ + call_mcp_tool(self.mcp_url, tool['name'], tool['arguments']) + for tool in tools + ] + ) + + async def stream(self, question: str) -> AsyncGenerator[str]: + """Stream the process of answering a question, possibly involving tool calls. + + Args: + question (str): The question to answer. + + Yields: + dict: Streaming output, including intermediate steps and final result. + """ + called_tools = [] + for i in range(10): + yield { + 'is_task_complete': False, + 'require_user_input': False, + 'content': f'Step {i}', + } + + response = '' + for chunk in await self.decide(question, called_tools): + response += chunk + yield { + 'is_task_complete': False, + 'require_user_input': False, + 'content': chunk, + } + tools = self.extract_tools(response) + if not tools: + break + results = await self.call_tool(tools) + + called_tools += [ + { + 'tool': tool['name'], + 'arguments': tool['arguments'], + 'isError': result.isError, + 'result': result.content[0].text, + } + for tool, result in zip(tools, results, strict=True) + ] + called_tools_history = called_tools_history_template.render( + called_tools=called_tools, question=question + ) + yield { + 'is_task_complete': False, + 'require_user_input': False, + 'content': called_tools_history, + } + + yield { + 'is_task_complete': True, + 'require_user_input': False, + 'content': 'Task completed', + } + + +if __name__ == '__main__': + agent = Agent( + token_stream_callback=lambda token: print(token, end='', flush=True), + mcp_url='https://gitmcp.io/google/A2A', + ) + + async def main(): + """Main function.""" + async for chunk in agent.stream('What is A2A Protocol?'): + print(chunk) + + asyncio.run(main()) diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent_executor.py b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent_executor.py new file mode 100644 index 00000000..61ad9ad5 --- /dev/null +++ b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent_executor.py @@ -0,0 +1,104 @@ +from a2a.server.agent_execution import AgentExecutor, RequestContext +from a2a.server.events import EventQueue +from a2a.types import ( + TaskArtifactUpdateEvent, + TaskState, + TaskStatus, + TaskStatusUpdateEvent, +) +from a2a.utils import new_agent_text_message, new_task, new_text_artifact +from typing import Optional, override + +from src.no_llm_framework.server.agent import Agent + +class CoreAgentExecutor(AgentExecutor): + """Test AgentProxy Implementation.""" + + def __init__(self, agent_index: int = None): + + mcp_url = {"1": "https://gitmcp.io/google/A2A", "2": "https://gitmcp.io/google/A2A"} + + self.agent = Agent( + mode='stream', + token_stream_callback=print, + mcp_url=[mcp_url[str(agent_index)]], + ) + + @override + async def execute( + self, + context: RequestContext, + event_queue: EventQueue, + ) -> None: + query = context.get_user_input() + task = context.current_task + + if not context.message: + raise Exception('No message provided') + + if not task: + task = new_task(context.message) + await event_queue.enqueue_event(task) + + async for event in self.agent.stream(query): + if event['is_task_complete']: + await event_queue.enqueue_event( + TaskArtifactUpdateEvent( + append=False, + context_id=task.context_id, + task_id=task.id, + last_chunk=True, + artifact=new_text_artifact( + name='current_result', + description='Result of request to agent.', + text=event['content'], + ), + ) + ) + await event_queue.enqueue_event( + TaskStatusUpdateEvent( + status=TaskStatus(state=TaskState.completed), + final=True, + context_id=task.context_id, + task_id=task.id, + ) + ) + elif event['require_user_input']: + await event_queue.enqueue_event( + TaskStatusUpdateEvent( + status=TaskStatus( + state=TaskState.input_required, + message=new_agent_text_message( + event['content'], + task.context_id, + task.id, + ), + ), + final=True, + context_id=task.context_id, + task_id=task.id, + ) + ) + else: + await event_queue.enqueue_event( + TaskStatusUpdateEvent( + append=True, + status=TaskStatus( + state=TaskState.working, + message=new_agent_text_message( + event['content'], + task.context_id, + task.id, + ), + ), + final=False, + context_id=task.context_id, + task_id=task.id, + ) + ) + + @override + async def cancel( + self, context: RequestContext, event_queue: EventQueue + ) -> None: + raise Exception('cancel not supported') diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/called_tools_history.jinja b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/called_tools_history.jinja new file mode 100644 index 00000000..99d05361 --- /dev/null +++ b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/called_tools_history.jinja @@ -0,0 +1,5 @@ +Previous tools have been called. {% for tool in called_tools %} +- Tool: {{ tool.name }} +- Arguments: {{ tool.arguments }} +- Result: {{ tool.result }} +{% endfor %} \ No newline at end of file diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/constant.py b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/constant.py new file mode 100644 index 00000000..a51e2c82 --- /dev/null +++ b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/constant.py @@ -0,0 +1,3 @@ +import os + +GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY') diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/decide.jinja b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/decide.jinja new file mode 100644 index 00000000..590cd376 --- /dev/null +++ b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/decide.jinja @@ -0,0 +1,43 @@ +You duty is to decide which tool to use to answer the question. +The question is: + +{{ question }} + +{{ called_tools }} + +{{ tool_prompt }} + +You must answer in the following format: + + + +Thoughts: +- ... +- ... +- ... + + + +```json +[ + { + "name": "tool_name", + "arguments": { + "argument_name": "argument_value" + } + }, + ... +] +``` + + +Note: +- You can leave the selected tools empty if you think none of the tools are relevant to the question or given contexts are enough to answer the question. +- You can select multiple tools if you think multiple tools are relevant to the question. +- You `tool_name` must be one of the tool names in the tools list, and you must spell it correctly. +- If there is no need to call any tool, Please give your answer by continuing the following format: + + + + + diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/mcp.py b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/mcp.py new file mode 100644 index 00000000..3ed77c20 --- /dev/null +++ b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/mcp.py @@ -0,0 +1,65 @@ +import asyncio +from pathlib import Path + +from jinja2 import Template +from mcp.client.session import ClientSession +from mcp.client.sse import sse_client +from mcp.types import CallToolResult, TextContent + +dir_path = Path(__file__).parent + +with Path(dir_path / 'tool.jinja').open('r') as f: + template = Template(f.read()) + + +async def get_mcp_tool_prompt(url: str) -> str: + """Get the MCP tool prompt for a given URL. + + Args: + url (str): The URL of the MCP tool. + + Returns: + str: The MCP tool prompt. + """ + async with ( + sse_client(url) as (read, write), + ClientSession(read, write) as session, + ): + await session.initialize() + + resources = await session.list_tools() + return template.render(tools=resources.tools) + + +async def call_mcp_tool( + url: str, tool_name: str, arguments: dict | None = None +) -> CallToolResult: + """Call an MCP tool with the given URL and tool name. + + Args: + url (str): The URL of the MCP tool. + tool_name (str): The name of the tool to call. + arguments (dict | None, optional): The arguments to pass to the tool. Defaults to None. + + Returns: + CallToolResult: The result of the tool call. + """ # noqa: E501 + async with ( + sse_client( + url=url, + ) as (read, write), + ClientSession(read, write) as session, + ): + await session.initialize() + + return await session.call_tool(tool_name, arguments=arguments) + + +if __name__ == '__main__': + print(asyncio.run(get_mcp_tool_prompt('https://gitmcp.io/google/A2A'))) + result = asyncio.run( + call_mcp_tool('https://gitmcp.io/google/A2A', 'fetch_A2A_documentation') + ) + for content in result.content: + if isinstance(content, TextContent): + print(content.text) diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/tool.jinja b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/tool.jinja new file mode 100644 index 00000000..d56bfb4b --- /dev/null +++ b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/tool.jinja @@ -0,0 +1,4 @@ +Tools{% for tool in tools %} +- {{loop.index}}: {{ tool.name }} + - Description: {{ tool.description }} + - Input Schema: {{tool.inputSchema.properties}}{% endfor %} diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/uv.lock b/samples/python/agents/a2a_multi_agent_on_single_port/uv.lock new file mode 100644 index 00000000..7a4dadcf --- /dev/null +++ b/samples/python/agents/a2a_multi_agent_on_single_port/uv.lock @@ -0,0 +1,736 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "a2a-sdk" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastapi" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "pydantic" }, + { name = "sse-starlette" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1e/3b/8fd1e3fe28606712c203b968a6fe2c8e7944b6df9e65c28976c66c19286c/a2a_sdk-0.3.0.tar.gz", hash = "sha256:d9638c71674183f32fe12f8865015e91a563a90a3aa9ed43020f1b23164862b3", size = 179006, upload-time = "2025-07-21T19:51:14.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/92/16bfbc2ef0ef037c5860ef3b13e482aeb1860b9643bf833ed522c995f639/a2a_sdk-0.3.0-py3-none-any.whl", hash = "sha256:54782eab3d0ad0d5842bfa07ff78d338ea836f1259ece51a825c53193c67c7d0", size = 103090, upload-time = "2025-07-21T19:51:12.613Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "asyncclick" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/e1e5fdf1c1bb7e6e614987c120a98d9324bf8edfaa5f5cd16a6235c9d91b/asyncclick-8.1.8.tar.gz", hash = "sha256:0f0eb0f280e04919d67cf71b9fcdfb4db2d9ff7203669c40284485c149578e4c", size = 232900, upload-time = "2025-01-06T09:46:52.694Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/cc/a436f0fc2d04e57a0697e0f87a03b9eaed03ad043d2d5f887f8eebcec95f/asyncclick-8.1.8-py3-none-any.whl", hash = "sha256:eb1ccb44bc767f8f0695d592c7806fdf5bd575605b4ee246ffd5fadbcfdbd7c6", size = 99093, upload-time = "2025-01-06T09:46:51.046Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/ae9e9d25522c6dc96ff167903880a0fe94d7bd31ed999198ee5017d977ed/asyncclick-8.1.8.0-py3-none-any.whl", hash = "sha256:be146a2d8075d4fe372ff4e877f23c8b5af269d16705c1948123b9415f6fd678", size = 99115, upload-time = "2025-01-06T09:50:52.72Z" }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, +] + +[[package]] +name = "certifi" +version = "2025.4.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "deprecated" +version = "1.2.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744, upload-time = "2025-01-27T10:46:25.7Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "fastapi" +version = "0.115.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/64/ec0788201b5554e2a87c49af26b77a4d132f807a0fa9675257ac92c6aa0e/fastapi-0.115.13.tar.gz", hash = "sha256:55d1d25c2e1e0a0a50aceb1c8705cd932def273c102bff0b1c1da88b3c6eb307", size = 295680, upload-time = "2025-06-17T11:49:45.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/4a/e17764385382062b0edbb35a26b7cf76d71e27e456546277a42ba6545c6e/fastapi-0.115.13-py3-none-any.whl", hash = "sha256:0a0cab59afa7bab22f5eb347f8c9864b681558c278395e94035a741fc10cd865", size = 95315, upload-time = "2025-06-17T11:49:44.106Z" }, +] + +[[package]] +name = "fastmcp" +version = "2.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup" }, + { name = "httpx" }, + { name = "mcp" }, + { name = "openapi-pydantic" }, + { name = "python-dotenv" }, + { name = "rich" }, + { name = "typer" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/d9/cc3eb61c59fec834a9492ea21df134381b4be76c35faa18cd2b0249278b8/fastmcp-2.3.4.tar.gz", hash = "sha256:f3fe004b8735b365a65ec2547eeb47db8352d5613697254854bc7c9c3c360eea", size = 998315, upload-time = "2025-05-15T00:54:51.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e6/310d1fe6708b7338e1f48915a13d8bf00fd0599acdc7bf98da4fd20fcb66/fastmcp-2.3.4-py3-none-any.whl", hash = "sha256:12a45f72dd95aeaa1a6a56281fff96ca46929def3ccd9f9eb125cb97b722fbab", size = 96393, upload-time = "2025-05-15T00:54:49.714Z" }, +] + +[[package]] +name = "google-auth" +version = "2.40.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/a5/38c21d0e731bb716cffcf987bd9a3555cb95877ab4b616cfb96939933f20/google_auth-2.40.1.tar.gz", hash = "sha256:58f0e8416a9814c1d86c9b7f6acf6816b51aba167b2c76821965271bac275540", size = 280975, upload-time = "2025-05-07T01:04:55.3Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/b1/1272c6e80847ba5349f5ccb7574596393d1e222543f5003cb810865c3575/google_auth-2.40.1-py2.py3-none-any.whl", hash = "sha256:ed4cae4f5c46b41bae1d19c036e06f6c371926e97b19e816fc854eff811974ee", size = 216101, upload-time = "2025-05-07T01:04:53.612Z" }, +] + +[[package]] +name = "google-genai" +version = "1.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "google-auth" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/12/ad9f08be2ca85122ca50ac69ae70454f18a3c7d840bcc4ed43f517ab47be/google_genai-1.20.0.tar.gz", hash = "sha256:dccca78f765233844b1bd4f1f7a2237d9a76fe6038cf9aa72c0cd991e3c107b5", size = 201550, upload-time = "2025-06-11T23:57:16.411Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/b4/08f3ea414060a7e7d4436c08bb22d03dabef74cc05ef13ef8cd846156d5b/google_genai-1.20.0-py3-none-any.whl", hash = "sha256:ccd61d6ebcb14f5c778b817b8010e3955ae4f6ddfeaabf65f42f6d5e3e5a8125", size = 203039, upload-time = "2025-06-11T23:57:14.631Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/08/c1395a292bb23fd03bdf572a1357c5a733d3eecbab877641ceacab23db6e/importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580", size = 55767, upload-time = "2025-01-20T22:21:30.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971, upload-time = "2025-01-20T22:21:29.177Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "mcp" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/8d/0f4468582e9e97b0a24604b585c651dfd2144300ecffd1c06a680f5c8861/mcp-1.9.0.tar.gz", hash = "sha256:905d8d208baf7e3e71d70c82803b89112e321581bcd2530f9de0fe4103d28749", size = 281432, upload-time = "2025-05-15T18:51:06.615Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/d5/22e36c95c83c80eb47c83f231095419cf57cf5cca5416f1c960032074c78/mcp-1.9.0-py3-none-any.whl", hash = "sha256:9dfb89c8c56f742da10a5910a1f64b0d2ac2c3ed2bd572ddb1cfab7f35957178", size = 125082, upload-time = "2025-05-15T18:51:04.916Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "no-llm-framework" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "a2a-sdk" }, + { name = "asyncclick" }, + { name = "colorama" }, + { name = "fastmcp" }, + { name = "google-genai" }, + { name = "jinja2" }, + { name = "rich" }, +] + +[package.metadata] +requires-dist = [ + { name = "a2a-sdk", specifier = ">=0.3.0" }, + { name = "asyncclick", specifier = ">=8.1.8" }, + { name = "colorama", specifier = ">=0.4.6" }, + { name = "fastmcp", specifier = ">=2.3.4" }, + { name = "google-genai" }, + { name = "jinja2", specifier = ">=3.1.6" }, + { name = "rich", specifier = ">=14.0.0" }, +] + +[[package]] +name = "openapi-pydantic" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892, upload-time = "2025-01-08T19:29:27.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.33.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecated" }, + { name = "importlib-metadata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/8d/1f5a45fbcb9a7d87809d460f09dc3399e3fbd31d7f3e14888345e9d29951/opentelemetry_api-1.33.1.tar.gz", hash = "sha256:1c6055fc0a2d3f23a50c7e17e16ef75ad489345fd3df1f8b8af7c0bbf8a109e8", size = 65002, upload-time = "2025-05-16T18:52:41.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/44/4c45a34def3506122ae61ad684139f0bbc4e00c39555d4f7e20e0e001c8a/opentelemetry_api-1.33.1-py3-none-any.whl", hash = "sha256:4db83ebcf7ea93e64637ec6ee6fabee45c5cbe4abd9cf3da95c43828ddb50b83", size = 65771, upload-time = "2025-05-16T18:52:17.419Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.33.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/12/909b98a7d9b110cce4b28d49b2e311797cffdce180371f35eba13a72dd00/opentelemetry_sdk-1.33.1.tar.gz", hash = "sha256:85b9fcf7c3d23506fbc9692fd210b8b025a1920535feec50bd54ce203d57a531", size = 161885, upload-time = "2025-05-16T18:52:52.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/8e/ae2d0742041e0bd7fe0d2dcc5e7cce51dcf7d3961a26072d5b43cc8fa2a7/opentelemetry_sdk-1.33.1-py3-none-any.whl", hash = "sha256:19ea73d9a01be29cacaa5d6c8ce0adc0b7f7b4d58cc52f923e4413609f670112", size = 118950, upload-time = "2025-05-16T18:52:37.297Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.54b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecated" }, + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/2c/d7990fc1ffc82889d466e7cd680788ace44a26789809924813b164344393/opentelemetry_semantic_conventions-0.54b1.tar.gz", hash = "sha256:d1cecedae15d19bdaafca1e56b29a66aa286f50b5d08f036a145c7f3e9ef9cee", size = 118642, upload-time = "2025-05-16T18:52:53.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/80/08b1698c52ff76d96ba440bf15edc2f4bc0a279868778928e947c1004bdd/opentelemetry_semantic_conventions-0.54b1-py3-none-any.whl", hash = "sha256:29dab644a7e435b58d3a3918b58c333c92686236b30f7891d5e51f02933ca60d", size = 194938, upload-time = "2025-05-16T18:52:38.796Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/ab/5250d56ad03884ab5efd07f734203943c8a8ab40d551e208af81d0257bf2/pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d", size = 786540, upload-time = "2025-04-29T20:38:55.02Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/12/46b65f3534d099349e38ef6ec98b1a5a81f42536d17e0ba382c28c67ba67/pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb", size = 443900, upload-time = "2025-04-29T20:38:52.724Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sse-starlette" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/5f/28f45b1ff14bee871bacafd0a97213f7ec70e389939a80c60c0fb72a9fc9/sse_starlette-2.3.5.tar.gz", hash = "sha256:228357b6e42dcc73a427990e2b4a03c023e2495ecee82e14f07ba15077e334b2", size = 17511, upload-time = "2025-05-12T18:23:52.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/48/3e49cf0f64961656402c0023edbc51844fe17afe53ab50e958a6dbbbd499/sse_starlette-2.3.5-py3-none-any.whl", hash = "sha256:251708539a335570f10eaaa21d1848a10c42ee6dc3a9cf37ef42266cdb1c52a8", size = 10233, upload-time = "2025-05-12T18:23:50.722Z" }, +] + +[[package]] +name = "starlette" +version = "0.46.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" }, +] + +[[package]] +name = "typer" +version = "0.15.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/89/c527e6c848739be8ceb5c44eb8208c52ea3515c6cf6406aa61932887bf58/typer-0.15.4.tar.gz", hash = "sha256:89507b104f9b6a0730354f27c39fae5b63ccd0c95b1ce1f1a6ba0cfd329997c3", size = 101559, upload-time = "2025-05-14T16:34:57.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/62/d4ba7afe2096d5659ec3db8b15d8665bdcb92a3c6ff0b95e99895b335a9c/typer-0.15.4-py3-none-any.whl", hash = "sha256:eb0651654dcdea706780c466cf06d8f174405a659ffff8f163cfbfee98c0e173", size = 45258, upload-time = "2025-05-14T16:34:55.583Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, +] + +[[package]] +name = "urllib3" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815, upload-time = "2025-04-19T06:02:50.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483, upload-time = "2025-04-19T06:02:48.42Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "wrapt" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800, upload-time = "2025-01-14T10:34:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824, upload-time = "2025-01-14T10:34:22.999Z" }, + { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920, upload-time = "2025-01-14T10:34:25.386Z" }, + { url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690, upload-time = "2025-01-14T10:34:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861, upload-time = "2025-01-14T10:34:29.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174, upload-time = "2025-01-14T10:34:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721, upload-time = "2025-01-14T10:34:32.91Z" }, + { url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763, upload-time = "2025-01-14T10:34:34.903Z" }, + { url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585, upload-time = "2025-01-14T10:34:36.13Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676, upload-time = "2025-01-14T10:34:37.962Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871, upload-time = "2025-01-14T10:34:39.13Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312, upload-time = "2025-01-14T10:34:40.604Z" }, + { url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062, upload-time = "2025-01-14T10:34:45.011Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155, upload-time = "2025-01-14T10:34:47.25Z" }, + { url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471, upload-time = "2025-01-14T10:34:50.934Z" }, + { url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208, upload-time = "2025-01-14T10:34:52.297Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339, upload-time = "2025-01-14T10:34:53.489Z" }, + { url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232, upload-time = "2025-01-14T10:34:55.327Z" }, + { url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476, upload-time = "2025-01-14T10:34:58.055Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377, upload-time = "2025-01-14T10:34:59.3Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986, upload-time = "2025-01-14T10:35:00.498Z" }, + { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750, upload-time = "2025-01-14T10:35:03.378Z" }, + { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload-time = "2025-01-14T10:35:44.018Z" }, +] + +[[package]] +name = "zipp" +version = "3.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545, upload-time = "2024-11-10T15:05:20.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630, upload-time = "2024-11-10T15:05:19.275Z" }, +] From 0b9ce1192e010906bce35e1534c9ef241239f9a4 Mon Sep 17 00:00:00 2001 From: ChenXAn <2277587992@qq.com> Date: Fri, 15 Aug 2025 14:08:23 +0800 Subject: [PATCH 2/6] fix: README --- .../python/agents/a2a_multi_agent_on_single_port/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/README.md b/samples/python/agents/a2a_multi_agent_on_single_port/README.md index 703e4be6..31f1157f 100644 --- a/samples/python/agents/a2a_multi_agent_on_single_port/README.md +++ b/samples/python/agents/a2a_multi_agent_on_single_port/README.md @@ -80,6 +80,8 @@ uv run --env-file .env python -m src.no_llm_framework.client --question "What is 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. -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. +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. Developers are responsible for implementing appropriate security measures, such as input validation and secure handling of credentials to protect their systems and users. From 7839d4fa1f3d9deb97eb9958c7c7ea625a5f39fa Mon Sep 17 00:00:00 2001 From: ChenXAn <2277587992@qq.com> Date: Fri, 15 Aug 2025 14:17:40 +0800 Subject: [PATCH 3/6] Renamed the variable mcp_url_dict. --- .../src/no_llm_framework/server/agent_executor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent_executor.py b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent_executor.py index 61ad9ad5..7c380a05 100644 --- a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent_executor.py +++ b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent_executor.py @@ -16,12 +16,12 @@ class CoreAgentExecutor(AgentExecutor): def __init__(self, agent_index: int = None): - mcp_url = {"1": "https://gitmcp.io/google/A2A", "2": "https://gitmcp.io/google/A2A"} + mcp_url_dict = {"1": "https://gitmcp.io/google/A2A", "2": "https://gitmcp.io/google/A2A"} self.agent = Agent( mode='stream', token_stream_callback=print, - mcp_url=[mcp_url[str(agent_index)]], + mcp_url=[mcp_url_dict[str(agent_index)]], ) @override From 79b1122043a3097786676ba90723dd98479b1007 Mon Sep 17 00:00:00 2001 From: ChenXAn <2277587992@qq.com> Date: Fri, 15 Aug 2025 14:39:28 +0800 Subject: [PATCH 4/6] fix the format --- .../a2a-mcp-without-framework/README.md | 77 +-- .../a2a-mcp-without-framework/response.xml | 486 ------------------ .../a2a_multi_agent_on_single_port/README.md | 2 +- .../no_llm_framework/server/agent_executor.py | 2 +- 4 files changed, 3 insertions(+), 564 deletions(-) delete mode 100644 samples/python/agents/a2a-mcp-without-framework/response.xml diff --git a/samples/python/agents/a2a-mcp-without-framework/README.md b/samples/python/agents/a2a-mcp-without-framework/README.md index eae7a30f..564b455b 100644 --- a/samples/python/agents/a2a-mcp-without-framework/README.md +++ b/samples/python/agents/a2a-mcp-without-framework/README.md @@ -7,79 +7,4 @@ This repository demonstrates how to set up and use the [a2a-python SDK](https:// - **A2A (Agent-to-Agent):** A protocol and SDK for communication with AI Agents. - **This Example:** Shows how to run a basic A2A server and client, exchange messages, and view the response. -## Prerequisites - -- Python 3.13+ -- [uv](https://github.com/astral-sh/uv) (for fast dependency management and running) -- An API key for Gemini (set as `GEMINI_API_KEY`) - -## Installation - -1. **Clone the repository:** - - ```bash - git clone - cd - ``` - -2. **Install dependencies:** - - ```bash - uv pip install -e . - ``` - -3. **Set environment variables:** - - ```bash - export GEMINI_API_KEY=your-gemini-api-key - ``` - - Or create a `.env` file with: - - ```sh - GEMINI_API_KEY=your-gemini-api-key - ``` - -## Running the Example - -### 1. Start the Server - -```bash -uv run --env-file .env python -m src.no_llm_framework.server.__main__ -``` - -- The server will start on port `9999`. - -### 2. Run the Client - -In a new terminal: - -```bash -uv run --env-file .env python -m src.no_llm_framework.client --question "What is A2A protocol?" -``` - -- The client will connect to the server and send a request. - -### 3. View the Response - -- The response from the client will be saved to [`response.xml`](./response.xml). - -## File Structure - -- `src/no_llm_framework/server/`: Server implementation. -- `src/no_llm_framework/client/`: Client implementation. -- `response.xml`: Example response from the client. - -## Troubleshooting - -- **Missing dependencies:** Make sure you have `uv` installed. -- **API key errors:** Ensure `GEMINI_API_KEY` is set correctly. -- **Port conflicts:** Make sure port 9999 is free. - -## Disclaimer - -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. - -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. - -Developers are responsible for implementing appropriate security measures, such as input validation and secure handling of credentials to protect their systems and users. +more information refer to [a2a-mcp-without-framework](https://github.com/a2aproject/a2a-samples/blob/main/samples/python/agents/a2a-mcp-without-framework/README.md) diff --git a/samples/python/agents/a2a-mcp-without-framework/response.xml b/samples/python/agents/a2a-mcp-without-framework/response.xml deleted file mode 100644 index 685707ee..00000000 --- a/samples/python/agents/a2a-mcp-without-framework/response.xml +++ /dev/null @@ -1,486 +0,0 @@ - -Thoughts: -- The question directly asks about the A2A protocol. -- The A2A Protocol Agent is specifically designed to answer questions about the A2A protocol. -- No other agent is relevant. - - - -```json -[ - { - "name": "A2A Protocol Agent", - "prompt": "What is A2A protocol?" - } -] -``` - -Step 0 -Thoughts: -- The question asks for a definition of the A2A protocol. This is a general question about the Google A2A project. -- The `fetch_A2A_documentation` tool is designed to retrieve the entire documentation, which is likely to contain the definition. This should be the first tool used. -- If the definition isn't directly found in the fetched documentation, `search_A2A_documentation` could be used to search for relevant sections within the documentation. - - - -```json -[ - { - "name": "fetch_A2A_documentation", - "arguments": {} - }, - { - "name": "search_A2A_documentation", - "arguments": { - "query": "What is A2A protocol?" - } - } -] -``` - -Previous tools have been called. -- Tool: -- Arguments: {} -- Result: # A2A (Agent2Agent) Protocol High-Level Summary - -This project defines and demonstrates the **Agent2Agent (A2A) protocol**, an open standard initiated by Google designed to enable communication and interoperability between disparate AI agent systems. The core goal is to allow agents built on different frameworks (e.g., LangGraph, CrewAI, Google ADK, Genkit) or by different vendors to discover each other's capabilities, negotiate interaction modes (text, forms, files, potentially audio/video later), and collaborate on tasks. - -The repository provides: - -1. **Formal Specification:** A detailed JSON Schema (`specification/json/a2a.json`) defining the structure of A2A messages, including requests, responses, task states, artifacts, and agent metadata (Agent Cards). -2. **Core Concepts Documentation (Implied):** Links in the main README suggest documentation covering agent discovery, task lifecycle, artifact exchange, streaming updates, push notifications, and enterprise readiness. -3. **Sample Implementations:** - * **Common Libraries:** Reusable Python (`samples/python/common`) and JavaScript/TypeScript (`samples/js/src`) code for building A2A clients and servers, handling JSON-RPC communication, task management, and potentially authentication. - * **Example Agents:** Demonstrations of integrating A2A into various agent frameworks: - * **Python:** LangGraph (currency conversion, streaming), CrewAI (image generation, file artifacts), Google ADK (expense reports, form handling). - * **JavaScript/TypeScript:** Genkit (movie info via API, code generation with file artifacts). - * **Example Hosts:** Applications that *consume* A2A services: - * CLIs in both Python and JS for direct interaction. - * A Python-based multi-agent orchestrator (using Google ADK) that delegates tasks to other A2A agents. -4. **Demo Web Application:** A web UI (`demo/ui`, likely using Mesop) demonstrating multi-agent interactions facilitated by the A2A protocol, including visualization of conversations, tasks, events, and agent discovery. It features a backend service coordinating with the host agent. - -Key features of the A2A protocol highlighted by the specification and samples include: agent discovery via Agent Cards, standardized task management (send, get, cancel), support for different content types (text, files, structured data) via `Parts` and `Artifacts`, streaming updates for long-running tasks, and mechanisms for push notifications. The project is open source and encourages community contribution. - -# A2A (Agent2Agent) Protocol - -## 1. Overview - -* **Project Name:** Agent2Agent (A2A) Protocol -* **Purpose:** An open protocol by Google enabling communication and interoperability between AI agents built on different frameworks or by different vendors. -* **Core Goal:** Allow agents to discover capabilities, negotiate interaction, and collaborate securely on tasks. -* **Communication:** Uses JSON-RPC 2.0 over HTTP(S). Supports standard request/response and Server-Sent Events (SSE) for streaming. -* **Key Components:** Specification (JSON Schema), Common Libraries (Python, JS/TS), Sample Agents (LangGraph, CrewAI, ADK, Genkit), Sample Hosts (CLI, Orchestrator), Demo Web App (Mesop). - -## 2. Protocol Specification (`specification/json/a2a.json`) - -### 2.1. Core JSON-RPC Structures - -* **`JSONRPCMessage`:** Base for requests/responses. Contains `jsonrpc: "2.0"` and optional `id`. -* **`JSONRPCRequest`:** Represents a request. - * `method`: String identifying the operation (e.g., "message/send"). - * `params`: Object or Array containing parameters for the method. - * `id`: Unique identifier (string/number) for request/response correlation. Omitted/null for notifications. -* **`JSONRPCResponse`:** Represents a response. - * `result`: Contains the successful result data (can be `null`). Mutually exclusive with `error`. - * `error`: Contains an error object if the request failed. Mutually exclusive with `result`. - * `id`: Must match the corresponding request `id`. -* **`JSONRPCError`:** Represents an error. - * `code`: Integer error code. - * `message`: String description of the error. - * `data`: Optional additional error details. - -### 2.2. Key A2A Data Objects - -* **`AgentCard`:** Metadata describing an agent. Found typically at `/.well-known/agent.json`. - * `name`: (string) Human-readable name. - * `description`: (string | null) Agent description. - * `url`: (string) Base URL endpoint for the agent's A2A service. - * `provider`: (`AgentProvider` | null) Organization details. - * `version`: (string) Agent/API version. - * `documentationUrl`: (string | null) Link to documentation. - * `capabilities`: (`AgentCapabilities`) Features supported (streaming, push). - * `authentication`: (`AgentAuthentication` | null) Auth schemes/credentials needed. - * `defaultInputModes`: (string[]) Default supported input types (e.g., "text", "file"). - * `defaultOutputModes`: (string[]) Default supported output types. - * `skills`: (`AgentSkill[]`) List of specific capabilities. -* **`AgentCapabilities`:** - * `streaming`: (boolean) Supports `message/stream`. - * `pushNotifications`: (boolean) Supports `tasks/pushNotification/set|get`. - * `stateTransitionHistory`: (boolean) Supports providing detailed history. -* **`AgentSkill`:** - * `id`: (string) Unique skill ID. - * `name`: (string) Skill name. - * `description`: (string | null) Skill description. - * `tags`: (string[] | null) Keywords. - * `examples`: (string[] | null) Usage examples. - * `inputModes`: (string[] | null) Overrides default inputs for this skill. - * `outputModes`: (string[] | null) Overrides default outputs for this skill. -* **`Task`:** Represents a unit of work processed by an agent. - * `id`: (string) Unique task identifier. - * `sessionId`: (string | null) Groups related tasks. - * `status`: (`TaskStatus`) Current state and associated message. - * `artifacts`: (`Artifact[]` | null) Outputs generated by the task. - * `history`: (`Message[]` | null) (Optional) History of messages exchanged for this task (if requested via `historyLength`). - * `metadata`: (object | null) Additional task metadata. -* **`TaskStatus`:** - * `state`: (`TaskState`) Current lifecycle state (enum). - * `message`: (`Message` | null) Message associated with this status (e.g., progress update, final response text, input prompt). - * `timestamp`: (string) ISO 8601 timestamp of the status update. -* **`TaskState` (Enum):** - * `submitted`: Task received, not yet started. - * `working`: Task is actively being processed. - * `input-required`: Agent requires further input from the user/client. - * `completed`: Task finished successfully. - * `canceled`: Task was canceled. - * `failed`: Task failed due to an error. - * `unknown`: State cannot be determined. -* **`Message`:** Communication unit between user and agent. - * `role`: ("user" | "agent") Sender role. - * `parts`: (`Part[]`) Content parts (text, file, data). - * `metadata`: (object | null) Message-specific metadata. -* **`Part` (Union Type):** Represents a piece of content within a Message or Artifact. - * **`TextPart`:** - * `type`: "text" - * `text`: (string) The textual content. - * **`FilePart`:** - * `type`: "file" - * `file`: (`FileContent`) File details (bytes or URI). - * **`DataPart`:** - * `type`: "data" - * `data`: (object) Structured JSON data (e.g., for forms). - * `metadata`: (object | null) Optional metadata for the specific part. -* **`FileContent`:** Represents file data. - * `name`: (string | null) Filename. - * `mimeType`: (string | null) MIME type. - * `bytes`: (string | null) Base64 encoded file content. *Mutually exclusive with `uri`.* - * `uri`: (string | null) URI pointing to the file content. *Mutually exclusive with `bytes`.* -* **`Artifact`:** Output generated by a task. - * `name`: (string | null) Artifact name. - * `description`: (string | null) Artifact description. - * `parts`: (`Part[]`) Content parts. - * `index`: (integer, default 0) Order index, useful for streaming/updates. - * `append`: (boolean | null) For streaming, indicates if content should append to artifact at the same index. - * `lastChunk`: (boolean | null) For streaming, indicates the final chunk for this artifact. - * `metadata`: (object | null) Artifact metadata. -* **`PushNotificationConfig`:** Configuration for push notifications. - * `url`: (string) Endpoint URL for the agent to POST notifications to. - * `token`: (string | null) Optional token for the agent to include. - * `authentication`: (`AuthenticationInfo` | null) Auth details the agent needs to call the URL. -* **`TaskPushNotificationConfig`:** Associates a `PushNotificationConfig` with a task ID. - -### 2.3. A2A RPC Methods - -* **`message/send`:** (Request/Response) - * Sends a message to initiate or continue a task. - * `params`: `TaskSendParams` (includes `id`, `sessionId`, `message`, optionally `pushNotification`, `historyLength`, `metadata`). - * `result`: `Task` (final state after synchronous processing). -* **`message/stream`:** (Request/Stream) - * Sends a message and subscribes to real-time updates via SSE. - * `params`: `TaskSendParams`. - * `result` (stream events): `TaskStatusUpdateEvent` or `TaskArtifactUpdateEvent`. Final event has `final: true`. -* **`tasks/get`:** (Request/Response) - * Retrieves the current state of a task. - * `params`: `TaskQueryParams` (includes `id`, optionally `historyLength`). - * `result`: `Task`. -* **`tasks/cancel`:** (Request/Response) - * Requests cancellation of a running task. - * `params`: `TaskIdParams` (includes `id`). - * `result`: `Task` (updated state, likely 'canceled') or error if not cancelable. -* **`tasks/pushNotification/set`:** (Request/Response) - * Sets or updates the push notification configuration for a task. - * `params`: `TaskPushNotificationConfig`. - * `result`: `TaskPushNotificationConfig` (confirmed config). -* **`tasks/pushNotification/get`:** (Request/Response) - * Retrieves the current push notification configuration for a task. - * `params`: `TaskIdParams`. - * `result`: `TaskPushNotificationConfig`. -* **`tasks/resubscribe`:** (Request/Stream) - * Resubscribes to task updates after a connection interruption (SSE). - * `params`: `TaskQueryParams`. - * `result` (stream events): `TaskStatusUpdateEvent` or `TaskArtifactUpdateEvent`. - -### 2.4. Streaming Update Events (Result of `message/stream` or `tasks/resubscribe`) - -* **`TaskStatusUpdateEvent`:** Signals a change in task status. - * `id`: (string) Task ID. - * `status`: (`TaskStatus`) The new status object. - * `final`: (boolean) True if this is the terminal update for the task. - * `metadata`: (object | null) Event metadata. -* **`TaskArtifactUpdateEvent`:** Signals a new or updated artifact. - * `id`: (string) Task ID. - * `artifact`: (`Artifact`) The artifact data. - * `final`: (boolean, usually false for artifacts) Can signal end concurrently with status. - * `metadata`: (object | null) Event metadata. - -### 2.5. Standard Error Codes - -* `-32700`: `JSONParseError` - Invalid JSON payload. -* `-32600`: `InvalidRequestError` - Invalid JSON-RPC request object. -* `-32601`: `MethodNotFoundError` - Method does not exist. -* `-32602`: `InvalidParamsError` - Invalid method parameters. -* `-32603`: `InternalError` - Internal server error. - -### 2.6. A2A Specific Error Codes - -* `-32001`: `TaskNotFoundError` - Specified task ID not found. -* `-32002`: `TaskNotCancelableError` - Task is in a final state and cannot be canceled. -* `-32003`: `PushNotificationNotSupportedError` - Agent does not support push notifications. -* `-32004`: `UnsupportedOperationError` - The requested operation is not supported. -* `-32005`: `ContentTypeNotSupportedError` - Mismatch in supported content types. - -## 3. Core Concepts - -* **Agent Discovery:** Clients find agents and their capabilities by fetching the `AgentCard` JSON, typically from `/.well-known/agent.json`. -* **Task Lifecycle:** Tasks progress through states defined in `TaskState` (submitted -> working -> [input-required] -> completed/canceled/failed). -* **Communication:** Uses `Message` objects containing `Part`s (text, file, data). Task outputs are represented as `Artifact`s, also containing `Part`s. -* **Streaming:** Long-running tasks can provide real-time updates using SSE via `message/stream`. Updates are sent as `TaskStatusUpdateEvent` and `TaskArtifactUpdateEvent`. -* **Push Notifications:** Agents can proactively notify clients about task updates using webhook URLs provided via `tasks/pushNotification/set`. Authentication mechanisms (e.g., Bearer tokens via JWT signed with keys from agent's JWKS endpoint) are supported. -* **Authentication:** Defined in `AgentCard` and `PushNotificationConfig`. Can involve various schemes (e.g., API keys, OAuth, JWT). Samples use JWT for push notifications. -* **Forms:** Structured data can be requested and submitted using `DataPart` within Messages/Artifacts (demonstrated in ADK sample). - -## 4. Implementations & Samples - -### 4.1. Common Libraries - -* **Python (`samples/python/common`)**: - * `client/`: `A2AClient` for making requests, `A2ACardResolver` for discovery. - * `server/`: `A2AServer` (Starlette-based), `TaskManager` base class, `InMemoryTaskManager`. - * `types.py`: Pydantic models mirroring the JSON schema. - * `utils/`: Helpers for push notification auth (JWT signing/verification, JWKS endpoint). -* **JavaScript/TypeScript (`samples/js/src`)**: - * `client/`: `A2AClient` implementation using `fetch`. - * `server/`: `A2AServer` (Express-based), `TaskStore` interface, `InMemoryTaskStore`, `FileStore`. - * `schema.ts`: TypeScript interfaces matching the JSON schema. - * `handler.ts`, `error.ts`, `utils.ts`: Support code for the server. - -### 4.2. Python Samples - -* **Location:** `samples/python/agents/` & `samples/python/hosts/` -* **Setup:** Uses `uv` and `pyproject.toml`. Requires Python >= 3.12/3.13. API keys via `.env`. -* **Agents:** - * **LangGraph (`agents/langgraph`)**: Currency conversion agent. Demonstrates tool use, multi-turn (`input-required`), and **streaming** (`message/stream`). - * **CrewAI (`agents/crewai`)**: Image generation agent. Demonstrates multi-turn and handling **file artifacts** (images). - * **Google ADK (`agents/google_adk`)**: Expense reimbursement agent. Demonstrates multi-turn and handling **forms** using `DataPart`. -* **Hosts:** - * **CLI (`hosts/cli`)**: Simple command-line client to interact with any A2A agent. Supports streaming and optional push notification listening. - * **Multi-Agent Orchestrator (`hosts/multiagent`)**: An ADK-based "Host Agent" that manages connections (`RemoteAgentConnections`) to other A2A agents and delegates tasks based on instructions. - -### 4.3. JavaScript/TypeScript Samples - -* **Location:** `samples/js/` -* **Setup:** Uses `npm`/`pnpm`, `tsx`, `tsconfig.json`. Requires Node.js >= 18. API keys via environment variables. Framework: **Genkit**. -* **Agents (`src/agents/`)**: - * **Movie Agent (`movie-agent`)**: Uses TMDB API via Genkit tools to answer movie questions. Demonstrates tool use and multi-turn (`AWAITING_USER_INPUT` mapped to `input-required`). - * **Coder Agent (`coder`)**: Generates code files. Demonstrates producing multiple **file artifacts** via streaming updates. Uses custom Genkit format (`code-format.ts`). -* **Hosts:** - * **CLI (`src/cli.ts`)**: Command-line client for interacting with JS agents. - -### 4.4. Demo Application (`demo/`) - -* **UI (`demo/ui`)**: Web application built with **Mesop**. - * Visualizes conversations with multiple agents via the host orchestrator. - * Renders text, images, forms. - * Allows dynamic agent registration via URL. - * Provides views for task list and event logs. -* **Service (`demo/ui/service`)**: Backend service for the Mesop UI. - * `server/`: Manages conversations, routes messages, interfaces with the host agent (`ADKHostManager` or `InMemoryFakeAgentManager`). - * `client/`: Client used by the UI to talk to its *own* backend service. - -## 5. Development & Setup - -* **Prerequisites:** Python (>=3.12 or 3.13), Node.js (>=18), `uv` (for Python), `npm`/`pnpm` (for JS). -* **API Keys:** Required for LLM access (e.g., `GOOGLE_API_KEY`, `TMDB_API_KEY`), typically set via `.env` files or environment variables. -* **Running Samples:** Generally involves running an agent server (`uv run ...` or `npm run ...`) and then a host client/app (e.g., `uv run hosts/cli --agent `). - -## 6. Contribution - -* See `CONTRIBUTING.md`. -* GitHub discussion available as the primary means of communication. -* GitHub issues for bugs and feature requests. -* Google Form for private feedback. - - -- Tool: -- Arguments: {'query': 'What is A2A protocol?'} -- Result: ## Query - -What is A2A protocol?. - -## Response - -### Sources: -Important: you can fetch the full content of any source using the fetch_url_content tool - -#### (google/A2A/docs/index.md)[https://raw.githubusercontent.com/google/A2A/main/docs/index.md] (Score: 0.63) -- - -The **Agent2Agent (A2A) Protocol** is an open standard designed to enable seamless communication and collaboration between AI agents. In a world where agents are built using diverse frameworks and by different vendors, A2A provides a common language, breaking down silos and fostering interoperability. - -- [Blog Post: Announcing the Agent2Agent Protocol (A2A)](https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/) -- [Watch the A2A Demo Video](https://storage.googleapis.com/gweb-developer-goog-blog-assets/original_videos/A2A_demo_v4.mp4) - -![A2A Main Graphic](assets/a2a-main.png){width="50%"} -{style="text-align: center; margin-bottom:1em; margin-top:2em;"} - ---- - -# - -#### (google/A2A/docs/specification.md)[https://raw.githubusercontent.com/google/A2A/main/docs/specification.md] (Score: 0.63) -- - -The Agent2Agent (A2A) Protocol is an open standard designed to facilitate communication and interoperability between independent, potentially opaque AI agent systems. In an ecosystem where agents might be built using different frameworks, languages, or by different vendors, A2A provides a common language and interaction model. - -This document provides the detailed technical specification for the A2A protocol. Its primary goal is to enable agents to: - -- Discover each other's capabilities. -- Negotiate interaction modalities (text, files, structured data). -- Manage collaborative tasks. -- Securely exchange information to achieve user goals **without needing access to each other's internal state, memory, or tools.** - -# - -#### (google/A2A/llms.txt)[https://raw.githubusercontent.com/google/A2A/main/llms.txt] (Score: 0.62) -- operability -9. AI agents -10. Agent communication standard - - -- [index.md](https://raw.githubusercontent.com/google/A2A/main/docs/index.md): Agent2Agent (A2A) Protocol Documentation -The Agent2Agent (A2A) Protocol is an open standard that enables seamless communication and collaboration between AI agents built on diverse frameworks and by different vendors. It provides a common language, breaking down silos and fostering interoperability. A2A facilitates dynamic, multimodal communication between agents as peers, allowing them to delegate sub-tasks, exchange information, and coordinate actions to solve complex problems. The protocol ensures security and preserves intellectual property by enabling agents to interact without sharing internal memory, tools, or proprietary logic. To get started, users can read the introduction, dive into the specification, follow tutorials, or explore code samples. - -A code example is not explicitly provided in the document; however, the document references a Python quickstart tutorial: -```markdow - -#### (google/A2A/docs/specification/details.md)[https://raw.githubusercontent.com/google/A2A/main/docs/specification/details.md] (Score: 0.62) -- - -The Agent2Agent (A2A) Protocol is an open standard designed to facilitate communication and interoperability between independent, potentially opaque AI agent systems. In an ecosystem where agents might be built using different frameworks, languages, or vendors, A2A provides a common language and interaction model. This enables agents to discover each other's capabilities, negotiate interaction modalities (text, files, structured data), manage collaborative tasks, and securely exchange information to achieve user goals **without needing access to each other's internal state, memory, or tools.** - -**Key Goals:** - -- **Interoperability:** Bridge the gap between disparate agentic systems. -- **Collaboration:** Enable agents to delegate tasks, exchange context, and work together on complex user requests. -- **Discovery:** Allow agents to dynamically find and understand the capabilities of other agents. -- **Flexibility:** Support various interaction modes (text, files, forms, streaming). -- **Security:** Facilitate secure communication patterns suitable for enterprise environments. -- **Asynchronicity:** Support long-running tasks and interactions that may involve human-in-the-loop scenarios. - -This document provides the detailed technical specification for the A2A protocol, defining its data structures, communication methods, and core concepts. - -# - -#### (google/A2A/docs/community.md)[https://raw.githubusercontent.com/google/A2A/main/docs/community.md] (Score: 0.62) -- - -The **Agent2Agent (A2A) protocol** is generating significant buzz across the -tech world, and for good reason! This open interoperability protocol is designed -to enable **seamless collaboration between AI agents across diverse frameworks -and vendors**. By standardizing communication, A2A aims to unlock complex -workflows, enhance productivity, and foster a new era of **"Agent -Interoperability"**. Don't just take our word for it – see what the community is -saying! - - -#### (google/A2A/docs/topics/a2a-and-mcp.md)[https://raw.githubusercontent.com/google/A2A/main/docs/topics/a2a-and-mcp.md] (Score: 0.60) -- A2A) - -- **Focus:** A2A standardizes how independent, often opaque, **AI agents communicate and collaborate with each other as peers.** -- **Mechanism:** It provides an application-level protocol for agents to: - - Discover each other's high-level skills and capabilities (via Agent Cards). - - Negotiate interaction modalities (text, files, structured data). - - Manage shared, stateful, and potentially long-running tasks. - - Exchange conversational context, instructions, and complex, multi-part results. -- **Use Cases:** - - A customer service agent delegating a complex billing inquiry to a specialized billing agent, maintaining context of the customer interaction. - - A travel planning agent coordinating with separate flight, hotel, and activity booking agents, managing a multi-stage booking process. - - Agents exchanging information and status updates for a collaborative project that evolves over time. -- **Key Difference from Tool Interaction:** A2A allows for more dynamic, stateful, and potentially multi-modal interactions than typically seen with simple tool calls. Agents using A2A communicate *as agents* (or on behalf of users) rather than just invoking a discrete function - -#### (google/A2A/docs/topics/enterprise-ready.md)[https://raw.githubusercontent.com/google/A2A/main/docs/topics/enterprise-ready.md] (Score: 0.60) -- - -The Agent2Agent (A2A) protocol is designed with enterprise requirements at its core. Instead of inventing new, proprietary standards for security and operations, A2A aims to integrate seamlessly with existing enterprise infrastructure and widely adopted best practices. A2A treats remote agents as standard, HTTP-based enterprise applications. This approach allows organizations to leverage their existing investments and expertise in security, monitoring, governance, and identity management. - -A key principle of A2A is that agents are typically "opaque" – they do not share internal memory, tools, or direct resource access with each other. This opacity naturally aligns with standard client/server security paradigms. - - -#### (google/A2A/docs/topics/what-is-a2a.md)[https://raw.githubusercontent.com/google/A2A/main/docs/topics/what-is-a2a.md] (Score: 0.60) -- - -Adopting A2A can lead to significant advantages: - -- **Increased Interoperability:** Break down silos between different AI agent ecosystems, allowing agents from various vendors and frameworks to work together. -- **Enhanced Agent Capabilities:** Allow developers to create more sophisticated applications by composing the strengths of multiple specialized agents. -- **Reduced Integration Complexity:** Standardize the "how" of agent communication, allowing teams to focus on the "what" – the value their agents provide. -- **Fostering Innovation:** Encourage the development of a richer ecosystem of specialized agents that can readily plug into larger collaborative workflows. -- **Future-Proofing:** Provide a flexible framework that can adapt as agent technologies continue to evolve. - -By establishing common ground for agent-to-agent communication, A2A aims to accelerate the adoption and utility of AI agents across diverse industries and applications, paving the way for more powerful and collaborative AI systems. - -[Watch the A2A Demo Video](https://storage.googleapis.com/gweb-developer-goog-blog-assets/original_videos/A2A_demo_v4.mp4) - -Next, learn about the [Key Concepts](./key-concepts.md) that form the foundation of the A2A protoc -- - -A2A provides a standardized way for these independent, often "opaque" (black-box) agentic systems to interact. It defines: - -- **A common transport and format:** JSON-RPC 2.0 over HTTP(S) for how messages are structured and transmitted. -- **Discovery mechanisms (Agent Cards):** How agents can advertise their capabilities and be found by other agents. -- **Task management workflows:** How collaborative tasks are initiated, progressed, and completed. This includes support for tasks that may be long-running or require multiple turns of interaction. -- **Support for various data modalities:** How agents exchange not just text, but also files, structured data (like forms), and potentially other rich media. -- **Core principles for security and asynchronicity:** Guidelines for secure communication and handling tasks that might take significant time or involve human-in-the-loop processes. - -# - -#### (google/A2A/docs/llms.txt)[https://raw.githubusercontent.com/google/A2A/main/docs/llms.txt] (Score: 0.60) -- - -* **Project Name:** Agent2Agent (A2A) Protocol -* **Purpose:** An open protocol by Google enabling communication and interoperability between AI agents built on different frameworks or by different vendors. -* **Core Goal:** Allow agents to discover capabilities, negotiate interaction, and collaborate securely on tasks. -* **Communication:** Uses JSON-RPC 2.0 over HTTP(S). Supports standard request/response and Server-Sent Events (SSE) for streaming. -* **Key Components:** Specification (JSON Schema), Common Libraries (Python, JS/TS), Sample Agents (LangGraph, CrewAI, ADK, Genkit), Sample Hosts (CLI, Orchestrator), Demo Web App (Mesop). - -# - -Step 1 -Thoughts: -- The previous response provides a comprehensive explanation of the A2A protocol. It's highly detailed and seems to cover most aspects of the protocol. -- There's no need to call any additional tools as the information already obtained is sufficient to answer the question thoroughly. - - - -[] - - - -The A2A (Agent2Agent) protocol is an open standard initiated by Google to enable communication and interoperability between different AI agent systems. Its core goal is to allow agents built on various frameworks or by different vendors to discover each other's capabilities, negotiate interaction methods (text, files, structured data, potentially audio/video), and collaborate on tasks. - -Key features include: - -* **Agent Discovery:** Agents discover each other's capabilities via Agent Cards, typically located at `/.well-known/agent.json`. -* **Standardized Task Management:** A standardized workflow for managing tasks (send, get, cancel). -* **Support for Various Content Types:** Supports various content types through `Parts` and `Artifacts` (text, files, structured data). -* **Streaming Updates:** Supports streaming updates for long-running tasks using Server-Sent Events (SSE). -* **Push Notifications:** Provides mechanisms for push notifications to keep clients updated. -* **JSON-RPC 2.0 over HTTP(S):** Uses JSON-RPC 2.0 over HTTP(S) for communication, supporting both standard request/response and SSE for streaming. - -The protocol is defined by a JSON Schema, and supporting libraries and sample implementations are available in Python and JavaScript/TypeScript. These samples demonstrate integration with various agent frameworks (LangGraph, CrewAI, Google ADK, Genkit) and include example agents, hosts, and a demo web application. The protocol also incorporates considerations for security and enterprise readiness. - - - -Thoughts: -- The question "What is A2A protocol?" has already been answered comprehensively by the A2A Protocol Agent. -- No further agents are needed. The provided answer is sufficient. - - - -```json -[] -``` - - - -The A2A Protocol Agent's response provides a complete and accurate definition of the A2A protocol, including its key features and functionalities. No further information is required. - \ No newline at end of file diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/README.md b/samples/python/agents/a2a_multi_agent_on_single_port/README.md index 31f1157f..ad6710d2 100644 --- a/samples/python/agents/a2a_multi_agent_on_single_port/README.md +++ b/samples/python/agents/a2a_multi_agent_on_single_port/README.md @@ -80,7 +80,7 @@ uv run --env-file .env python -m src.no_llm_framework.client --question "What is 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. -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). +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. diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent_executor.py b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent_executor.py index 7c380a05..409e3df7 100644 --- a/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent_executor.py +++ b/samples/python/agents/a2a_multi_agent_on_single_port/src/no_llm_framework/server/agent_executor.py @@ -7,7 +7,7 @@ TaskStatusUpdateEvent, ) from a2a.utils import new_agent_text_message, new_task, new_text_artifact -from typing import Optional, override +from typing import override from src.no_llm_framework.server.agent import Agent From 2bbe76a838e30fda1525b0c338df26ae22d14c89 Mon Sep 17 00:00:00 2001 From: ChenXAn <2277587992@qq.com> Date: Fri, 15 Aug 2025 14:43:52 +0800 Subject: [PATCH 5/6] Capitalized the first letter. --- samples/python/agents/a2a-mcp-without-framework/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/python/agents/a2a-mcp-without-framework/README.md b/samples/python/agents/a2a-mcp-without-framework/README.md index 564b455b..afb998fa 100644 --- a/samples/python/agents/a2a-mcp-without-framework/README.md +++ b/samples/python/agents/a2a-mcp-without-framework/README.md @@ -7,4 +7,4 @@ This repository demonstrates how to set up and use the [a2a-python SDK](https:// - **A2A (Agent-to-Agent):** A protocol and SDK for communication with AI Agents. - **This Example:** Shows how to run a basic A2A server and client, exchange messages, and view the response. -more information refer to [a2a-mcp-without-framework](https://github.com/a2aproject/a2a-samples/blob/main/samples/python/agents/a2a-mcp-without-framework/README.md) +More information refer to [a2a-mcp-without-framework](https://github.com/a2aproject/a2a-samples/blob/main/samples/python/agents/a2a-mcp-without-framework/README.md) From 582752cf9d961f5a5f5092a4444b16126d3ec468 Mon Sep 17 00:00:00 2001 From: ChenXAn <2277587992@qq.com> Date: Fri, 15 Aug 2025 14:49:08 +0800 Subject: [PATCH 6/6] fix --- .../a2a-mcp-without-framework/README.md | 77 +++++++++++++++++- .../response.xml | 0 .../a2a_multi_agent_on_single_port/README.md | 79 +------------------ 3 files changed, 77 insertions(+), 79 deletions(-) rename samples/python/agents/{a2a_multi_agent_on_single_port => a2a-mcp-without-framework}/response.xml (100%) diff --git a/samples/python/agents/a2a-mcp-without-framework/README.md b/samples/python/agents/a2a-mcp-without-framework/README.md index afb998fa..eae7a30f 100644 --- a/samples/python/agents/a2a-mcp-without-framework/README.md +++ b/samples/python/agents/a2a-mcp-without-framework/README.md @@ -7,4 +7,79 @@ This repository demonstrates how to set up and use the [a2a-python SDK](https:// - **A2A (Agent-to-Agent):** A protocol and SDK for communication with AI Agents. - **This Example:** Shows how to run a basic A2A server and client, exchange messages, and view the response. -More information refer to [a2a-mcp-without-framework](https://github.com/a2aproject/a2a-samples/blob/main/samples/python/agents/a2a-mcp-without-framework/README.md) +## Prerequisites + +- Python 3.13+ +- [uv](https://github.com/astral-sh/uv) (for fast dependency management and running) +- An API key for Gemini (set as `GEMINI_API_KEY`) + +## Installation + +1. **Clone the repository:** + + ```bash + git clone + cd + ``` + +2. **Install dependencies:** + + ```bash + uv pip install -e . + ``` + +3. **Set environment variables:** + + ```bash + export GEMINI_API_KEY=your-gemini-api-key + ``` + + Or create a `.env` file with: + + ```sh + GEMINI_API_KEY=your-gemini-api-key + ``` + +## Running the Example + +### 1. Start the Server + +```bash +uv run --env-file .env python -m src.no_llm_framework.server.__main__ +``` + +- The server will start on port `9999`. + +### 2. Run the Client + +In a new terminal: + +```bash +uv run --env-file .env python -m src.no_llm_framework.client --question "What is A2A protocol?" +``` + +- The client will connect to the server and send a request. + +### 3. View the Response + +- The response from the client will be saved to [`response.xml`](./response.xml). + +## File Structure + +- `src/no_llm_framework/server/`: Server implementation. +- `src/no_llm_framework/client/`: Client implementation. +- `response.xml`: Example response from the client. + +## Troubleshooting + +- **Missing dependencies:** Make sure you have `uv` installed. +- **API key errors:** Ensure `GEMINI_API_KEY` is set correctly. +- **Port conflicts:** Make sure port 9999 is free. + +## Disclaimer + +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. + +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. + +Developers are responsible for implementing appropriate security measures, such as input validation and secure handling of credentials to protect their systems and users. diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/response.xml b/samples/python/agents/a2a-mcp-without-framework/response.xml similarity index 100% rename from samples/python/agents/a2a_multi_agent_on_single_port/response.xml rename to samples/python/agents/a2a-mcp-without-framework/response.xml diff --git a/samples/python/agents/a2a_multi_agent_on_single_port/README.md b/samples/python/agents/a2a_multi_agent_on_single_port/README.md index ad6710d2..8226500c 100644 --- a/samples/python/agents/a2a_multi_agent_on_single_port/README.md +++ b/samples/python/agents/a2a_multi_agent_on_single_port/README.md @@ -7,81 +7,4 @@ This repository demonstrates how to set up and use the [a2a-python SDK](https:// - **A2A (Agent-to-Agent):** A protocol and SDK for communication with AI Agents. - **This Example:** Shows how to support multiple A2AStarletteApplication instances or AgentExecutor implementations on a single port. -## Prerequisites - -- Python 3.13+ -- [uv](https://github.com/astral-sh/uv) (for fast dependency management and running) -- An API key for Gemini (set as `GEMINI_API_KEY`) - -## Installation - -1. **Clone the repository:** - - ```bash - git clone - cd - ``` - -2. **Install dependencies:** - - ```bash - uv pip install -e . - ``` - -3. **Set environment variables:** - - ```bash - export GEMINI_API_KEY=your-gemini-api-key - ``` - - Or create a `.env` file with: - - ```sh - GEMINI_API_KEY=your-gemini-api-key - ``` - -## Running the Example - -### 1. Start the Server - -```bash -uv run --env-file .env python -m src.no_llm_framework.server.__main__ -``` - -- The server will start on port `9999`. - -### 2. Run the Client - -In a new terminal: - -```bash -uv run --env-file .env python -m src.no_llm_framework.client --question "What is A2A protocol?" -``` - -- The client will connect to the server and send a request. - -### 3. View the Response - -- The response from the client will be saved to [`response.xml`](./response.xml). - -## File Structure - -- `src/no_llm_framework/server/`: Server implementation. -- `src/no_llm_framework/client/`: Client implementation. -- `response.xml`: Example response from the client. - -## Troubleshooting - -- **Missing dependencies:** Make sure you have `uv` installed. -- **API key errors:** Ensure `GEMINI_API_KEY` is set correctly. -- **Port conflicts:** Make sure port 9999 is free. - -## Disclaimer - -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. - -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. - -Developers are responsible for implementing appropriate security measures, such as input validation and secure handling of credentials to protect their systems and users. +More information refer to [a2a-mcp-without-framework](https://github.com/a2aproject/a2a-samples/blob/main/samples/python/agents/a2a-mcp-without-framework/README.md)