From 4814447da46796b2b5ccbbb8036d278a6aa9d96e Mon Sep 17 00:00:00 2001 From: chrischristiansen-glitch Date: Sat, 23 May 2026 02:46:41 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20add=20agents/core-logic/app.py=20?= =?UTF-8?q?=E2=80=94=20FastAPI=20Cloud=20Run=20entrypoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agents/core-logic/app.py | 125 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 agents/core-logic/app.py diff --git a/agents/core-logic/app.py b/agents/core-logic/app.py new file mode 100644 index 0000000..d31074f --- /dev/null +++ b/agents/core-logic/app.py @@ -0,0 +1,125 @@ +# agents/core-logic/app.py +# OSVauco-NMTMD-GCOS — FastAPI HTTP entrypoint for Cloud Run +# Wraps the ADK root_agent with a /run endpoint and a /health check. +# Usage: uvicorn app:app --host 0.0.0.0 --port 8080 + +import os +import logging +from contextlib import asynccontextmanager + +from fastapi import FastAPI, HTTPException +from fastapi.responses import JSONResponse +from pydantic import BaseModel + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# ── ADK imports ─────────────────────────────────────────────── +try: + from google.adk.runners import Runner + from google.adk.sessions import InMemorySessionService + from google.genai.types import Content, Part + from agent import root_agent +except ImportError as e: + logger.error(f"Failed to import ADK dependencies: {e}") + raise + +# ── Session service (in-memory for stateless Cloud Run) ─────────────── +session_service = InMemorySessionService() + +APP_NAME = os.environ.get("CLOUD_RUN_SERVICE", "gcp-orchestrator") + + +# ── Lifespan ─────────────────────────────────────────────────── +@asynccontextmanager +async def lifespan(app: FastAPI): + logger.info(f"OSVauco agent '{APP_NAME}' starting up") + yield + logger.info(f"OSVauco agent '{APP_NAME}' shutting down") + + +# ── App ──────────────────────────────────────────────────────── +app = FastAPI( + title="OSVauco GCP Agent", + description="ADK-based multi-agent orchestrator on Cloud Run", + version="1.0.0", + lifespan=lifespan, +) + + +# ── Models ───────────────────────────────────────────────────── +class RunRequest(BaseModel): + user_id: str + session_id: str + message: str + + +class RunResponse(BaseModel): + user_id: str + session_id: str + response: str + + +# ── Routes ───────────────────────────────────────────────────── +@app.get("/health") +async def health(): + """Cloud Run health check endpoint.""" + return JSONResponse({"status": "ok", "service": APP_NAME}) + + +@app.post("/run", response_model=RunResponse) +async def run(req: RunRequest): + """ + Run the OSVauco ADK agent for a single turn. + Creates (or reuses) a session, sends the user message, + and returns the final agent response. + """ + try: + # Create or reuse session + try: + session = await session_service.get_session( + app_name=APP_NAME, + user_id=req.user_id, + session_id=req.session_id, + ) + except Exception: + session = await session_service.create_session( + app_name=APP_NAME, + user_id=req.user_id, + session_id=req.session_id, + ) + + runner = Runner( + agent=root_agent, + app_name=APP_NAME, + session_service=session_service, + ) + + user_content = Content( + role="user", + parts=[Part(text=req.message)], + ) + + final_response = "" + async for event in runner.run_async( + user_id=req.user_id, + session_id=req.session_id, + new_message=user_content, + ): + if event.is_final_response() and event.content: + for part in event.content.parts: + if part.text: + final_response += part.text + + logger.info( + f"[{req.user_id}/{req.session_id}] Agent response length: {len(final_response)}" + ) + return RunResponse( + user_id=req.user_id, + session_id=req.session_id, + response=final_response, + ) + + except Exception as e: + logger.error(f"Agent run failed: {e}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e))