Skip to content

feat(financial-advisor): make recipe deployable for ADC integration - #2562

Open
zeroasterisk wants to merge 4 commits into
mainfrom
feat/financial-agent-deployment
Open

zeroasterisk wants to merge 4 commits into
mainfrom
feat/financial-agent-deployment

Conversation

@zeroasterisk

Copy link
Copy Markdown
Collaborator

Summary

Makes contrib/python/financial-advisor deployable as a containerized service as a step on the journey toward deployment to Application Design Center (ADC).

This was generated and verified using the repo skill make-python-recipe-deployable via prompt:

Make the recipe contrib/python/financial-advisor deployable

Changes

  • Container & Serving Files:
    • Dockerfile & .dockerignore: Container packaging targeting port 8080 with uv
    • fast_api_app.py: FastAPI server entrypoint wrapping ADK Runner
    • app_utils/a2a.py: A2A protocol endpoint implementation (/.well-known/agent-card.json and JSON-RPC)
    • app_utils/services.py: In-memory session and artifact service configuration
    • app_utils/reasoning_engine_adapter.py: Reasoning Engine adapter
    • agents-cli-manifest.yaml: Manifest configuring Cloud Run deployment target in us-central1
  • Recipe Configuration:
    • pyproject.toml & uv.lock: Added serving dependencies (a2a-sdk, aiohttp, gcsfs, opentelemetry-resourcedetector-gcp) and google-adk[gcp,otel-gcp] extras
    • financial_advisor/agent.py: Instantiated App object
    • manifest.yaml: Set deployable: true
    • .env.example: Documented serving and deployment environment variables

Verification

  • uv lock --python 3.11 updated dependencies cleanly.
  • ruff format and ruff check passed with zero errors.
  • uv run validate manifest and uv run validate structure passed.
  • Unit tests (pytest tests/ -q) passed (2/2).
  • Boot verification via TestClient(app) confirmed:
    • /list-apps -> 200
    • Agent card -> 200

Configure serving infrastructure and container definitions for
contrib/python/financial-advisor to facilitate deployment to ADC.

Generated via the make-python-recipe-deployable repo skill using prompt:
`Make the recipe contrib/python/financial-advisor deployable`

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated Correctness review — 1 finding(s).

Comment thread contrib/python/financial-advisor/financial_advisor/fast_api_app.py
Comment thread contrib/python/financial-advisor/pyproject.toml Outdated
Comment thread contrib/python/financial-advisor/.env.example Outdated
Comment thread contrib/python/financial-advisor/manifest.yaml Outdated
Comment thread contrib/python/financial-advisor/financial_advisor/fast_api_app.py

@happyhuman happyhuman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I left a few comments. Please keep in mind the skill cannot guarantee that its updates to the recipe is sufficient for deployability, and a manual verification step is required at the end.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated Correctness review — 2 finding(s).

Comment thread contrib/python/financial-advisor/financial_advisor/fast_api_app.py
…nd ergonomics

- Production container: run as non-root appuser (UID 1000), multi-stage layer caching,
  graceful shutdown (--timeout-graceful-shutdown 10), /healthz liveness probe, and
  PYTHONUNBUFFERED=1.
- Multi-agent robustness: add semantic descriptions to subagents for valid Gemini
  tool declarations, inject ADK session state templates, add search failure fallbacks,
  and enforce out-of-scope domain boundaries.
- A2A & serving: fix agent card interface duplication memory leak, extract public
  generate_agent_card helper, sanitize Reasoning Engine request parsing to HTTP 400,
  add root .well-known redirect, and harmonize ports to 8080.
- Security & governance: scrub leaked corporate email, harden .dockerignore against
  credential leaks, add indirect prompt injection defenses, and document least-privilege IAM roles.
- Ergonomics & onboarding: add 8-topic Troubleshooting & FAQ to README.md, gate cloud OTel
  to prevent local container startup hangs, and expand hermetic test_runnability.py coverage.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated Security review — 2 finding(s).

attach_reasoning_engine_routes,
)

load_dotenv()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Avoid calling load_dotenv() in non-__init__ modules. Environment variables should be loaded in the package __init__.py to ensure consistent initialization across all entry points.

if __name__ == "__main__":
import uvicorn

port = int(os.environ.get("PORT", "8080"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Avoid using hardcoded defaults on environment variable reads. These defaults should be moved to .env.example instead; this pattern is present in 6 instances across the recipe.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated Maintainability review — 1 finding(s).


@app.post("/api/stream_reasoning_engine")
async def stream_query(request: Request) -> responses.StreamingResponse:
try:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The request body loading and dictionary validation logic in stream_query (lines 85-101) is duplicated verbatim in the query route (lines 131-147). Consider extracting this validation logic into a shared private helper function to improve maintainability and simplify future schema updates.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated Correctness review — 2 finding(s).

# works. The sync route below draws the same distinction for the
# `""` and `async` buckets via iscoroutinefunction.
try:
stream = method(**kwargs)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The stream generator calls 'method(**kwargs)' directly without checking if the resolved method is a coroutine function. If the method is an async coroutine, it returns a coroutine object instead of an iterable, crashing the stream; check inspect.iscoroutinefunction(method) and await it first as done in the query endpoint.


# 0.3 uses method names that include a '/' like "message/send"
# 1.0 uses PascalCase like "SendMessage"
json_body = getattr(request, "_json", {}) or {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Accessing the private '_json' attribute of 'request' is unreliable since it is only populated after 'await request.json()' has been called. If the body hasn't been parsed yet, it will return an empty dict and default the A2A-Version to 1.0 even for 0.3 requests; convert 'build' to an async method and await 'request.json()'.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated Hygiene review — 0 finding(s).

Also, on lines this PR does not change:

  • contrib/python/financial-advisor/deployment/test_deployment.py:56 — The variable project_id was deleted from the environment reads, but is still referenced in the subsequent condition. Please restore the project_id lookup to avoid a NameError.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated Maintainability review — 4 finding(s).

agent_version: str | None = None,
) -> AgentCard:
"""Generate an A2A AgentCard for an agent conforming to the A2A specification."""
resolved_agent_version = agent_version or os.getenv(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Avoid specifying hardcoded default values inside environment reads like os.getenv. The default should be defined in the .env.example file.

body = await request.json()
except Exception as exc:
raise HTTPException(
status_code=400, detail="Invalid JSON in request body"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Avoid using bare numeric literals for HTTP status codes. Use appropriate named constants from fastapi.status or http.HTTPStatus instead.

otel_to_cloud=_otel_to_cloud_enabled(),
lifespan=lifespan,
)
app.title = "financial-advisor"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Avoid hardcoding application metadata like title and description strings. Read these values dynamically from package configuration instead.

"google-genai (>=1.5.0,<2.0.0)",
"pydantic (>=2.10.6,<3.0.0)",
"absl-py (>=2.2.1,<3.0.0)",
"google-adk[gcp,otel-gcp] (>=1.0.0)",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do not hardcode dependency names and versions in deploy.py. Parse the requirements dynamically from pyproject.toml to avoid duplication.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated Security review — 1 finding(s).

RUN uv sync --frozen

# Grant write permissions for ADK browser runtime config to avoid PermissionError on startup
RUN chmod -R a+w /code/.venv/lib/python3.11/site-packages/google/adk/cli/browser/assets/config 2>/dev/null || true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Avoid granting world-writable permissions (chmod -R a+w) to the package configuration directory. This violates the principle of least privilege and introduces code-tampering or privilege escalation risks within the container.

@happyhuman happyhuman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please review the comments left by the agent, and also fix the broken test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants