Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 1 addition & 40 deletions src/a2a/server/apps/jsonrpc/fastapi_app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import importlib.resources
import json
import logging

from collections.abc import Awaitable, Callable
Expand All @@ -18,62 +16,25 @@
except ImportError:
FastAPI = Any

_package_fastapi_installed = False

from a2a.server.apps.jsonrpc.jsonrpc_app import (
CallContextBuilder,
JSONRPCApplication,
)
from a2a.server.context import ServerCallContext
from a2a.server.request_handlers.request_handler import RequestHandler
from a2a.types.a2a_pb2 import AgentCard
from a2a.utils.constants import (
AGENT_CARD_WELL_KNOWN_PATH,
DEFAULT_RPC_URL,
)


logger = logging.getLogger(__name__)


class A2AFastAPI(FastAPI):
"""A FastAPI application that adds A2A-specific OpenAPI components."""

_a2a_components_added: bool = False

def openapi(self) -> dict[str, Any]:
"""Generates the OpenAPI schema for the application."""
if self.openapi_schema:
return self.openapi_schema

# Try to use the a2a.json schema generated from the proto file
# if available, instead of generating one from the python types.
try:
from a2a import types # noqa: PLC0415

schema_file = importlib.resources.files(types).joinpath('a2a.json')
if schema_file.is_file():
self.openapi_schema = json.loads(
schema_file.read_text(encoding='utf-8')
)
if self.openapi_schema:
return self.openapi_schema
except Exception: # noqa: BLE001
logger.warning(
"Could not load 'a2a.json' from 'a2a.types'. Falling back to auto-generation."
)

openapi_schema = super().openapi()
if not self._a2a_components_added:
# A2ARequest is now a Union type of proto messages, so we can't use
# model_json_schema. Instead, we just mark it as added without
# adding the schema since proto types don't have Pydantic schemas.
# The OpenAPI schema will still be functional for the endpoints.
self._a2a_components_added = True
return openapi_schema


class A2AFastAPIApplication(JSONRPCApplication):

Check notice on line 37 in src/a2a/server/apps/jsonrpc/fastapi_app.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Copy/pasted code

see src/a2a/server/apps/jsonrpc/starlette_app.py (23-41)
"""A FastAPI application implementing the A2A protocol server endpoints.

Handles incoming JSON-RPC requests, routes them to the appropriate
Expand Down Expand Up @@ -180,7 +141,7 @@
Returns:
A configured FastAPI application instance.
"""
app = A2AFastAPI(**kwargs)
app = FastAPI(**kwargs)

self.add_routes_to_app(app, agent_card_url, rpc_url)

Expand Down
44 changes: 43 additions & 1 deletion src/a2a/server/apps/rest/fastapi_app.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import importlib.resources
import json
import logging

from collections.abc import Awaitable, Callable
Expand Down Expand Up @@ -56,6 +58,45 @@
}


class A2AFastAPI(FastAPI):
"""A FastAPI application that adds A2A-specific OpenAPI components."""

_a2a_components_added: bool = False
rpc_url: str = ''

def openapi(self) -> dict[str, Any]:
"""Generates the OpenAPI schema for the application."""
if self.openapi_schema:
return self.openapi_schema

# Try to use the a2a.json schema generated from the proto file
# if available, instead of generating one from the python types.
try:
from a2a import types # noqa: PLC0415

schema_file = importlib.resources.files(types).joinpath('a2a.json')
if schema_file.is_file():
self.openapi_schema = json.loads(
schema_file.read_text(encoding='utf-8')
)
if self.rpc_url and self.openapi_schema:
paths = self.openapi_schema.get('paths', {})
self.openapi_schema['paths'] = {
f'{self.rpc_url}{k}': v for k, v in paths.items()
}
if self.openapi_schema:
return self.openapi_schema
except Exception: # noqa: BLE001
logger.warning(
"Could not load 'a2a.json' from 'a2a.types'. Falling back to auto-generation."
)
Comment on lines +89 to +92
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

Catching a broad Exception is generally discouraged as it can hide unexpected errors. It's better to catch specific exceptions that you expect to handle, such as ImportError, OSError, and json.JSONDecodeError. This will make the code more robust and easier to debug. Additionally, logging the specific exception provides more context for troubleshooting.

Suggested change
except Exception: # noqa: BLE001
logger.warning(
"Could not load 'a2a.json' from 'a2a.types'. Falling back to auto-generation."
)
except (ImportError, OSError, json.JSONDecodeError) as e:
logger.warning(
"Could not load 'a2a.json' from 'a2a.types'. Falling back to auto-generation: %s",
e,
)


openapi_schema = super().openapi()
if not self._a2a_components_added:
self._a2a_components_added = True
return openapi_schema


class A2ARESTFastAPIApplication:
"""A FastAPI application implementing the A2A protocol server REST endpoints.

Expand Down Expand Up @@ -140,7 +181,8 @@ def build(
Returns:
A configured FastAPI application instance.
"""
app = FastAPI(**kwargs)
app = A2AFastAPI(**kwargs)
app.rpc_url = rpc_url

@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(
Expand Down
14 changes: 9 additions & 5 deletions tck/sut_agent.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import asyncio
import logging
import os
import uuid

from datetime import datetime, timezone

import grpc.aio
import uvicorn

from fastapi import FastAPI
from starlette.applications import Starlette

Check failure on line 12 in tck/sut_agent.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

ruff (F401)

tck/sut_agent.py:12:36: F401 `starlette.applications.Starlette` imported but unused help: Remove unused import: `starlette.applications.Starlette`

import a2a.compat.v0_3.a2a_v0_3_pb2_grpc as a2a_v0_3_grpc
import a2a.types.a2a_pb2_grpc as a2a_grpc
Expand All @@ -18,7 +19,8 @@
from a2a.server.agent_execution.context import RequestContext
from a2a.server.apps import (
A2ARESTFastAPIApplication,
A2AStarletteApplication,

Check failure on line 22 in tck/sut_agent.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

ruff (F401)

tck/sut_agent.py:22:5: F401 `a2a.server.apps.A2AStarletteApplication` imported but unused help: Remove unused import: `a2a.server.apps.A2AStarletteApplication`
A2AFastAPIApplication,
)
from a2a.server.events.event_queue import EventQueue
from a2a.server.request_handlers.default_request_handler import (
Expand Down Expand Up @@ -196,22 +198,24 @@
task_store=task_store,
)

main_app = Starlette()
main_app = FastAPI()

# JSONRPC
jsonrpc_server = A2AStarletteApplication(
jsonrpc_server = A2AFastAPIApplication(
agent_card=agent_card,
http_handler=request_handler,
)
jsonrpc_server.add_routes_to_app(main_app, rpc_url=JSONRPC_URL)
json_rpc_app = jsonrpc_server.build(rpc_url=JSONRPC_URL)

# REST
rest_server = A2ARESTFastAPIApplication(
agent_card=agent_card,
http_handler=request_handler,
)
rest_app = rest_server.build(rpc_url=REST_URL)
main_app.mount('', rest_app)
rest_app = rest_server.build()

main_app.mount(REST_URL,rest_app)
main_app.mount('',json_rpc_app)

config = uvicorn.Config(
main_app, host='127.0.0.1', port=http_port, log_level='info'
Expand Down
Loading