-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
152 lines (120 loc) · 4.48 KB
/
Copy pathmain.py
File metadata and controls
152 lines (120 loc) · 4.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import json
import os
import uuid
from typing import Any
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
from agent import root_agent
from weather_service import fetch_weather_by_city
APP_NAME = "mcp_weather_service"
USER_ID = "public_api_user"
app = FastAPI(
title="mcp-agent",
version="1.0.0",
description="MCP-powered weather agent built with Google ADK and FastAPI.",
)
session_service = InMemorySessionService()
runner = Runner(
agent=root_agent,
app_name=APP_NAME,
session_service=session_service,
)
class WeatherRequest(BaseModel):
city: str = Field(..., min_length=1, description="City name, for example Delhi")
class WeatherResponse(BaseModel):
city: str
weather_data: dict[str, Any] | None
answer: str
status: str
def extract_json_text(text: str) -> str:
cleaned = text.strip()
if cleaned.startswith("```"):
cleaned = cleaned.strip("`").strip()
if cleaned.lower().startswith("json"):
cleaned = cleaned[4:].strip()
return cleaned
async def run_agent(city: str) -> dict[str, Any]:
session_id = str(uuid.uuid4())
await session_service.create_session(
app_name=APP_NAME,
user_id=USER_ID,
session_id=session_id,
)
prompt = (
f"Input JSON: {json.dumps({'city': city}, ensure_ascii=True)}\n"
"Use the get_weather MCP tool exactly once.\n"
"Return only valid JSON with keys city, weather_data, answer, status.\n"
"Do not add markdown."
)
user_message = types.Content(
role="user",
parts=[types.Part(text=prompt)],
)
final_text = None
async for event in runner.run_async(
user_id=USER_ID,
session_id=session_id,
new_message=user_message,
):
if event.is_final_response() and event.content and event.content.parts:
for part in event.content.parts:
if part.text and part.text.strip():
final_text = part.text.strip()
if not final_text:
raise RuntimeError("The ADK agent returned no final response.")
payload = json.loads(extract_json_text(final_text))
if not isinstance(payload, dict):
raise ValueError("The ADK agent returned JSON, but it was not a JSON object.")
return payload
@app.get("/health")
async def health() -> dict[str, str]:
return {"status": "ok", "service": "mcp-agent"}
@app.post("/weather", response_model=WeatherResponse)
async def weather(request: WeatherRequest) -> WeatherResponse:
city = request.city.strip()
if not city:
raise HTTPException(status_code=400, detail="The 'city' field must not be empty.")
try:
payload = await run_agent(city)
except json.JSONDecodeError:
try:
fallback_weather = await fetch_weather_by_city(city)
return WeatherResponse(
city=city,
weather_data=fallback_weather,
answer="The ADK agent returned malformed JSON. Check Cloud Run logs and troubleshooting steps.",
status="error",
)
except Exception as fallback_error:
raise HTTPException(
status_code=500,
detail=f"Agent returned malformed JSON and fallback lookup failed: {fallback_error}",
)
except Exception as agent_error:
try:
fallback_weather = await fetch_weather_by_city(city)
return WeatherResponse(
city=city,
weather_data=fallback_weather,
answer=f"Agent execution failed, but direct weather retrieval worked: {agent_error}",
status="error",
)
except Exception:
raise HTTPException(status_code=500, detail=f"Agent execution failed: {agent_error}")
payload["city"] = str(payload.get("city") or city)
payload["weather_data"] = payload.get("weather_data")
payload["answer"] = str(payload.get("answer") or "Weather lookup completed.")
payload["status"] = str(payload.get("status") or "success")
if payload["status"] not in {"success", "error"}:
payload["status"] = "success" if payload["weather_data"] else "error"
return WeatherResponse(**payload)
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"main:app",
host="0.0.0.0",
port=int(os.environ.get("PORT", "8080")),
)