-
Notifications
You must be signed in to change notification settings - Fork 60
Add A2A Support #727
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add A2A Support #727
Conversation
WalkthroughAdds A2A (Agent‑to‑Agent) protocol support: new JSON‑RPC endpoints (streaming and buffered), agent‑card and health endpoints, A2A executor and event conversion, persistent context/task storage backends (in‑memory/SQLite/Postgres) with a factory, configuration schema updates, example configs, tests, and documentation. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as A2A Client
participant Handler as handle_a2a_jsonrpc
participant Executor as A2AAgentExecutor
participant Store as A2AContextStore
participant Llama as Llama Stack Client
participant Queue as EventQueue
Client->>Handler: POST /a2a/jsonrpc (method: message / message/stream)
Handler->>Handler: inspect request (streaming?)
Handler->>Executor: execute(context, event_queue)
Executor->>Store: get(context_id)
Store-->>Executor: conversation_id or None
alt no conversation_id
Executor->>Llama: create conversation / start new
Llama-->>Executor: conversation_id
Executor->>Store: set(context_id, conversation_id)
end
Executor->>Llama: retrieve_response(QueryRequest) (streaming)
Llama-->>Executor: stream chunks (deltas, tool calls, final artifact)
Executor->>Queue: emit TaskStatusUpdateEvent / TaskArtifactUpdateEvent
Queue-->>Handler: events relayed
Handler-->>Client: StreamingResponse (SSE) or buffered HTTP Response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (6)
lightspeed-stack.yaml (1)
5-5: Consider adding a comment explaining this is for local development.The
base_urlis set tohttp://localhost:8080, which is appropriate for local development but will need to be overridden for production deployments.Apply this diff to add a clarifying comment:
port: 8080 + # Base URL for A2A agent card - override with externally reachable URL in production base_url: http://localhost:8080 auth_enabled: falsesrc/models/config.py (1)
154-155: UseAnyHttpUrlfor URL validation.The
base_urlfield accepts any string, which may allow invalid URLs. Using Pydantic'sAnyHttpUrltype provides automatic validation.Apply this diff to add URL validation:
- # Externally reachable base URL for the service; if unset, code may fallback - base_url: Optional[str] = None + # Externally reachable base URL for the service; if unset, code may fallback + base_url: Optional[AnyHttpUrl] = NoneNote: If you need to support non-HTTP schemes or have other requirements, you can use custom validation with
@field_validator.src/app/endpoints/a2a.py (4)
123-299: Consider extracting helper methods to reduce cognitive complexity.The
_process_task_streamingmethod is 177 lines long and handles multiple responsibilities: input validation, query preparation, model selection, streaming, and error handling. This increases cognitive complexity and makes testing harder.Consider extracting:
- Input validation logic (lines 141-155) →
_validate_user_input- Query request preparation (lines 160-176) →
_prepare_query_request- Model selection (lines 178-183) →
_select_model- Stream processing (lines 207-272) →
_process_stream_eventsThis would improve readability, testability, and maintainability while keeping the main method focused on orchestration.
338-397: Extract agent card config parsing to a separate helper function.The agent card configuration parsing logic (60 lines) makes the function harder to test and reason about. Extracting it would improve modularity.
Consider extracting to a function like:
def _parse_agent_card_from_config( config: dict[str, Any], base_url: str ) -> AgentCard: """Parse agent card from configuration dictionary.""" # Lines 344-397 would go here ...This would:
- Make testing easier (can test parsing separately)
- Improve readability of
get_lightspeed_agent_card- Enable reuse if needed elsewhere
560-734: Extract streaming and non-streaming handlers to reduce complexity.The
handle_a2a_jsonrpcfunction is 175 lines long and handles two distinct execution paths (streaming and non-streaming). This creates high cognitive complexity.Consider extracting:
async def _handle_streaming_request( a2a_app, scope, receive, request: Request ) -> StreamingResponse: """Handle A2A streaming request with SSE.""" # Lines 637-708 ... async def _handle_buffered_request( a2a_app, scope, receive, request: Request ) -> Response: """Handle A2A non-streaming request.""" # Lines 710-734 ...Then the main handler becomes:
async def handle_a2a_jsonrpc(...): # Setup (lines 587-623) ... if is_streaming_request: return await _handle_streaming_request(a2a_app, scope, receive, request) else: return await _handle_buffered_request(a2a_app, scope, receive, request)This would improve readability, testability, and maintainability.
737-751: Consider retrieving a2a-sdk version dynamically.The
a2a_sdk_versionis hardcoded as"0.2.1", which may become outdated as the dependency is upgraded.Apply this diff to get the version dynamically:
+from importlib.metadata import version, PackageNotFoundError + @router.get("/a2a/health") async def a2a_health_check(): """ Health check endpoint for A2A service. Returns: Dict with health status information. """ + try: + a2a_version = version("a2a-sdk") + except PackageNotFoundError: + a2a_version = "unknown" + return { "status": "healthy", "service": "lightspeed-a2a", "version": __version__, - "a2a_sdk_version": "0.2.1", + "a2a_sdk_version": a2a_version, "timestamp": datetime.now().isoformat() }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
lightspeed-stack.yaml(1 hunks)pyproject.toml(2 hunks)src/app/endpoints/a2a.py(1 hunks)src/app/routers.py(2 hunks)src/models/config.py(4 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use absolute imports for internal modules (e.g., from auth import get_auth_dependency)
Files:
src/models/config.pysrc/app/routers.pysrc/app/endpoints/a2a.py
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: All modules start with descriptive module-level docstrings explaining purpose
Use logger = logging.getLogger(name) for module logging after import logging
Define type aliases at module level for clarity
All functions require docstrings with brief descriptions
Provide complete type annotations for all function parameters and return types
Use typing_extensions.Self in model validators where appropriate
Use modern union syntax (str | int) and Optional[T] or T | None consistently
Function names use snake_case with descriptive, action-oriented prefixes (get_, validate_, check_)
Avoid in-place parameter modification; return new data structures instead of mutating arguments
Use appropriate logging levels: debug, info, warning, error with clear messages
All classes require descriptive docstrings explaining purpose
Class names use PascalCase with conventional suffixes (Configuration, Error/Exception, Resolver, Interface)
Abstract base classes should use abc.ABC and @AbstractMethod for interfaces
Provide complete type annotations for all class attributes
Follow Google Python docstring style for modules, classes, and functions, including Args, Returns, Raises, Attributes sections as needed
Files:
src/models/config.pysrc/app/routers.pysrc/app/endpoints/a2a.py
src/{models/config.py,configuration.py}
📄 CodeRabbit inference engine (CLAUDE.md)
src/{models/config.py,configuration.py}: All configuration uses Pydantic models extending ConfigurationBase
Configuration base models must set model_config with extra="forbid" to reject unknown fields
Files:
src/models/config.py
src/{models/**/*.py,configuration.py}
📄 CodeRabbit inference engine (CLAUDE.md)
src/{models/**/*.py,configuration.py}: Use @field_validator and @model_validator for custom validation in Pydantic models
Use precise type hints in configuration (e.g., Optional[FilePath], PositiveInt, SecretStr)
Files:
src/models/config.py
src/models/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/**/*.py: Pydantic models: use BaseModel for data models and extend ConfigurationBase for configuration
Use @model_validator and @field_validator for Pydantic model validation
Files:
src/models/config.py
src/app/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use standard FastAPI imports (from fastapi import APIRouter, HTTPException, Request, status, Depends) in FastAPI app code
Files:
src/app/routers.pysrc/app/endpoints/a2a.py
src/{app/**/*.py,client.py}
📄 CodeRabbit inference engine (CLAUDE.md)
Use async def for I/O-bound operations and external API calls
Files:
src/app/routers.pysrc/app/endpoints/a2a.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
In API endpoints, raise FastAPI HTTPException with appropriate status codes for error handling
Files:
src/app/endpoints/a2a.py
pyproject.toml
📄 CodeRabbit inference engine (CLAUDE.md)
pyproject.toml: ALWAYS check pyproject.toml for existing dependencies before adding new ones
ALWAYS verify current library versions in pyproject.toml rather than assuming versions
Prefer reading supported Python versions and tool configs from pyproject.toml
Files:
pyproject.toml
🧬 Code graph analysis (3)
src/models/config.py (1)
src/utils/checks.py (1)
file_check(38-57)
src/app/routers.py (1)
tests/unit/app/test_routers.py (1)
include_router(35-50)
src/app/endpoints/a2a.py (8)
src/authentication/__init__.py (1)
get_auth_dependency(14-52)src/authorization/middleware.py (1)
authorize(111-122)src/configuration.py (3)
configuration(73-77)service_configuration(80-84)customization(127-131)src/models/config.py (2)
config(140-146)Action(331-383)src/models/requests.py (1)
QueryRequest(73-225)src/app/endpoints/query.py (2)
select_model_and_provider_id(419-497)evaluate_model_hints(142-181)src/client.py (2)
AsyncLlamaStackClientHolder(18-55)get_client(49-55)src/utils/mcp_headers.py (1)
mcp_headers_dependency(15-26)
🔇 Additional comments (8)
src/app/routers.py (1)
21-22: LGTM!The A2A router registration correctly omits the version prefix, which is appropriate since the A2A protocol requires specific paths like
/.well-known/agent.jsonand/a2a. The placement after other non-versioned endpoints (health, authorized, metrics) is consistent with the existing pattern.Also applies to: 49-51
src/models/config.py (2)
379-383: LGTM!The A2A action enum members follow the established naming conventions and cover the necessary A2A protocol actions. The comment clearly identifies the purpose of these actions.
487-488: LGTM!The agent card fields use appropriate types:
FilePathfor path validation anddict[str, Any]for flexible configuration storage.src/app/endpoints/a2a.py (4)
71-80: LGTM!The constructor properly initializes the executor with authentication context and MCP headers, with appropriate default handling.
301-317: LGTM!The cancellation placeholder is appropriately implemented with
NotImplementedErrorand clear documentation. This is acceptable for a WIP feature.
178-183: No issues found -evaluate_model_hintsproperly handlesNoneuser_conversation.The function explicitly accepts
user_conversation: UserConversation | Noneand includes an appropriate None check at line 150. WhenNoneis passed, the function correctly returns model and provider IDs from the query request without attempting to access conversation history. This is intentional design supporting both conversational and non-conversational query flows.
54-57: Original review comment concerns are not applicable to the current deployment.The configuration uses a single worker (
workers: 1), eliminating the multi-worker process isolation concern. While the async endpoint does have awaits between read and write operations on_CONTEXT_TO_CONVERSATION, concurrent access to the same key would be unusual since each request typically operates on a distinctcontext_id. Additionally, the suggested fix usingthreading.Lockis incorrect for async code—asyncio.Lockwould be needed if synchronization were required, though it's unlikely necessary given the usage pattern.No changes are required.
Likely an incorrect or invalid review comment.
pyproject.toml (1)
44-45: Pin the a2a-sdk version to prevent unexpected breaking changes.The
a2a-sdkdependency lacks any version specification, which poses supply chain and stability risks. The codebase is written for the 0.2.x API using camelCase field names (e.g.,inputModes,outputModes,pushNotifications,stateTransitionHistory), which would break with a2a-sdk 0.3.x where these fields were refactored to snake_case. Without version constraints, builds become non-reproducible and may pull breaking changes.Apply this diff to add a version constraint:
- # Used for A2A protocol support - "a2a-sdk", + # Used for A2A protocol support + "a2a-sdk>=0.2.1,<0.3.0",
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (4)
src/app/endpoints/a2a.py (4)
43-43: Use__name__for the logger to match the actual module.The logger is initialized with
"app.endpoints.handlers"which doesn't match this module's path (app.endpoints.a2a).As per coding guidelines, apply this diff:
-logger = logging.getLogger("app.endpoints.handlers") +logger = logging.getLogger(__name__)
362-362: Replacegetattrwith direct attribute access.Since
base_urlis a defined field onServiceConfiguration, direct attribute access with a fallback is more type-safe.Apply this diff:
- base_url = getattr(service_config, "base_url", "http://localhost:8080") + base_url = service_config.base_url or "http://localhost:8080"
517-521: Add authorization decorator for consistency.The endpoint uses authentication but lacks authorization. For consistency with other protected endpoints, add the
@authorizedecorator.Apply this diff:
@router.get("/.well-known/agent.json", response_model=AgentCard) @router.get("/.well-known/agent-card.json", response_model=AgentCard) +@authorize(Action.A2A_AGENT_CARD) async def get_agent_card( auth: Annotated[AuthTuple, Depends(auth_dependency)], ) -> AgentCard:
601-602: Improve auth token extraction robustness.The tuple indexing
auth[3] if len(auth) > 3 else ""is brittle and depends onAuthTuplestructure.Consider a more explicit approach:
- # Extract auth token - auth_token = auth[3] if len(auth) > 3 else "" + # Extract auth token from AuthTuple (user_id, username, is_admin, token) + try: + auth_token = auth[3] if len(auth) > 3 else "" + except (IndexError, TypeError): + logger.warning("Failed to extract auth token from auth tuple") + auth_token = ""
🧹 Nitpick comments (2)
src/app/endpoints/a2a.py (2)
49-58: Consider external state storage for production deployment.The in-memory
_TASK_STOREand_CONTEXT_TO_CONVERSATIONwill not persist across restarts or scale across multiple workers/pods. For production, consider using Redis, database, or distributed cache.
125-326: Consider decomposing this complex streaming handler.The method handles multiple concerns: input validation, model selection, response streaming, and event handling. While functionally correct, extracting helper methods (e.g.,
_handle_turn_awaiting_input,_handle_turn_complete,_handle_step_progress) would improve readability and testability.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/app/endpoints/a2a.py(1 hunks)tests/unit/app/test_routers.py(5 hunks)tests/unit/models/config/test_dump_configuration.py(2 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: All modules start with descriptive module-level docstrings explaining purpose
Use logger = logging.getLogger(name) for module logging after import logging
Define type aliases at module level for clarity
All functions require docstrings with brief descriptions
Provide complete type annotations for all function parameters and return types
Use typing_extensions.Self in model validators where appropriate
Use modern union syntax (str | int) and Optional[T] or T | None consistently
Function names use snake_case with descriptive, action-oriented prefixes (get_, validate_, check_)
Avoid in-place parameter modification; return new data structures instead of mutating arguments
Use appropriate logging levels: debug, info, warning, error with clear messages
All classes require descriptive docstrings explaining purpose
Class names use PascalCase with conventional suffixes (Configuration, Error/Exception, Resolver, Interface)
Abstract base classes should use abc.ABC and @AbstractMethod for interfaces
Provide complete type annotations for all class attributes
Follow Google Python docstring style for modules, classes, and functions, including Args, Returns, Raises, Attributes sections as needed
Files:
tests/unit/models/config/test_dump_configuration.pytests/unit/app/test_routers.pysrc/app/endpoints/a2a.py
tests/{unit,integration}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests
Do not use unittest in tests; pytest is the standard
Files:
tests/unit/models/config/test_dump_configuration.pytests/unit/app/test_routers.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use pytest-mock to create AsyncMock objects for async interactions in tests
Use the shared auth mock constant: MOCK_AUTH = ("mock_user_id", "mock_username", False, "mock_token") in tests
Files:
tests/unit/models/config/test_dump_configuration.pytests/unit/app/test_routers.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use absolute imports for internal modules (e.g., from auth import get_auth_dependency)
Files:
src/app/endpoints/a2a.py
src/app/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use standard FastAPI imports (from fastapi import APIRouter, HTTPException, Request, status, Depends) in FastAPI app code
Files:
src/app/endpoints/a2a.py
src/{app/**/*.py,client.py}
📄 CodeRabbit inference engine (CLAUDE.md)
Use async def for I/O-bound operations and external API calls
Files:
src/app/endpoints/a2a.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
In API endpoints, raise FastAPI HTTPException with appropriate status codes for error handling
Files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (1)
src/app/endpoints/a2a.py (8)
src/authentication/__init__.py (1)
get_auth_dependency(14-52)src/authorization/middleware.py (1)
authorize(111-122)src/configuration.py (3)
configuration(73-77)service_configuration(80-84)customization(127-131)src/models/config.py (2)
config(140-146)Action(331-383)src/models/requests.py (1)
QueryRequest(73-225)src/app/endpoints/query.py (2)
select_model_and_provider_id(419-497)evaluate_model_hints(142-181)src/client.py (2)
AsyncLlamaStackClientHolder(18-55)get_client(49-55)src/utils/mcp_headers.py (1)
mcp_headers_dependency(15-26)
🔇 Additional comments (6)
tests/unit/models/config/test_dump_configuration.py (1)
104-104: LGTM! Test expectations updated correctly.The addition of
"base_url": Noneto the expected serialized output correctly reflects the new optional field inServiceConfiguration.Also applies to: 403-403
tests/unit/app/test_routers.py (1)
25-25: LGTM! Tests correctly updated for the new A2A router.The test updates properly account for the new
a2arouter with the correct expectations: 16 total routers, presence in the registered routers, and an empty prefix (appropriate for A2A protocol well-known paths).Also applies to: 68-68, 84-84, 93-93, 109-109
src/app/endpoints/a2a.py (4)
118-121: ValueError is acceptable in this executor context.The previous review suggested HTTPException, but since this method is called by the A2A SDK's request handler (not directly by FastAPI), ValueError is appropriate. The SDK will catch and convert exceptions to proper A2A protocol error responses.
544-569: LGTM! A2A app builder is well-structured.The function correctly instantiates the A2A application with authentication context and the shared task store for multi-turn conversations.
607-748: LGTM! Streaming and non-streaming modes are correctly implemented.The implementation properly:
- Detects request type by inspecting the JSON-RPC method
- Handles body re-provision after initial read
- Uses asyncio.Queue for streaming coordination with appropriate timeout (300s)
- Returns correct response types (StreamingResponse vs Response)
- Properly manages ASGI protocol interactions
751-765: LGTM! Health check endpoint is appropriately implemented.The endpoint returns useful metadata without requiring authentication, which is correct for health monitoring purposes.
|
Hi @luis5tb I'm just curious. Does this PR add support to expose Lightspeed Stack as an Agent over the A2A protocol? It does not appear to add support fro Lightspeed Stack delegating to other Agents over A2A. The project I'm currently on will need to support A2A in the future, so I just look for examples. I'm just being nosy. |
It is adding support to expose the lightspeed agent over A2A protocol, but it is not adding support for lightspeed stack to call other A2A exposed agents. I suppose that type of support should be on LlamaStack side, or wait for Lightspeed stack to support responses and handle the hand off |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (3)
src/app/endpoints/a2a.py (3)
125-326: Consider extracting helper methods to reduce complexity.The
_process_task_streamingmethod handles multiple concerns (input validation, conversation mapping, model selection, streaming, status updates, error handling) in 200+ lines, triggering pylint complexity warnings.Consider extracting helpers for:
- Input validation and extraction
- Conversation ID resolution
- Query request preparation
- Event type handling (turn_complete, turn_awaiting_input, step_progress)
This would improve readability and testability while reducing cognitive load.
544-569: Consider more specific return type annotation.The return type
Anybypasses type checking. Consider using a more specific type likeCallableor the actual ASGI application type from the a2a SDK:-def _create_a2a_app(auth_token: str, mcp_headers: dict[str, dict[str, str]]) -> Any: +def _create_a2a_app(auth_token: str, mcp_headers: dict[str, dict[str, str]]) -> Callable:Or import and use the specific Starlette ASGI type if available from the a2a library.
688-716: Consider more specific return type for response_generator.The return type
Anycould be more specific to indicate this is an async generator:- async def response_generator() -> Any: + async def response_generator() -> AsyncIterator[bytes]:This requires importing
AsyncIteratorfromcollections.abcortyping.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/app/endpoints/a2a.py(1 hunks)src/models/config.py(5 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use absolute imports for internal modules (e.g., from auth import get_auth_dependency)
Files:
src/models/config.pysrc/app/endpoints/a2a.py
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: All modules start with descriptive module-level docstrings explaining purpose
Use logger = logging.getLogger(name) for module logging after import logging
Define type aliases at module level for clarity
All functions require docstrings with brief descriptions
Provide complete type annotations for all function parameters and return types
Use typing_extensions.Self in model validators where appropriate
Use modern union syntax (str | int) and Optional[T] or T | None consistently
Function names use snake_case with descriptive, action-oriented prefixes (get_, validate_, check_)
Avoid in-place parameter modification; return new data structures instead of mutating arguments
Use appropriate logging levels: debug, info, warning, error with clear messages
All classes require descriptive docstrings explaining purpose
Class names use PascalCase with conventional suffixes (Configuration, Error/Exception, Resolver, Interface)
Abstract base classes should use abc.ABC and @AbstractMethod for interfaces
Provide complete type annotations for all class attributes
Follow Google Python docstring style for modules, classes, and functions, including Args, Returns, Raises, Attributes sections as needed
Files:
src/models/config.pysrc/app/endpoints/a2a.py
src/{models/config.py,configuration.py}
📄 CodeRabbit inference engine (CLAUDE.md)
src/{models/config.py,configuration.py}: All configuration uses Pydantic models extending ConfigurationBase
Configuration base models must set model_config with extra="forbid" to reject unknown fields
Files:
src/models/config.py
src/{models/**/*.py,configuration.py}
📄 CodeRabbit inference engine (CLAUDE.md)
src/{models/**/*.py,configuration.py}: Use @field_validator and @model_validator for custom validation in Pydantic models
Use precise type hints in configuration (e.g., Optional[FilePath], PositiveInt, SecretStr)
Files:
src/models/config.py
src/models/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/**/*.py: Pydantic models: use BaseModel for data models and extend ConfigurationBase for configuration
Use @model_validator and @field_validator for Pydantic model validation
Files:
src/models/config.py
src/app/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use standard FastAPI imports (from fastapi import APIRouter, HTTPException, Request, status, Depends) in FastAPI app code
Files:
src/app/endpoints/a2a.py
src/{app/**/*.py,client.py}
📄 CodeRabbit inference engine (CLAUDE.md)
Use async def for I/O-bound operations and external API calls
Files:
src/app/endpoints/a2a.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
In API endpoints, raise FastAPI HTTPException with appropriate status codes for error handling
Files:
src/app/endpoints/a2a.py
🧠 Learnings (1)
📚 Learning: 2025-10-29T13:05:22.412Z
Learnt from: luis5tb
PR: lightspeed-core/lightspeed-stack#727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.412Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (2)
src/models/config.py (1)
src/utils/checks.py (1)
file_check(38-57)
src/app/endpoints/a2a.py (8)
src/authentication/__init__.py (1)
get_auth_dependency(14-52)src/authorization/middleware.py (1)
authorize(111-122)src/configuration.py (3)
configuration(73-77)service_configuration(80-84)customization(127-131)src/models/config.py (2)
config(141-147)Action(332-384)src/models/requests.py (1)
QueryRequest(73-225)src/app/endpoints/query.py (2)
select_model_and_provider_id(419-497)evaluate_model_hints(142-181)src/client.py (2)
AsyncLlamaStackClientHolder(18-55)get_client(49-55)src/utils/mcp_headers.py (1)
mcp_headers_dependency(15-26)
🔇 Additional comments (13)
src/models/config.py (5)
8-8: LGTM! Import moved to module level as requested.The
yamlimport is now correctly placed at the module level, addressing the previous review comment.
155-156: LGTM! Field properly documented.The
base_urlfield is correctly typed asOptional[str]with a clear comment explaining its purpose for external service reachability.
380-384: LGTM! A2A action enums properly defined.The new A2A protocol action enums follow existing conventions and enable proper authorization checks for the new endpoints.
488-489: LGTM! Agent card fields properly typed.The new fields follow Pydantic conventions with appropriate types:
FilePathfor validation anddict[str, Any]for the loaded configuration.
502-517: LGTM! Robust error handling implemented.The YAML loading logic correctly implements the error handling requested in the previous review:
- File existence/readability validated via
checks.file_check- YAML parsing errors caught with descriptive messages
- File I/O errors handled separately
- Proper exception chaining with
from esrc/app/endpoints/a2a.py (8)
1-46: LGTM! Module structure follows conventions.The module has proper docstring, complete imports, and follows the established pattern of using the shared logger name
"app.endpoints.handlers"(as per coding guidelines for endpoint handlers).Based on learnings
63-102: LGTM! Clean executor implementation.The
LightspeedAgentExecutorclass properly implements theAgentExecutorinterface with:
- Complete type annotations
- Google-style docstrings
- Clear separation between task preparation and processing
104-123: LGTM! Appropriate exception type for internal method.The
ValueErroris suitable for this internal method, allowing the calling layer to handle and translate to appropriate HTTP responses.
350-511: LGTM! Robust agent card generation with fallback.The function properly:
- Loads from YAML configuration when available
- Falls back to comprehensive hardcoded defaults
- Constructs proper AgentCard objects with all required fields
- Includes detailed skill definitions for OpenShift assistance
517-541: LGTM! Endpoint correctly implements A2A spec requirements.The endpoint appropriately uses authentication without authorization enforcement, allowing clients to discover security schemes as required by the A2A protocol. Error handling is properly implemented with logging.
603-607: LGTM! Robust auth token extraction.The try/except block makes the auth token extraction more resilient, handling potential tuple index errors gracefully with appropriate logging.
572-753: LGTM! Comprehensive A2A JSON-RPC handling.The endpoint properly:
- Applies authorization via
@authorize(Action.A2A_JSONRPC)- Detects streaming vs non-streaming from JSON-RPC method
- Bridges A2A SDK ASGI app with FastAPI responses
- Handles body re-reading correctly after method detection
- Implements proper cleanup in streaming path (cancellation, timeouts)
- Logs appropriately throughout the execution flow
The complexity is justified by the dual-mode (streaming/non-streaming) requirements.
756-769: LGTM! Simple and effective health check.The health check endpoint appropriately provides service metadata without authentication requirements, enabling easy monitoring and diagnostics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/app/endpoints/a2a.py (1)
49-57: Consider persistent storage for production multi-worker deployments.The module-level in-memory state (
_TASK_STOREand_CONTEXT_TO_CONVERSATION) will not be shared across multiple worker processes, potentially causing lost conversation history and task state. For production deployments withservice.workers > 1, consider integrating with the existingDatabaseConfigurationorConversationCacheConfigurationto persist state.
🧹 Nitpick comments (1)
src/app/endpoints/a2a.py (1)
125-327: Complex but appropriate for streaming protocol handling.The
_process_task_streamingmethod handles the intricate A2A streaming protocol with proper state transitions, comprehensive error handling, and detailed logging. While the method is complex (pylint disabled), it correctly manages:
- Task state transitions (working, input_required, completed, failed)
- Streaming event handling (step_progress, turn_awaiting_input, turn_complete)
- Conversation history persistence via
_CONTEXT_TO_CONVERSATION- Graceful error recovery with broad exception handlers
The complexity is justified by the streaming protocol requirements. Consider extracting event handlers to separate methods in future refactoring.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/app/endpoints/a2a.py(1 hunks)src/models/config.py(5 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use absolute imports for internal modules (e.g., from auth import get_auth_dependency)
Files:
src/app/endpoints/a2a.pysrc/models/config.py
src/app/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use standard FastAPI imports (from fastapi import APIRouter, HTTPException, Request, status, Depends) in FastAPI app code
Files:
src/app/endpoints/a2a.py
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: All modules start with descriptive module-level docstrings explaining purpose
Use logger = logging.getLogger(name) for module logging after import logging
Define type aliases at module level for clarity
All functions require docstrings with brief descriptions
Provide complete type annotations for all function parameters and return types
Use typing_extensions.Self in model validators where appropriate
Use modern union syntax (str | int) and Optional[T] or T | None consistently
Function names use snake_case with descriptive, action-oriented prefixes (get_, validate_, check_)
Avoid in-place parameter modification; return new data structures instead of mutating arguments
Use appropriate logging levels: debug, info, warning, error with clear messages
All classes require descriptive docstrings explaining purpose
Class names use PascalCase with conventional suffixes (Configuration, Error/Exception, Resolver, Interface)
Abstract base classes should use abc.ABC and @AbstractMethod for interfaces
Provide complete type annotations for all class attributes
Follow Google Python docstring style for modules, classes, and functions, including Args, Returns, Raises, Attributes sections as needed
Files:
src/app/endpoints/a2a.pysrc/models/config.py
src/{app/**/*.py,client.py}
📄 CodeRabbit inference engine (CLAUDE.md)
Use async def for I/O-bound operations and external API calls
Files:
src/app/endpoints/a2a.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
In API endpoints, raise FastAPI HTTPException with appropriate status codes for error handling
Files:
src/app/endpoints/a2a.py
src/{models/config.py,configuration.py}
📄 CodeRabbit inference engine (CLAUDE.md)
src/{models/config.py,configuration.py}: All configuration uses Pydantic models extending ConfigurationBase
Configuration base models must set model_config with extra="forbid" to reject unknown fields
Files:
src/models/config.py
src/{models/**/*.py,configuration.py}
📄 CodeRabbit inference engine (CLAUDE.md)
src/{models/**/*.py,configuration.py}: Use @field_validator and @model_validator for custom validation in Pydantic models
Use precise type hints in configuration (e.g., Optional[FilePath], PositiveInt, SecretStr)
Files:
src/models/config.py
src/models/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/**/*.py: Pydantic models: use BaseModel for data models and extend ConfigurationBase for configuration
Use @model_validator and @field_validator for Pydantic model validation
Files:
src/models/config.py
🧠 Learnings (1)
📚 Learning: 2025-10-29T13:05:22.412Z
Learnt from: luis5tb
PR: lightspeed-core/lightspeed-stack#727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.412Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (2)
src/app/endpoints/a2a.py (8)
src/authentication/__init__.py (1)
get_auth_dependency(14-52)src/authorization/middleware.py (1)
authorize(111-122)src/configuration.py (3)
configuration(73-77)service_configuration(80-84)customization(127-131)src/models/config.py (2)
config(141-147)Action(332-384)src/models/requests.py (1)
QueryRequest(73-225)src/app/endpoints/query.py (2)
select_model_and_provider_id(419-497)evaluate_model_hints(142-181)src/client.py (2)
AsyncLlamaStackClientHolder(18-55)get_client(49-55)src/utils/mcp_headers.py (1)
mcp_headers_dependency(15-26)
src/models/config.py (1)
src/utils/checks.py (1)
file_check(38-57)
🔇 Additional comments (16)
src/models/config.py (5)
8-8: LGTM! YAML import moved to module level.The inline import has been correctly moved to module level as suggested in the previous review.
155-156: LGTM! Base URL field added appropriately.The
base_urlfield is properly typed asOptional[str]with a clear comment explaining its purpose for externally reachable URLs.
380-384: LGTM! A2A action enum members added.The new A2A-related actions follow the existing naming convention and are properly documented.
488-489: LGTM! Agent card configuration fields added.The fields are properly typed with
FilePathfor the path anddict[str, Any]for the parsed configuration data.
502-517: LGTM! YAML loading includes proper error handling.The agent card YAML loading logic correctly:
- Validates file existence and readability with
checks.file_check- Catches specific exceptions (
yaml.YAMLError,OSError)- Provides descriptive error messages with file path context
- Uses proper exception chaining with
from eThis addresses the past review comment about adding error handling.
src/app/endpoints/a2a.py (11)
1-46: LGTM! Module structure follows conventions.The module has a clear docstring, organized imports, and proper setup. The logger name
"app.endpoints.handlers"follows the intentional pattern shared across endpoint handlers. Based on learnings.
63-124: LGTM! Agent executor class well-structured.The
LightspeedAgentExecutorclass properly implements the A2AAgentExecutorinterface with:
- Complete type annotations
- Comprehensive Google-style docstrings
- Specific exception types (ValueError) rather than generic Exception
- Clear separation of concerns between task preparation and execution
328-344: LGTM! Cancellation appropriately marked as not implemented.The
cancelmethod correctly raisesNotImplementedErrorwith proper documentation and logging. This is acceptable for a work-in-progress feature.
350-416: LGTM! Agent card generation from configuration implemented correctly.The function properly:
- Uses direct attribute access for
base_url(notgetattr)- Performs explicit
Nonecheck rather than relying on truthiness- Parses YAML configuration into A2A SDK types
- Includes proper fallback URL
This addresses previous review comments about base_url handling.
418-511: LGTM! Sensible fallback agent card implementation.The default agent card provides well-structured OpenShift installation skills with clear descriptions, examples, and appropriate capabilities. The logging message clearly indicates when defaults are being used.
517-541: LGTM! Agent card endpoint correctly serves metadata.The endpoint serves the A2A agent card at well-known locations with proper exception handling and logging. The lack of
@authorizedecorator is intentional, as confirmed in previous review discussion, since the agent card contains security scheme information and should be accessible for discovery.
544-569: LGTM! A2A app factory properly configured.The function correctly creates a per-request A2A application instance with authentication context while sharing the persistent task store across requests. This design allows FastAPI's authorization middleware to work while maintaining A2A protocol compliance.
572-653: LGTM! Endpoint setup with robust error handling.The endpoint properly:
- Applies authorization with
@authorize(Action.A2A_JSONRPC)- Extracts auth token with bounds checking and exception handling (addresses past review concern)
- Detects streaming requests by parsing the JSON-RPC method
- Handles body re-provision for ASGI app delegation
- Includes comprehensive error handling and logging
The auth token extraction at lines 602-607 already includes the defensive programming suggested in past reviews.
654-727: LGTM! Streaming path correctly implemented.The streaming request handling properly:
- Uses an
asyncio.Queuefor chunk relay between ASGI app and response generator- Runs the A2A app as a background task to avoid blocking
- Includes a 300-second timeout to prevent indefinite hangs
- Ensures task cleanup in the
finallyblock- Returns
StreamingResponsewith correcttext/event-streammedia typeThe async patterns and resource management are solid.
729-753: LGTM! Non-streaming path correctly buffers response.The non-streaming request handling appropriately buffers the entire response before returning, including status code, headers, and body. The header decoding from bytes is handled correctly.
756-770: LGTM! Health check endpoint provides useful metadata.The health check endpoint appropriately returns service status, version information, and timestamp without requiring authentication. This is standard practice for health check endpoints.
eranco74
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
src/app/endpoints/a2a.py (5)
267-275: Remove commented-out code.Lines 267-275 contain commented-out code from the old
update_statusapproach. This should be removed to improve code clarity.Apply this diff:
- # await task_updater.update_status( - # TaskState.completed, - # message=new_agent_text_message( - # final_text, - # context_id=context_id, - # task_id=task_id, - # ), - # final=True, - # ) task_metadata = {
307-314: Remove commented-out code.Lines 307-314 contain commented-out code that should be cleaned up for maintainability.
Apply this diff:
- # await task_updater.update_status( - # TaskState.working, - # message=new_agent_text_message( - # delta_text, - # context_id=context_id, - # task_id=task_id, - # ), - # ) await task_updater.add_artifact(
329-337: Remove commented-out code.Lines 329-337 contain commented-out code that should be removed.
Apply this diff:
- # await task_updater.update_status( - # TaskState.completed, - # message=new_agent_text_message( - # final_text, - # context_id=context_id, - # task_id=task_id, - # ), - # final=True, - # ) await task_updater.add_artifact(
407-418: Consider validating skill configuration fields.The skill parsing uses
.get()without defaults for required fields likeid,name, anddescription, which could result inNonevalues if the config is incomplete. Consider adding validation or defaults for required AgentSkill fields to prevent runtime errors.Example approach:
skills = [ AgentSkill( id=skill.get("id", "unknown_skill"), name=skill.get("name", "Unknown Skill"), description=skill.get("description", ""), tags=skill.get("tags", []), input_modes=skill.get("inputModes", ["text/plain"]), output_modes=skill.get("outputModes", ["text/plain"]), examples=skill.get("examples", []), ) for skill in config.get("skills", []) ]
765-789: Add timeout for non-streaming A2A app execution.The non-streaming path calls
await a2a_app(scope, receive, buffering_send)at Line 782 without a timeout. If the A2A app hangs, the request will hang indefinitely. Add a timeout consistent with the streaming mode (300 seconds).Apply this diff:
- await a2a_app(scope, receive, buffering_send) + try: + await asyncio.wait_for( + a2a_app(scope, receive, buffering_send), + timeout=300.0 + ) + except asyncio.TimeoutError: + logger.error("Timeout executing non-streaming A2A request") + return Response( + content=json.dumps({"error": "Request timeout"}).encode(), + status_code=504, + headers={"content-type": "application/json"}, + )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/app/endpoints/a2a.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use absolute imports for internal modules (e.g., from auth import get_auth_dependency)
Files:
src/app/endpoints/a2a.py
src/app/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use standard FastAPI imports (from fastapi import APIRouter, HTTPException, Request, status, Depends) in FastAPI app code
Files:
src/app/endpoints/a2a.py
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: All modules start with descriptive module-level docstrings explaining purpose
Use logger = logging.getLogger(name) for module logging after import logging
Define type aliases at module level for clarity
All functions require docstrings with brief descriptions
Provide complete type annotations for all function parameters and return types
Use typing_extensions.Self in model validators where appropriate
Use modern union syntax (str | int) and Optional[T] or T | None consistently
Function names use snake_case with descriptive, action-oriented prefixes (get_, validate_, check_)
Avoid in-place parameter modification; return new data structures instead of mutating arguments
Use appropriate logging levels: debug, info, warning, error with clear messages
All classes require descriptive docstrings explaining purpose
Class names use PascalCase with conventional suffixes (Configuration, Error/Exception, Resolver, Interface)
Abstract base classes should use abc.ABC and @AbstractMethod for interfaces
Provide complete type annotations for all class attributes
Follow Google Python docstring style for modules, classes, and functions, including Args, Returns, Raises, Attributes sections as needed
Files:
src/app/endpoints/a2a.py
src/{app/**/*.py,client.py}
📄 CodeRabbit inference engine (CLAUDE.md)
Use async def for I/O-bound operations and external API calls
Files:
src/app/endpoints/a2a.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
In API endpoints, raise FastAPI HTTPException with appropriate status codes for error handling
Files:
src/app/endpoints/a2a.py
🧠 Learnings (4)
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-09-18T16:46:33.353Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-18T16:46:33.353Z
Learning: Applies to **/*.py : Use logger = logging.getLogger(__name__) for module logging after import logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-09-18T16:46:33.353Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-18T16:46:33.353Z
Learning: Applies to src/app/endpoints/**/*.py : In API endpoints, raise FastAPI HTTPException with appropriate status codes for error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (1)
src/app/endpoints/a2a.py (7)
src/authentication/__init__.py (1)
get_auth_dependency(14-52)src/authorization/middleware.py (1)
authorize(111-122)src/models/config.py (2)
config(141-147)Action(332-384)src/models/requests.py (1)
QueryRequest(73-225)src/app/endpoints/query.py (2)
select_model_and_provider_id(419-497)evaluate_model_hints(142-181)src/client.py (2)
AsyncLlamaStackClientHolder(18-55)get_client(49-55)src/utils/mcp_headers.py (1)
mcp_headers_dependency(15-26)
🔇 Additional comments (11)
src/app/endpoints/a2a.py (11)
1-49: LGTM: Module setup follows project conventions.The module docstring, imports, logger configuration, and router setup are all correctly structured. The shared logger name
"app.endpoints.handlers"is intentional per established project patterns.
52-60: Acknowledge known multi-worker limitation.The in-memory state management has been previously flagged as incompatible with multi-worker deployments. As this is a WIP PR and the long-term plan question from eranco74 remains open, ensure the roadmap addresses persistent storage (e.g., Redis-backed TaskStore or database-backed conversation mapping) before production deployment.
66-85: LGTM: Executor initialization is clean.The class and method docstrings follow Google style, type annotations are complete, and the initialization logic correctly handles optional MCP headers.
87-126: LGTM: Task preparation logic is correct.The
executemethod properly orchestrates task preparation and processing. The_prepare_taskmethod correctly handles existing tasks and creates new ones with appropriate error handling usingValueError.
128-361: Complex but acceptable streaming implementation.The
_process_task_streamingmethod handles multiple event types (turn_awaiting_input, turn_complete, step_progress) with appropriate state management and error recovery. The broad exception handlers are justified for resilience in event processing, and the complexity is inherent to the streaming state machine.
363-379: LGTM: Cancellation placeholder is clear.The
cancelmethod appropriately raisesNotImplementedErrorwith clear documentation and logging. This is an acceptable pattern for unimplemented features.
385-546: LGTM: Agent card generation handles both config and fallback.The function properly checks for configured agent card settings and provides a comprehensive hardcoded fallback. The explicit
Nonecheck forbase_urlis correct, and all required AgentCard fields are populated.
552-576: Reconsider authentication requirement for agent card endpoint.The endpoint currently requires authentication via
auth_dependency, but per the previous discussion, the agent card "should be accessible without authorization" since it describes the security scheme. Consider removing the auth dependency entirely to make this endpoint truly public per A2A protocol conventions, where agent cards are typically discoverable without authentication.If the agent card should be public, apply this diff:
@router.get("/.well-known/agent.json", response_model=AgentCard) @router.get("/.well-known/agent-card.json", response_model=AgentCard) -async def get_agent_card( # pylint: disable=unused-argument - auth: Annotated[AuthTuple, Depends(auth_dependency)], -) -> AgentCard: +async def get_agent_card() -> AgentCard:
636-642: LGTM: Auth token extraction is now robust.The auth token extraction correctly handles potential errors with try/except and provides appropriate fallback behavior.
689-763: LGTM: Streaming implementation with proper cleanup.The streaming mode correctly uses an asyncio queue to bridge the ASGI app and StreamingResponse, includes appropriate timeout handling (300s), and ensures proper cleanup of the background task.
792-806: LGTM: Health check endpoint is straightforward.The health check endpoint provides useful diagnostic information including service status, version, and timestamp.
|
JIRA for this PR is LCORE-275 - https://issues.redhat.com/browse/LCORE-275 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (7)
src/app/endpoints/a2a.py (7)
67-67: Use more specific type annotation instead ofAnyfor the content parameter.The
contentparameter usesAny, which bypasses type checking. Consider using a Union type or defining a Protocol to represent the expected Llama Stack content types (str, TextContentItem, ImageContentItem, or list).Apply this diff to improve type safety:
-def _convert_llama_content_to_a2a_parts(content: Any) -> list[Part]: +def _convert_llama_content_to_a2a_parts(content: str | list | object | None) -> list[Part]:Or better yet, define a type alias for the expected content types at the module level:
LlamaContent = str | list[Any] | object | None def _convert_llama_content_to_a2a_parts(content: LlamaContent) -> list[Part]:
89-89: TODO: Image content handling not implemented.The code currently skips image content items. If the A2A protocol or Lightspeed agent card supports image modes, this may need to be implemented.
Do you want me to generate an implementation for handling image content, or should this TODO be tracked in a separate issue?
222-348: Consider refactoring_process_task_streamingto reduce complexity.This method is flagged with
too-many-locals,too-many-branches,too-many-statementsand spans 126 lines. Consider extracting helper methods for:
- User input validation and extraction
- Conversation ID resolution
- Query request building
- Response processing and event publishing
This would improve maintainability and testability without changing behavior.
568-578: Simplify exception handling or provide more specific error responses.The current exception handler catches all exceptions, logs them, and re-raises. This doesn't add value beyond logging. Consider either:
- Removing the try/except if logging is handled elsewhere
- Converting exceptions to appropriate HTTPExceptions with status codes
Option 1 - Remove try/except:
""" - try: - logger.info("Serving A2A Agent Card") - agent_card = get_lightspeed_agent_card() - logger.info("Agent Card URL: %s", agent_card.url) - logger.info( - "Agent Card capabilities: streaming=%s", agent_card.capabilities.streaming - ) - return agent_card - except Exception as exc: - logger.error("Error serving A2A Agent Card: %s", str(exc)) - raise + logger.info("Serving A2A Agent Card") + agent_card = get_lightspeed_agent_card() + logger.info("Agent Card URL: %s", agent_card.url) + logger.info( + "Agent Card capabilities: streaming=%s", agent_card.capabilities.streaming + ) + return agent_card
581-607: Use more specific return type annotation instead ofAny.The function returns the result of
A2AStarletteApplication.build()but usesAnyas the return type. Consider using the actual return type from the A2A SDK for better type safety.Based on the A2A SDK, the return type should be a Starlette ASGI application:
+from starlette.applications import Starlette + -def _create_a2a_app(auth_token: str, mcp_headers: dict[str, dict[str, str]]) -> Any: +def _create_a2a_app(auth_token: str, mcp_headers: dict[str, dict[str, str]]) -> Starlette:
609-792: Consider refactoringhandle_a2a_jsonrpcto reduce complexity.This endpoint handler spans 183 lines and is flagged with
too-many-localsandtoo-many-statements. Consider extracting helper functions for:
- JSON-RPC method detection and request parsing
- Streaming response handling
- Non-streaming response handling
This would improve maintainability and make the code easier to test without changing behavior.
794-808: Consider importing A2A SDK version instead of hardcoding it.The
a2a_sdk_versionis hardcoded as "0.2.1" which could become outdated. Consider importing the version from the A2A SDK package if available.try: from a2a import __version__ as a2a_sdk_version except (ImportError, AttributeError): a2a_sdk_version = "unknown"Then use it in the health check response:
return { "status": "healthy", "service": "lightspeed-a2a", "version": __version__, - "a2a_sdk_version": "0.2.1", + "a2a_sdk_version": a2a_sdk_version, "timestamp": datetime.now().isoformat(), }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/app/endpoints/a2a.py(1 hunks)src/app/endpoints/query.py(3 hunks)src/app/endpoints/streaming_query.py(2 hunks)src/constants.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/constants.pysrc/app/endpoints/query.pysrc/app/endpoints/streaming_query.pysrc/app/endpoints/a2a.py
src/**/constants.py
📄 CodeRabbit inference engine (CLAUDE.md)
Define shared constants in central
constants.pyfile with descriptive comments
Files:
src/constants.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/query.pysrc/app/endpoints/streaming_query.pysrc/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/query.pysrc/app/endpoints/streaming_query.pysrc/app/endpoints/a2a.py
🧠 Learnings (5)
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (1)
src/app/endpoints/streaming_query.py (1)
src/app/endpoints/query.py (1)
get_rag_toolgroups(799-826)
🔇 Additional comments (4)
src/app/endpoints/streaming_query.py (1)
1034-1042: LGTM! Consistent API update from vector_dbs to vector_stores.The renaming from
vector_db_idstovector_store_idsis consistent with the changes inquery.pyand aligns with the Llama Stack API evolution. The updated code correctly accessesvector_store.idfrom.dataand normalizes empty toolgroups toNone.src/app/endpoints/query.py (2)
701-709: LGTM! Consistent API update from vector_dbs to vector_stores.The code correctly updates the API call from
client.vector_dbs.list()toclient.vector_stores.list().dataand renames the variable tovector_store_ids. This change is consistent with the Llama Stack API evolution and matches the updates instreaming_query.py.
799-826: LGTM! Function signature and implementation updated consistently.The
get_rag_toolgroupsfunction has been updated to usevector_store_idsthroughout:
- Parameter renamed from
vector_db_idstovector_store_ids- Toolgroup args key updated to match ("vector_store_ids")
- Documentation updated appropriately
This is consistent with the caller updates in both
query.pyandstreaming_query.py.src/constants.py (1)
5-5: Llama Stack version 0.3.0 has been released and is available.Web verification confirms that Llama Stack v0.3.0 was officially released on October 22, 2025, and is available from GitHub releases and Python package archives. The version bump from 0.2.22 to 0.3.0 is valid. However, ensure that A2A-related functionality has been tested against this version to confirm full compatibility.
src/app/endpoints/a2a.py
Outdated
| if hasattr(payload, "delta"): | ||
| delta = payload.delta | ||
| if delta.type == "text" and delta.text: | ||
| yield TaskStatusUpdateEvent( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why isn't this a TaskArtifactUpdate? This is the "a chunk of the response content, but not the final chunk" case, right?
i'd only expect a TaskStatusUpdateEvent when the state of the task changed (e.g. to "input_required") or maybe if an event occured (like a tool call) but there's no actual response content that's intended for the user to read/see directly.
I think you copied this from the ADK but i have the same questions about their impl...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know for sure what is the best approach here, I tried with just status updates, with just artifact updates (as you suggest here) and with this mix (artifact for the final events, updates for the intermediate) as it was what it was implemented in ADK
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/app/endpoints/a2a.py (2)
98-153: Add typing and a short init docstring to TaskResultAggregator.
TaskResultAggregatoris missing type annotations on__init__and attributes, and tools are flagging it; a tiny update fixes both pylint/pydocstyle and mypy/pyright, and aligns with repo typing guidelines. (This was already raised in a previous review.)class TaskResultAggregator: """Aggregates the task status updates and provides the final task state.""" - def __init__(self): - self._task_state = TaskState.working - self._task_status_message = None + def __init__(self) -> None: + """Initialize the task result aggregator with default working state.""" + self._task_state: TaskState = TaskState.working + self._task_status_message: Any | None = None - def process_event(self, event: Any) -> None: + def process_event( + self, event: TaskStatusUpdateEvent | TaskArtifactUpdateEvent | Any + ) -> None:
474-542: Use HTTPException and guard optional customization config when building AgentCard.
get_lightspeed_agent_cardassumesconfiguration.customizationand.agent_card_configare non-None and raisesValueErrorif the config is missing. Type checkers flag this as optional access, and for endpoints you want a proper HTTP error rather than a bareValueError. A very small change fixes both and matches earlier review feedback.def get_lightspeed_agent_card() -> AgentCard: @@ - # Get base URL from configuration or construct it - service_config = configuration.service_configuration + # Get base URL from configuration or construct it + service_config = configuration.service_configuration @@ - if not configuration.customization.agent_card_config: - raise ValueError("Agent card configuration not found") - - config = configuration.customization.agent_card_config + customization_cfg = configuration.customization + if customization_cfg is None or not customization_cfg.agent_card_config: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Agent card configuration not found", + ) + + config = customization_cfg.agent_card_configSupport import (near the top):
-from fastapi import APIRouter, Depends, Request +from fastapi import APIRouter, Depends, Request, HTTPException, statusThis aligns with the FastAPI endpoint error-handling guideline and cleans up the
reportOptionalMemberAccessonagent_card_config. As per coding guidelines, and echoing the earlier suggestion from the prior review.Check FastAPI's recommended pattern for raising internal configuration errors from within helper functions used by endpoints (e.g., HTTPException with 5xx status codes).
🧹 Nitpick comments (1)
src/app/endpoints/a2a.py (1)
788-802: Use the a2a-sdk's runtime version instead of hardcoding.The a2a-sdk Python package does expose
__version__in itssrc/a2a/__init__.pymodule, making it accessible at runtime. The current hardcoded value of"0.2.1"is outdated (latest release is 0.3.15 as of November 2025) and will drift further out of sync with package updates. Replace the hardcoded string withfrom a2a import __version__and reference it directly in the response dictionary to keep the health check endpoint in sync with the actual installed version.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
lightspeed-stack.yaml(1 hunks)pyproject.toml(2 hunks)src/app/endpoints/a2a.py(1 hunks)src/app/endpoints/query.py(3 hunks)src/app/endpoints/streaming_query.py(1 hunks)src/app/routers.py(2 hunks)src/constants.py(1 hunks)src/models/config.py(5 hunks)tests/unit/app/test_routers.py(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
- src/app/endpoints/streaming_query.py
- tests/unit/app/test_routers.py
- src/app/endpoints/query.py
- src/app/routers.py
- src/constants.py
- pyproject.toml
- src/models/config.py
- lightspeed-stack.yaml
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/app/endpoints/a2a.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/a2a.py
🧠 Learnings (6)
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (1)
src/app/endpoints/a2a.py (6)
src/authentication/__init__.py (1)
get_auth_dependency(14-60)src/authorization/middleware.py (1)
authorize(110-121)src/models/requests.py (1)
QueryRequest(73-233)src/app/endpoints/query.py (1)
select_model_and_provider_id(464-532)src/client.py (2)
AsyncLlamaStackClientHolder(18-55)get_client(49-55)src/utils/mcp_headers.py (1)
mcp_headers_dependency(15-26)
🪛 GitHub Actions: Pydocstyle
src/app/endpoints/a2a.py
[error] 101-101: pydocstyle D107: Missing docstring in init
[error] 610-610: pydocstyle D401: First line should be in imperative mood; perhaps 'Main' (found in function 'handle_a2a_jsonrpc')
[error] 720-720: pydocstyle D401: First line should be in imperative mood (perhaps 'Generate', not 'Generator')
🪛 GitHub Actions: Pyright
src/app/endpoints/a2a.py
[error] 195-195: Argument of type "str | None" cannot be assigned to parameter "task_id" of type "str" in function "init".
[error] 195-195: Argument of type "str | None" cannot be assigned to parameter "context_id" of type "str" in function "init".
[error] 257-257: Argument of type "str | None" cannot be assigned to parameter "key" of type "str" in function "get".
[error] 265-265: Arguments missing for parameters "system_prompt", "attachments", "no_tools", "generate_topic_summary", "media_type".
[error] 290-290: Argument of type "str | None" cannot be assigned to parameter "key" of type "str" in function "setitem".
[error] 304-304: Argument of type "str | None" cannot be assigned to parameter "task_id" of type "str" in function "init".
[error] 309-309: Argument of type "str | None" cannot be assigned to parameter "context_id" of type "str" in function "init".
[error] 380-380: Argument of type "str | None" cannot be assigned to parameter "task_id" of type "str" in function "init".
[error] 390-390: Argument of type "str | None" cannot be assigned to parameter "context_id" of type "str" in function "init".
[error] 400-400: Argument of type "str | None" cannot be assigned to parameter "task_id" of type "str" in function "init".
[error] 402-402: Argument of type "str | None" cannot be assigned to parameter "context_id" of type "str" in function "init".
[error] 415-415: Argument of type "str | None" cannot be assigned to parameter "task_id" of type "str" in function "init".
[error] 425-425: Argument of type "str | None" cannot be assigned to parameter "context_id" of type "str" in function "init".
[error] 438-438: Argument of type "str | None" cannot be assigned to parameter "task_id" of type "str" in function "init".
[error] 448-448: Argument of type "str | None" cannot be assigned to parameter "context_id" of type "str" in function "init".
[error] 492-492: "agent_card_config" is not a known attribute of None (reportOptionalMemberAccess).
[error] 495-495: "agent_card_config" is not a known attribute of None (reportOptionalMemberAccess).
[error] 669-669: Cannot access attribute "copy" for class "Scope". Attribute "copy" is unknown (reportAttributeAccessIssue).
[error] 757-757: Argument of type "CoroutineType[Any, Any, Any]" cannot be assigned to parameter "content" of type "ContentStream" in function "init".
🪛 GitHub Actions: Python linter
src/app/endpoints/a2a.py
[error] 89-89: pylint: W0511 TODO: Handle image content if needed (fixme)
[error] 207-207: pylint: W0718 Catching too general exception (broad-exception-caught)
[error] 216-216: pylint: W0718 Catching too general exception (broad-exception-caught)
🪛 GitHub Actions: Type checks
src/app/endpoints/a2a.py
[error] 101-101: Mypy error during step 'uv run mypy ...': Function is missing a return type annotation. Use '-> None' if function does not return a value. [no-untyped-def]
[error] 195-195: Mypy error during step 'uv run mypy ...': Argument 2 to "TaskUpdater" has incompatible type "str | None"; expected "str". [arg-type]
[error] 195-195: Mypy error during step 'uv run mypy ...': Argument 3 to "TaskUpdater" has incompatible type "str | None"; expected "str". [arg-type]
[error] 257-257: Mypy error during step 'uv run mypy ...': Argument 1 to "get" of "dict" has incompatible type "str | None"; expected "str". [arg-type]
[error] 290-290: Mypy error during step 'uv run mypy ...': Invalid index type "str | None" for "dict[str, str]"; expected type "str". [index]
[error] 298-298: Mypy error during step 'uv run mypy ...': Call to untyped function "TaskResultAggregator" in typed context. [no-untyped-call]
[error] 304-304: Mypy error during step 'uv run mypy ...': Argument "task_id" to "TaskStatusUpdateEvent" has incompatible type "str | None"; expected "str". [arg-type]
[error] 309-309: Mypy error during step 'uv run mypy ...': Argument "context_id" to "TaskStatusUpdateEvent" has incompatible type "str | None"; expected "str". [arg-type]
[error] 380-380: Mypy error during step 'uv run mypy ...': Argument "task_id" to "TaskStatusUpdateEvent" has incompatible type "str | None"; expected "str". [arg-type]
[error] 390-390: Mypy error during step 'uv run mypy ...': Argument "context_id" to "TaskStatusUpdateEvent" has incompatible type "str | None"; expected "str". [arg-type]
[error] 400-400: Mypy error during step 'uv run mypy ...': Argument "task_id" to "TaskArtifactUpdateEvent" has incompatible type "str | None"; expected "str". [arg-type]
[error] 402-402: Mypy error during step 'uv run mypy ...': Argument "context_id" to "TaskArtifactUpdateEvent" has incompatible type "str | None"; expected "str". [arg-type]
[error] 415-415: Mypy error during step 'uv run mypy ...': Argument "task_id" to "TaskStatusUpdateEvent" has incompatible type "str | None"; expected "str". [arg-type]
[error] 425-425: Mypy error during step 'uv run mypy ...': Argument "context_id" to "TaskStatusUpdateEvent" has incompatible type "str | None"; expected "str". [arg-type]
[error] 438-438: Mypy error during step 'uv run mypy ...': Argument "task_id" to "TaskStatusUpdateEvent" has incompatible type "str | None"; expected "str". [arg-type]
[error] 448-448: Mypy error during step 'uv run mypy ...': Argument "context_id" to "TaskStatusUpdateEvent" has incompatible type "str | None"; expected "str". [arg-type]
[error] 675-675: Mypy error during step 'uv run mypy ...': Function is missing a return type annotation. Use '-> None' if function does not return a value. [no-untyped-def]
🪛 GitHub Check: Bandit
src/app/endpoints/a2a.py
[notice] 638-638:
Possible hardcoded password: ''
🔇 Additional comments (3)
src/app/endpoints/a2a.py (3)
603-785: I'll help you verify the review comment. Let me start by investigating the codebase and confirming the issues identified.
<function_calls>
#!/bin/bashFind and inspect the a2a.py file
fd -n "a2a.py" --type f | head -20
</function_calls>
#!/bin/bashCheck the file size and read the specific lines mentioned in the review
wc -l src/app/endpoints/a2a.py
</function_calls>
<function_calls>
#!/bin/bashRead the function handle_a2a_jsonrpc from lines 603-785
sed -n '603,785p' src/app/endpoints/a2a.py
</function_calls>
<function_calls>
#!/bin/bashCheck project requirements to understand dependencies
find . -name "requirements*.txt" -o -name "pyproject.toml" -o -name "setup.py" | head -10
</function_calls>
<function_calls>
#!/bin/bashRead the pyproject.toml to check FastAPI and Starlette versions
cat pyproject.toml | head -100
</function_calls>
<function_calls>
Starlette StreamingResponse AsyncIterator bytes content type specification
</function_calls>
191-221: I encountered a repository cloning issue, but I was able to verify key technical details through the web search. Let me provide my assessment:Verification Results:
✅ APIConnectionError exists and is correctly namespaced — The web search confirms that
llama_stack_client.APIConnectionErroris the proper exception class for network connectivity failures in the Llama Stack Python client. The import approach (import llama_stack_client) and usage pattern shown in the review are correct.✅ Error handling pattern is validated — The recommendation to wrap
retrieve_response()in a try-except block catchingAPIConnectionErroraligns with documented SDK practices and the coding guidelines forsrc/**/{client,app/endpoints/**}.pyfiles.
⚠️ Unable to verify remaining issues — The repository clone failed, preventing direct inspection of:
- The actual type annotations for
task_id/context_idinRequestContext- Current
QueryRequestconstructor signature and required parameters- The exact exception handlers at lines 207 and 216
- Whether the changes align with the actual current code structure
Assessment:
Since I cannot access the repository directly to validate:
- Whether
context.task_idandcontext.context_idare actuallystr | None- The exact signature of
QueryRequestand what parameters it requires- The current state of exception handling blocks
...I need to request manual verification or an alternative approach to complete this task.
Would you be able to:
- Share the current content of
src/app/endpoints/a2a.py(or at least lines 191-250), or- Provide access to the repository in a way that allows cloning?
Alternatively, I can mark this for verification pending direct code inspection.
548-572: Based on the web search results, I've found important information about FastAPI best practices. Let me now analyze what this means for the review comment:Key Finding from Web Search:
The FastAPI best practices indicate that:
- Do NOT wrap internal errors in HTTPException at the endpoint level — instead, let unexpected exceptions bubble up to middleware/global exception handlers
- Use a global exception handler to catch exceptions, log safely, and return sanitized 5xx responses
- Follow RFC 9457 (Problem Details) for structured error responses with request-id correlation
- HTTPException is primarily for expected/handled cases (usually 4xx), not for wrapping unexpected internal errors
This is fundamentally different from what the review suggests. The review recommends catching generic
Exceptionand raisingHTTPException(status_code=500, ...)at the endpoint level, which would:
- Bypass any global exception handler
- Prevent proper centralized logging and observability
- Miss opportunity for structured error tracking
The review's suggestion actually conflicts with FastAPI best practices documented in the web search results.
Let me verify the coding guidelines one more time to see if they provide additional context:
Looking back at the provided coding guidelines:
src/app/endpoints/**/*.py: "Use FastAPIHTTPExceptionwith appropriate status codes for API endpoint error handling"This guideline is somewhat ambiguous but in context of FastAPI best practices, it should mean: use
HTTPExceptionfor expected errors that should result in 4xx or specific 5xx responses, not for catching and wrapping generic exceptions.
Rewritten Review Comment:
Reconsider generic exception wrapping in
get_agent_card—FastAPI best practices suggest letting unexpected errors bubble to global handlers instead.The current code catches and re-raises a generic
Exception, which bubbles up as an unstructured 500. The suggested diff wraps this inHTTPException(500)at the endpoint level, but FastAPI conventions recommend:
- Let unexpected exceptions bubble naturally to a global exception handler (via
@app.exception_handler(Exception))- Use HTTPException primarily for expected/handled errors (4xx cases and specific error conditions)
- Centralize error logging, sanitization, and observability in a global handler to avoid bypassing middleware and prevent leaking internal details
Instead of wrapping at the endpoint, ensure the app has a global exception handler that:
- Logs the full traceback and request context server-side
- Returns a sanitized RFC 9457 problem-details JSON response with a request-id
- Captures critical errors to observability tools (Sentry, etc.)
If
get_lightspeed_agent_card()throws expected errors that should map to specific 5xx codes (e.g., 503 for upstream unavailability), handle those explicitly; otherwise, let generic exceptions propagate.Verify whether the app already has global exception handlers in place before applying endpoint-level error wrapping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (1)
src/app/endpoints/a2a.py (1)
88-90: Address the TODO comment to fix pylint warning.The pipeline fails with
W0511 TODO: Handle image content if needed. As discussed in a previous review, replace the TODO with an explanatory note to satisfy the linter while documenting the intentional omission.if item.type == "text": parts.append(Part(root=TextPart(text=item.text))) - # TODO: Handle image content if needed + # Note: Image content is not currently handled; extend this branch if needed.
🧹 Nitpick comments (6)
src/app/endpoints/a2a.py (1)
806-820: Minor: Consider usingtimezone.utcfor consistent timestamp formatting.The health check uses
datetime.now().isoformat()without timezone information, while other timestamps in this file usedatetime.now(timezone.utc).isoformat(). For consistency across the API, consider adding timezone info.return { "status": "healthy", "service": "lightspeed-a2a", "version": __version__, "a2a_sdk_version": "0.2.1", - "timestamp": datetime.now().isoformat(), + "timestamp": datetime.now(timezone.utc).isoformat(), }tests/unit/app/endpoints/test_streaming_query.py (1)
372-757: Vector store mocks and RAG expectations align with the new API shapeThe
retrieve_response_*tests here now:
- Build vector store mocks via
mock_vector_db.id = "VectorDB-1"andmock_client.vector_stores.list.return_value = mocker.Mock(data=[mock_vector_db])/Mock(data=[]).- Assert
toolgroupsusingget_rag_toolgroups(["VectorDB-1"])orNoneas appropriate.This matches the updated production behavior (vector stores instead of vector DBs) and keeps the RAG wiring and shield cases well covered. Only tiny nit: naming these locals
mock_vector_storeinstead ofmock_vector_dbwould better reflect the new semantics, but that’s purely cosmetic.tests/unit/app/endpoints/test_query.py (2)
531-653: Non‑streaming retrieve_response tests correctly target vector stores and RAG toolgroupsThe
test_retrieve_response_no_returned_message,*_message_without_content, and*_vector_db_availabletests now:
- Mock
mock_client.vector_stores.list.return_value = mocker.Mock(data=[mock_vector_db])withmock_vector_db.id = "VectorDB-1".- Expect
toolgroups=get_rag_toolgroups(["VectorDB-1"])when a vector store is present.This matches the new non‑streaming RAG behavior and keeps the fallback/empty‑content paths intact. As in the streaming tests, you might eventually rename
mock_vector_db→mock_vector_storefor clarity, but that’s optional.
1420-1432: get_rag_toolgroups test now validates vector_store_ids correctly
test_get_rag_toolgroupsnow asserts that:
- An empty list yields
None.- A non‑empty list leads to a single toolgroup named
"builtin::rag/knowledge_search"withargs["vector_store_ids"]equal to the input list.That’s exactly the behavior expected from the updated
get_rag_toolgroupsimplementation. The local variable is still calledvector_db_ids; consider renaming tovector_store_idsfor consistency with the production signature.src/app/endpoints/query.py (2)
736-745: Vector store–based RAG integration inretrieve_responselooks correctThe new block:
- Fetches vector stores via
await client.vector_stores.list(), derivesvector_store_ids = [vector_store.id ...], and- Builds
toolgroups = (get_rag_toolgroups(vector_store_ids) or []) + [mcp_server.name ...], later normalizing an empty list toNone.This is consistent with the tests and ensures:
- When there are no vector stores, RAG is effectively disabled and only MCP toolgroups (if any) are passed.
- When
no_tools=True, this block is skipped entirely, so you correctly avoid both RAG and MCP headers/toolgroups.One thing to keep in mind operationally is that this adds an extra remote call (
vector_stores.list) on the hot path; if that ever becomes a bottleneck, caching the IDs or reusing the response across calls would be a reasonable follow‑up.
832-859: Updateget_rag_toolgroupsdocstring/terminology to match vector store usageThe implementation and type signature now correctly use
vector_store_idsand setargs["vector_store_ids"], but the docstring still talks about “vector DB list” and “vector database IDs”. That’s slightly misleading given the API shift.Suggest tightening the docstring and parameter description to say “vector stores” everywhere, e.g.:
-def get_rag_toolgroups( - vector_store_ids: list[str], -) -> list[Toolgroup] | None: - """ - Return a list of RAG Tool groups if the given vector DB list is not empty. -… - Parameters: - vector_store_ids (list[str]): List of vector database identifiers to include in the toolgroup. -… - list[Toolgroup] | None: A list with a single RAG toolgroup if - vector_store_ids is non-empty; otherwise, None. - """ +def get_rag_toolgroups( + vector_store_ids: list[str], +) -> list[Toolgroup] | None: + """ + Return a list of RAG tool groups if the given vector store list is not empty. +… + Parameters: + vector_store_ids (list[str]): List of vector store identifiers to include in the toolgroup. +… + list[Toolgroup] | None: A list with a single RAG toolgroup if + vector_store_ids is non-empty; otherwise, None. + """(And optionally align test variable names with this terminology.)
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (5)
src/app/endpoints/a2a.py(1 hunks)src/app/endpoints/query.py(3 hunks)tests/unit/app/endpoints/test_query.py(16 hunks)tests/unit/app/endpoints/test_streaming_query.py(12 hunks)tests/unit/models/config/test_dump_configuration.py(3 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
tests/{unit,integration}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests; do not use unittest framework
Unit tests must achieve 60% code coverage; integration tests must achieve 10% coverage
Files:
tests/unit/app/endpoints/test_query.pytests/unit/app/endpoints/test_streaming_query.pytests/unit/models/config/test_dump_configuration.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
pytest-mockwith AsyncMock objects for mocking in tests
Files:
tests/unit/app/endpoints/test_query.pytests/unit/app/endpoints/test_streaming_query.pytests/unit/models/config/test_dump_configuration.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/app/endpoints/query.pysrc/app/endpoints/a2a.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/query.pysrc/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/query.pysrc/app/endpoints/a2a.py
🧠 Learnings (6)
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
🪛 GitHub Actions: Pydocstyle
src/app/endpoints/a2a.py
[error] 102-102: pydocstyle: D107 - Missing docstring in init
[error] 628-628: pydocstyle: D401 - First line should be in imperative mood; found 'Main'
[error] 738-738: pydocstyle: D401 - First line should be in imperative mood (perhaps 'Generate', not 'Generator')
🪛 GitHub Actions: Pyright
src/app/endpoints/a2a.py
[error] 278-278: Step 'uv run pyright src' failed: Arguments missing for parameters "system_prompt", "attachments", "no_tools", "generate_topic_summary", "media_type" (reportCallIssue)
[error] 510-510: Step 'uv run pyright src' failed: "agent_card_config" is not a known attribute of "None" (reportOptionalMemberAccess)
[error] 513-513: Step 'uv run pyright src' failed: "agent_card_config" is not a known attribute of "None" (reportOptionalMemberAccess)
[error] 687-687: Step 'uv run pyright src' failed: Cannot access attribute "copy" for class "Scope". Attribute "copy" is unknown (reportAttributeAccessIssue)
[error] 775-775: Step 'uv run pyright src' failed: Argument of type "CoroutineType[Any, Any, Any]" cannot be assigned to parameter "content" of type "ContentStream" in function "init". Type "CoroutineType[Any, Any, Any]" is not assignable to type "ContentStream". "CoroutineType[Any, Any, Any]" is incompatible with protocol "AsyncIterable[Content]". "aiter" is not present (reportArgumentType)
🪛 GitHub Actions: Python linter
src/app/endpoints/a2a.py
[warning] 90-90: Pylint: W0511 TODO: Handle image content if needed (fixme)
[warning] 214-214: Pylint: W0718 Catching too general exception (broad-exception-caught)
[warning] 223-223: Pylint: W0718 Catching too general exception (broad-exception-caught)
🪛 GitHub Check: Bandit
src/app/endpoints/a2a.py
[notice] 638-638:
Possible hardcoded password: ''
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: build-pr
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
- GitHub Check: e2e_tests (azure)
- GitHub Check: e2e_tests (ci)
🔇 Additional comments (7)
tests/unit/models/config/test_dump_configuration.py (1)
100-189: base_url field expectation correctly aligned with new ServiceConfiguration schemaThe added
"base_url": Noneentries in all threeservicesections keep these golden JSON expectations in sync with the newServiceConfiguration.base_urlfield and its default. The placement and value look correct, and there are no regressions introduced here.Also applies to: 400-504, 585-683
src/app/endpoints/a2a.py (1)
278-283: I'm unable to access the repository to verify the QueryRequest signature due to persistent cloning failures. Without being able to examine:
- The
QueryRequestmodel definition and its parameter requirements- The imports in
src/app/endpoints/a2a.py- Other usages of
QueryRequestin the codebase- Whether the parameters mentioned have default values
I cannot definitively confirm or refute the pyright error about missing arguments.
Cannot verify - insufficient access to codebase. The repository cloning mechanism is unavailable, preventing verification of the
QueryRequestsignature and parameter requirements. Manual verification of the model definition, parameter defaults, and actual type checking results is required to determine if missing arguments need to be provided at lines 278-283 insrc/app/endpoints/a2a.py.tests/unit/app/endpoints/test_streaming_query.py (1)
1505-1612: no_tools streaming flows correctly bypass RAG and MCP despite vector store changesThe tests for
test_retrieve_response_no_tools_bypasses_mcp_and_ragandtest_retrieve_response_no_tools_false_preserves_functionalityconfirm that:
- With
no_tools=True,agent.extra_headersis cleared andtoolgroups=None, so MCP and RAG (via vector stores) are fully bypassed.- With
no_tools=False,toolgroupscombinesget_rag_toolgroups(["VectorDB-1"])with MCP server names.This gives good confidence that the new vector store–based RAG path hasn’t regressed the no-tools behavior for streaming queries.
tests/unit/app/endpoints/test_query.py (4)
655-811: Shield‑selection tests remain valid after switching to vector storesFor the one/two/four‑shield scenarios, you now mock
vector_stores.listto returnMock(data=[]), so:
get_rag_toolgroupscontributes nothing,- Shield selection logic is still verified purely via
available_input_shields/available_output_shieldsandget_agentcall expectations.This keeps shield behavior well isolated from RAG/vector store changes.
886-1005: Attachment handling tests unaffected and compatible with vector store changesThe attachment‑focused
retrieve_responsetests now also patchmock_client.vector_stores.list.return_value = mocker.Mock(data=[]), ensuring:
- Attachments →
documentsmapping is still what’s asserted,- RAG/toolgroups remain
Noneso the assertions stay independent of vector store presence.This is a good separation of concerns for attachment behavior.
1125-1361: MCP + vector store interaction tests correctly reflect new toolgroup constructionThe MCP tests (
*_with_mcp_servers,*_empty_token,*_and_mcp_headers) now:
- Mock
vector_stores.listto return an emptydatalist, so RAG contributes no toolgroups.- Assert
toolgroups=[mcp_server.name for mcp_server in mcp_servers], and verifyextra_headers["X-LlamaStack-Provider-Data"]encodes only the MCP headers.This matches the production behavior where RAG is optional and MCP toolgroups are appended on top of any RAG toolgroups.
1645-1761: no_tools behavior for non‑streaming queries stays intact with vector storesThe
test_retrieve_response_no_tools_bypasses_mcp_and_ragand*_no_tools_false_preserves_functionalitytests now:
- Mock a single vector store with
.id = "VectorDB-1"and correspondingvector_stores.list.- For
no_tools=True, assertextra_headers == {}andtoolgroups=None, confirming RAG and MCP are both bypassed.- For
no_tools=False, assert MCP headers are set andtoolgroups = get_rag_toolgroups(["VectorDB-1"]) + ["filesystem-server"].These checks give good coverage that the new vector store–based RAG path coexists correctly with the no‑tools flag and MCP integration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (2)
src/app/endpoints/a2a.py (2)
68-96: Replace TODO with explicit comment to satisfy pylint and clarify behaviorThe
# TODO: Handle image content if neededtriggers pylint (W0511) and leaves the current behavior unclear. Since images are currently ignored, make that explicit without using a TODO.elif isinstance(content, list): for item in content: if hasattr(item, "type"): if item.type == "text": parts.append(Part(root=TextPart(text=item.text))) - # TODO: Handle image content if needed + # Note: image content is currently ignored; extend this branch if image + # handling is required in the future. elif isinstance(item, str): parts.append(Part(root=TextPart(text=item)))
99-154: Add__init__docstring and tighten TaskResultAggregator typing
TaskResultAggregator.__init__is missing a docstring (pydocstyle D107), and_task_statelacks an explicit type annotation. You can also narrow the event type from bareAnyto the A2A event types you handle.class TaskResultAggregator: """Aggregates the task status updates and provides the final task state.""" def __init__(self) -> None: - self._task_state = TaskState.working - self._task_status_message: Message | None = None + """Initialize the task result aggregator with default working state.""" + self._task_state: TaskState = TaskState.working + self._task_status_message: Message | None = None - def process_event(self, event: Any) -> None: + def process_event( + self, + event: TaskStatusUpdateEvent | TaskArtifactUpdateEvent | Any, + ) -> None: @@ @property def task_status_message(self) -> Any: """Return the current task status message.""" return self._task_status_message
🧹 Nitpick comments (3)
src/models/config.py (1)
223-224: Complete the comment and consider URL validation.The comment "if unset, code may fallback" is incomplete—fallback to what? Also, if
base_urlis expected to be a valid HTTP/HTTPS URL, consider usingAnyHttpUrlfor validation (already imported on line 19).- # Externally reachable base URL for the service; if unset, code may fallback - base_url: Optional[str] = None + # Externally reachable base URL for the service; if unset, derived from host:port + base_url: Optional[AnyHttpUrl] = Nonesrc/app/endpoints/query.py (1)
832-858: Update docstring to reflect the parameter rename.The docstring on line 836 still references "vector DB list" but the parameter is now
vector_store_ids. Update for consistency.def get_rag_toolgroups( vector_store_ids: list[str], ) -> list[Toolgroup] | None: """ - Return a list of RAG Tool groups if the given vector DB list is not empty. + Return a list of RAG Tool groups if the given vector store list is not empty. Generate a list containing a RAG knowledge search toolgroup if - vector database IDs are provided. + vector store IDs are provided. Parameters: vector_store_ids (list[str]): List of vector database identifiers to include in the toolgroup.src/app/endpoints/a2a.py (1)
159-179: AnnotateLightspeedAgentExecutor.__init__and instance attributesTo align with the project guidelines (typed constructors and attributes), add an explicit return type to
__init__and annotate the instance attributes.def __init__( self, auth_token: str, mcp_headers: dict[str, dict[str, str]] | None = None - ): - """ - Initialize the Lightspeed agent executor. - - Args: - auth_token: Authentication token for the request - mcp_headers: MCP headers for context propagation - """ - self.auth_token = auth_token - self.mcp_headers = mcp_headers or {} + ) -> None: + """Initialize the Lightspeed agent executor.""" + self.auth_token: str = auth_token + self.mcp_headers: dict[str, dict[str, str]] = mcp_headers or {}
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (12)
lightspeed-stack.yaml(1 hunks)pyproject.toml(2 hunks)src/app/endpoints/a2a.py(1 hunks)src/app/endpoints/query.py(3 hunks)src/app/endpoints/streaming_query.py(1 hunks)src/app/routers.py(2 hunks)src/constants.py(1 hunks)src/models/config.py(5 hunks)tests/unit/app/endpoints/test_query.py(16 hunks)tests/unit/app/endpoints/test_streaming_query.py(12 hunks)tests/unit/app/test_routers.py(5 hunks)tests/unit/models/config/test_dump_configuration.py(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- tests/unit/app/endpoints/test_streaming_query.py
- src/app/endpoints/streaming_query.py
- tests/unit/app/endpoints/test_query.py
- lightspeed-stack.yaml
- tests/unit/app/test_routers.py
🧰 Additional context used
📓 Path-based instructions (9)
tests/{unit,integration}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests; do not use unittest framework
Unit tests must achieve 60% code coverage; integration tests must achieve 10% coverage
Files:
tests/unit/models/config/test_dump_configuration.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
pytest-mockwith AsyncMock objects for mocking in tests
Files:
tests/unit/models/config/test_dump_configuration.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/constants.pysrc/models/config.pysrc/app/endpoints/a2a.pysrc/app/routers.pysrc/app/endpoints/query.py
src/**/constants.py
📄 CodeRabbit inference engine (CLAUDE.md)
Define shared constants in central
constants.pyfile with descriptive comments
Files:
src/constants.py
pyproject.toml
📄 CodeRabbit inference engine (CLAUDE.md)
pyproject.toml: Configure pylint withsource-roots = "src"
Excludesrc/auth/k8s.pyfrom pyright type checking
Files:
pyproject.toml
src/models/config.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/config.py: All configuration must use Pydantic models extendingConfigurationBasewithextra="forbid"to reject unknown fields
Use type hintsOptional[FilePath],PositiveInt,SecretStrfor Pydantic configuration models
Files:
src/models/config.py
src/models/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/**/*.py: Use@field_validatorand@model_validatorfor custom validation in Pydantic models
Pydantic configuration classes must extendConfigurationBase; data models must extendBaseModel
Files:
src/models/config.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/a2a.pysrc/app/endpoints/query.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/a2a.pysrc/app/endpoints/query.py
🧠 Learnings (7)
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Always check `pyproject.toml` for existing dependencies and versions before adding new ones
Applied to files:
pyproject.toml
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (3)
src/models/config.py (1)
src/utils/checks.py (1)
file_check(38-57)
src/app/endpoints/a2a.py (3)
src/models/requests.py (1)
QueryRequest(73-233)src/app/endpoints/query.py (2)
select_model_and_provider_id(464-532)retrieve_response(640-799)src/client.py (2)
AsyncLlamaStackClientHolder(18-55)get_client(49-55)
src/app/routers.py (1)
tests/unit/app/test_routers.py (1)
include_router(39-54)
🪛 GitHub Actions: Pydocstyle
src/app/endpoints/a2a.py
[error] 102-102: D107: Missing docstring in init
[error] 628-628: D401: First line should be in imperative mood; try rephrasing (found 'Main')
[error] 738-738: D401: First line should be in imperative mood (perhaps 'Generate', not 'Generator')
🪛 GitHub Actions: Pyright
src/app/endpoints/a2a.py
[error] 278-278: pyright: Arguments missing for parameters "system_prompt", "attachments", "no_tools", "generate_topic_summary", "media_type" (reportCallIssue)
[error] 510-510: "agent_card_config" is not a known attribute of "None" (reportOptionalMemberAccess)
[error] 513-513: "agent_card_config" is not a known attribute of "None" (reportOptionalMemberAccess)
[error] 687-687: Cannot access attribute "copy" for class "Scope". Attribute "copy" is unknown (reportAttributeAccessIssue)
[error] 775-775: Argument of type "CoroutineType[Any, Any, Any]" cannot be assigned to parameter "content" of type "ContentStream" in function "init"
🪛 GitHub Actions: Python linter
src/app/endpoints/a2a.py
[warning] 90-90: TODO: Handle image content if needed (fixme) (W0511)
[warning] 214-214: Catching too general exception (broad-exception-caught) (W0718)
[warning] 223-223: Catching too general exception (broad-exception-caught) (W0718)
🪛 GitHub Check: Bandit
src/app/endpoints/a2a.py
[notice] 638-638:
Possible hardcoded password: ''
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: build-pr
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
- GitHub Check: e2e_tests (ci)
- GitHub Check: e2e_tests (azure)
🔇 Additional comments (13)
pyproject.toml (1)
57-58: LGTM!The PyYAML dependency is properly version-pinned and aligns with the existing
types-PyYAMLin dev dependencies for type checking support.src/models/config.py (4)
8-8: LGTM!The
yamlimport is now at module level as recommended in the past review.
461-466: LGTM!The A2A action enum values follow the existing naming convention and are properly documented.
594-595: LGTM!The field declarations follow existing patterns with proper type annotations.
608-624: LGTM!The YAML loading logic addresses the past review feedback with proper error handling for both
yaml.YAMLErrorandOSError. Thechecks.file_checkvalidates file existence and readability upfront.tests/unit/models/config/test_dump_configuration.py (3)
105-105: LGTM!Test expectations correctly updated to include the new
base_urlfield in the serialized service configuration.
405-405: LGTM!Consistent update to test expectations for the quota limiters configuration test.
590-590: LGTM!Consistent update to test expectations for the BYOK RAG configuration test.
src/constants.py (1)
5-5: I'll help you verify the version compatibility concern. Let me start by examining the repository structure and checking the current state of the dependencies and code.
<function_calls>
#!/bin/bashFind pyproject.toml and check current dependencies
find . -name "pyproject.toml" -type f | head -5
</function_calls>Now let me check the pyproject.toml file to see the current pinned versions:
<function_calls>
#!/bin/bashRead pyproject.toml to check llama-stack dependencies
cat pyproject.toml | grep -A 30 "dependencies"
</function_calls>Let me also check the query.py file to see how vector_stores is being used:
<function_calls>
#!/bin/bashFind query.py and check vector_stores usage
find . -name "query.py" -type f
</function_calls>Let me search for vector_stores usage in the codebase:
<function_calls>
#!/bin/bashSearch for vector_stores usage
rg "vector_stores" -n -C 3
</function_calls>Now let me verify the llama-stack-client API availability by checking documentation or release notes:
<function_calls>
llama-stack-client 0.2.22 vector_stores API
</function_calls><function_calls>
llama-stack-client 0.3.0 release notes API changes
</function_calls><function_calls>
llama-stack-client PyPI versions changelog
</function_calls>src/app/routers.py (1)
25-26: A2A router integration and unversioned prefix look consistentImporting
a2awith the other endpoint modules and includinga2a.routerwithout a versioned prefix aligns with existing unversioned endpoints (health/metrics) and matches the described A2A exposure. No issues from a routing or ordering perspective.Also applies to: 60-61
src/app/endpoints/a2a.py (3)
806-820: A2A health endpoint looks straightforward and aligned with expectationsThe health check returns simple string metadata (status, service name, version, SDK version, timestamp) and is fully typed and documented. This is a good fit for basic readiness/liveness checks.
621-777: I'll verify the issues raised in the review comment by checking the codebase and Starlette documentation.
<function_calls>
find . -name requirements.txt -o -name setup.py -o -name pyproject.toml | head -5
</function_calls>
<function_calls>
cat pyproject.toml | grep -A 20 "[project]" | grep -i starlette
</function_calls>Let me search for dependency information more directly:
<function_calls>
cat pyproject.toml | grep -i starlette
</function_calls>Now let me examine the file in question and understand the context better:
<function_calls>
wc -l src/app/endpoints/a2a.py
</function_calls><function_calls>
sed -n '621,777p' src/app/endpoints/a2a.py | head -100
</function_calls>Let me get the exact docstring and relevant code sections:
<function_calls>
sed -n '625,635p' src/app/endpoints/a2a.py
</function_calls><function_calls>
sed -n '655,670p' src/app/endpoints/a2a.py
</function_calls><function_calls>
sed -n '700,730p' src/app/endpoints/a2a.py
</function_calls>Let me check for imports to understand type usage:
<function_calls>
head -50 src/app/endpoints/a2a.py | grep -E "^import|^from"
</function_calls>Now let me search for the web query about Starlette:
<function_calls>
Starlette 0.47.x StreamingResponse content parameter accepted types AsyncIterator
</function_calls>Let me also check the actual types from Starlette documentation:
<function_calls>
Starlette StreamingResponse signature content types async iterator bytes
</function_calls>Let me check the FastAPI/Starlette imports and see what types are being used:
<function_calls>
grep -n "StreamingResponse|AsyncIterator|AsyncGenerator" src/app/endpoints/a2a.py
</function_calls><function_calls>
grep -n "from typing|from collections" src/app/endpoints/a2a.py
</function_calls>Let me look at the full imports section:
<function_calls>
sed -n '1,30p' src/app/endpoints/a2a.py
</function_calls>Now let me verify the docstring issue by checking pydocstyle rules:
<function_calls>
pydocstyle D401 rule imperative mood docstring "Main" vs "Handle"
</function_calls>Let me also verify the scope.copy() issue with Starlette Request:
<function_calls>
python3 << 'EOF'Check if dict() copy works for ASGI scope TypedDict
Starlette uses Scope TypedDict which is immutable
Using .copy() on TypedDict is not valid in Pyright
Using dict() creates a new dict from the TypedDict which is valid
print("dict(typed_dict) is the correct way to copy a TypedDict in Pyright")
print("typed_dict.copy() would fail: TypedDict has no 'copy' method")
EOF
</function_calls>Let me search for the response_generator function in the provided code section:
<function_calls>
sed -n '720,750p' src/app/endpoints/a2a.py
</function_calls>Let me check the full section with response_generator:
<function_calls>
sed -n '718,770p' src/app/endpoints/a2a.py
</function_calls>Let me verify by checking the actual code structure around the response_generator function:
<function_calls>
sed -n '708,777p' src/app/endpoints/a2a.py
</function_calls>Let me check the complete function signature and return statement:
<function_calls>
sed -n '621,640p' src/app/endpoints/a2a.py
</function_calls><function_calls>
sed -n '770,777p' src/app/endpoints/a2a.py
</function_calls>Let me verify the imports include AsyncIterator:
<function_calls>
grep "AsyncIterator" src/app/endpoints/a2a.py
</function_calls>Let me check typing imports more thoroughly:
<function_calls>
sed -n '1,40p' src/app/endpoints/a2a.py
</function_calls>Now let me verify the exact issues with a comprehensive check:
<function_calls>
python3 << 'EOF'Verify the three main issues:
Issue 1: Pydocstyle D401 - Docstring should use imperative mood, not descriptive
"Main A2A JSON-RPC endpoint..." is descriptive
"Handle A2A JSON-RPC requests..." is imperative
print("Issue 1 - D401 Docstring mood:")
print(" Original: 'Main A2A JSON-RPC endpoint...' (descriptive, violates D401)")
print(" Fixed: 'Handle A2A JSON-RPC requests...' (imperative, correct)")Issue 2: Pyright error - Scope TypedDict has no copy() method
print("\nIssue 2 - scope.copy():")
print(" Problem: request.scope is a Scope TypedDict, which has no copy() method")
print(" Solution: dict(request.scope) creates a new dict from the TypedDict")Issue 3: StreamingResponse content type
print("\nIssue 3 - StreamingResponse content type:")
print(" Problem: response_generator() returns a coroutine, not an async iterable")
print(" Solution: Type hint as AsyncIterator[bytes] and use yield (not await)")Issue 4: response_generator docstring
print("\nIssue 4 - response_generator docstring:")
print(" Original: 'Generator that yields chunks from the queue.' (descriptive)")
print(" Fixed: 'Generate chunks from the queue for streaming response.' (imperative)")
EOF
</function_calls>Let me verify that the code currently uses yield correctly:
<function_calls>
sed -n '718,760p' src/app/endpoints/a2a.py | grep -A 5 "yield"
</function_calls>Let me get a cleaner view of the response_generator function:
<function_calls>
sed -n '728,760p' src/app/endpoints/a2a.py
</function_calls>Perfect. Now let me verify Starlette's StreamingResponse API:
<function_calls>
"StreamingResponse" site:github.com/encode/starlette AsyncIterator bytes
</function_calls>Let me check the Starlette documentation more directly:
<function_calls>
Starlette StreamingResponse class "AsyncIterator" OR "async def" content parameter
</function_calls>Let me check what the current code shows and verify the fixes are compatible:
<function_calls>
sed -n '621,635p' src/app/endpoints/a2a.py
</function_calls><function_calls>
sed -n '656,658p' src/app/endpoints/a2a.py
</function_calls><function_calls>
sed -n '662,664p' src/app/endpoints/a2a.py
</function_calls>Let me verify pydocstyle D401 rule is applicable:
<function_calls>
pydocstyle D401 "First line should be in imperative mood" examples
</function_calls>Now let me generate a comprehensive Python script to validate the fixes:
<function_calls>
python3 << 'EOF'
import jsonVerify the proposed fixes address real issues:
issues = {
"D401_handler": {
"rule": "pydocstyle D401 - First line must be imperative (verb starting)",
"original": "Main A2A JSON-RPC endpoint following the A2A protocol specification.",
"original_status": "VIOLATES - starts with noun 'Main'",
"proposed": "Handle A2A JSON-RPC requests following the A2A protocol specification.",
"proposed_status": "COMPLIANT - starts with verb 'Handle'"
},
"scope_copy": {
"rule": "Pyright - Scope TypedDict has no copy() method",
"original_code": "scope = request.scope.copy()",
"original_status": "ERROR - TypedDict.copy() doesn't exist",
"proposed_code": "scope = dict(request.scope)",
"proposed_status": "VALID - dict() constructor works with TypedDict"
},
"streaming_response": {
"rule": "Pyright - StreamingResponse expects AsyncIterator[bytes], not coroutine",
"original_code": "response_generator() with no return type hint",
"original_status": "ERROR - inferred as coroutine",
"proposed_code": "async def response_generator() -> AsyncIterator[bytes]:",
"proposed_status": "VALID - explicit AsyncIterator[bytes] type"
},
"D401_generator": {
"rule": "pydocstyle D401 - response_generator docstring",
"original": "Generator that yields chunks from the queue.",
"original_status": "VIOLATES - starts with noun 'Generator'",
"proposed": "Generate chunks from the queue for streaming response.",
"proposed_status": "COMPLIANT - starts with verb 'Generate'"
}
}print(json.dumps(issues, indent=2))
EOF
</function_calls>Based on my verification, all four issues identified in the review comment are accurate and the proposed fixes are correct:
D401 Docstring Mood: "Main A2A JSON-RPC endpoint..." violates pydocstyle D401 (must use imperative mood). The proposed "Handle A2A JSON-RPC requests..." is correct.
scope.copy() Issue: The Scope TypedDict has no
copy()method. Usingdict(request.scope)is the correct workaround that Pyright accepts.StreamingResponse Type: The
response_generator()function needs explicitAsyncIterator[bytes]return type annotation to ensure Pyright recognizes it as an async generator (which yields bytes), not a coroutine.response_generator Docstring: "Generator that yields chunks..." violates D401. The proposed "Generate chunks from the queue for streaming response." is correct imperative form.
Fix docstring mood,
scope.copy()type issue, and streaming generator typingThis block has several reported static-analysis issues that can be addressed together:
- Pydocstyle D401 on the handler docstring (must use imperative mood)
- Pyright error on
request.scope.copy()(Scope TypedDict has nocopymethod)- Pyright error on
response_generatorreturn type (needs explicitAsyncIterator[bytes]annotation)- Pydocstyle D401 on the
response_generatordocstringasync def handle_a2a_jsonrpc( # pylint: disable=too-many-locals,too-many-statements @@ - """ - Main A2A JSON-RPC endpoint following the A2A protocol specification. + """ + Handle A2A JSON-RPC requests following the A2A protocol specification. @@ - # Setup scope for A2A app - scope = request.scope.copy() + # Setup scope for A2A app + scope = dict(request.scope) @@ - async def response_generator() -> Any: - """Generator that yields chunks from the queue.""" + async def response_generator() -> AsyncIterator[bytes]: + """Generate chunks from the queue for streaming response.""" @@Ensure
AsyncIteratoris imported fromtyping(orcollections.abcin Python 3.9+).
492-560: Import style verified: usefrom fastapi import statusfor status constantsWeb search confirms that for FastAPI 0.115.x, the recommended approach is:
- Import
HTTPExceptionfromfastapi(idiomatic way)- Import
statusfromfastapi(convenience re-export of Starlette's constants, preferred overstarlette.status)- Use
status.HTTP_500_INTERNAL_SERVER_ERRORrather than bare integersThe proposed fix aligns with FastAPI best practices. The guard on
configuration.customizationbefore accessingagent_card_configis appropriate for resolving the Pyright type warning, and raisingHTTPExceptionwith explicit status codes instead of a bareValueErroris the correct pattern for endpoint error handling (per the coding guidelines on file patternssrc/app/endpoints/**/*.py).Unable to verify against actual codebase due to repository access issues, but the technical approach and import style are sound.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (3)
src/app/endpoints/a2a.py (3)
57-65: Acknowledge in-memory state limitation for multi-worker deployments.The in-memory
_TASK_STOREand_CONTEXT_TO_CONVERSATIONwon't persist across workers. This was flagged in prior reviews and is a known limitation. Consider adding a code comment documenting this limitation and the plan for persistent storage.
749-789: Fix async generator return type annotation to resolve pyright error.Pyright reports that the argument type doesn't match
ContentStream. Theresponse_generatorfunction needsAsyncIterator[bytes]return type annotation.- async def response_generator() -> Any: - """Generate chunks from the queue for streaming response.""" + async def response_generator() -> AsyncIterator[bytes]: + """Generate chunks from the queue for streaming response.""" chunk_count = 0
280-286: Fix missing required parameters forQueryRequestto resolve pyright error.Pyright reports missing parameters:
system_prompt,attachments,no_tools,generate_topic_summary,media_type. Supply explicit values to satisfy the type checker.# Build internal query request with conversation_id for history query_request = QueryRequest( query=user_input, conversation_id=conversation_id_hint, model=model, provider=provider, + system_prompt=None, + attachments=None, + no_tools=False, + generate_topic_summary=True, + media_type=None, )
🧹 Nitpick comments (5)
docs/a2a_protocol.md (1)
18-49: Add language specifier to fenced code block for linting compliance.The ASCII diagram code blocks on lines 18 and 214 are missing language specifiers, triggering MD040 warnings. Use
textorplaintextas the language identifier.-``` +```text ┌─────────────────────────────────────────────────────────────────┐ │ A2A Client │src/app/endpoints/responses_a2a.py (1)
626-639: Consider using timezone-aware datetime for consistency.The health check uses
datetime.now().isoformat()without timezone. Other parts of the code usedatetime.now(timezone.utc).isoformat()for timezone-aware timestamps. Consider aligning for consistency.return { "status": "healthy", "service": "lightspeed-responses-a2a", "version": __version__, "a2a_sdk_version": "0.2.1", "api_type": "responses", - "timestamp": datetime.now().isoformat(), + "timestamp": datetime.now(timezone.utc).isoformat(), }src/app/endpoints/a2a.py (1)
818-831: Consider using timezone-aware datetime for consistency.Similar to
responses_a2a.py, the health check usesdatetime.now().isoformat()without timezone. Other parts of the code usedatetime.now(timezone.utc)for consistency.return { "status": "healthy", "service": "lightspeed-a2a", "version": __version__, "a2a_sdk_version": "0.2.1", - "timestamp": datetime.now().isoformat(), + "timestamp": datetime.now(timezone.utc).isoformat(), }tests/unit/app/endpoints/test_a2a.py (2)
42-47: Consider documenting all MOCK_AUTH tuple elements.The comment explains only the first element (UUID), but the tuple has four elements. For better maintainability, document what each element represents.
# User ID must be proper UUID MOCK_AUTH = ( - "00000001-0001-0001-0001-000000000001", - "mock_username", - False, - "mock_token", + "00000001-0001-0001-0001-000000000001", # user_id (UUID) + "mock_username", # username + False, # is_admin or similar flag + "mock_token", # auth_token )
560-570: Enhance tests to validate actual behavior.These tests only check that global stores exist and have basic types. Consider testing actual behavior such as storing and retrieving conversation mappings, or remove these tests if the stores are implementation details not meant to be tested directly.
Example improvements:
def test_context_to_conversation_mapping_stores_and_retrieves(self) -> None: """Test that context-to-conversation mapping works correctly.""" test_context_id = "ctx-test-123" test_conversation_id = "conv-test-456" _CONTEXT_TO_CONVERSATION[test_context_id] = test_conversation_id assert _CONTEXT_TO_CONVERSATION[test_context_id] == test_conversation_id # Clean up del _CONTEXT_TO_CONVERSATION[test_context_id]
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
docs/a2a_protocol.md(1 hunks)pyproject.toml(2 hunks)src/app/endpoints/README.md(2 hunks)src/app/endpoints/a2a.py(1 hunks)src/app/endpoints/responses_a2a.py(1 hunks)src/app/routers.py(2 hunks)tests/unit/app/endpoints/test_a2a.py(1 hunks)tests/unit/app/endpoints/test_responses_a2a.py(1 hunks)tests/unit/app/test_routers.py(5 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/app/endpoints/README.md
🚧 Files skipped from review as they are similar to previous changes (2)
- src/app/routers.py
- pyproject.toml
🧰 Additional context used
📓 Path-based instructions (5)
tests/{unit,integration}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests; do not use unittest framework
Unit tests must achieve 60% code coverage; integration tests must achieve 10% coverage
Files:
tests/unit/app/endpoints/test_a2a.pytests/unit/app/test_routers.pytests/unit/app/endpoints/test_responses_a2a.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
pytest-mockwith AsyncMock objects for mocking in tests
Files:
tests/unit/app/endpoints/test_a2a.pytests/unit/app/test_routers.pytests/unit/app/endpoints/test_responses_a2a.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/app/endpoints/responses_a2a.pysrc/app/endpoints/a2a.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/responses_a2a.pysrc/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/responses_a2a.pysrc/app/endpoints/a2a.py
🧠 Learnings (6)
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (3)
tests/unit/app/endpoints/test_a2a.py (1)
src/app/endpoints/a2a.py (11)
_convert_llama_content_to_a2a_parts(68-96)get_lightspeed_agent_card(495-572)LightspeedAgentExecutor(162-489)TaskResultAggregator(99-156)a2a_health_check(819-832)task_state(149-151)task_status_message(154-156)process_event(107-146)execute(183-229)_process_task_streaming(231-357)cancel(473-489)
tests/unit/app/endpoints/test_responses_a2a.py (2)
src/app/endpoints/responses_a2a.py (8)
_convert_responses_content_to_a2a_parts(64-80)get_responses_agent_card(405-414)responses_a2a_health_check(627-640)get_responses_agent_card_endpoint(605-623)execute(103-141)_process_task_streaming(143-264)cancel(387-402)_convert_stream_to_events(266-385)src/app/endpoints/a2a.py (4)
execute(183-229)_process_task_streaming(231-357)cancel(473-489)_convert_stream_to_events(359-471)
src/app/endpoints/a2a.py (6)
src/authentication/__init__.py (1)
get_auth_dependency(14-60)src/configuration.py (3)
configuration(73-77)service_configuration(80-84)customization(127-131)src/models/requests.py (1)
QueryRequest(73-233)src/app/endpoints/query.py (2)
select_model_and_provider_id(464-532)evaluate_model_hints(139-178)src/utils/mcp_headers.py (1)
mcp_headers_dependency(15-26)src/app/endpoints/responses_a2a.py (2)
execute(103-141)_process_task_streaming(143-264)
🪛 GitHub Actions: Pyright
src/app/endpoints/responses_a2a.py
[error] 191-191: pyright: Arguments missing for parameters 'system_prompt', 'attachments', 'no_tools', 'generate_topic_summary', 'media_type' (reportCallIssue).
[error] 573-573: pyright: Argument of type 'CoroutineType[Any, Any, Any]' cannot be assigned to parameter 'content' of type 'ContentStream' in init.
src/app/endpoints/a2a.py
[error] 281-281: pyright: Arguments missing for parameters 'system_prompt', 'attachments', 'no_tools', 'generate_topic_summary', 'media_type' (reportCallIssue).
[error] 787-787: pyright: Argument of type 'CoroutineType[Any, Any, Any]' cannot be assigned to parameter 'content' of type 'ContentStream' in init.
🪛 GitHub Check: Bandit
src/app/endpoints/responses_a2a.py
[notice] 475-475:
Possible hardcoded password: ''
src/app/endpoints/a2a.py
[notice] 638-638:
Possible hardcoded password: ''
🪛 markdownlint-cli2 (0.18.1)
docs/a2a_protocol.md
18-18: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
214-214: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: build-pr
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
- GitHub Check: e2e_tests (azure)
- GitHub Check: e2e_tests (ci)
🔇 Additional comments (28)
src/app/endpoints/responses_a2a.py (5)
1-56: LGTM for module structure and imports.The module follows the project's conventions with proper docstring, absolute imports, type annotations, and the shared logger pattern (
"app.endpoints.handlers"). Based on learnings, this logger name is intentionally shared across endpoint handlers.
64-80: LGTM for content conversion utility.The function has proper type annotations, docstring, and straightforward logic for converting Responses API output to A2A Parts.
83-142: LGTM for executor class structure and error handling.The
ResponsesAgentExecutorclass follows the established pattern fromLightspeedAgentExecutor, with proper type annotations, docstrings, and error handling. The broad exception catching is appropriately marked with pylint disable comments.
266-385: LGTM for stream-to-events conversion logic.The method properly handles various Responses API event types (
response.created,response.output_text.delta,response.completed, etc.) and converts them to appropriate A2A events. The context-to-response-id mapping is correctly maintained for multi-turn conversations.
405-443: LGTM for agent card and app factory.The
get_responses_agent_cardfunction correctly derives from the base agent card and updates the URL. The_create_responses_a2a_appfactory properly wires up theResponsesAgentExecutorwith the shared_TASK_STORE.src/app/endpoints/a2a.py (7)
1-55: LGTM for module structure.The module follows project conventions with proper docstring, absolute imports, complete type annotations, and the intentionally shared logger pattern.
68-96: LGTM for content conversion utility.The function properly handles various content types (str, list, objects) and includes a clear note about image content not being handled yet.
99-157: LGTM for TaskResultAggregator implementation.The class has proper type annotations for instance attributes,
__init__docstring, and clear priority-based state handling logic. The mutation ofevent.status.stateon line 146 to prevent premature client termination is well-documented.
162-230: LGTM for executor class structure.The
LightspeedAgentExecutorclass properly implements the A2AAgentExecutorinterface with correct type annotations, docstrings, and error handling patterns.
359-472: LGTM for stream-to-events conversion.The
_convert_stream_to_eventsmethod properly handles Llama Stack event types (turn_awaiting_input,turn_complete,step_progress) and converts them to appropriate A2A events. Tool call handling correctly filters for successful parse status before emitting events.
495-572: LGTM for agent card generation.The function properly guards against missing configuration with appropriate
HTTPExceptionresponses, and correctly parses skills, provider, and capabilities from the configuration.
578-602: LGTM for agent card endpoint.The endpoint correctly serves the agent card at well-known locations. Per past discussion, the
@authorizedecorator is intentionally omitted since the agent card contains security scheme information and should be accessible without authorization.tests/unit/app/test_routers.py (3)
28-30: LGTM for new router imports.The imports for
a2aandresponses_a2acorrectly extend the endpoint imports list.
72-91: LGTM for router registration assertions.The test correctly verifies that both
a2a.routerandresponses_a2a.routerare registered, with the total count updated to 20.
100-120: LGTM for prefix assertions.Both A2A routers are correctly mounted at the root level (empty prefix
""), which aligns with the A2A protocol's well-known path conventions (e.g.,/.well-known/agent.json).tests/unit/app/endpoints/test_responses_a2a.py (7)
1-43: LGTM for test module structure and imports.The test module follows project conventions with proper imports, pylint disable comments for test patterns, and a well-structured mock auth tuple.
45-107: LGTM for test fixtures.The
dummy_requestandsetup_configuration_fixturefixtures provide comprehensive test setup including:
- Proper A2A-related configuration (
agent_card_config)- Correct patching of
app.endpoints.a2a.configuration- Authorization actions setup
110-181: LGTM for content conversion tests.Comprehensive test coverage for
_convert_responses_content_to_a2a_partsincluding:
- Empty output handling
- Message output item conversion
- Content as list handling
- Non-message and non-assistant role filtering
- Multiple message items
183-208: LGTM for agent card tests.Tests verify that the Responses agent card correctly inherits from the base configuration and has the
/responses/a2aURL path.
210-381: LGTM for executor tests.Good coverage of
ResponsesAgentExecutorincluding:
- Initialization with and without MCP headers
- Error handling for missing message
- Task creation and enqueueing
- Graceful error handling with failure event
- Input required state handling
- Cancel raises NotImplementedError
Uses proper
AsyncMockandMagicMockpatterns per coding guidelines.
383-541: LGTM for stream conversion and context mapping tests.Thorough tests for:
- Context-to-response-id mapping structure
response.createdevent handling with context update- Text delta event conversion
- Response completed artifact generation
- Function call event handling
Good practice: cleanup of
_CONTEXT_TO_RESPONSE_IDafter test (line 436).
543-574: LGTM for endpoint handler tests.Integration-style tests verify:
- Health check returns correct structure and values
- Agent card endpoint returns proper
AgentCardtype with correct URLtests/unit/app/endpoints/test_a2a.py (6)
50-137: LGTM!The fixtures are well-structured and follow pytest best practices. The separation between full and minimal configurations enables testing both success and error paths effectively.
143-199: LGTM!Comprehensive test coverage for the content conversion function. The tests properly cover all input types mentioned in the relevant code snippet.
346-382: LGTM!The tests properly validate both success and error paths for agent card generation, checking all required fields and error handling.
387-555: LGTM!Comprehensive test coverage for the executor with proper use of AsyncMock for async operations, good error handling validation, and appropriate mocking of external dependencies.
575-606: LGTM!The integration-style endpoint tests provide good coverage with appropriate mocking of authorization. The health check validation ensures all required response fields are present.
308-322: Unfortunately, I'm unable to access the repository due to persistent cloning failures. Without access to the codebase, I cannot verify:
- The actual implementation of
TaskResultAggregator.process_event()- Whether event mutation is documented or intentional
- Other code that may depend on this mutation behavior
However, based on the test snippet and review comment, here is the rewritten review:
Clarify whether event mutation is part of the public API contract.
The test validates that
process_eventmutates the input event'sstatus.state(line 321). This is atypical behavior—mutating input parameters creates tight coupling between callers and implementation details. Verify whether this mutation is:
- Intentionally part of the API contract (document it clearly in
TaskResultAggregator.process_event()docstring if so)- An unintended side effect that should be removed
- Necessary for streaming use cases (if yes, consider alternatives like returning the modified event instead of mutating the input)
If mutation is intentional, consider whether the test should validate the returned value or side effects more explicitly rather than asserting on the mutated input object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (4)
src/app/endpoints/responses_a2a.py (2)
190-196: Fix missing required parameters forQueryRequestto resolve pyright error.The pipeline fails because
QueryRequestis missing required parameters. Supply explicit values for all required parameters.# Build internal query request query_request = QueryRequest( query=user_input, conversation_id=previous_response_id, model=model, provider=provider, + system_prompt=None, + attachments=None, + no_tools=False, + generate_topic_summary=True, + media_type=None, )
544-575: Fix async generator return type annotation to resolve pyright error.The pipeline fails because
response_generatorreturnsAnybutStreamingResponseexpectsContentStream. Change the return type toAsyncIterator[bytes].- async def response_generator() -> Any: - """Generator that yields chunks from the queue.""" + async def response_generator() -> AsyncIterator[bytes]: + """Generate chunks from the queue for streaming response.""" chunk_count = 0Ensure
AsyncIteratoris already imported fromtyping(line 8).src/app/endpoints/a2a.py (2)
280-286: Fix missing required parameters forQueryRequestto resolve pyright error.The pipeline fails because
QueryRequestis missing required parameters.# Build internal query request with conversation_id for history query_request = QueryRequest( query=user_input, conversation_id=conversation_id_hint, model=model, provider=provider, + system_prompt=None, + attachments=None, + no_tools=False, + generate_topic_summary=True, + media_type=None, )
749-789: Fix async generator return type annotation to resolve pyright error.The pipeline fails because
response_generatorreturnsAnybutStreamingResponseexpectsContentStream. The function is an async generator yielding bytes.- async def response_generator() -> Any: - """Generate chunks from the queue for streaming response.""" + async def response_generator() -> AsyncIterator[bytes]: + """Generate chunks from the queue for streaming response.""" chunk_count = 0
AsyncIteratoris already imported fromtyping(line 9).
🧹 Nitpick comments (4)
docs/a2a_protocol.md (2)
18-49: Add language specifier to fenced code block for linter compliance.The ASCII architecture diagram lacks a language specifier, triggering MD040. Use
textfor plain text diagrams.-``` +```text ┌─────────────────────────────────────────────────────────────────┐ │ A2A Client │
214-248: Add language specifier to event flow diagram.Similar to the architecture diagram, this ASCII art block needs a language specifier.
-``` +```text A2A Request │src/app/endpoints/responses_a2a.py (1)
626-640: LGTM! Health check endpoint.The health endpoint correctly includes relevant metadata and uses
datetime.now()for the timestamp. Consider usingdatetime.now(timezone.utc)for consistency with other timestamps in the file (e.g., lines 233, 255, 319).return { "status": "healthy", "service": "lightspeed-responses-a2a", "version": __version__, "a2a_sdk_version": "0.2.1", "api_type": "responses", - "timestamp": datetime.now().isoformat(), + "timestamp": datetime.now(timezone.utc).isoformat(), }src/app/endpoints/a2a.py (1)
818-832: LGTM! Health check endpoint with appropriate metadata.The health endpoint returns relevant service information. Similar to responses_a2a.py, consider using UTC timezone for consistency:
return { "status": "healthy", "service": "lightspeed-a2a", "version": __version__, "a2a_sdk_version": "0.2.1", - "timestamp": datetime.now().isoformat(), + "timestamp": datetime.now(timezone.utc).isoformat(), }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
docs/a2a_protocol.md(1 hunks)pyproject.toml(2 hunks)src/app/endpoints/README.md(2 hunks)src/app/endpoints/a2a.py(1 hunks)src/app/endpoints/responses_a2a.py(1 hunks)src/app/routers.py(2 hunks)tests/unit/app/endpoints/test_a2a.py(1 hunks)tests/unit/app/endpoints/test_responses_a2a.py(1 hunks)tests/unit/app/test_routers.py(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- tests/unit/app/endpoints/test_a2a.py
- pyproject.toml
- tests/unit/app/endpoints/test_responses_a2a.py
🧰 Additional context used
📓 Path-based instructions (5)
tests/{unit,integration}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests; do not use unittest framework
Unit tests must achieve 60% code coverage; integration tests must achieve 10% coverage
Files:
tests/unit/app/test_routers.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
pytest-mockwith AsyncMock objects for mocking in tests
Files:
tests/unit/app/test_routers.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/app/routers.pysrc/app/endpoints/responses_a2a.pysrc/app/endpoints/a2a.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/responses_a2a.pysrc/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/responses_a2a.pysrc/app/endpoints/a2a.py
🧠 Learnings (6)
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (1)
src/app/routers.py (1)
tests/unit/app/test_routers.py (1)
include_router(40-55)
🪛 GitHub Actions: Pyright
src/app/endpoints/responses_a2a.py
[error] 191-191: pyright: Arguments missing for parameters "system_prompt", "attachments", "no_tools", "generate_topic_summary", "media_type" (reportCallIssue)
[error] 573-573: pyright: Argument of type "CoroutineType[Any, Any, Any]" cannot be assigned to parameter "content" of type "ContentStream" in function "init". Type "CoroutineType[Any, Any, Any]" is not assignable to protocol "AsyncIterable[Content]".
src/app/endpoints/a2a.py
[error] 281-281: pyright: Arguments missing for parameters "system_prompt", "attachments", "no_tools", "generate_topic_summary", "media_type" (reportCallIssue)
[error] 787-787: pyright: Argument of type "CoroutineType[Any, Any, Any]" cannot be assigned to parameter "content" of type "ContentStream" in function "init". Type "CoroutineType[Any, Any, Any]" is not assignable to protocol "AsyncIterable[Content]".
🪛 GitHub Check: Bandit
src/app/endpoints/responses_a2a.py
[notice] 475-475:
Possible hardcoded password: ''
src/app/endpoints/a2a.py
[notice] 638-638:
Possible hardcoded password: ''
🪛 markdownlint-cli2 (0.18.1)
docs/a2a_protocol.md
18-18: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
214-214: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: build-pr
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
- GitHub Check: e2e_tests (azure)
- GitHub Check: e2e_tests (ci)
🔇 Additional comments (9)
tests/unit/app/test_routers.py (1)
28-29: LGTM! Router tests properly updated for A2A endpoints.The test updates correctly validate the new
a2aandresponses_a2arouter registrations with empty prefixes, consistent with the A2A protocol's.well-knownpath requirements.Also applies to: 72-72, 90-91, 100-100, 119-120
src/app/endpoints/README.md (1)
6-9: LGTM! Documentation entries for A2A endpoints are well-structured.The new entries follow the existing documentation format and provide appropriate links to the A2A protocol documentation.
Also applies to: 61-64
src/app/routers.py (1)
25-27: LGTM! A2A router registrations follow existing patterns.The new routers are correctly imported and registered without a prefix, which is appropriate for the A2A protocol's
.well-knowndiscovery paths.Also applies to: 60-63
docs/a2a_protocol.md (1)
1-614: Comprehensive A2A protocol documentation.The documentation provides thorough coverage of the A2A integration including architecture diagrams, endpoint specifications, configuration examples, and testing instructions. The A2A Inspector link at line 272 is now correctly pointing to
https://github.com/a2aproject/a2a-inspector.src/app/endpoints/responses_a2a.py (2)
83-102: LGTM! ResponsesAgentExecutor class structure.The executor class is well-structured with proper type annotations for
__init__and instance attributes, following the same pattern asLightspeedAgentExecutorina2a.py.
405-414: LGTM! Agent card URL transformation for Responses API variant.The
get_responses_agent_card()function correctly derives the Responses A2A agent card by updating the URL to point to/responses/a2a.src/app/endpoints/a2a.py (3)
57-65: Acknowledge in-memory state limitation for multi-worker deployments.The module-level
_TASK_STOREand_CONTEXT_TO_CONVERSATIONwill be isolated per worker process, causing state inconsistencies in multi-worker deployments. This is a known limitation per previous review discussions.Consider documenting this limitation in the A2A protocol documentation or adding a TODO to track persistent storage implementation for production deployments.
99-157: LGTM! TaskResultAggregator class with proper type annotations.The class correctly implements task state aggregation with proper priority handling (failed > auth_required > input_required > working) and includes type annotations for all attributes and methods.
495-572: LGTM! Agent card generation from configuration.The
get_lightspeed_agent_card()function properly handles configuration parsing with appropriate null checks and raisesHTTPExceptionwith 500 status for missing configuration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/app/endpoints/a2a.py (1)
57-65: Acknowledge multi-worker deployment limitation.As noted in previous reviews,
_TASK_STORE(InMemoryTaskStore) and_CONTEXT_TO_CONVERSATIONare module-level and isolated per worker. This is a known limitation for multi-instance deployments. Consider adding a comment documenting this limitation or tracking it for future improvement.
🧹 Nitpick comments (3)
src/app/endpoints/responses_a2a.py (2)
410-419: Consider using URL manipulation instead of string replace.The
str.replace("/a2a", "/responses/a2a")approach works but could behave unexpectedly if the base URL contains/a2aelsewhere. A more robust approach would be to append/responsesbefore/a2aor reconstruct the URL.def get_responses_agent_card() -> AgentCard: """Get the agent card for the Responses A2A endpoint.""" agent_card = get_lightspeed_agent_card() - # Update the URL to point to the responses endpoint - agent_card.url = agent_card.url.replace("/a2a", "/responses/a2a") + # Update the URL to point to the responses endpoint + if agent_card.url.endswith("/a2a"): + agent_card.url = agent_card.url[:-4] + "/responses/a2a" + else: + agent_card.url = agent_card.url.replace("/a2a", "/responses/a2a") return agent_card
631-644: Use timezone-aware datetime for consistency.Line 644 uses
datetime.now()without timezone, while other timestamps in this file (e.g., line 238) usedatetime.now(timezone.utc). Use the same pattern for consistency.return { "status": "healthy", "service": "lightspeed-responses-a2a", "version": __version__, "a2a_sdk_version": "0.2.1", "api_type": "responses", - "timestamp": datetime.now().isoformat(), + "timestamp": datetime.now(timezone.utc).isoformat(), }src/app/endpoints/a2a.py (1)
823-837: Use timezone-aware datetime for consistency.Line 836 uses
datetime.now()without timezone, while other timestamps in this file usedatetime.now(timezone.utc). Apply the same pattern for consistency.return { "status": "healthy", "service": "lightspeed-a2a", "version": __version__, "a2a_sdk_version": "0.2.1", - "timestamp": datetime.now().isoformat(), + "timestamp": datetime.now(timezone.utc).isoformat(), }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (6)
pyproject.toml(2 hunks)src/app/endpoints/a2a.py(1 hunks)src/app/endpoints/responses_a2a.py(1 hunks)tests/unit/app/endpoints/test_a2a.py(1 hunks)tests/unit/app/endpoints/test_responses_a2a.py(1 hunks)tests/unit/app/test_routers.py(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- pyproject.toml
- tests/unit/app/endpoints/test_a2a.py
🧰 Additional context used
📓 Path-based instructions (5)
tests/{unit,integration}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests; do not use unittest framework
Unit tests must achieve 60% code coverage; integration tests must achieve 10% coverage
Files:
tests/unit/app/endpoints/test_responses_a2a.pytests/unit/app/test_routers.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
pytest-mockwith AsyncMock objects for mocking in tests
Files:
tests/unit/app/endpoints/test_responses_a2a.pytests/unit/app/test_routers.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/app/endpoints/responses_a2a.pysrc/app/endpoints/a2a.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/responses_a2a.pysrc/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/responses_a2a.pysrc/app/endpoints/a2a.py
🧠 Learnings (6)
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (3)
tests/unit/app/endpoints/test_responses_a2a.py (1)
src/app/endpoints/responses_a2a.py (9)
_convert_responses_content_to_a2a_parts(64-80)get_responses_agent_card(410-419)ResponsesAgentExecutor(83-407)responses_a2a_health_check(632-645)get_responses_agent_card_endpoint(610-628)execute(103-141)_process_task_streaming(143-269)cancel(392-407)_convert_stream_to_events(271-390)
src/app/endpoints/responses_a2a.py (4)
src/models/requests.py (1)
QueryRequest(73-233)src/app/endpoints/query.py (1)
select_model_and_provider_id(464-532)src/client.py (2)
AsyncLlamaStackClientHolder(18-55)get_client(49-55)src/utils/responses.py (1)
extract_text_from_response_output_item(6-56)
src/app/endpoints/a2a.py (3)
src/models/requests.py (1)
QueryRequest(73-233)src/app/endpoints/query.py (3)
select_model_and_provider_id(464-532)evaluate_model_hints(139-178)retrieve_response(640-799)src/app/endpoints/responses_a2a.py (2)
execute(103-141)_process_task_streaming(143-269)
🪛 GitHub Check: Bandit
src/app/endpoints/responses_a2a.py
[notice] 475-475:
Possible hardcoded password: ''
src/app/endpoints/a2a.py
[notice] 638-638:
Possible hardcoded password: ''
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: build-pr
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
- GitHub Check: e2e_tests (ci)
- GitHub Check: e2e_tests (azure)
🔇 Additional comments (25)
src/app/endpoints/responses_a2a.py (7)
64-80: LGTM!The conversion function correctly delegates to the shared utility
extract_text_from_response_output_itemand properly handles empty outputs.
83-141: LGTM!The
ResponsesAgentExecutorclass mirrors the pattern froma2a.pycorrectly. The error handling structure properly catches exceptions to publish failure events, with appropriatepylint: disablecomments for the broad exception catches.
143-269: LGTM!The streaming processing method correctly handles the Responses API flow:
- Validates required IDs
- Handles missing input gracefully
- Persists response IDs for multi-turn conversations
- Uses the
TaskResultAggregatorpattern consistently witha2a.pyThe
QueryRequestnow includes all required parameters, addressing the previous pyright error.
271-391: LGTM!The stream-to-events conversion handles the key Responses API event types appropriately:
response.created- extracts and persists response IDresponse.output_text.delta- yields working status updatesresponse.function_call_arguments.doneandresponse.mcp_call.arguments.done- yields tool/MCP call statusresponse.completed- yields final artifact with accumulated content
392-408: LGTM!The cancel method stub is consistent with the
a2a.pyimplementation, raisingNotImplementedErrorwith a clear message.
476-480: Bandit warning is a false positive.The empty string fallback for
auth_tokenis intentional and safe. It represents a missing/unavailable token that will be passed downstream, where authentication failures will be handled appropriately. This is consistent with the pattern ina2a.py.
549-580: LGTM!The
response_generatorhas the correctAsyncIterator[bytes]return type annotation and imperative mood docstring, addressing the previous review comments. The cleanup logic properly cancels the app task when the generator exits.tests/unit/app/test_routers.py (1)
28-29: LGTM!The test updates correctly:
- Import both new A2A routers
- Update router count expectations from 18 to 20
- Verify both routers are registered with empty prefixes (consistent with their route definitions like
/a2aand/responses/a2a)Also applies to: 72-72, 90-91, 100-100, 119-120
tests/unit/app/endpoints/test_responses_a2a.py (7)
36-54: LGTM!The
MOCK_AUTHtuple anddummy_requestfixture are properly structured for testing the A2A endpoints with authentication and authorization.
57-108: LGTM!The configuration fixture provides comprehensive test data including agent card configuration with skills, provider, and capabilities. Patching
app.endpoints.a2a.configurationensuresget_lightspeed_agent_card()uses the test config.
113-181: LGTM!The
TestConvertResponsesContentToA2APartsclass provides comprehensive coverage:
- Empty output handling
- Message extraction with string and list content
- Filtering of non-message and non-assistant items
- Multiple message handling
186-208: LGTM!The agent card tests verify the URL transformation to
/responses/a2aand inheritance of configuration values from the base agent card.
213-381: LGTM!The
TestResponsesAgentExecutorclass provides thorough coverage of the executor lifecycle:
- Initialization with default and custom MCP headers
- Error handling for missing messages
- Task creation flow
- Error propagation with failure events
- Input-required handling
- Cancel behavior
400-436: Good practice: cleaning up global state after test.The test properly cleans up
_CONTEXT_TO_RESPONSE_IDafter modifying it. This prevents test pollution when running tests in sequence.
543-574: LGTM!The endpoint handler tests verify the health check response structure and agent card endpoint behavior, providing good integration-style coverage.
src/app/endpoints/a2a.py (10)
68-97: LGTM!The content conversion function properly handles string content, list content with text items, and content objects. The note about image content being unhandled is appropriate.
99-157: LGTM!The
TaskResultAggregatorproperly implements state priority (failed > auth_required > input_required > working) and includes the docstring for__init__that was requested in previous reviews. The inline comment at lines 142-147 explains the mutation ofevent.status.statefor non-final events.
159-230: LGTM!The
LightspeedAgentExecutorclass properly implements the A2AAgentExecutorinterface with complete type annotations (addressing past review comments). The error handling structure appropriately catches broad exceptions to ensure failure events are published.
231-363: LGTM!The streaming processing method correctly:
- Validates required context IDs
- Handles missing input with
input_requiredstatus- Maps A2A context to Llama Stack conversation for multi-turn
- Builds
QueryRequestwith all required parameters- Aggregates events and publishes final status
364-477: LGTM!The stream-to-events conversion handles Llama Stack Agent API events appropriately:
turn_awaiting_input→input_requiredstatusturn_complete→ final artifact with converted contentstep_progress→ working status for text deltas and tool calls (only when parsing succeeds)
478-495: LGTM!The cancel method stub is consistent with
responses_a2a.py, properly documenting that cancellation is not supported.
500-578: LGTM!The agent card generation function properly:
- Uses explicit None check for
base_url(addressing past review)- Raises
HTTPExceptionwith 500 status for missing configuration (addressing past review)- Parses all agent card fields from configuration
583-608: LGTM!The agent card endpoint correctly uses authentication without authorization, as the A2A protocol requires agent cards to be discoverable. The previous review discussion confirmed this is intentional.
610-636: LGTM!The A2A app factory correctly creates a per-request executor with auth context while sharing the
_TASK_STOREfor task persistence across requests within the same worker.
638-795: LGTM!The main A2A JSON-RPC endpoint correctly:
- Applies authorization via
@authorize(Action.A2A_JSONRPC)- Extracts auth token with proper error handling
- Detects streaming requests by parsing JSON-RPC method
- Uses correct scope transformation with
dict(request.scope)- Implements streaming via
asyncio.Queuewith proper cleanup- Has correct
AsyncIterator[bytes]return type annotationThe Bandit warning about hardcoded password at line 673 is a false positive for the empty string fallback.
|
The future direction of this PR should be influenced by the outcome of google/adk-python#3800 (tries to clarify how A2A protocol objects should be used) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (11)
src/models/config.py (1)
366-370: Consider validatingbase_urlwithAnyHttpUrlinstead of plainstr.Using
AnyHttpUrlhere would give you early validation of misconfigured external URLs (schema/host/port), consistent with how other URL fields (e.g., JWK/JWT) are typed in this module, and reduce runtime surprises when constructing A2A-facing URLs.-class ServiceConfiguration(ConfigurationBase): +class ServiceConfiguration(ConfigurationBase): @@ - base_url: Optional[str] = Field( + base_url: Optional[AnyHttpUrl] = Field( None, title="Base URL", description="Externally reachable base URL for the service; needed for A2A support.", )Based on learnings, this keeps configuration strict while still allowing the field to be optional.
src/a2a_storage/sqlite_context_store.py (1)
15-21: SQLite context store implementation is solid; a couple of optional refinements to consider.Functionally this looks correct and matches the intended A2A context‑to‑conversation mapping semantics. Two non‑blocking improvements you might consider:
- For
set(), instead of a manual “delete then insert”, you could use the dialect’s native upsert (e.g.insert().on_conflict_do_updatefor SQLite) which is a bit more idiomatic and avoids an extra round trip, especially if the table grows.- If
initialize()might be called concurrently from multiple tasks, wrapping the_initializedcheck andmetadata.create_allin anasyncio.Lock(or similar) would make the initialization path explicitly safe under concurrency, thoughcreate_allis idempotent so this is mostly polish.None of this blocks the current implementation.
Please confirm against your SQLAlchemy version which upsert helpers (
on_conflict_do_updateor equivalent) are available for SQLite so you can adopt them safely if you choose to refine theset()implementation.Also applies to: 34-61, 68-121, 122-136, 137-143
tests/unit/models/config/test_a2a_state_configuration.py (1)
18-103: Configuration validation tests are thorough; tightentmp_pathtyping.These tests nicely cover default/memory, sqlite, postgres (with options), mutual exclusivity, and
extra="forbid"behavior, matchingA2AStateConfiguration’s contract. To help static type‑checking, consider annotatingtmp_pathasPathinstead ofstrin the sqlite/combined tests so it matches pytest’s fixture type.tests/unit/app/endpoints/test_query.py (1)
1422-1434: Update local variable naming to “vector_store_ids” for clarity.
get_rag_toolgroupsnow producesargs["vector_store_ids"], but the test still names the inputvector_db_ids. Renaming the local variable tovector_store_idswould better reflect the current API and avoid confusion between the legacy “vector DB” terminology and the new vector store abstraction.docs/a2a_protocol.md (1)
18-49: Add a language to fenced code blocks to satisfy markdownlint.The architecture ASCII diagram is in an unlabeled code fence, which triggers MD040. You can fix this by specifying a language like
text:-``` +```text ... -``` +```Same applies to the later event‑flow diagram block.
src/a2a_storage/postgres_context_store.py (1)
13-21: Consider using a shared metadata registry or scoping metadata per-engine.The module-level
metadataobject is used for table creation. If this module is imported in contexts where multiple engines are used (though unlikely given the factory pattern), the same metadata could be bound to different engines. Currently the factory ensures a single shared engine, so this is not an immediate issue.src/a2a_storage/storage_factory.py (2)
26-29: Potential race condition in class-level state access.The class-level variables
_engine,_task_store, and_context_storeare accessed and modified in async methods without synchronization. In concurrent scenarios (e.g., multiple requests hitting the factory simultaneously during startup), this could lead to race conditions where multiple engines/stores are created before assignment.Consider adding an
asyncio.Lockat the class level:class A2AStorageFactory: """Factory for creating A2A storage backends...""" _engine: AsyncEngine | None = None _task_store: TaskStore | None = None _context_store: A2AContextStore | None = None + _lock: asyncio.Lock = asyncio.Lock()Then use the lock in the creation methods:
@classmethod async def create_task_store(cls, config: A2AStateConfiguration) -> TaskStore: - if cls._task_store is not None: - return cls._task_store + async with cls._lock: + if cls._task_store is not None: + return cls._task_store + # ... rest of creation logic ...
176-181:reset()may leak database connections.The
reset()method clears references without disposing the engine. If called when an engine exists, connections may leak. Consider either:
- Making
reset()async and callingawait cls._engine.dispose()first- Documenting that
cleanup()should be called beforereset()in tests@classmethod - def reset(cls) -> None: + async def reset(cls) -> None: """Reset factory state (for testing purposes).""" + if cls._engine is not None: + await cls._engine.dispose() cls._engine = None cls._task_store = None cls._context_store = Nonesrc/app/endpoints/a2a.py (3)
71-94: Potential race condition in lazy store initialization.The
_get_task_store()and_get_context_store()functions modify global state without synchronization. In concurrent request scenarios during startup, multiple stores could be created. Consider adding anasyncio.Lockto synchronize access:+_store_lock = asyncio.Lock() + async def _get_task_store() -> TaskStore: """Get the A2A task store, creating it if necessary.""" global _task_store # pylint: disable=global-statement - if _task_store is None: - _task_store = await A2AStorageFactory.create_task_store(configuration.a2a_state) + async with _store_lock: + if _task_store is None: + _task_store = await A2AStorageFactory.create_task_store(configuration.a2a_state) return _task_store
583-583: Consider making protocol version configurable or deriving from SDK.The
protocol_version="0.2.1"is hardcoded. If the A2A SDK version changes, this may become stale. Consider deriving this from the SDK or making it configurable.
770-770: Consider making the streaming timeout configurable.The 300-second (5-minute) timeout for streaming chunks is hardcoded. For long-running agent tasks or quick interactions, this may not be optimal. Consider making it configurable or deriving from service configuration.
+# Could be moved to configuration +STREAMING_CHUNK_TIMEOUT = 300.0 + # In response_generator: - chunk = await asyncio.wait_for(chunk_queue.get(), timeout=300.0) + chunk = await asyncio.wait_for(chunk_queue.get(), timeout=STREAMING_CHUNK_TIMEOUT)
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (27)
CLAUDE.md(1 hunks)docs/a2a_protocol.md(1 hunks)examples/lightspeed-stack-a2a-state-pg.yaml(1 hunks)examples/lightspeed-stack-a2a-state-sqlite.yaml(1 hunks)lightspeed-stack.yaml(1 hunks)pyproject.toml(2 hunks)src/a2a_storage/__init__.py(1 hunks)src/a2a_storage/context_store.py(1 hunks)src/a2a_storage/in_memory_context_store.py(1 hunks)src/a2a_storage/postgres_context_store.py(1 hunks)src/a2a_storage/sqlite_context_store.py(1 hunks)src/a2a_storage/storage_factory.py(1 hunks)src/app/endpoints/README.md(2 hunks)src/app/endpoints/a2a.py(1 hunks)src/app/routers.py(2 hunks)src/configuration.py(2 hunks)src/models/config.py(7 hunks)tests/unit/a2a_storage/__init__.py(1 hunks)tests/unit/a2a_storage/test_in_memory_context_store.py(1 hunks)tests/unit/a2a_storage/test_sqlite_context_store.py(1 hunks)tests/unit/a2a_storage/test_storage_factory.py(1 hunks)tests/unit/app/endpoints/test_a2a.py(1 hunks)tests/unit/app/endpoints/test_query.py(16 hunks)tests/unit/app/endpoints/test_streaming_query.py(12 hunks)tests/unit/app/test_routers.py(3 hunks)tests/unit/models/config/test_a2a_state_configuration.py(1 hunks)tests/unit/models/config/test_dump_configuration.py(6 hunks)
✅ Files skipped from review due to trivial changes (1)
- tests/unit/a2a_storage/init.py
🚧 Files skipped from review as they are similar to previous changes (5)
- tests/unit/app/endpoints/test_a2a.py
- lightspeed-stack.yaml
- tests/unit/app/test_routers.py
- tests/unit/models/config/test_dump_configuration.py
- pyproject.toml
🧰 Additional context used
📓 Path-based instructions (8)
tests/{unit,integration}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests; do not use unittest framework
Unit tests must achieve 60% code coverage; integration tests must achieve 10% coverage
Files:
tests/unit/a2a_storage/test_in_memory_context_store.pytests/unit/a2a_storage/test_storage_factory.pytests/unit/app/endpoints/test_query.pytests/unit/models/config/test_a2a_state_configuration.pytests/unit/app/endpoints/test_streaming_query.pytests/unit/a2a_storage/test_sqlite_context_store.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
pytest-mockwith AsyncMock objects for mocking in tests
Files:
tests/unit/a2a_storage/test_in_memory_context_store.pytests/unit/a2a_storage/test_storage_factory.pytests/unit/app/endpoints/test_query.pytests/unit/models/config/test_a2a_state_configuration.pytests/unit/app/endpoints/test_streaming_query.pytests/unit/a2a_storage/test_sqlite_context_store.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/a2a_storage/postgres_context_store.pysrc/app/routers.pysrc/a2a_storage/context_store.pysrc/a2a_storage/__init__.pysrc/a2a_storage/sqlite_context_store.pysrc/configuration.pysrc/models/config.pysrc/a2a_storage/storage_factory.pysrc/a2a_storage/in_memory_context_store.pysrc/app/endpoints/a2a.py
src/**/__init__.py
📄 CodeRabbit inference engine (CLAUDE.md)
Package
__init__.pyfiles must contain brief package descriptions
Files:
src/a2a_storage/__init__.py
src/models/config.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/config.py: All configuration must use Pydantic models extendingConfigurationBasewithextra="forbid"to reject unknown fields
Use type hintsOptional[FilePath],PositiveInt,SecretStrfor Pydantic configuration models
Files:
src/models/config.py
src/models/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/**/*.py: Use@field_validatorand@model_validatorfor custom validation in Pydantic models
Pydantic configuration classes must extendConfigurationBase; data models must extendBaseModel
Files:
src/models/config.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/a2a.py
🧠 Learnings (7)
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/models/config.py : All configuration must use Pydantic models extending `ConfigurationBase` with `extra="forbid"` to reject unknown fields
Applied to files:
tests/unit/models/config/test_a2a_state_configuration.pysrc/configuration.py
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (10)
tests/unit/a2a_storage/test_in_memory_context_store.py (1)
src/a2a_storage/in_memory_context_store.py (5)
InMemoryA2AContextStore(11-92)ready(86-92)get(29-46)delete(63-76)initialize(78-84)
tests/unit/a2a_storage/test_storage_factory.py (4)
src/a2a_storage/storage_factory.py (4)
A2AStorageFactory(18-181)create_task_store(31-74)create_context_store(77-118)cleanup(167-174)src/a2a_storage/in_memory_context_store.py (2)
InMemoryA2AContextStore(11-92)ready(86-92)src/a2a_storage/sqlite_context_store.py (2)
SQLiteA2AContextStore(23-143)ready(137-143)src/models/config.py (4)
config(325-342)config(1297-1305)A2AStateConfiguration(1250-1305)SQLiteDatabaseConfiguration(157-164)
src/a2a_storage/postgres_context_store.py (2)
src/a2a_storage/context_store.py (5)
delete(37-42)A2AContextStore(6-57)initialize(45-49)get(17-25)ready(52-57)src/a2a_storage/in_memory_context_store.py (4)
delete(63-76)initialize(78-84)get(29-46)ready(86-92)
tests/unit/models/config/test_a2a_state_configuration.py (1)
src/models/config.py (6)
config(325-342)config(1297-1305)A2AStateConfiguration(1250-1305)SQLiteDatabaseConfiguration(157-164)PostgreSQLDatabaseConfiguration(177-261)storage_type(1288-1294)
src/app/routers.py (1)
tests/unit/app/test_routers.py (1)
include_router(37-52)
src/a2a_storage/__init__.py (5)
src/a2a_storage/context_store.py (1)
A2AContextStore(6-57)src/a2a_storage/in_memory_context_store.py (1)
InMemoryA2AContextStore(11-92)src/a2a_storage/sqlite_context_store.py (1)
SQLiteA2AContextStore(23-143)src/a2a_storage/postgres_context_store.py (1)
PostgresA2AContextStore(24-142)src/a2a_storage/storage_factory.py (1)
A2AStorageFactory(18-181)
src/configuration.py (1)
src/models/config.py (1)
A2AStateConfiguration(1250-1305)
src/models/config.py (2)
src/utils/checks.py (1)
file_check(38-57)src/configuration.py (1)
a2a_state(163-167)
tests/unit/a2a_storage/test_sqlite_context_store.py (3)
src/a2a_storage/sqlite_context_store.py (5)
SQLiteA2AContextStore(23-143)initialize(51-61)ready(137-143)get(68-90)delete(122-135)src/a2a_storage/context_store.py (4)
initialize(45-49)ready(52-57)get(17-25)delete(37-42)src/a2a_storage/in_memory_context_store.py (4)
initialize(78-84)ready(86-92)get(29-46)delete(63-76)
src/a2a_storage/storage_factory.py (5)
src/a2a_storage/context_store.py (2)
A2AContextStore(6-57)initialize(45-49)src/a2a_storage/in_memory_context_store.py (2)
InMemoryA2AContextStore(11-92)initialize(78-84)src/a2a_storage/sqlite_context_store.py (2)
SQLiteA2AContextStore(23-143)initialize(51-61)src/a2a_storage/postgres_context_store.py (2)
PostgresA2AContextStore(24-142)initialize(52-62)src/models/config.py (4)
config(325-342)config(1297-1305)A2AStateConfiguration(1250-1305)storage_type(1288-1294)
🪛 GitHub Actions: E2E Tests
tests/unit/models/config/test_a2a_state_configuration.py
[error] 8-8: ModuleNotFoundError: No module named 'llama_stack'
src/configuration.py
[error] 8-8: ModuleNotFoundError: No module named 'llama_stack'
🪛 GitHub Actions: Integration tests
src/configuration.py
[error] 8-8: ModuleNotFoundError: No module named 'llama_stack' while importing replace_env_vars from llama_stack.core.stack.
🪛 GitHub Actions: Pyright
src/configuration.py
[error] 8-8: Import "llama_stack.core.stack" could not be resolved (reportMissingImports)
src/app/endpoints/a2a.py
[error] 11-11: Import "llama_stack.apis.agents.openai_responses" could not be resolved (reportMissingImports)
🪛 GitHub Actions: Python linter
src/configuration.py
[error] 8-8: E0401: Unable to import 'llama_stack.core.stack' (import-error)
src/app/endpoints/a2a.py
[error] 11-11: E0401: Unable to import 'llama_stack.apis.agents.openai_responses' (import-error)
[error] 67-67: C0103: Constant name "_task_store" doesn't conform to UPPER_CASE naming style (invalid-name)
[error] 68-68: C0103: Constant name "_context_store" doesn't conform to UPPER_CASE naming style (invalid-name)
🪛 GitHub Actions: Unit tests
src/configuration.py
[error] 8-8: ModuleNotFoundError: No module named 'llama_stack' when importing replace_env_vars from llama_stack.core.stack. Ensure dependency 'llama-stack' is installed or available in PYTHONPATH.
🪛 GitHub Check: Bandit
src/app/endpoints/a2a.py
[notice] 682-682:
Possible hardcoded password: ''
🪛 markdownlint-cli2 (0.18.1)
docs/a2a_protocol.md
18-18: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
318-318: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: build-pr
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
🔇 Additional comments (21)
CLAUDE.md (1)
21-30: A2A storage and cache structure doc is clear and aligned with the new modules.The added entries for
a2a_storageandcacheaccurately describe the new components and help contributors discover the A2A state backends and conversation cache. No issues from a code-architecture perspective.tests/unit/app/endpoints/test_streaming_query.py (1)
503-505: Vector store API mocking and quota-exceeded behavior look consistent.
- Updating the tests to use
mock_client.vector_stores.list.return_value = mocker.Mock(data=[mock_vector_db])andmock_vector_db.id = "VectorDB-1"aligns the mocks with the newer vector store API and keeps expectations in sync across all RAG/MCP-related tests.- The new
test_query_endpoint_quota_exceededcorrectly drives theRateLimitErrorpath and asserts both the429status and presence of the quota error message and model name in the streamed SSE payload, which is valuable coverage for this failure mode.No functional issues spotted in these changes.
Please double‑check against the current
llama_stack_clientandlitellmversions that:
vector_stores.list()really returns an object with a.dataattribute containing items with an.idfield, andRateLimitErrorstill carries the information your handler uses to compose"The model quota has been exceeded"so the assertion remains stable across library upgrades.Also applies to: 548-549, 605-606, 663-664, 723-724, 777-778, 832-833, 1244-1245, 1324-1325, 1389-1390, 1646-1648, 1696-1698, 1858-1899, 1901-1916
src/models/config.py (1)
1091-1136: A2A configuration and agent card loading align with the config model conventions.
Customization.agent_card_path/agent_card_configplus the validator that runschecks.file_checkand wrapsyaml.safe_loadinYAMLError/OSErrorhandling provide a robust way to surface misconfigured agent card YAML without crashing elsewhere.A2AStateConfigurationcorrectly extendsConfigurationBase, forbids extras, and enforces mutual exclusivity betweensqliteandpostgreswhile exposing a simplestorage_typeandconfigproperty for the storage factory.- Wiring
a2a_state: A2AStateConfigurationwith adefault_factoryinto the top‑levelConfigurationgives a clean default (“memory” backend) while still allowing explicit persistent backends.This all fits well with the existing configuration patterns.
Based on learnings, the new A2A config model and fields correctly adopt the
ConfigurationBase+ validators approach expected for config types.Also applies to: 1250-1305, 1549-1553, 762-820
src/app/routers.py (1)
23-25: A2A router wiring is consistent with existing unversioned endpoints.Importing
a2aand includinga2a.routeralongsidehealth,authorized, andmetricskeeps the A2A endpoints unversioned as per the existing pattern for road‑core style routes. No issues spotted here.Also applies to: 59-60
tests/unit/a2a_storage/test_in_memory_context_store.py (1)
1-95: In-memory A2A context store tests provide good behavioral coverage.The fixture + async tests nicely cover initialization, non‑existent keys, set/overwrite, delete (including idempotent delete), multiple mappings, and the
initialize()no‑op behavior. This should catch most regressions in the in‑memory implementation.src/configuration.py (2)
162-167:a2a_stateaccessor is consistent with other configuration helpers.The new property follows the same pattern (None‑check + direct field access) as the existing configuration accessors and cleanly exposes
A2AStateConfigurationto callers.
8-8: Fixllama_stack.core.stackimport so configuration loading doesn't break CI.All pipelines are currently failing with
ModuleNotFoundError: No module named 'llama_stack'when importingreplace_env_varsfromllama_stack.core.stack. That means any configuration load will crash before tests can run.If you don't want to make
llama-stacka hard dependency in all environments (including CI), consider guarding the import and degrading gracefully when it's unavailable, e.g.:-import logging -from typing import Any, Optional - -# We want to support environment variable replacement in the configuration -# similarly to how it is done in llama-stack, so we use their function directly -from llama_stack.core.stack import replace_env_vars +import logging +from typing import Any, Optional + +# We want to support environment variable replacement in the configuration +# similarly to how it is done in llama-stack. If llama_stack is not installed, +# fall back to a no-op and log a warning instead of crashing at import time. +try: + from llama_stack.core.stack import replace_env_vars as _replace_env_vars +except ModuleNotFoundError: # pragma: no cover - exercised in environments w/o llama_stack + _replace_env_vars = Noneand in
load_configuration:- with open(filename, encoding="utf-8") as fin: - config_dict = yaml.safe_load(fin) - config_dict = replace_env_vars(config_dict) + with open(filename, encoding="utf-8") as fin: + config_dict = yaml.safe_load(fin) + if _replace_env_vars is not None: + config_dict = _replace_env_vars(config_dict) + else: + logger.warning( + "llama_stack.core.stack not available; skipping env var substitution" + )Alternatively, ensure
llama-stackis declared as a required dependency inpyproject.tomland is installed in all CI jobs so the existing import succeeds.Either way, this needs to be addressed before merging, since it currently prevents tests from running.
Also applies to: 57-62
src/app/endpoints/README.md (1)
6-9: Endpoint documentation for A2A handlers is accurate and well-linked.The additions for
a2a.pyandresponses_a2a.pyclearly describe the Agent API vs. Responses API variants and point to the central A2A protocol documentation, which should help future maintainers discover these endpoints.Also applies to: 61-64
examples/lightspeed-stack-a2a-state-pg.yaml (1)
22-30: Postgres A2A state example aligns with configuration model.The
a2a_state.postgresblock matchesPostgreSQLDatabaseConfiguration(host/port/db/user/password/namespace/ssl_mode) and is consistent withA2AStateConfiguration’s shape, so this is a good, realistic example (withssl_mode: disableclearly appropriate for local/dev use).examples/lightspeed-stack-a2a-state-sqlite.yaml (1)
22-24: SQLite A2A state example is consistent with the config schema.
a2a_state.sqlite.db_pathmatchesSQLiteDatabaseConfigurationand howA2AStateConfiguration(sqlite=...)is used in tests, so this example will validate cleanly against the current models.src/a2a_storage/__init__.py (1)
1-23: Clean, well‑scoped A2A storage public API.Docstring, absolute imports, and
__all__give a clear, centralized surface (A2AContextStore+ concrete stores +A2AStorageFactory) without over‑exposing internals. No changes needed.tests/unit/a2a_storage/test_sqlite_context_store.py (1)
14-24: Solid coverage of SQLiteA2AContextStore; consider disposing the engine in the fixture.The async
storefixture plus the tests give good coverage of readiness, CRUD, overwrite, and auto‑initialization behavior. For slightly cleaner resource management (and to avoid potential "unclosed engine" warnings), you could turn the fixture into ayieldfixture andawait engine.dispose()in teardown.tests/unit/app/endpoints/test_query.py (1)
541-621: The review comment lacks substantive feedback and contains formatting issues.The comment includes an internal scratchpad (non-standard format for a review comment) and provides only vague approval without concrete analysis. While the code snippet shows proper use of pytest async tests with pytest-mock (using
mocker.Mock()andMockerFixture), the review comment itself offers no actionable guidance. The statement "vector store mocks correctly reflect the newvector_stores.listAPI" cannot be verified without examining the production code implementation, and the follow-up request for verification via shell script is left incomplete. A proper review comment should either identify specific issues with clear guidance or provide substantive approval with reasoning, not tentative suggestions pending external verification.src/a2a_storage/context_store.py (1)
1-57: LGTM! Clean abstract interface definition.The abstract base class follows best practices with proper use of ABC and
@abstractmethoddecorators, complete type annotations, and comprehensive docstrings following Google Python conventions. The interface is well-designed for the context-to-conversation mapping use case.src/a2a_storage/in_memory_context_store.py (1)
1-92: LGTM! Well-implemented in-memory store.The implementation follows all coding guidelines: uses the
logger = logging.getLogger(__name__)pattern, has complete type annotations, proper docstrings, and correctly usesasyncio.Lockfor thread-safe access to the shared dictionary. Good documentation noting the single-worker deployment limitation.src/a2a_storage/postgres_context_store.py (1)
93-119: LGTM! Correct PostgreSQL upsert implementation.The use of
pg_insertwithon_conflict_do_updateis the idiomatic way to handle upserts in PostgreSQL via SQLAlchemy. The transaction is properly managed withsession_maker.begin().src/app/endpoints/a2a.py (5)
678-682: False positive: Bandit hardcoded password warning.The Bandit static analysis flags
auth_token = ""as a possible hardcoded password. This is a false positive — it's a safe fallback value when authentication token extraction fails, not a credential. You can suppress this with a# noseccomment if desired:
179-245: Well-structured executor with appropriate error handling.The
A2AAgentExecutorclass is well-implemented with:
- Proper type annotations on
__init__and instance attributes- Comprehensive docstrings
- Appropriate broad exception handling with
pylint: disablecomments- Good separation between
execute()and_process_task_streaming()
375-486: LGTM! Clean stream conversion implementation.The
_convert_stream_to_eventsmethod properly handles the various Responses API event types and converts them to appropriate A2A events. Good use ofgetattrwith defaults for safe attribute access, and the text accumulation pattern for the final artifact is correct.
832-846: LGTM! Clean health check endpoint.Simple and effective health check with appropriate metadata. Note that
a2a_sdk_versionis hardcoded; consider importing from the A2A package if available.
11-13: Verify import path forOpenAIResponseObjectStream.Web search confirms that
OpenAIResponseObjectStreamexists in the llama-stack Responses API and is used for OpenAI-compatible streaming support. However, the exact module pathllama_stack.apis.agents.openai_responsescould not be verified in the repository. The pipeline failure may indicate either a missingllama-stackdependency in the CI environment or an incorrect import path. Verify the correct import location in your installed version of llama-stack and update accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/app/endpoints/a2a.py (2)
67-68: Add pylint disable comments for module-level singleton naming.Pylint flags
_task_storeand_context_storeas constant naming violations (C0103) because they are module-level and snake_case. These are mutable singletons, not constants, so the warning is a false positive.Apply this diff to suppress the warnings:
# Task store and context store are created lazily based on configuration. # For multi-worker deployments, configure 'a2a_state' with 'sqlite' or 'postgres' # to share state across workers. -_TASK_STORE: TaskStore | None = None -_CONTEXT_STORE: A2AContextStore | None = None +_TASK_STORE: TaskStore | None = None # pylint: disable=invalid-name +_CONTEXT_STORE: A2AContextStore | None = None # pylint: disable=invalid-name
162-163: Consider documenting the in-place event modification.Line 163 modifies
event.status.statein place to force intermediate events to showTaskState.working. While this may be intentional for streaming behavior, in-place parameter modification can be unexpected and goes against the guideline to "avoid in-place parameter modification anti-patterns; return new data structures instead."Consider either:
- Documenting this behavior explicitly in the
process_eventdocstring- Creating a copy of the event before modification
- Returning the modified event instead of modifying in place
Example for option 1:
def process_event( self, event: TaskStatusUpdateEvent | TaskArtifactUpdateEvent | Any ) -> None: """ Process an event from the agent run and detect signals about the task status. + + Note: For TaskStatusUpdateEvent instances, this method modifies the event's + status.state in place to force intermediate events to show "working" state + for streaming clients.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
pyproject.toml(1 hunks)src/app/endpoints/a2a.py(1 hunks)tests/unit/app/test_routers.py(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/unit/app/test_routers.py
🧰 Additional context used
📓 Path-based instructions (4)
pyproject.toml
📄 CodeRabbit inference engine (CLAUDE.md)
pyproject.toml: Configure pylint withsource-roots = "src"
Excludesrc/auth/k8s.pyfrom pyright type checking
Files:
pyproject.toml
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/app/endpoints/a2a.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/a2a.py
🧠 Learnings (7)
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Always check `pyproject.toml` for existing dependencies and versions before adding new ones
Applied to files:
pyproject.toml
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (1)
src/app/endpoints/a2a.py (7)
src/a2a_storage/context_store.py (2)
A2AContextStore(6-57)get(17-25)src/app/endpoints/query.py (1)
select_model_and_provider_id(459-527)src/client.py (2)
AsyncLlamaStackClientHolder(18-55)get_client(49-55)src/utils/responses.py (1)
extract_text_from_response_output_item(6-56)src/a2a_storage/in_memory_context_store.py (1)
get(29-46)src/a2a_storage/sqlite_context_store.py (1)
get(68-90)src/a2a_storage/postgres_context_store.py (1)
get(69-91)
🪛 GitHub Check: Bandit
src/app/endpoints/a2a.py
[notice] 682-682:
Possible hardcoded password: ''
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: build-pr
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
- GitHub Check: E2E: library mode / azure
- GitHub Check: E2E: server mode / azure
- GitHub Check: E2E: server mode / ci
- GitHub Check: E2E: library mode / ci
🔇 Additional comments (14)
pyproject.toml (3)
44-45: Dependency versions appropriately constrained.All new dependencies for A2A support follow the project's version pinning pattern:
a2a-sdk>=0.3.4,<0.4.0(addressing the prior review)aiosqlite>=0.21.0andasyncpg>=0.31.0for async database backendsPyYAML>=6.0.0for configuration handlingVersions are stable and consistent with the established dependency strategy.
Also applies to: 52-53, 62-62
194-195: Pylint configuration correctly set per guidelines.Line 195 correctly configures
source-roots = "src"as required by coding guidelines.
66-79: Verify pyright exclusion path matches coding guidelines.The pyright configuration excludes
"src/authentication/k8s.py"(line 71), but the coding guidelines reference"src/auth/k8s.py". Confirm whether this path naming difference reflects an intended rename or if the guidelines need updating.src/app/endpoints/a2a.py (11)
71-94: LGTM!The lazy initialization pattern for task and context stores is well-implemented with proper type annotations, docstrings, and explicit global statement usage. The factory pattern integration is clean.
97-113: LGTM!The conversion helper correctly transforms Responses API output to A2A Parts with proper type annotations and docstrings. The use of
Anyfor the output list is appropriate given the dynamic nature of the Responses API.
246-373: LGTM!The streaming task processing logic is well-structured with proper async operations, context persistence for multi-turn conversations, and clean integration with the aggregator pattern. The error handling and metadata extraction are handled correctly.
375-486: LGTM!The stream-to-events conversion logic comprehensively handles all event types (text deltas, tool calls, MCP calls, completion) with proper text accumulation and artifact assembly. The fallback logic for final text is well thought out.
509-586: LGTM!The agent card generation properly validates configuration, handles None cases with appropriate HTTP exceptions, and constructs the AgentCard with all required fields. The explicit None checks and error handling improvements from past reviews are well-implemented.
592-616: LGTM!The agent card endpoint correctly provides unauthenticated access (authentication only, no authorization) as required by the A2A protocol specification for agent discovery. Error handling with proper logging is in place.
619-644: LGTM!The A2A app factory function cleanly composes the A2A SDK components (executor, task store, request handler, application) with proper async initialization of the task store.
729-803: LGTM!The streaming implementation correctly uses a queue-based coordination pattern between the ASGI app and the response generator, with proper timeout handling (300s), task cleanup in the finally block, and appropriate content-type (text/event-stream) for A2A SSE streaming.
806-829: LGTM!The non-streaming implementation correctly buffers the entire response before returning, with proper handling of status code, headers (including byte-to-string decoding), and body accumulation.
832-846: LGTM!The health check endpoint appropriately provides unauthenticated access with useful metadata including service status, versions, and timestamp. This is standard for health monitoring endpoints.
678-682: Static analysis false positive - no action needed.Bandit flags the empty string
""as a "possible hardcoded password" on line 682. This is a false positive—the empty string is a safe default value when auth token extraction fails, not a hardcoded credential. The code properly attempts to extract the real token from the auth tuple first.
|
/test images |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (6)
src/a2a_storage/sqlite_context_store.py (2)
34-50: Annotate private attributes in__init__for clearer typesThe private attributes set in
__init__(_engine,_session_maker,_create_table,_initialized) are currently untyped. Given the project guideline to fully type class attributes, it would be good to annotate them explicitly for better static checking and readability.For example:
- self._engine = engine - self._session_maker = async_sessionmaker(engine, expire_on_commit=False) - self._create_table = create_table - self._initialized = False + self._engine: AsyncEngine = engine + self._session_maker: async_sessionmaker = async_sessionmaker( + engine, expire_on_commit=False + ) + self._create_table: bool = create_table + self._initialized: bool = False
92-120: Alignsetdocstring with actual upsert implementationThe docstring says “Uses INSERT OR REPLACE”, but the code performs an explicit delete followed by an insert inside a transaction. The behavior is fine, but the mismatch can confuse readers.
Consider updating just the docstring:
- """Store a context-to-conversation mapping. - - Uses INSERT OR REPLACE to handle both new and existing mappings. + """Store a context-to-conversation mapping. + + Replaces any existing mapping for the same context ID by deleting + and inserting a new row in a single transaction.src/a2a_storage/postgres_context_store.py (1)
35-50: Add type annotations to private attributes inPostgresA2AContextStoreFor consistency with project guidelines and the SQLite implementation, it would help to explicitly type the private attributes initialized here:
- self._engine = engine - self._session_maker = async_sessionmaker(engine, expire_on_commit=False) - self._create_table = create_table - self._initialized = False + self._engine: AsyncEngine = engine + self._session_maker: async_sessionmaker = async_sessionmaker( + engine, expire_on_commit=False + ) + self._create_table: bool = create_table + self._initialized: bool = Falsesrc/configuration.py (1)
12-25: ExposeA2AStateConfigurationviaAppConfigusing a direct type annotationThe new
a2a_stateproperty follows the same pattern as the other configuration accessors and correctly guards against an unset configuration. SinceA2AStateConfigurationis already imported, you can drop the string literal in the return type for consistency with the rest of the file:- @property - def a2a_state(self) -> "A2AStateConfiguration": + @property + def a2a_state(self) -> A2AStateConfiguration:Also applies to: 162-167
docs/a2a_protocol.md (2)
18-49: Add language specifier to fenced code block.The ASCII architecture diagram lacks a language specifier. Add
textor leave it as a plain code block to satisfy linting.-``` +```text ┌─────────────────────────────────────────────────────────────────┐ │ A2A Client │
315-349: Add language specifier to event flow diagram.Same issue as the architecture diagram - add
textspecifier.-``` +```text A2A Request │
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (25)
CLAUDE.md(1 hunks)docs/a2a_protocol.md(1 hunks)examples/lightspeed-stack-a2a-state-pg.yaml(1 hunks)examples/lightspeed-stack-a2a-state-sqlite.yaml(1 hunks)lightspeed-stack.yaml(1 hunks)pyproject.toml(1 hunks)src/a2a_storage/__init__.py(1 hunks)src/a2a_storage/context_store.py(1 hunks)src/a2a_storage/in_memory_context_store.py(1 hunks)src/a2a_storage/postgres_context_store.py(1 hunks)src/a2a_storage/sqlite_context_store.py(1 hunks)src/a2a_storage/storage_factory.py(1 hunks)src/app/endpoints/README.md(2 hunks)src/app/endpoints/a2a.py(1 hunks)src/app/routers.py(2 hunks)src/configuration.py(2 hunks)src/models/config.py(7 hunks)tests/unit/a2a_storage/__init__.py(1 hunks)tests/unit/a2a_storage/test_in_memory_context_store.py(1 hunks)tests/unit/a2a_storage/test_sqlite_context_store.py(1 hunks)tests/unit/a2a_storage/test_storage_factory.py(1 hunks)tests/unit/app/endpoints/test_a2a.py(1 hunks)tests/unit/app/test_routers.py(5 hunks)tests/unit/models/config/test_a2a_state_configuration.py(1 hunks)tests/unit/models/config/test_dump_configuration.py(6 hunks)
✅ Files skipped from review due to trivial changes (1)
- tests/unit/app/endpoints/test_a2a.py
🚧 Files skipped from review as they are similar to previous changes (6)
- pyproject.toml
- tests/unit/a2a_storage/test_in_memory_context_store.py
- CLAUDE.md
- tests/unit/app/test_routers.py
- tests/unit/a2a_storage/init.py
- examples/lightspeed-stack-a2a-state-pg.yaml
🧰 Additional context used
📓 Path-based instructions (8)
tests/{unit,integration}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests; do not use unittest framework
Unit tests must achieve 60% code coverage; integration tests must achieve 10% coverage
Files:
tests/unit/models/config/test_dump_configuration.pytests/unit/models/config/test_a2a_state_configuration.pytests/unit/a2a_storage/test_sqlite_context_store.pytests/unit/a2a_storage/test_storage_factory.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
pytest-mockwith AsyncMock objects for mocking in tests
Files:
tests/unit/models/config/test_dump_configuration.pytests/unit/models/config/test_a2a_state_configuration.pytests/unit/a2a_storage/test_sqlite_context_store.pytests/unit/a2a_storage/test_storage_factory.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/models/config.pysrc/a2a_storage/postgres_context_store.pysrc/a2a_storage/context_store.pysrc/a2a_storage/storage_factory.pysrc/app/routers.pysrc/a2a_storage/sqlite_context_store.pysrc/a2a_storage/__init__.pysrc/a2a_storage/in_memory_context_store.pysrc/app/endpoints/a2a.pysrc/configuration.py
src/models/config.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/config.py: All configuration must use Pydantic models extendingConfigurationBasewithextra="forbid"to reject unknown fields
Use type hintsOptional[FilePath],PositiveInt,SecretStrfor Pydantic configuration models
Files:
src/models/config.py
src/models/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/**/*.py: Use@field_validatorand@model_validatorfor custom validation in Pydantic models
Pydantic configuration classes must extendConfigurationBase; data models must extendBaseModel
Files:
src/models/config.py
src/**/__init__.py
📄 CodeRabbit inference engine (CLAUDE.md)
Package
__init__.pyfiles must contain brief package descriptions
Files:
src/a2a_storage/__init__.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/a2a.py
🧠 Learnings (8)
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/models/config.py : All configuration must use Pydantic models extending `ConfigurationBase` with `extra="forbid"` to reject unknown fields
Applied to files:
tests/unit/models/config/test_a2a_state_configuration.pysrc/configuration.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to tests/**/*.py : Use `pytest-mock` with AsyncMock objects for mocking in tests
Applied to files:
tests/unit/a2a_storage/test_storage_factory.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (10)
tests/unit/models/config/test_a2a_state_configuration.py (1)
src/models/config.py (6)
config(325-342)config(1297-1305)A2AStateConfiguration(1250-1305)SQLiteDatabaseConfiguration(157-164)PostgreSQLDatabaseConfiguration(177-261)storage_type(1288-1294)
tests/unit/a2a_storage/test_sqlite_context_store.py (1)
src/a2a_storage/sqlite_context_store.py (5)
SQLiteA2AContextStore(23-143)initialize(51-61)ready(137-143)get(68-90)delete(122-135)
src/models/config.py (2)
src/utils/checks.py (1)
file_check(38-57)src/configuration.py (1)
a2a_state(163-167)
src/a2a_storage/postgres_context_store.py (3)
src/a2a_storage/context_store.py (5)
delete(37-42)A2AContextStore(6-57)initialize(45-49)get(17-25)ready(52-57)src/a2a_storage/in_memory_context_store.py (4)
delete(63-76)initialize(78-84)get(29-46)ready(86-92)src/a2a_storage/sqlite_context_store.py (5)
delete(122-135)initialize(51-61)_ensure_initialized(63-66)get(68-90)ready(137-143)
src/a2a_storage/context_store.py (1)
src/a2a_storage/in_memory_context_store.py (4)
get(29-46)delete(63-76)initialize(78-84)ready(86-92)
src/app/routers.py (1)
tests/unit/app/test_routers.py (1)
include_router(37-52)
src/a2a_storage/sqlite_context_store.py (3)
src/a2a_storage/context_store.py (5)
delete(37-42)A2AContextStore(6-57)initialize(45-49)get(17-25)ready(52-57)src/a2a_storage/in_memory_context_store.py (4)
delete(63-76)initialize(78-84)get(29-46)ready(86-92)src/a2a_storage/postgres_context_store.py (5)
delete(121-134)initialize(52-62)_ensure_initialized(64-67)get(69-91)ready(136-142)
src/a2a_storage/__init__.py (5)
src/a2a_storage/context_store.py (1)
A2AContextStore(6-57)src/a2a_storage/in_memory_context_store.py (1)
InMemoryA2AContextStore(11-92)src/a2a_storage/sqlite_context_store.py (1)
SQLiteA2AContextStore(23-143)src/a2a_storage/postgres_context_store.py (1)
PostgresA2AContextStore(24-142)src/a2a_storage/storage_factory.py (1)
A2AStorageFactory(18-181)
src/a2a_storage/in_memory_context_store.py (3)
src/a2a_storage/context_store.py (5)
A2AContextStore(6-57)get(17-25)delete(37-42)initialize(45-49)ready(52-57)src/a2a_storage/postgres_context_store.py (4)
get(69-91)delete(121-134)initialize(52-62)ready(136-142)src/a2a_storage/sqlite_context_store.py (4)
get(68-90)delete(122-135)initialize(51-61)ready(137-143)
src/configuration.py (1)
src/models/config.py (1)
A2AStateConfiguration(1250-1305)
🪛 GitHub Check: Bandit
src/app/endpoints/a2a.py
[notice] 682-682:
Possible hardcoded password: ''
🪛 markdownlint-cli2 (0.18.1)
docs/a2a_protocol.md
18-18: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
315-315: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: build-pr
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
- GitHub Check: E2E: server mode / ci
- GitHub Check: E2E: library mode / ci
- GitHub Check: E2E: library mode / azure
- GitHub Check: E2E: server mode / azure
🔇 Additional comments (19)
lightspeed-stack.yaml (1)
5-5:base_urladdition is consistent with the service configuration schemaDefining
base_url: http://localhost:8080alongsidehost/portmatches the newServiceConfiguration.base_urlfield and keeps this example config aligned with the dump tests that default toNonewhen unset.src/app/endpoints/README.md (1)
6-9: A2A endpoint documentation entries look consistentThe new
a2a.pyandresponses_a2a.pysections clearly describe the A2A endpoints and point to the shared protocol doc, matching the style of the other endpoint entries.Also applies to: 61-63
src/app/routers.py (1)
23-25: A2A router wiring is straightforward and consistentImporting
a2aand includinga2a.routerwithout a prefix aligns with how other unversioned endpoints are exposed here. This keeps routing simple while tests can assert the new router’s presence.Also applies to: 59-60
tests/unit/models/config/test_dump_configuration.py (1)
101-106: Updated expectations forbase_urlanda2a_statekeep dumps in sync with the modelThe added
"base_url": Noneunderserviceand the top-level"a2a_state": {"sqlite": None, "postgres": None}entries correctly reflect the expandedConfiguration/A2AStateConfigurationsurface and ensure dump tests fail if these new fields drift from the model defaults. Based on learnings, this helps catch unknown or missing configuration keys early whenextra="forbid"is enforced in config models.Also applies to: 190-193, 406-411, 510-513, 595-601, 694-697
src/a2a_storage/__init__.py (1)
1-23: A2A storage package exports are clear and coherentThe
__init__docstring concisely documents the purpose of the package, and__all__exposes exactly the expected public surface (A2AContextStorevariants plusA2AStorageFactory) using absolute imports as per guidelines.tests/unit/a2a_storage/test_storage_factory.py (1)
1-172: LGTM! Test file follows pytest-mock guidelines and provides good coverage.The test suite properly uses
mocker.patch.objectwithPropertyMockfor mocking configuration properties, addresses the previous review feedback, and covers key factory behaviors including store creation, reuse, cleanup, reset, and error handling for invalid configurations.src/a2a_storage/context_store.py (1)
1-57: Well-designed abstract interface.The abstract base class properly defines the contract for A2A context stores with complete type annotations, comprehensive docstrings following Google conventions, and appropriate use of
ABCwith@abstractmethoddecorators.tests/unit/a2a_storage/test_sqlite_context_store.py (1)
1-147: Comprehensive test coverage for SQLite context store.The test suite effectively covers the store's lifecycle (initialization, ready state), CRUD operations (get/set/delete), edge cases (nonexistent keys, overwrites), and the auto-initialization behavior. Good use of pytest fixtures with temporary database files.
examples/lightspeed-stack-a2a-state-sqlite.yaml (1)
1-24: Valid example configuration for SQLite-backed A2A state.The configuration correctly structures the
a2a_statesection with only thesqlitesub-object (no erroneoustypefield), matching theA2AStateConfigurationmodel expectations.docs/a2a_protocol.md (1)
208-245: Configuration examples correctly structured.The
a2a_stateconfiguration examples now correctly omit thetypefield and use only the allowed sub-objects (sqlite,postgres), matching theA2AStateConfigurationmodel that usesextra="forbid". This addresses the previous review feedback.src/a2a_storage/in_memory_context_store.py (1)
1-92: Clean implementation following all coding guidelines.The in-memory context store correctly implements the
A2AContextStoreinterface with proper logging, type annotations, docstrings, and async lock protection for thread-safe access to the internal dictionary.src/a2a_storage/storage_factory.py (2)
18-75: Well-structured factory with clear separation of concerns.The factory correctly implements singleton patterns for shared resources, uses match/case for clean dispatch logic, and provides both task and context store creation with consistent error handling for misconfigured backends.
176-181:reset()doesn't dispose engine connections.The synchronous
reset()method clears references but doesn't callengine.dispose(), potentially leaking database connections. This is acceptable for testing (where the engine may not be fully initialized), but consider adding a note in the docstring or ensuring tests usecleanup()when the engine was created.src/models/config.py (1)
1250-1306: LGTM! Well-structured configuration class.The
A2AStateConfigurationclass follows the established patterns in this codebase (similar toDatabaseConfigurationandConversationHistoryConfiguration). The validator correctly ensures at most one backend is configured, defaulting to in-memory storage when neither is set. The convenience properties provide clean access to the storage type and configuration.src/app/endpoints/a2a.py (5)
295-305: QueryRequest instantiation looks correct.All required parameters are explicitly provided, addressing the Pyright issue from the past review. The explicit None values for optional parameters make the intent clear.
527-537: Error handling appropriately uses HTTPException.The function correctly raises
HTTPExceptionwith status 500 for configuration errors, as addressed in past reviews. This provides proper API error responses when agent card configuration is missing.
729-803: Streaming implementation is well-designed.The streaming path correctly:
- Uses
asyncio.Queueto pass chunks from ASGI app to response generator- Runs A2A app in background task
- Implements proper cleanup in the finally block
- Uses timeout to prevent hanging (300s)
- Returns
StreamingResponsewith SSE content typeThe implementation properly handles the ASGI protocol for streaming responses.
805-829: Non-streaming implementation correctly buffers response.The buffered path:
- Captures status code and headers from
http.response.start- Accumulates body chunks from
http.response.body- Returns a complete
Responseobject with proper status and headersThe header decoding on line 828 assumes header keys and values are bytes, which is correct for ASGI.
1-846: Excellent A2A endpoint implementation!This is a comprehensive and well-structured implementation of the A2A protocol support. The code demonstrates:
- Proper architecture: Clean separation between executor, aggregator, and endpoint logic
- Multi-turn support: Context store integration for persistent conversation mapping
- Dual execution paths: Correctly handles both streaming and non-streaming requests
- Error handling: Appropriate use of HTTPException and broad exception catches where needed
- Type safety: Complete type annotations throughout
- Documentation: All functions and classes have proper docstrings
The implementation addresses all concerns raised in past reviews and follows the established coding guidelines for this project.
Based on learnings and coding guidelines.
| def test_sqlite_configuration(self, tmp_path: str) -> None: | ||
| """Test SQLite configuration.""" | ||
| db_path = f"{tmp_path}/test.db" | ||
| sqlite_config = SQLiteDatabaseConfiguration(db_path=db_path) | ||
| config = A2AStateConfiguration(sqlite=sqlite_config) | ||
|
|
||
| assert config.storage_type == "sqlite" | ||
| assert config.sqlite is not None | ||
| assert config.sqlite.db_path == db_path | ||
| assert config.config == sqlite_config |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix tmp_path type annotation.
The tmp_path parameter is typed as str, but pytest's tmp_path fixture provides a pathlib.Path object. The code works because f-string coerces Path to string, but the type hint is incorrect.
- def test_sqlite_configuration(self, tmp_path: str) -> None:
+ def test_sqlite_configuration(self, tmp_path: Path) -> None:Also add the import at the top:
from pathlib import Path🤖 Prompt for AI Agents
In tests/unit/models/config/test_a2a_state_configuration.py around lines 27-36,
change the tmp_path parameter type from str to pathlib.Path and add "from
pathlib import Path" at the top of the file; construct the DB path using
tmp_path / "test.db" and pass it as a string to SQLiteDatabaseConfiguration
(e.g., db_path = tmp_path / "test.db" and
SQLiteDatabaseConfiguration(db_path=str(db_path))) so the type hint matches
pytest's fixture and the config still receives a string path.
| def test_both_sqlite_and_postgres_raises_error(self, tmp_path: str) -> None: | ||
| """Test that configuring both SQLite and PostgreSQL raises ValidationError.""" | ||
| db_path = f"{tmp_path}/test.db" | ||
| sqlite_config = SQLiteDatabaseConfiguration(db_path=db_path) | ||
| postgres_config = PostgreSQLDatabaseConfiguration( | ||
| host="localhost", | ||
| port=5432, | ||
| db="test", | ||
| user="test", | ||
| password="test", | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same tmp_path type annotation issue.
Consistent with the earlier comment, update the type hint here as well.
- def test_both_sqlite_and_postgres_raises_error(self, tmp_path: str) -> None:
+ def test_both_sqlite_and_postgres_raises_error(self, tmp_path: Path) -> None:🤖 Prompt for AI Agents
In tests/unit/models/config/test_a2a_state_configuration.py around lines 76 to
86, the tmp_path parameter is annotated as str but the pytest tmp_path fixture
provides a pathlib.Path; change the annotation to pathlib.Path (or Path) and
ensure Path is imported from pathlib at the top of the file if not already
present so the test uses the correct type.
- Maps contextID to conversations ID so that the agent has the needed content - Make use of TaskState completed, failed, working and input_required - Add model card configuration option through yaml file - Uses artifacts updates for the streaming and the final chunk
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/a2a_storage/storage_factory.py (1)
145-157: URL-encode password in PostgreSQL connection string.If the password contains special characters (e.g.,
@,/,:,?,#), the connection string will be malformed.Apply this fix:
+from urllib.parse import quote_plus + # In _get_or_create_engine method (around line 149): - password = pg.password.get_secret_value() if pg.password else "" + password = quote_plus(pg.password.get_secret_value()) if pg.password else "" connection_string = ( f"postgresql+asyncpg://{pg.user}:{password}" f"@{pg.host}:{pg.port}/{pg.db}" )
🧹 Nitpick comments (4)
docs/a2a_protocol.md (2)
18-49: Add language specifier to code fence for markdown linting.The ASCII architecture diagram should specify a language for the fenced code block to satisfy markdown linting rules (MD040). Adding
textas the language specifier would resolve the warning while maintaining readability.Apply this change:
-``` +```text ┌─────────────────────────────────────────────────────────────────┐ │ A2A Client │
315-349: Add language specifier to code fence for markdown linting.The event flow diagram should specify a language for the fenced code block to satisfy markdown linting rules (MD040). Adding
textas the language specifier would resolve the warning.Apply this change:
-``` +```text A2A Request │tests/unit/app/endpoints/test_a2a.py (1)
7-7: Refactor to use pytest-mock instead of unittest.mock imports.The project configuration explicitly bans
unittest.mockin favor ofpytest-mock(seepyproject.tomlline 207). Instead of importingAsyncMockandMagicMockfromunittest.mock, use themockerfixture methods provided by pytest-mock:-from unittest.mock import AsyncMock, MagicMock +# Use mocker.AsyncMock() and mocker.MagicMock() insteadThen throughout the file, replace:
AsyncMock(...)withmocker.AsyncMock(...)MagicMock(...)withmocker.MagicMock(...)This aligns with the project's testing standards and coding guidelines.
Based on coding guidelines, the project mandates pytest-mock usage over unittest.mock.
src/a2a_storage/sqlite_context_store.py (1)
92-120: Consider clarifying the upsert implementation comment.The code comment on line 104 mentions "Use SQLite's INSERT OR REPLACE" but the implementation uses a delete-then-insert pattern. While this is functionally correct and atomic within the transaction, the comment is misleading.
Either update the comment to reflect the actual implementation:
async with self._session_maker.begin() as session: - # Use SQLite's INSERT OR REPLACE + # Upsert by deleting existing row and inserting new values await session.execute(Or use SQLite's native INSERT OR REPLACE if preferred:
from sqlalchemy.dialects.sqlite import insert as sqlite_insert async with self._session_maker.begin() as session: stmt = sqlite_insert(a2a_context_table).values( context_id=context_id, conversation_id=conversation_id, ) stmt = stmt.on_conflict_do_update( index_elements=['context_id'], set_={'conversation_id': conversation_id} ) await session.execute(stmt)
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (25)
CLAUDE.md(1 hunks)docs/a2a_protocol.md(1 hunks)examples/lightspeed-stack-a2a-state-pg.yaml(1 hunks)examples/lightspeed-stack-a2a-state-sqlite.yaml(1 hunks)lightspeed-stack.yaml(1 hunks)pyproject.toml(1 hunks)src/a2a_storage/__init__.py(1 hunks)src/a2a_storage/context_store.py(1 hunks)src/a2a_storage/in_memory_context_store.py(1 hunks)src/a2a_storage/postgres_context_store.py(1 hunks)src/a2a_storage/sqlite_context_store.py(1 hunks)src/a2a_storage/storage_factory.py(1 hunks)src/app/endpoints/README.md(2 hunks)src/app/endpoints/a2a.py(1 hunks)src/app/routers.py(2 hunks)src/configuration.py(2 hunks)src/models/config.py(7 hunks)tests/unit/a2a_storage/__init__.py(1 hunks)tests/unit/a2a_storage/test_in_memory_context_store.py(1 hunks)tests/unit/a2a_storage/test_sqlite_context_store.py(1 hunks)tests/unit/a2a_storage/test_storage_factory.py(1 hunks)tests/unit/app/endpoints/test_a2a.py(1 hunks)tests/unit/app/test_routers.py(5 hunks)tests/unit/models/config/test_a2a_state_configuration.py(1 hunks)tests/unit/models/config/test_dump_configuration.py(6 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/app/endpoints/README.md
🚧 Files skipped from review as they are similar to previous changes (10)
- CLAUDE.md
- tests/unit/a2a_storage/test_in_memory_context_store.py
- tests/unit/a2a_storage/test_storage_factory.py
- src/a2a_storage/in_memory_context_store.py
- tests/unit/models/config/test_a2a_state_configuration.py
- lightspeed-stack.yaml
- src/a2a_storage/init.py
- examples/lightspeed-stack-a2a-state-pg.yaml
- src/configuration.py
- tests/unit/app/test_routers.py
🧰 Additional context used
📓 Path-based instructions (8)
tests/{unit,integration}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests; do not use unittest framework
Unit tests must achieve 60% code coverage; integration tests must achieve 10% coverage
Files:
tests/unit/models/config/test_dump_configuration.pytests/unit/a2a_storage/__init__.pytests/unit/a2a_storage/test_sqlite_context_store.pytests/unit/app/endpoints/test_a2a.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
pytest-mockwith AsyncMock objects for mocking in tests
Files:
tests/unit/models/config/test_dump_configuration.pytests/unit/a2a_storage/__init__.pytests/unit/a2a_storage/test_sqlite_context_store.pytests/unit/app/endpoints/test_a2a.py
pyproject.toml
📄 CodeRabbit inference engine (CLAUDE.md)
pyproject.toml: Configure pylint withsource-roots = "src"
Excludesrc/auth/k8s.pyfrom pyright type checking
Files:
pyproject.toml
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/a2a_storage/sqlite_context_store.pysrc/a2a_storage/context_store.pysrc/app/routers.pysrc/a2a_storage/storage_factory.pysrc/a2a_storage/postgres_context_store.pysrc/models/config.pysrc/app/endpoints/a2a.py
src/models/config.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/config.py: All configuration must use Pydantic models extendingConfigurationBasewithextra="forbid"to reject unknown fields
Use type hintsOptional[FilePath],PositiveInt,SecretStrfor Pydantic configuration models
Files:
src/models/config.py
src/models/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/**/*.py: Use@field_validatorand@model_validatorfor custom validation in Pydantic models
Pydantic configuration classes must extendConfigurationBase; data models must extendBaseModel
Files:
src/models/config.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/a2a.py
🧠 Learnings (8)
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Always check `pyproject.toml` for existing dependencies and versions before adding new ones
Applied to files:
pyproject.toml
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/__init__.py : Package `__init__.py` files must contain brief package descriptions
Applied to files:
tests/unit/a2a_storage/__init__.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (4)
src/a2a_storage/sqlite_context_store.py (3)
src/a2a_storage/context_store.py (5)
delete(37-42)A2AContextStore(6-57)initialize(45-49)get(17-25)ready(52-57)src/a2a_storage/in_memory_context_store.py (4)
delete(63-76)initialize(78-84)get(29-46)ready(86-92)src/a2a_storage/postgres_context_store.py (5)
delete(121-134)initialize(52-62)_ensure_initialized(64-67)get(69-91)ready(136-142)
src/a2a_storage/context_store.py (3)
src/a2a_storage/in_memory_context_store.py (4)
get(29-46)delete(63-76)initialize(78-84)ready(86-92)src/a2a_storage/postgres_context_store.py (4)
get(69-91)delete(121-134)initialize(52-62)ready(136-142)src/a2a_storage/sqlite_context_store.py (4)
get(68-90)delete(122-135)initialize(51-61)ready(137-143)
src/a2a_storage/postgres_context_store.py (3)
src/a2a_storage/context_store.py (5)
delete(37-42)A2AContextStore(6-57)initialize(45-49)get(17-25)ready(52-57)src/a2a_storage/in_memory_context_store.py (4)
delete(63-76)initialize(78-84)get(29-46)ready(86-92)src/a2a_storage/sqlite_context_store.py (5)
delete(122-135)initialize(51-61)_ensure_initialized(63-66)get(68-90)ready(137-143)
src/models/config.py (2)
src/utils/checks.py (1)
file_check(38-57)src/configuration.py (1)
a2a_state(163-167)
🪛 GitHub Check: Bandit
src/app/endpoints/a2a.py
[notice] 682-682:
Possible hardcoded password: ''
🪛 markdownlint-cli2 (0.18.1)
docs/a2a_protocol.md
18-18: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
315-315: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: build-pr
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
- GitHub Check: E2E: server mode / azure
- GitHub Check: E2E: library mode / ci
- GitHub Check: E2E: library mode / azure
- GitHub Check: E2E: server mode / ci
🔇 Additional comments (35)
pyproject.toml (1)
44-62: LGTM! Dependency additions support A2A protocol integration.The new dependencies are properly versioned:
a2a-sdkwith appropriate upper bound to prevent breaking changesaiosqliteandasyncpgfor async database drivers supporting persistent A2A storagePyYAMLfor agent card configurationAll version constraints follow the project's pinning pattern.
tests/unit/a2a_storage/__init__.py (1)
1-1: LGTM! Test package properly documented.The
__init__.pyincludes a brief package description as expected for test modules.tests/unit/models/config/test_dump_configuration.py (1)
105-105: LGTM! Test expectations updated to match new configuration schema.The test assertions correctly include the new
base_urlfield underserviceand the newa2a_statesection withsqliteandpostgressub-fields, maintaining consistency across all test cases.Also applies to: 190-193, 410-410, 510-513, 600-600, 694-697
src/app/routers.py (1)
23-24: LGTM! A2A router correctly integrated.The A2A router is properly imported and registered at the root level (no prefix), which is correct for A2A protocol endpoints that must be accessible at standard paths like
/.well-known/agent.jsonand/a2a.Also applies to: 58-60
src/a2a_storage/context_store.py (1)
1-57: LGTM! Well-designed abstract interface following all coding guidelines.The
A2AContextStoreabstract base class correctly uses:
- ABC with
@abstractmethoddecorators for all interface methods- Complete type annotations with modern syntax (
str | None)- Google-style docstrings for class and all methods
- PascalCase naming convention
- Async patterns for I/O operations
- Clear separation of concerns with focused, single-responsibility methods
The interface provides a solid foundation for multiple storage backend implementations.
tests/unit/a2a_storage/test_sqlite_context_store.py (1)
1-147: LGTM! Comprehensive test coverage following pytest best practices.The test module correctly:
- Uses pytest framework with
@pytest.mark.asynciofor async tests- Employs fixtures for test setup with proper isolation
- Covers all CRUD operations, edge cases, and state management
- Tests both explicit initialization and auto-initialization behavior
- Uses descriptive test names that clearly indicate what is being tested
- Provides good coverage for the SQLite backend implementation
The tests align with the 60% unit test coverage guideline.
tests/unit/app/endpoints/test_a2a.py (1)
1-632: LGTM! Comprehensive test coverage for A2A endpoints.The test module provides excellent coverage with ~25 test methods across multiple test classes:
- Content conversion utilities
- Task state management and aggregation
- Agent card generation
- Executor lifecycle and error handling
- Context-to-conversation mapping
- Endpoint handlers
All tests follow pytest conventions with:
- Descriptive test names using
test_prefix- Proper fixtures for setup
- Type annotations
- Clear docstrings
- Appropriate use of
@pytest.mark.asynciofor async testsThe tests demonstrate thorough validation of both happy paths and error scenarios.
examples/lightspeed-stack-a2a-state-sqlite.yaml (1)
1-24: LGTM! Clear example configuration for SQLite-backed A2A state.The example configuration correctly demonstrates the new A2A state configuration with SQLite backend, aligning with the A2AStateConfiguration model introduced in this PR.
src/a2a_storage/sqlite_context_store.py (4)
1-20: LGTM! Clean imports and table definition.The module follows coding guidelines with absolute imports, proper logger setup, and a well-defined SQLAlchemy table schema for A2A context mappings.
34-61: LGTM! Proper initialization with type safety.The initialization logic correctly handles table creation with appropriate guards and follows the same pattern as the PostgreSQL implementation.
68-90: LGTM! Correct retrieval implementation.The get method properly ensures initialization, queries the database, and returns None when context is not found.
122-143: LGTM! Correct delete and ready implementations.Both methods properly implement the A2AContextStore interface with appropriate logging and state checking.
src/a2a_storage/storage_factory.py (4)
1-29: LGTM! Well-structured factory with proper singleton management.The class design appropriately uses class-level singletons for engine and store instances, with complete type annotations following coding guidelines.
30-74: LGTM! Robust task store creation with proper error handling.The factory method correctly handles all storage types with appropriate validation and initialization for database-backed stores.
76-118: LGTM! Consistent context store creation pattern.The method mirrors the task store creation logic with appropriate store-specific instantiation for each backend type.
166-181: LGTM! Proper resource cleanup methods.Both cleanup and reset methods appropriately manage the factory's lifecycle, with cleanup handling async engine disposal and reset providing test-friendly state clearing.
src/a2a_storage/postgres_context_store.py (3)
1-21: LGTM! Correct PostgreSQL-specific imports and table definition.The module properly imports PostgreSQL dialect-specific insert for native upsert support, with a table schema matching the SQLite implementation.
93-119: LGTM! Proper PostgreSQL native upsert implementation.The set method correctly uses PostgreSQL's INSERT ... ON CONFLICT DO UPDATE for efficient atomic upserts, which is more performant than the delete-insert pattern.
35-91: LGTM! Consistent implementation with SQLite counterpart.All methods properly implement the A2AContextStore interface with appropriate PostgreSQL-specific behavior where needed.
Also applies to: 121-142
src/models/config.py (6)
10-10: LGTM! YAML import correctly placed at module level.The import follows coding guidelines and was properly moved from inline usage as per previous review feedback.
366-370: LGTM! Well-defined base_url field for A2A support.The new field is properly typed as Optional[str] with clear documentation explaining its purpose for A2A protocol support.
815-819: LGTM! A2A protocol actions properly defined.The new action enum members follow the existing naming convention and cover the necessary A2A protocol operations for authorization.
1095-1096: LGTM! Agent card configuration with proper error handling.The new fields are well-typed, and the YAML loading logic correctly handles parsing and I/O errors with descriptive messages, addressing previous review feedback.
Also applies to: 1120-1134
1250-1306: LGTM! Well-designed A2A state configuration model.The A2AStateConfiguration class follows Pydantic best practices with proper validation, clear docstrings, and convenience properties for accessing the configured backend. The validator correctly ensures only one storage type is configured at a time.
1549-1553: LGTM! A2A state configuration properly integrated.The a2a_state field is correctly added to the main Configuration class with appropriate default factory and documentation.
src/app/endpoints/a2a.py (10)
1-69: LGTM! Comprehensive imports and proper module setup.The imports are well-organized with absolute imports following coding guidelines. The logger naming pattern is consistent with other endpoint handlers as per project conventions. Module-level singletons for task and context stores are appropriately typed.
Based on learnings, the logger name "app.endpoints.handlers" is intentionally shared across endpoint files for unified logging configuration.
71-94: LGTM! Proper lazy initialization of storage backends.The getter functions correctly implement lazy singleton initialization using the storage factory, with appropriate async handling and type annotations.
97-113: LGTM! Clean conversion from Responses API to A2A parts.The function correctly extracts text content from Responses API output and converts it to A2A Part objects with proper structure.
116-174: LGTM! Well-designed task result aggregator with proper state management.The aggregator correctly implements priority-based state tracking and prevents premature stream termination by ensuring intermediate events maintain "working" state. Type annotations and docstrings are complete.
179-244: LGTM! Robust executor initialization and error handling.The A2AAgentExecutor correctly implements the AgentExecutor interface with proper type annotations, task creation logic, and comprehensive error handling that publishes failure events to maintain protocol compliance.
246-373: LGTM! Comprehensive streaming task processing with multi-turn support.The method correctly implements the full A2A task lifecycle: validation, context mapping for multi-turn conversations, LLM streaming via Responses API, event conversion, and final status publishing. The integration with persistent context storage enables proper conversation continuity.
375-486: LGTM! Correct event stream conversion with comprehensive chunk handling.The method properly converts Responses API stream events to A2A protocol events, handling text deltas, tool calls, MCP calls, and final artifact assembly. The event mapping maintains A2A protocol compliance while preserving all relevant information.
488-586: LGTM! Proper cancellation stub and comprehensive agent card generation.The cancel method appropriately raises NotImplementedError with logging. The agent card generation correctly parses configuration, validates required fields, and raises HTTPException for missing configuration, following API error handling best practices.
592-644: LGTM! Agent card endpoint and A2A app factory correctly implemented.The agent card endpoint provides required A2A discovery at well-known URLs with appropriate logging. The _create_a2a_app factory properly assembles the A2A Starlette application with authentication context, task store, and request handler.
Based on previous review discussion, the agent card is intentionally accessible with authentication but without authorization enforcement, as agent cards contain security scheme information needed for discovery.
647-846: LGTM! Sophisticated A2A JSON-RPC handler with streaming support.The endpoint correctly implements the full A2A protocol flow:
- Per-request app creation with authentication context
- Detection of streaming vs non-streaming requests
- Proper body replay after parsing for method detection
- Queue-based streaming with timeout protection
- Buffered non-streaming response handling
- Comprehensive error handling and logging
The health check endpoint provides useful service information.
Note: The Bandit warning on line 682 about "hardcoded password" for empty string
""is a false positive - this is a fallback value when auth token extraction fails, not an actual password.
4e16313 to
629a3ab
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (7)
src/models/config.py (1)
366-370: Consider URL validation forbase_urlfield.The
base_urlfield accepts any string, but it's intended to be a URL. Other URL fields in this file useAnyHttpUrlfor validation (e.g.,JwkConfiguration.url). Consider usingAnyHttpUrlfor type safety, or document that an invalid URL will cause runtime errors in the A2A agent card generation.- base_url: Optional[str] = Field( + base_url: Optional[AnyHttpUrl] = Field( None, title="Base URL", description="Externally reachable base URL for the service; needed for A2A support.", )Note: If
AnyHttpUrlis too restrictive (e.g., doesn't allowlocalhostURLs in dev), an alternative is to add a@field_validatorto validate URL format while still allowing localhost.src/app/endpoints/a2a.py (2)
583-583: Consider making protocol_version configurable or deriving from SDK.The
protocol_versionis hardcoded as"0.2.1". This could drift from the actual a2a-sdk version being used. Consider deriving this from the SDK or making it configurable.+from a2a import __version__ as a2a_sdk_version # If available + return AgentCard( ... - protocol_version="0.2.1", + protocol_version=a2a_sdk_version, # Or keep hardcoded if SDK doesn't expose thisIf the SDK doesn't expose protocol version, keeping it hardcoded is acceptable, but add a comment noting it should be updated when upgrading the SDK.
606-616: Consider reducing log verbosity for agent card endpoint.The agent card endpoint logs at INFO level for every request. In high-traffic scenarios, this could generate excessive logs. Consider using DEBUG level or removing the redundant logs since the access log already captures requests.
try: - logger.info("Serving A2A Agent Card") + logger.debug("Serving A2A Agent Card") agent_card = get_lightspeed_agent_card() - logger.info("Agent Card URL: %s", agent_card.url) - logger.info( + logger.debug("Agent Card URL: %s", agent_card.url) + logger.debug( "Agent Card capabilities: streaming=%s", agent_card.capabilities.streaming ) return agent_cardtests/unit/a2a_storage/test_sqlite_context_store.py (1)
84-88: Consider adding an assertion to verify the no-op delete.The test verifies that deleting a nonexistent key doesn't raise, but adding an assertion would make it more explicit.
@pytest.mark.asyncio async def test_delete_nonexistent_key(self, store: SQLiteA2AContextStore) -> None: """Test deleting a key that doesn't exist (should not raise).""" # Should not raise any exception await store.delete("nonexistent-context-id") + # Verify store still works after no-op delete + assert store.ready() is Truesrc/a2a_storage/sqlite_context_store.py (1)
15-20: Consider adding explicit String length for SQLite compatibility.While SQLite handles unbounded
Stringcolumns, specifying a length (e.g.,String(255)) improves schema documentation and portability. This is optional since both SQLite stores are internal.src/a2a_storage/in_memory_context_store.py (1)
29-46: Minor:if conversation_id:will treat empty string as falsy.If an empty string
""is a validconversation_id, this condition would incorrectly log "not found". In practice, this is unlikely to matter since conversation IDs are typically UUIDs, but usingif conversation_id is not None:would be more precise.async with self._lock: conversation_id = self._contexts.get(context_id) - if conversation_id: + if conversation_id is not None: logger.debug( "Context %s maps to conversation %s", context_id, conversation_id )src/a2a_storage/postgres_context_store.py (1)
13-21: Consider extracting shared table metadata to reduce duplication.The
a2a_context_tabledefinition is duplicated acrosssqlite_context_store.pyandpostgres_context_store.py. Extracting it to a shared module (e.g.,a2a_storage/models.pyor withincontext_store.py) would reduce duplication and ensure schema consistency.Example shared module structure:
# a2a_storage/models.py from sqlalchemy import Column, String, Table, MetaData metadata = MetaData() a2a_context_table = Table( "a2a_contexts", metadata, Column("context_id", String, primary_key=True), Column("conversation_id", String, nullable=False), )
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (24)
CLAUDE.md(1 hunks)docs/a2a_protocol.md(1 hunks)examples/lightspeed-stack-a2a-state-pg.yaml(1 hunks)examples/lightspeed-stack-a2a-state-sqlite.yaml(1 hunks)pyproject.toml(1 hunks)src/a2a_storage/__init__.py(1 hunks)src/a2a_storage/context_store.py(1 hunks)src/a2a_storage/in_memory_context_store.py(1 hunks)src/a2a_storage/postgres_context_store.py(1 hunks)src/a2a_storage/sqlite_context_store.py(1 hunks)src/a2a_storage/storage_factory.py(1 hunks)src/app/endpoints/README.md(1 hunks)src/app/endpoints/a2a.py(1 hunks)src/app/routers.py(2 hunks)src/configuration.py(2 hunks)src/models/config.py(7 hunks)tests/unit/a2a_storage/__init__.py(1 hunks)tests/unit/a2a_storage/test_in_memory_context_store.py(1 hunks)tests/unit/a2a_storage/test_sqlite_context_store.py(1 hunks)tests/unit/a2a_storage/test_storage_factory.py(1 hunks)tests/unit/app/endpoints/test_a2a.py(1 hunks)tests/unit/app/test_routers.py(5 hunks)tests/unit/models/config/test_a2a_state_configuration.py(1 hunks)tests/unit/models/config/test_dump_configuration.py(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
- tests/unit/models/config/test_dump_configuration.py
- src/configuration.py
- tests/unit/app/endpoints/test_a2a.py
- pyproject.toml
- CLAUDE.md
- src/a2a_storage/init.py
- src/app/endpoints/README.md
- tests/unit/a2a_storage/test_in_memory_context_store.py
- tests/unit/models/config/test_a2a_state_configuration.py
- docs/a2a_protocol.md
- examples/lightspeed-stack-a2a-state-pg.yaml
- tests/unit/a2a_storage/init.py
- examples/lightspeed-stack-a2a-state-sqlite.yaml
🧰 Additional context used
📓 Path-based instructions (7)
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/a2a_storage/in_memory_context_store.pysrc/a2a_storage/storage_factory.pysrc/a2a_storage/sqlite_context_store.pysrc/models/config.pysrc/a2a_storage/context_store.pysrc/app/routers.pysrc/app/endpoints/a2a.pysrc/a2a_storage/postgres_context_store.py
tests/{unit,integration}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests; do not use unittest framework
Unit tests must achieve 60% code coverage; integration tests must achieve 10% coverage
Files:
tests/unit/a2a_storage/test_sqlite_context_store.pytests/unit/a2a_storage/test_storage_factory.pytests/unit/app/test_routers.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
pytest-mockwith AsyncMock objects for mocking in tests
Files:
tests/unit/a2a_storage/test_sqlite_context_store.pytests/unit/a2a_storage/test_storage_factory.pytests/unit/app/test_routers.py
src/models/config.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/config.py: All configuration must use Pydantic models extendingConfigurationBasewithextra="forbid"to reject unknown fields
Use type hintsOptional[FilePath],PositiveInt,SecretStrfor Pydantic configuration models
Files:
src/models/config.py
src/models/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/**/*.py: Use@field_validatorand@model_validatorfor custom validation in Pydantic models
Pydantic configuration classes must extendConfigurationBase; data models must extendBaseModel
Files:
src/models/config.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/a2a.py
🧠 Learnings (7)
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to tests/**/*.py : Use `pytest-mock` with AsyncMock objects for mocking in tests
Applied to files:
tests/unit/a2a_storage/test_storage_factory.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (8)
src/a2a_storage/in_memory_context_store.py (3)
src/a2a_storage/context_store.py (5)
A2AContextStore(6-57)get(17-25)delete(37-42)initialize(45-49)ready(52-57)src/a2a_storage/postgres_context_store.py (4)
get(69-91)delete(121-134)initialize(52-62)ready(136-142)src/a2a_storage/sqlite_context_store.py (4)
get(68-90)delete(122-135)initialize(51-61)ready(137-143)
src/a2a_storage/storage_factory.py (5)
src/a2a_storage/context_store.py (2)
A2AContextStore(6-57)initialize(45-49)src/a2a_storage/in_memory_context_store.py (2)
InMemoryA2AContextStore(11-92)initialize(78-84)src/a2a_storage/sqlite_context_store.py (2)
SQLiteA2AContextStore(23-143)initialize(51-61)src/a2a_storage/postgres_context_store.py (2)
PostgresA2AContextStore(24-142)initialize(52-62)src/models/config.py (4)
config(325-342)config(1297-1305)A2AStateConfiguration(1250-1305)storage_type(1288-1294)
tests/unit/a2a_storage/test_sqlite_context_store.py (1)
src/a2a_storage/sqlite_context_store.py (5)
SQLiteA2AContextStore(23-143)initialize(51-61)ready(137-143)get(68-90)delete(122-135)
src/a2a_storage/sqlite_context_store.py (3)
src/a2a_storage/context_store.py (5)
delete(37-42)A2AContextStore(6-57)initialize(45-49)get(17-25)ready(52-57)src/a2a_storage/in_memory_context_store.py (4)
delete(63-76)initialize(78-84)get(29-46)ready(86-92)src/a2a_storage/postgres_context_store.py (5)
delete(121-134)initialize(52-62)_ensure_initialized(64-67)get(69-91)ready(136-142)
src/models/config.py (2)
src/utils/checks.py (1)
file_check(38-57)src/configuration.py (1)
a2a_state(163-167)
src/app/routers.py (1)
tests/unit/app/test_routers.py (1)
include_router(37-52)
src/app/endpoints/a2a.py (9)
src/authorization/middleware.py (1)
authorize(111-122)src/a2a_storage/storage_factory.py (2)
create_task_store(32-75)create_context_store(78-119)src/a2a_storage/context_store.py (2)
A2AContextStore(6-57)get(17-25)src/models/requests.py (1)
QueryRequest(73-267)src/app/endpoints/query.py (2)
select_model_and_provider_id(459-527)evaluate_model_hints(139-178)src/client.py (2)
AsyncLlamaStackClientHolder(18-55)get_client(49-55)src/utils/responses.py (1)
extract_text_from_response_output_item(6-56)src/a2a_storage/in_memory_context_store.py (1)
get(29-46)src/a2a_storage/postgres_context_store.py (1)
get(69-91)
src/a2a_storage/postgres_context_store.py (3)
src/a2a_storage/context_store.py (5)
delete(37-42)A2AContextStore(6-57)initialize(45-49)get(17-25)ready(52-57)src/a2a_storage/in_memory_context_store.py (4)
delete(63-76)initialize(78-84)get(29-46)ready(86-92)src/a2a_storage/sqlite_context_store.py (5)
delete(122-135)initialize(51-61)_ensure_initialized(63-66)get(68-90)ready(137-143)
🪛 GitHub Actions: Black
src/a2a_storage/storage_factory.py
[error] 1-1: Black formatting check failed: 1 file would be reformatted. Run 'uv tool run black .' to fix.
🪛 GitHub Actions: Python linter
src/a2a_storage/storage_factory.py
[error] 6-6: uv run pylint src tests: C0411: standard import "urllib.parse.quote_plus" should be placed before third party import "sqlalchemy.ext.asyncio.create_async_engine" (wrong-import-order)
🪛 GitHub Check: Bandit
src/app/endpoints/a2a.py
[notice] 682-682:
Possible hardcoded password: ''
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: build-pr
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
- GitHub Check: E2E: server mode / ci
- GitHub Check: E2E: server mode / azure
- GitHub Check: E2E: library mode / azure
- GitHub Check: E2E: library mode / ci
🔇 Additional comments (27)
src/models/config.py (4)
815-819: LGTM - A2A action enum members added.The new action enum members provide authorization controls for A2A protocol operations.
A2A_JSONRPCis actively used for the main endpoint authorization.
1250-1306: Well-structured configuration class following existing patterns.The
A2AStateConfigurationclass mirrors the established pattern fromDatabaseConfigurationwith proper mutual exclusivity validation and convenient accessor properties. The default to in-memory storage with clear documentation about multi-worker implications is a good design choice.
1119-1135: LGTM - YAML loading with proper error handling.The implementation correctly handles YAML parsing errors and OS errors with descriptive error messages including the file path. The yaml import is properly at module level.
1549-1553: LGTM - A2A state configuration properly integrated.The
a2a_statefield is correctly added with a default factory that provides backward compatibility (defaulting to in-memory storage).src/app/endpoints/a2a.py (6)
1-59: LGTM - Module setup follows project conventions.The imports are comprehensive and the shared logger name
"app.endpoints.handlers"follows the intentional project pattern for unified logging across endpoint handlers. Based on learnings, this is the correct approach.
97-114: LGTM - Clean conversion helper.The function properly delegates text extraction to the utility function and creates appropriate A2A Part objects. The implementation is concise and focused.
116-173: LGTM - Well-designed aggregator with clear state priority.The
TaskResultAggregatorproperly handles task state transitions with a clear priority hierarchy. The docstring explains the priority order, and the implementation correctly prevents premature stream termination by keeping intermediate events as "working" state.
176-504: LGTM - Comprehensive executor implementation.The
A2AAgentExecutorproperly implements the A2A SDK interface with:
- Robust error handling that publishes failure status on exceptions
- Streaming support with proper event conversion
- Multi-turn conversation support via context store
- Clear separation between task setup and streaming processing
The broad exception catches are appropriately documented as intentional last-resort handlers.
619-645: LGTM - Per-request app factory with auth context.The function correctly creates A2A apps per-request to inject authentication context while reusing the shared task store. This design allows FastAPI's auth middleware to work while delegating to the A2A SDK.
678-682: Auth token extraction is correctly guarded - Bandit false positive.The empty string
""on line 682 is a fallback for missing auth tokens, not a hardcoded password. This is the correct behavior when authentication data is unavailable. The Bandit static analysis hint is a false positive.tests/unit/app/test_routers.py (1)
26-26: LGTM - Tests correctly updated for A2A router.The test changes properly verify:
- A2A router is imported and registered
- Router count incremented from 16 to 17
- A2A router has an empty prefix (consistent with other unversioned endpoints like health and metrics)
Also applies to: 69-69, 88-88, 97-97, 117-117
src/app/routers.py (1)
23-24: LGTM - A2A router properly integrated.The A2A router is correctly added:
- Import with descriptive comment
- Registered without a prefix, consistent with other protocol-level endpoints (health, metrics)
- Placed after existing unversioned endpoints
Also applies to: 58-60
src/a2a_storage/context_store.py (1)
1-57: LGTM - Clean abstract interface for context storage.The
A2AContextStoreABC provides a well-designed contract for context-to-conversation mapping:
- Clear docstrings explaining purpose and multi-worker considerations
- Async methods for potentially I/O-bound operations
- Synchronous
ready()for quick status checks- All abstract methods properly decorated and documented
The interface follows the existing patterns in the codebase (similar to other storage abstractions).
tests/unit/a2a_storage/test_sqlite_context_store.py (1)
1-24: Well-structured test module with good coverage.The test fixture and test structure follow pytest conventions correctly. Tests cover initialization, CRUD operations, multiple contexts, persistence, and auto-initialization behavior.
src/a2a_storage/sqlite_context_store.py (1)
1-10: Module structure follows guidelines correctly.Module docstring, logging pattern, and imports are properly structured.
src/a2a_storage/storage_factory.py (2)
146-158: Password URL-encoding fix is correctly applied.The
quote_plusencoding for PostgreSQL passwords with special characters has been implemented as previously suggested.
27-29: Document singleton state management and ensure tests reset factory state between runs.Class-level mutable attributes (
_engine,_task_store,_context_store) used as singletons can cause state leakage between tests if not properly cleaned up. Ensure thereset()method is called in test teardown or fixtures to maintain test isolation, and add documentation explaining this requirement.src/a2a_storage/in_memory_context_store.py (2)
1-27: Clean implementation following guidelines.Module docstring, logger pattern, type annotations, and class docstring are all properly implemented. The use of
asyncio.Lockfor thread safety in async context is appropriate.
63-76: Good defensive logging for delete on nonexistent key.Unlike the SQLite/Postgres implementations which silently delete, this logs when attempting to delete a non-existent key. Consider aligning behavior across implementations for consistency, though the current approach provides better observability.
src/a2a_storage/postgres_context_store.py (3)
1-21: Well-structured module with proper PostgreSQL-specific imports.The use of
sqlalchemy.dialects.postgresql.insertfor proper upsert semantics is the correct approach for PostgreSQL.
93-119: Correct PostgreSQL upsert implementation.Using
INSERT ... ON CONFLICT DO UPDATEviapg_insertis the idiomatic and efficient approach for PostgreSQL upserts. This is preferable to the delete-then-insert pattern used in SQLite.
69-91: Consistent implementation with SQLite counterpart.The
get()method correctly uses_ensure_initialized()and handles the None case appropriately.tests/unit/a2a_storage/test_storage_factory.py (5)
1-17: LGTM!Imports are well-organized. Using
MockerFixturefrompytest_mockcomplies with the coding guidelines. ThePropertyMockimport fromunittest.mockis acceptable since it's used as anew_callableparameter withmocker.patch.object().
23-28: LGTM!The autouse fixture correctly isolates singleton factory state between tests by resetting before and after each test execution.
30-70: LGTM!Store creation tests properly verify both in-memory and SQLite backends. Good use of
tmp_pathfixture for SQLite tests and verification ofready()status on context stores.
72-121: LGTM!Reuse tests correctly use identity comparison (
is) to verify singleton behavior. Cleanup and reset tests thoroughly verify internal state management.
123-172: Consider adding error handling tests forcreate_context_store.The error handling tests only cover
create_task_store. Ifcreate_context_storehas similar validation logic, consider adding symmetrical tests for completeness:
test_invalid_storage_type_raises_error_for_context_storetest_sqlite_storage_type_without_config_raises_error_for_context_storetest_postgres_storage_type_without_config_raises_error_for_context_storeThis would ensure both factory methods handle misconfiguration consistently.
| async def _get_task_store() -> TaskStore: | ||
| """Get the A2A task store, creating it if necessary. | ||
| Returns: | ||
| TaskStore instance based on configuration. | ||
| """ | ||
| global _TASK_STORE # pylint: disable=global-statement | ||
| if _TASK_STORE is None: | ||
| _TASK_STORE = await A2AStorageFactory.create_task_store(configuration.a2a_state) | ||
| return _TASK_STORE | ||
|
|
||
|
|
||
| async def _get_context_store() -> A2AContextStore: | ||
| """Get the A2A context store, creating it if necessary. | ||
| Returns: | ||
| A2AContextStore instance based on configuration. | ||
| """ | ||
| global _CONTEXT_STORE # pylint: disable=global-statement | ||
| if _CONTEXT_STORE is None: | ||
| _CONTEXT_STORE = await A2AStorageFactory.create_context_store( | ||
| configuration.a2a_state | ||
| ) | ||
| return _CONTEXT_STORE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition in lazy store initialization.
The lazy initialization pattern could have a race condition if multiple concurrent requests arrive before the stores are created. While the A2AStorageFactory appears to handle singleton creation internally, the check-then-create pattern here isn't atomic.
Consider using an asyncio lock to prevent race conditions:
+_STORE_LOCK = asyncio.Lock()
+
async def _get_task_store() -> TaskStore:
"""Get the A2A task store, creating it if necessary."""
global _TASK_STORE # pylint: disable=global-statement
- if _TASK_STORE is None:
- _TASK_STORE = await A2AStorageFactory.create_task_store(configuration.a2a_state)
+ if _TASK_STORE is None:
+ async with _STORE_LOCK:
+ if _TASK_STORE is None: # Double-check after acquiring lock
+ _TASK_STORE = await A2AStorageFactory.create_task_store(configuration.a2a_state)
return _TASK_STOREAlternatively, if A2AStorageFactory already handles thread-safety internally, this may be acceptable as-is.
Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/a2a_storage/storage_factory.py (2)
3-6: Fix import order to comply with PEP 8.Standard library imports must precede third-party imports. Move the
urllib.parseimport (line 6) to line 4, before thesqlalchemyimport.Apply this diff:
import logging +from urllib.parse import quote_plus from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine -from urllib.parse import quote_plus
169-184: Add trailing newline to comply with formatting standards.The file must end with a newline character after line 184 to satisfy Black formatting requirements.
Add a newline after line 184.
🧹 Nitpick comments (2)
src/app/endpoints/a2a.py (2)
71-94: Potential race condition in lazy store initialization persists.As noted in a past review, the check-then-create pattern here isn't atomic. While the impact is likely low (duplicate store creation on startup), consider adding an
asyncio.Lockfor double-checked locking if multi-worker startup timing is a concern:_STORE_LOCK = asyncio.Lock() async def _get_task_store() -> TaskStore: global _TASK_STORE if _TASK_STORE is None: async with _STORE_LOCK: if _TASK_STORE is None: _TASK_STORE = await A2AStorageFactory.create_task_store(...) return _TASK_STOREThis is optional given that
A2AStorageFactorymay handle thread-safety internally.
840-846: Consider addressing previously flagged issues in health check.Two issues from past reviews appear unaddressed:
- SDK version: Hardcoded
"0.2.1"may not match actual SDK version- Timezone:
datetime.now().isoformat()should usedatetime.now(timezone.utc).isoformat()for consistency with other timestamps in this file (e.g., lines 342, 364, 420)return { "status": "healthy", "service": "lightspeed-a2a", "version": __version__, - "a2a_sdk_version": "0.2.1", - "timestamp": datetime.now().isoformat(), + "a2a_sdk_version": "0.3.4", # TODO: Import from a2a package if available + "timestamp": datetime.now(timezone.utc).isoformat(), }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
CLAUDE.md(1 hunks)docs/a2a_protocol.md(1 hunks)examples/lightspeed-stack-a2a-state-pg.yaml(1 hunks)examples/lightspeed-stack-a2a-state-sqlite.yaml(1 hunks)pyproject.toml(1 hunks)src/a2a_storage/__init__.py(1 hunks)src/a2a_storage/context_store.py(1 hunks)src/a2a_storage/in_memory_context_store.py(1 hunks)src/a2a_storage/postgres_context_store.py(1 hunks)src/a2a_storage/sqlite_context_store.py(1 hunks)src/a2a_storage/storage_factory.py(1 hunks)src/app/endpoints/a2a.py(1 hunks)src/configuration.py(2 hunks)src/models/config.py(7 hunks)tests/unit/a2a_storage/__init__.py(1 hunks)tests/unit/a2a_storage/test_in_memory_context_store.py(1 hunks)tests/unit/a2a_storage/test_sqlite_context_store.py(1 hunks)tests/unit/a2a_storage/test_storage_factory.py(1 hunks)tests/unit/app/endpoints/test_a2a.py(1 hunks)tests/unit/app/test_routers.py(5 hunks)tests/unit/models/config/test_a2a_state_configuration.py(1 hunks)tests/unit/models/config/test_dump_configuration.py(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
- pyproject.toml
- src/a2a_storage/init.py
- tests/unit/app/endpoints/test_a2a.py
- tests/unit/a2a_storage/test_in_memory_context_store.py
- tests/unit/models/config/test_a2a_state_configuration.py
- tests/unit/a2a_storage/init.py
- src/configuration.py
- docs/a2a_protocol.md
- src/a2a_storage/in_memory_context_store.py
- CLAUDE.md
- src/a2a_storage/sqlite_context_store.py
- examples/lightspeed-stack-a2a-state-pg.yaml
🧰 Additional context used
📓 Path-based instructions (7)
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/a2a_storage/context_store.pysrc/a2a_storage/storage_factory.pysrc/a2a_storage/postgres_context_store.pysrc/models/config.pysrc/app/endpoints/a2a.py
tests/{unit,integration}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests; do not use unittest framework
Unit tests must achieve 60% code coverage; integration tests must achieve 10% coverage
Files:
tests/unit/app/test_routers.pytests/unit/models/config/test_dump_configuration.pytests/unit/a2a_storage/test_storage_factory.pytests/unit/a2a_storage/test_sqlite_context_store.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
pytest-mockwith AsyncMock objects for mocking in tests
Files:
tests/unit/app/test_routers.pytests/unit/models/config/test_dump_configuration.pytests/unit/a2a_storage/test_storage_factory.pytests/unit/a2a_storage/test_sqlite_context_store.py
src/models/config.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/config.py: All configuration must use Pydantic models extendingConfigurationBasewithextra="forbid"to reject unknown fields
Use type hintsOptional[FilePath],PositiveInt,SecretStrfor Pydantic configuration models
Files:
src/models/config.py
src/models/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/**/*.py: Use@field_validatorand@model_validatorfor custom validation in Pydantic models
Pydantic configuration classes must extendConfigurationBase; data models must extendBaseModel
Files:
src/models/config.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/a2a.py
🧠 Learnings (8)
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to tests/**/*.py : Use `pytest-mock` with AsyncMock objects for mocking in tests
Applied to files:
tests/unit/a2a_storage/test_storage_factory.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/models/config.py : All configuration must use Pydantic models extending `ConfigurationBase` with `extra="forbid"` to reject unknown fields
Applied to files:
src/models/config.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (3)
src/a2a_storage/context_store.py (3)
src/a2a_storage/in_memory_context_store.py (4)
get(29-46)delete(63-76)initialize(78-84)ready(86-92)src/a2a_storage/postgres_context_store.py (4)
get(69-91)delete(121-134)initialize(52-62)ready(136-142)src/a2a_storage/sqlite_context_store.py (4)
get(68-90)delete(122-135)initialize(51-61)ready(137-143)
tests/unit/a2a_storage/test_sqlite_context_store.py (3)
src/a2a_storage/sqlite_context_store.py (5)
SQLiteA2AContextStore(23-143)initialize(51-61)ready(137-143)get(68-90)delete(122-135)src/a2a_storage/context_store.py (4)
initialize(45-49)ready(52-57)get(17-25)delete(37-42)src/a2a_storage/in_memory_context_store.py (4)
initialize(78-84)ready(86-92)get(29-46)delete(63-76)
src/models/config.py (2)
src/utils/checks.py (1)
file_check(38-57)src/configuration.py (1)
a2a_state(163-167)
🪛 GitHub Actions: Python linter
src/a2a_storage/storage_factory.py
[error] 6-6: Pylint: standard import 'urllib.parse.quote_plus' should be placed before third party import 'sqlalchemy.ext.asyncio.create_async_engine' (wrong-import-order)
🪛 GitHub Check: Bandit
src/app/endpoints/a2a.py
[notice] 682-682:
Possible hardcoded password: ''
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
- GitHub Check: E2E: server mode / ci
- GitHub Check: E2E: server mode / azure
- GitHub Check: E2E: library mode / ci
- GitHub Check: E2E: library mode / azure
- GitHub Check: build-pr
🔇 Additional comments (40)
tests/unit/app/test_routers.py (1)
26-26: LGTM! Test updates correctly verify A2A router registration.The test changes properly account for the new A2A router:
- Import added for
a2amodule- Router count incremented from 16 to 17
- Assertions verify
a2a.routerpresence and empty prefix (consistent with health/root endpoints)Also applies to: 69-69, 88-88, 97-97, 117-117
src/a2a_storage/postgres_context_store.py (6)
1-11: LGTM! Module setup follows coding guidelines.Module docstring present, proper imports, and logger follows the
logging.getLogger(__name__)pattern as per coding guidelines.
13-21: Table schema is consistent with SQLite implementation.The
a2a_contextstable schema mirrors the SQLite backend, ensuring consistent behavior across storage backends.
24-50: Class implementation follows established patterns.The
PostgresA2AContextStoreclass correctly implements the ABC contract with proper type annotations, docstrings, and async session management.
52-68: Initialization logic is correct.The
initialize()method properly guards against re-initialization and uses SQLAlchemy'screate_allfor schema creation.
69-134: CRUD operations correctly implemented.
get()properly usesscalar_one_or_none()to retrieve the conversation IDset()uses PostgreSQL-specific upsert withON CONFLICT DO UPDATEdelete()correctly removes mappings- All methods ensure initialization before use
136-142: Ready check is consistent with other implementations.src/app/endpoints/a2a.py (13)
1-58: Module setup and imports are well-organized.The imports are properly organized and the shared logger name
"app.endpoints.handlers"follows the established pattern in this codebase (based on learnings, this is intentional for unified logging across endpoint handlers).
97-114: Conversion function is well-implemented.The function correctly extracts text content and converts to A2A
Partobjects. Image handling is intentionally deferred as noted in previous reviews.
116-174: TaskResultAggregator correctly implements state priority logic.The state machine properly prioritizes terminal states (failed > auth_required > input_required > working) and the in-place modification at line 163 is intentional to prevent premature client termination during streaming.
179-196: A2AAgentExecutor initialization is properly typed and documented.
198-244: Execute method has comprehensive error handling.The method properly handles task creation, context propagation, and failure scenarios with appropriate error logging and status updates.
246-373: Streaming task processing is well-implemented.The method correctly:
- Validates required IDs
- Handles missing input gracefully with
input_requiredstate- Resolves and persists conversation mappings for multi-turn
- Uses the aggregator pattern for event processing
- Includes useful metadata in status events
375-486: Stream-to-events conversion handles all event types correctly.The generator properly converts Responses API events to A2A protocol events, accumulating text for the final artifact while emitting incremental status updates.
488-503: Cancel method correctly indicates unsupported operation.The
NotImplementedErrorwith logging is appropriate for this initial implementation.
592-616: Agent card endpoint correctly serves discovery information.The endpoint appropriately has authentication but no authorization decorator, as the agent card must be accessible to inform clients about available security schemes.
619-644: Per-request A2A app creation enables proper auth context isolation.Creating the A2A app per-request allows the
auth_tokenandmcp_headersto be injected into the executor, maintaining security context.
678-682: Bandit warning is a false positive.The Bandit notice about "Possible hardcoded password: ''" at line 682 is a false positive. The empty string
""is a fallback value for when auth token extraction fails, not a hardcoded password. This is defensive error handling.
729-803: Streaming implementation is robust.The queue-based chunk relay with background task, timeout handling, and cleanup in the
finallyblock provides a solid streaming implementation.
805-829: Non-streaming response buffering is correctly implemented.tests/unit/models/config/test_dump_configuration.py (1)
105-105: Test expectations correctly updated for new configuration fields.The additions of
base_url: Nonein service configuration anda2a_statesection withsqlite: Noneandpostgres: Noneproperly reflect the new A2A configuration schema introduced in this PR.Also applies to: 190-193, 410-410, 510-513, 600-600, 694-697
src/a2a_storage/context_store.py (1)
1-57: Well-designed abstract base class for context storage.The
A2AContextStoreABC:
- Follows coding guidelines with module docstring and class docstring
- Uses
ABCwith@abstractmethoddecorators as required- Has complete type annotations using modern syntax (
str | None)- Method signatures align with the concrete implementations (InMemory, SQLite, PostgreSQL)
ready()is correctly synchronous as it only checks an in-memory flagtests/unit/a2a_storage/test_sqlite_context_store.py (3)
1-8: LGTM!Module docstring and import structure follow coding guidelines. Absolute imports are used correctly for internal modules.
14-24: LGTM!The fixture correctly creates isolated test instances with proper initialization and type annotations.
26-147: LGTM!Comprehensive test coverage including initialization, CRUD operations, edge cases, persistence, and auto-initialization behavior. All tests follow pytest best practices with proper decorators, docstrings, and type annotations.
src/a2a_storage/storage_factory.py (4)
19-29: LGTM!Class structure follows coding guidelines with descriptive docstring and proper type annotations for class-level attributes. The singleton pattern with reset capability is appropriate for this factory.
31-75: LGTM!Method follows coding guidelines with proper async declaration, complete type annotations, and Google-style docstring. The singleton reuse pattern and match-case structure are appropriate.
77-119: LGTM!Method structure mirrors
create_task_storewith proper async handling, type annotations, and docstring. Context store initialization is correctly handled for each backend type.
121-167: LGTM!Engine creation logic correctly handles both SQLite and PostgreSQL backends. Password URL-encoding with
quote_plusis properly implemented. Engine reuse for connection pooling is appropriate.examples/lightspeed-stack-a2a-state-sqlite.yaml (1)
1-24: LGTM!The YAML configuration structure correctly demonstrates A2A state configuration with SQLite backend. The use of
/tmp/datapaths is appropriate for example configurations and consistent with other storage paths in the file.src/models/config.py (7)
10-10: LGTM!YAML import correctly placed at module level after standard library imports.
366-370: LGTM!The
base_urlfield is correctly structured with appropriate type annotation and clear documentation for A2A protocol support.
815-819: LGTM!A2A protocol action enums follow existing naming conventions and are properly documented with a descriptive comment.
1095-1096: LGTM!Agent card fields use proper Pydantic types with modern syntax.
FilePathandOptional[dict[str, Any]]are appropriate choices.
1119-1135: LGTM!YAML loading implementation includes comprehensive error handling for both parse errors and I/O errors. Using
safe_loadandchecks.file_checkfollows security best practices. Exception chaining withfrom eprovides good error context.
1250-1306: LGTM!
A2AStateConfigurationfollows all coding guidelines: extendsConfigurationBase, has comprehensive docstring, uses proper type annotations, and includes appropriate validation. The default to in-memory storage when no backend is configured is sensible for backwards compatibility.
1549-1553: LGTM!The
a2a_statefield correctly usesdefault_factoryto create a fresh configuration instance with sensible in-memory defaults.tests/unit/a2a_storage/test_storage_factory.py (3)
1-17: LGTM!Import structure follows guidelines with proper ordering and absolute imports for internal modules. The
pytest-mockframework is correctly imported.
23-28: LGTM!The
autousefixture correctly ensures factory state isolation between tests with proper type annotation and yield pattern.
30-172: LGTM!Comprehensive test coverage including:
- Default in-memory stores
- SQLite-backed stores
- Singleton reuse behavior
- Cleanup and reset operations
- Error handling for invalid configurations
All tests follow pytest best practices with proper async decorators, docstrings, type annotations, and correct use of
pytest-mockvia themockerfixture.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/app/endpoints/a2a.py (1)
186-197: Add explicit return type toA2AAgentExecutor.__init__To satisfy the “all functions typed” guideline and keep type checkers happy,
__init__should declare-> None.- def __init__( - self, auth_token: str, mcp_headers: dict[str, dict[str, str]] | None = None - ): + def __init__( + self, auth_token: str, mcp_headers: dict[str, dict[str, str]] | None = None + ) -> None:
🧹 Nitpick comments (5)
src/a2a_storage/in_memory_context_store.py (1)
22-27: In‑memory context store implementation looks correct; consider typing private attrsThe get/set/delete/initialize/ready behavior and locking are sound for a single‑process in‑memory store. To fully align with the “type‑annotate all class attributes” guideline, you could add explicit annotations for
_lockand_initialized:@@ - logger.debug("Initializing InMemoryA2AContextStore") - self._contexts: dict[str, str] = {} - self._lock = asyncio.Lock() - self._initialized = True + logger.debug("Initializing InMemoryA2AContextStore") + self._contexts: dict[str, str] = {} + self._lock: asyncio.Lock = asyncio.Lock() + self._initialized: bool = TrueThis keeps runtime behavior unchanged while making the internal state clearer to readers and type checkers.
Also applies to: 78-92
tests/unit/a2a_storage/test_sqlite_context_store.py (1)
11-25: SQLite store tests cover behavior well; consider small fixture hygiene tweaksThe tests exercise all key behaviors of
SQLiteA2AContextStore(ready flag, CRUD, overwrite, multiple contexts, auto‑init) and look good. Two optional hygiene improvements:
Explicit async fixture decorator (if suite ever switches to strict asyncio mode)
If your pytest‑asyncio configuration usesasyncio_mode=strict, async fixtures must use@pytest_asyncio.fixtureinstead of@pytest.fixture. To future‑proof:
- @pytest.fixture
- async def store(self, tmp_path: Path) -> SQLiteA2AContextStore:
- import pytest_asyncio
- @pytest_asyncio.fixture
- async def store(self, tmp_path: Path) -> SQLiteA2AContextStore:
2. **Dispose the engine to avoid lingering resources/warnings** If you start seeing resource warnings from SQLAlchemy in tests, you can yield the store and dispose the engine in teardown: ```diff - context_store = SQLiteA2AContextStore(engine, create_table=True) - await context_store.initialize() - return context_store + context_store = SQLiteA2AContextStore(engine, create_table=True) + await context_store.initialize() + try: + yield context_store + finally: + await engine.dispose()Both are optional and mainly about long‑term test robustness.
Also applies to: 31-41, 129-147
examples/lightspeed-stack-a2a-state-sqlite.yaml (1)
1-24: Example A2A SQLite state config is clear and consistentThe example cleanly wires
a2a_state.sqlite.db_pathinto the existing service/llama_stack configuration structure. It’s a good dev/default template; consumers should override the/tmppaths for production deployments.src/app/endpoints/a2a.py (2)
572-586: Consider sourcingprotocol_versionfrom configuration or a single constant
protocol_versiondefaults to the literal"0.3.0". That’s fine for now, but it can drift from the actual A2A protocol version supported by the stack or SDK.Since you already read
protocolVersionfrom config, you could:
- Make that config value required, or
- Centralize the default (e.g., a module-level
A2A_PROTOCOL_VERSIONconstant used both here and anywhere else you need it).This avoids having a hardcoded protocol version buried in one place.
832-846: Avoid hardcodinga2a_sdk_versionin health checkThe health endpoint hardcodes
"0.3.4"fora2a_sdk_version. This will drift as the dependency is upgraded.Prefer importing the SDK version if it’s exposed, or at least centralizing it in one module-level constant so there’s a single place to update:
-from a2a_sdk import __version__ as a2a_sdk_version # if available +from a2a_sdk import __version__ as a2a_sdk_version # if available ... - "a2a_sdk_version": "0.3.4", + "a2a_sdk_version": a2a_sdk_version,If the SDK doesn’t expose a version constant, a small helper module holding this value would still be better than embedding the literal here.
Does the Python `a2a-sdk` package expose a public `__version__` (or similar) that can be imported and used at runtime?
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
CLAUDE.md(1 hunks)docs/a2a_protocol.md(1 hunks)examples/lightspeed-stack-a2a-state-pg.yaml(1 hunks)examples/lightspeed-stack-a2a-state-sqlite.yaml(1 hunks)pyproject.toml(1 hunks)src/a2a_storage/__init__.py(1 hunks)src/a2a_storage/context_store.py(1 hunks)src/a2a_storage/in_memory_context_store.py(1 hunks)src/a2a_storage/postgres_context_store.py(1 hunks)src/a2a_storage/sqlite_context_store.py(1 hunks)src/a2a_storage/storage_factory.py(1 hunks)src/app/endpoints/a2a.py(1 hunks)src/configuration.py(2 hunks)src/models/config.py(7 hunks)tests/unit/a2a_storage/__init__.py(1 hunks)tests/unit/a2a_storage/test_in_memory_context_store.py(1 hunks)tests/unit/a2a_storage/test_sqlite_context_store.py(1 hunks)tests/unit/a2a_storage/test_storage_factory.py(1 hunks)tests/unit/app/endpoints/test_a2a.py(1 hunks)tests/unit/app/test_routers.py(5 hunks)tests/unit/models/config/test_a2a_state_configuration.py(1 hunks)tests/unit/models/config/test_dump_configuration.py(6 hunks)
✅ Files skipped from review due to trivial changes (1)
- tests/unit/a2a_storage/init.py
🚧 Files skipped from review as they are similar to previous changes (10)
- tests/unit/models/config/test_a2a_state_configuration.py
- tests/unit/a2a_storage/test_in_memory_context_store.py
- docs/a2a_protocol.md
- tests/unit/models/config/test_dump_configuration.py
- pyproject.toml
- examples/lightspeed-stack-a2a-state-pg.yaml
- src/a2a_storage/postgres_context_store.py
- tests/unit/a2a_storage/test_storage_factory.py
- src/a2a_storage/storage_factory.py
- src/a2a_storage/context_store.py
🧰 Additional context used
📓 Path-based instructions (8)
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/a2a_storage/in_memory_context_store.pysrc/configuration.pysrc/a2a_storage/sqlite_context_store.pysrc/a2a_storage/__init__.pysrc/models/config.pysrc/app/endpoints/a2a.py
src/**/__init__.py
📄 CodeRabbit inference engine (CLAUDE.md)
Package
__init__.pyfiles must contain brief package descriptions
Files:
src/a2a_storage/__init__.py
tests/{unit,integration}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests; do not use unittest framework
Unit tests must achieve 60% code coverage; integration tests must achieve 10% coverage
Files:
tests/unit/a2a_storage/test_sqlite_context_store.pytests/unit/app/endpoints/test_a2a.pytests/unit/app/test_routers.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
pytest-mockwith AsyncMock objects for mocking in tests
Files:
tests/unit/a2a_storage/test_sqlite_context_store.pytests/unit/app/endpoints/test_a2a.pytests/unit/app/test_routers.py
src/models/config.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/config.py: All configuration must use Pydantic models extendingConfigurationBasewithextra="forbid"to reject unknown fields
Use type hintsOptional[FilePath],PositiveInt,SecretStrfor Pydantic configuration models
Files:
src/models/config.py
src/models/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/**/*.py: Use@field_validatorand@model_validatorfor custom validation in Pydantic models
Pydantic configuration classes must extendConfigurationBase; data models must extendBaseModel
Files:
src/models/config.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/a2a.py
🧠 Learnings (7)
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/models/config.py : All configuration must use Pydantic models extending `ConfigurationBase` with `extra="forbid"` to reject unknown fields
Applied to files:
src/configuration.pysrc/models/config.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (5)
src/a2a_storage/in_memory_context_store.py (1)
src/a2a_storage/context_store.py (5)
A2AContextStore(6-57)get(17-25)delete(37-42)initialize(45-49)ready(52-57)
src/configuration.py (1)
src/models/config.py (1)
A2AStateConfiguration(1250-1305)
src/a2a_storage/__init__.py (5)
src/a2a_storage/context_store.py (1)
A2AContextStore(6-57)src/a2a_storage/in_memory_context_store.py (1)
InMemoryA2AContextStore(11-92)src/a2a_storage/sqlite_context_store.py (1)
SQLiteA2AContextStore(23-143)src/a2a_storage/postgres_context_store.py (1)
PostgresA2AContextStore(24-142)src/a2a_storage/storage_factory.py (1)
A2AStorageFactory(19-184)
tests/unit/a2a_storage/test_sqlite_context_store.py (1)
src/a2a_storage/sqlite_context_store.py (5)
SQLiteA2AContextStore(23-143)initialize(51-61)ready(137-143)get(68-90)delete(122-135)
src/models/config.py (2)
src/utils/checks.py (1)
file_check(38-57)src/configuration.py (1)
a2a_state(163-167)
🪛 GitHub Check: Bandit
src/app/endpoints/a2a.py
[notice] 682-682:
Possible hardcoded password: ''
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
- GitHub Check: build-pr
- GitHub Check: E2E: library mode / azure
- GitHub Check: E2E: server mode / ci
- GitHub Check: E2E: server mode / azure
- GitHub Check: E2E: library mode / ci
🔇 Additional comments (8)
CLAUDE.md (1)
21-30: Project structure updates for A2A storage and cache look consistentThe new
a2a_storage/andcache/entries clearly describe their roles and match the code layout; nothing to change here.src/models/config.py (2)
366-370: A2A configuration surface (base_url, actions, A2AStateConfiguration) is well‑designedThe additions for A2A look coherent:
ServiceConfiguration.base_urlcleanly exposes an externally reachable URL used in agent card generation.- New
Actionenum members for A2A operations integrate with existing RBAC without breaking callers that iterate over actions.A2AStateConfigurationcorrectly models “memory/sqlite/postgres” via mutually exclusive sqlite/postgres configs plusstorage_typeandconfigconvenience properties, matching howA2AStorageFactorydispatches backends.- Wiring
a2a_state: A2AStateConfiguration = Field(default_factory=A2AStateConfiguration, …)intoConfigurationgives a sensible default (in‑memory) while still allowing DB‑backed state.No changes needed here; the modeling aligns with the broader configuration patterns in this module.
Also applies to: 815-820, 1250-1306, 1549-1553
10-10: Agent card YAML loading is solid; add a small type check for robustnessThe agent card loading flow is in good shape: import at module level, file existence checked via
checks.file_check, and PyYAML errors/OSErrors wrapped in clearValueErrors. One small hardening step would be to ensure the parsed YAML is a mapping, sinceagent_card_configis typed asdict[str, Any]and downstream code likely assumes a dict:if self.agent_card_path is not None: checks.file_check(self.agent_card_path, "agent card") try: with open(self.agent_card_path, "r", encoding="utf-8") as f: - self.agent_card_config = yaml.safe_load(f) + loaded = yaml.safe_load(f) + if not isinstance(loaded, dict): + raise ValueError( + f"Agent card file '{self.agent_card_path}' must contain a YAML mapping " + f"at the top level, got {type(loaded).__name__}" + ) + self.agent_card_config = loadedThis keeps mis‑shaped YAML from propagating as runtime type errors later. As per coding guidelines, centralizing such validation in the model validator is preferred.
tests/unit/app/test_routers.py (1)
9-27: Router tests correctly updated for the new A2A routerIncluding
a2ain the endpoint imports, bumping the expected router count to 17, and asserting both presence and empty prefix fora2a.routerkeep these tests aligned with the new routing configuration. This preserves the existing pattern of explicitly counting routers.Also applies to: 68-89, 96-117
src/configuration.py (1)
11-25: A2A state exposure on AppConfig is consistent with existing accessorsImporting
A2AStateConfigurationand adding thea2a_stateproperty that guards on_configurationand delegates toself._configuration.a2a_statematches the pattern of the other configuration accessors. This gives A2A components a clean, typed entry point into the config.Also applies to: 162-167
src/a2a_storage/__init__.py (1)
1-23: Clear and minimal public API surface for A2A storageThe module‑level docstring concisely explains the purpose (task and context storage, single/multi‑worker guidance), and re‑exporting the context stores plus
A2AStorageFactoryvia__all__gives downstream code a simple import path (from a2a_storage import …). This is a solid package initializer.tests/unit/app/endpoints/test_a2a.py (1)
50-139: A2A endpoint tests provide strong coverage and follow project testing patternsThe A2A tests are thorough:
- Config fixtures use
AppConfig.init_from_dictand patchapp.endpoints.a2a.configuration, matching how the app actually consumes configuration (includingbase_url,agent_card_config, anda2a_state)._convert_responses_content_to_a2a_partscases (empty/single/multi/None text) andTaskResultAggregatorstate‑transition tests clearly define expected semantics for streaming events.get_lightspeed_agent_cardis exercised for default/custom protocol versions and error cases whenagent_card_configis missing.A2AAgentExecutortests cover initialization, missing‑message error, new‑task creation, error handling (failed status event), no‑input path (input_required), and the NotImplemented cancel path.- Context/task store tests reset module globals and
A2AStorageFactorybetween runs, which is important for isolation, and the endpoint handler tests (a2a_health_check,get_agent_card) verify the key response fields.Overall this is solid, idiomatic pytest + pytest‑mock usage and gives good safety around the new A2A surface.
Also applies to: 145-338, 343-436, 441-609, 614-653, 658-689
src/a2a_storage/sqlite_context_store.py (1)
34-60: SQLiteA2AContextStore initialization and schema handling look solidAsync initialization, table creation, and the
_initializedguard are clean and consistent with the rest of the storage API. No functional issues stand out here.
…ent API This patch ensures that A2A wrapper only supports responses API, given Agent API is deprecated. In addition, it reorganized the A2A implementation to match the ADK implementation with regards to events updates. Now using StatusUpdates instead of ArtifactsUpdates for the intermediate streamming
Add options for task and context IDs to not only be stored in memory
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/a2a_storage/storage_factory.py (1)
179-184: Missing trailing newline.The file should end with a newline character after line 184. This was flagged in a past review.
🧹 Nitpick comments (3)
pyproject.toml (1)
124-124: Consider removing redundantaiosqlitefrom dev dependencies.
aiosqlite>=0.21.0is already declared in the main dependencies (line 52). The unversionedaiosqlitein dev dependencies appears redundant.tests/unit/a2a_storage/test_sqlite_context_store.py (1)
26-29: Redundant@pytest.mark.asynciomarkers.With
asyncio_mode = "auto"configured inpyproject.toml, the@pytest.mark.asynciodecorators are unnecessary and can be removed for cleaner test code.src/a2a_storage/postgres_context_store.py (1)
13-21: Consider scoping metadata to the class.The module-level
metadataobject is shared across all instances. If multiple engines/databases are used,metadata.create_allwill attempt to create tables on all engines that have used this metadata. This is typically fine for single-database deployments but worth noting.If isolation is needed in the future:
-# Define the table metadata -metadata = MetaData() - -a2a_context_table = Table( - "a2a_contexts", - metadata, - Column("context_id", String, primary_key=True), - Column("conversation_id", String, nullable=False), -) +class PostgresA2AContextStore(A2AContextStore): + ... + _metadata = MetaData() + _table = Table( + "a2a_contexts", + _metadata, + Column("context_id", String, primary_key=True), + Column("conversation_id", String, nullable=False), + )
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (24)
CLAUDE.md(1 hunks)docs/a2a_protocol.md(1 hunks)examples/lightspeed-stack-a2a-state-pg.yaml(1 hunks)examples/lightspeed-stack-a2a-state-sqlite.yaml(1 hunks)pyproject.toml(1 hunks)src/a2a_storage/__init__.py(1 hunks)src/a2a_storage/context_store.py(1 hunks)src/a2a_storage/in_memory_context_store.py(1 hunks)src/a2a_storage/postgres_context_store.py(1 hunks)src/a2a_storage/sqlite_context_store.py(1 hunks)src/a2a_storage/storage_factory.py(1 hunks)src/app/endpoints/README.md(1 hunks)src/app/endpoints/a2a.py(1 hunks)src/app/routers.py(2 hunks)src/configuration.py(2 hunks)src/models/config.py(7 hunks)tests/unit/a2a_storage/__init__.py(1 hunks)tests/unit/a2a_storage/test_in_memory_context_store.py(1 hunks)tests/unit/a2a_storage/test_sqlite_context_store.py(1 hunks)tests/unit/a2a_storage/test_storage_factory.py(1 hunks)tests/unit/app/endpoints/test_a2a.py(1 hunks)tests/unit/app/test_routers.py(5 hunks)tests/unit/models/config/test_a2a_state_configuration.py(1 hunks)tests/unit/models/config/test_dump_configuration.py(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
- src/app/endpoints/README.md
- tests/unit/models/config/test_a2a_state_configuration.py
- tests/unit/a2a_storage/test_in_memory_context_store.py
- tests/unit/app/test_routers.py
- src/a2a_storage/in_memory_context_store.py
- src/configuration.py
- CLAUDE.md
- examples/lightspeed-stack-a2a-state-sqlite.yaml
- tests/unit/a2a_storage/init.py
- src/a2a_storage/init.py
- docs/a2a_protocol.md
- tests/unit/a2a_storage/test_storage_factory.py
- src/a2a_storage/sqlite_context_store.py
🧰 Additional context used
📓 Path-based instructions (8)
pyproject.toml
📄 CodeRabbit inference engine (CLAUDE.md)
pyproject.toml: Configure pylint withsource-roots = "src"
Excludesrc/auth/k8s.pyfrom pyright type checking
Files:
pyproject.toml
tests/{unit,integration}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests; do not use unittest framework
Unit tests must achieve 60% code coverage; integration tests must achieve 10% coverage
Files:
tests/unit/a2a_storage/test_sqlite_context_store.pytests/unit/models/config/test_dump_configuration.pytests/unit/app/endpoints/test_a2a.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
pytest-mockwith AsyncMock objects for mocking in tests
Files:
tests/unit/a2a_storage/test_sqlite_context_store.pytests/unit/models/config/test_dump_configuration.pytests/unit/app/endpoints/test_a2a.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Use absolute imports for internal modules in LCS project (e.g.,from auth import get_auth_dependency)
All modules must start with descriptive docstrings explaining their purpose
Uselogger = logging.getLogger(__name__)pattern for module logging
All functions must include complete type annotations for parameters and return types, using modern syntax (str | int) andOptional[Type]orType | None
All functions must have docstrings with brief descriptions following Google Python docstring conventions
Function names must use snake_case with descriptive, action-oriented names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying input parameters
Useasync deffor I/O operations and external API calls
All classes must include descriptive docstrings explaining their purpose following Google Python docstring conventions
Class names must use PascalCase with descriptive names and standard suffixes:Configurationfor config classes,Error/Exceptionfor exceptions,Resolverfor strategy patterns,Interfacefor abstract base classes
Abstract classes must use ABC with@abstractmethoddecorators
Include complete type annotations for all class attributes in Python classes
Useimport loggingand module logger pattern with standard log levels: debug, info, warning, error
Files:
src/a2a_storage/context_store.pysrc/app/routers.pysrc/models/config.pysrc/a2a_storage/storage_factory.pysrc/a2a_storage/postgres_context_store.pysrc/app/endpoints/a2a.py
src/models/config.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/config.py: All configuration must use Pydantic models extendingConfigurationBasewithextra="forbid"to reject unknown fields
Use type hintsOptional[FilePath],PositiveInt,SecretStrfor Pydantic configuration models
Files:
src/models/config.py
src/models/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/models/**/*.py: Use@field_validatorand@model_validatorfor custom validation in Pydantic models
Pydantic configuration classes must extendConfigurationBase; data models must extendBaseModel
Files:
src/models/config.py
src/app/endpoints/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI
HTTPExceptionwith appropriate status codes for API endpoint error handling
Files:
src/app/endpoints/a2a.py
src/**/{client,app/endpoints/**}.py
📄 CodeRabbit inference engine (CLAUDE.md)
Handle
APIConnectionErrorfrom Llama Stack in integration code
Files:
src/app/endpoints/a2a.py
🧠 Learnings (9)
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Always check `pyproject.toml` for existing dependencies and versions before adding new ones
Applied to files:
pyproject.toml
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/models/config.py : All configuration must use Pydantic models extending `ConfigurationBase` with `extra="forbid"` to reject unknown fields
Applied to files:
src/models/config.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `logger = logging.getLogger(__name__)` pattern for module logging
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-10-29T13:05:22.438Z
Learnt from: luis5tb
Repo: lightspeed-core/lightspeed-stack PR: 727
File: src/app/endpoints/a2a.py:43-43
Timestamp: 2025-10-29T13:05:22.438Z
Learning: In the lightspeed-stack repository, endpoint files in src/app/endpoints/ intentionally use a shared logger name "app.endpoints.handlers" rather than __name__, allowing unified logging configuration across all endpoint handlers (query.py, streaming_query.py, a2a.py).
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : Use `import logging` and module logger pattern with standard log levels: debug, info, warning, error
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/app/endpoints/**/*.py : Use FastAPI `HTTPException` with appropriate status codes for API endpoint error handling
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-08-06T06:02:21.060Z
Learnt from: eranco74
Repo: lightspeed-core/lightspeed-stack PR: 348
File: src/utils/endpoints.py:91-94
Timestamp: 2025-08-06T06:02:21.060Z
Learning: The direct assignment to `agent._agent_id` in `src/utils/endpoints.py` is a necessary workaround for the missing agent rehydration feature in the LLS client SDK. This allows preserving conversation IDs when handling existing agents.
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/*.py : All functions must include complete type annotations for parameters and return types, using modern syntax (`str | int`) and `Optional[Type]` or `Type | None`
Applied to files:
src/app/endpoints/a2a.py
📚 Learning: 2025-11-24T16:58:04.410Z
Learnt from: CR
Repo: lightspeed-core/lightspeed-stack PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:58:04.410Z
Learning: Applies to src/**/{client,app/endpoints/**}.py : Handle `APIConnectionError` from Llama Stack in integration code
Applied to files:
src/app/endpoints/a2a.py
🧬 Code graph analysis (4)
src/app/routers.py (1)
tests/unit/app/test_routers.py (1)
include_router(37-52)
src/models/config.py (2)
src/utils/checks.py (1)
file_check(38-57)src/configuration.py (1)
a2a_state(163-167)
src/a2a_storage/postgres_context_store.py (1)
src/a2a_storage/context_store.py (5)
delete(37-42)A2AContextStore(6-57)initialize(45-49)get(17-25)ready(52-57)
src/app/endpoints/a2a.py (6)
src/a2a_storage/storage_factory.py (3)
A2AStorageFactory(19-184)create_task_store(32-75)create_context_store(78-119)src/a2a_storage/context_store.py (2)
A2AContextStore(6-57)get(17-25)src/models/requests.py (1)
QueryRequest(73-267)src/app/endpoints/query.py (1)
select_model_and_provider_id(459-527)src/client.py (2)
AsyncLlamaStackClientHolder(18-55)get_client(49-55)src/utils/responses.py (1)
extract_text_from_response_output_item(6-56)
🪛 GitHub Check: Bandit
src/app/endpoints/a2a.py
[notice] 682-682:
Possible hardcoded password: ''
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: build-pr
- GitHub Check: E2E: library mode / azure
- GitHub Check: E2E: server mode / azure
- GitHub Check: E2E: server mode / ci
- GitHub Check: E2E: library mode / ci
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
🔇 Additional comments (14)
pyproject.toml (1)
44-62: LGTM! Dependencies are well-structured with appropriate version constraints.The A2A-related dependencies are correctly versioned and aligned with the project's versioning patterns.
src/a2a_storage/context_store.py (1)
1-57: LGTM! Well-designed abstract interface.The abstract base class follows best practices: proper ABC usage with
@abstractmethoddecorators, complete type annotations with modern syntax, and comprehensive Google-style docstrings for all methods.tests/unit/a2a_storage/test_sqlite_context_store.py (1)
1-147: LGTM! Comprehensive test coverage.The tests cover initialization, CRUD operations, overwrite behavior, multi-context handling, and auto-initialization. Good use of
tmp_pathfixture for test isolation.src/a2a_storage/postgres_context_store.py (1)
35-142: LGTM! Solid PostgreSQL implementation.The implementation correctly uses PostgreSQL's
INSERT ... ON CONFLICTfor upserts, proper async session management, and follows the abstract interface contract. Good use of_ensure_initialized()pattern for lazy initialization.src/a2a_storage/storage_factory.py (1)
31-167: LGTM! Well-structured factory pattern.The factory cleanly abstracts storage backend creation with proper configuration validation, shared engine reuse for connection pooling, and appropriate logging. The URL-encoded password handling for PostgreSQL is correctly implemented.
tests/unit/models/config/test_dump_configuration.py (1)
105-105: LGTM! Test expectations correctly updated for new configuration fields.The test expectations have been properly updated to reflect the new
base_urlfield inServiceConfigurationand the newa2a_statetop-level configuration field. The additions are consistent across all three test functions and match the structure of the new configuration model.Also applies to: 190-193, 410-410, 510-513, 600-600, 694-697
src/app/routers.py (1)
23-24: LGTM! A2A router properly integrated.The A2A router has been correctly imported and registered following the existing patterns in the file. The placement without a prefix allows the A2A endpoints (like
/.well-known/agent.jsonand/a2a) to be accessible at the expected root-level paths per the A2A protocol specification.Also applies to: 59-60
examples/lightspeed-stack-a2a-state-pg.yaml (1)
22-30: LGTM! Example configuration demonstrates PostgreSQL backend for A2A state.The example configuration clearly shows how to configure A2A state persistence using PostgreSQL. The settings are appropriate for a multi-worker deployment where state needs to be shared across workers. The
namespace: a2aprovides good schema separation from other tables.tests/unit/app/endpoints/test_a2a.py (1)
1-888: Excellent test coverage for A2A endpoints.This test file provides comprehensive coverage of the A2A protocol implementation:
- Conversion functions and utility classes are thoroughly tested
- TaskResultAggregator state transitions and priority handling are verified
- A2AAgentExecutor execution paths, error handling, and APIConnectionError recovery are tested
- Agent card generation with various configurations is covered
- Integration tests verify the endpoint handlers work correctly
The tests follow pytest patterns (per coding guidelines), use proper mocking with AsyncMock, and include both happy path and error scenarios. Great work on ensuring the A2A feature is well-tested!
src/models/config.py (5)
366-370: New base_url field properly added to ServiceConfiguration.The
base_urlfield is correctly typed asOptional[str]and includes a clear description explaining its use for A2A support. This allows the service to advertise its externally-reachable URL in the agent card.
815-819: A2A action enum members appropriately added.The new A2A-related actions (
A2A_AGENT_CARD,A2A_TASK_EXECUTION,A2A_MESSAGE,A2A_JSONRPC) are properly defined as enum members, enabling fine-grained authorization control for A2A protocol operations.
1095-1096: Agent card configuration loading properly implemented.The agent card configuration loading follows good practices:
- YAML import at module level (per past review feedback)
- File existence checked with
checks.file_check- YAML parsing wrapped in try/except with proper error handling
- Both
yaml.YAMLErrorandOSErrorcaught with descriptive error messages- Errors include the file path for debugging
This addresses the past review comment about adding error handling for YAML parsing.
Also applies to: 1120-1134
1250-1306: Well-structured A2AStateConfiguration class.The
A2AStateConfigurationclass follows the established patterns in this file:
- Extends
ConfigurationBasewithextra="forbid"(per coding guidelines)- Complete type annotations for all fields
- Clear docstring explaining purpose and multi-worker considerations
- Validation ensures at most one backend is configured
- Convenient properties (
storage_typeandconfig) for accessing configuration- Defaults to "memory" storage when neither SQLite nor PostgreSQL configured
- Mirrors the pattern used in
DatabaseConfigurationandConversationHistoryConfigurationThe implementation is clean, well-documented, and consistent with the rest of the codebase.
1549-1553: A2A state field properly integrated into main Configuration.The
a2a_statefield is correctly added with:
- Type annotation using the new
A2AStateConfigurationclass- Default factory for automatic initialization
- Clear title and description
- Positioned logically before
quota_handlersThis completes the integration of A2A state configuration into the main configuration model.
| _engine: AsyncEngine | None = None | ||
| _task_store: TaskStore | None = None | ||
| _context_store: A2AContextStore | None = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition in class-level state initialization.
The class-level state (_engine, _task_store, _context_store) combined with the check-then-act pattern (e.g., if cls._task_store is not None: return) is not thread-safe. In concurrent async scenarios (e.g., multiple requests initializing stores simultaneously), this could lead to duplicate initialization or race conditions.
Consider using an asyncio Lock to protect initialization:
+import asyncio
+
class A2AStorageFactory:
...
_engine: AsyncEngine | None = None
_task_store: TaskStore | None = None
_context_store: A2AContextStore | None = None
+ _init_lock: asyncio.Lock = asyncio.Lock()
@classmethod
async def create_task_store(cls, config: A2AStateConfiguration) -> TaskStore:
- if cls._task_store is not None:
- return cls._task_store
- ...
+ async with cls._init_lock:
+ if cls._task_store is not None:
+ return cls._task_store
+ ...Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/a2a_storage/storage_factory.py around lines 27 to 29, the class-level
state variables _engine, _task_store, and _context_store are initialized with a
check-then-act pattern that can race in concurrent async contexts; protect
initialization with a class-level asyncio.Lock (e.g., _init_lock =
asyncio.Lock()) and wrap any "if cls._task_store is None: cls._task_store =
await ..." logic inside "async with cls._init_lock:" so only one coroutine
performs creation, re-check the attribute after acquiring the lock, set
attributes only once, and ensure the lock-protected block covers engine creation
too; also handle exceptions by leaving state consistent (unset or cleaned) so
failed inits can be retried.
Description
This PR is adding A2A wrapper to streaming_query_v2 so that agents created can be consumed using the standard A2A endpoints. It is adding 2 endpoints:
Type of change
Related Tickets & Documents
Checklist before requesting a review
Testing
Summary by CodeRabbit
New Features
Configuration
Dependencies
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.