Files
2026-07-13 13:30:30 +08:00

134 lines
4.8 KiB
Python

import json
import logging
from pathlib import Path
from dotenv import load_dotenv
from fastapi import FastAPI, WebSocket
from fastapi.staticfiles import StaticFiles
from google.adk.agents import LlmAgent
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, StdioServerParameters
from google.genai import types
from starlette.websockets import WebSocketDisconnect
load_dotenv()
APP_NAME = "ADK MCP example"
STATIC_DIR = Path("static")
session_service = InMemorySessionService()
artifacts_service = InMemoryArtifactService()
ct_server_params = StdioServerParameters(
command="python",
args=["./mcp_server/cocktail.py"],
)
def create_agent(server_params: StdioServerParameters):
"""Creates an ADK Agent with tools from MCP Server."""
agent_instruction = """You are Gemini, a helpful and reliable AI assistant. Your main goal is to provide clear, accurate, and well-supported answers.
- When needed, use your tools to find the most current information to address the user's query.
- Carefully combine the information you find into a complete answer.
- If you cannot find the specific information requested using your tools, let the user know.
- Please format your response using Markdown to make it easy to read and understand.
"""
root_agent = LlmAgent(
model="gemini-2.5-flash",
name="ai_assistant",
instruction=agent_instruction,
tools=[MCPToolset(connection_params=server_params)],
)
return root_agent
async def process_message_with_runner(runner: Runner, session_id: str, question: str):
"""Processes a single message using the provided runner."""
content = types.Content(role="user", parts=[types.Part(text=question)])
events_async = runner.run_async(
session_id=session_id, user_id=session_id, new_message=content
)
response_parts = []
async for event in events_async:
if event.content.role == "model" and event.content.parts[0].text:
print("[agent]:", event.content.parts[0].text)
response_parts.append(event.content.parts[0].text)
return response_parts
async def run_adk_agent_session(
websocket: WebSocket, server_params: StdioServerParameters, session_id: str
):
"""Handles client-to-agent communication over WebSocket for a session."""
root_agent = create_agent(server_params)
runner = Runner(
app_name=APP_NAME,
agent=root_agent,
artifact_service=artifacts_service,
session_service=session_service,
)
logging.info(f"Agent session started for {session_id} with runner and agent.")
try:
while True:
text = await websocket.receive_text()
logging.info(f"Received from {session_id}: {text}")
response_parts = await process_message_with_runner(runner, session_id, text)
if not response_parts:
continue
# Send the text to the client
ai_message = "\n".join(response_parts)
logging.info(
f"Sending to {session_id}: {ai_message[:100]}..."
) # Log snippet
await websocket.send_text(json.dumps({"message": ai_message}))
except WebSocketDisconnect:
# This block executes when the client disconnects
logging.info(f"Client {session_id} disconnected.")
finally:
logging.info(f"Closing runner for session {session_id}...")
await runner.close()
logging.info(f"Runner closed for session {session_id}. Agent session ending.")
# FastAPI web app
app = FastAPI()
STATIC_DIR = "static" # Or your directory name
@app.websocket("/ws/{session_id}")
async def websocket_endpoint(
websocket: WebSocket, session_id: str
): # Use str for session_id
"""Client websocket endpoint"""
await websocket.accept()
logging.info(f"Client #{session_id} connected and WebSocket accepted.")
try:
# Start agent session
# Ensure session is created before starting the agent task
await session_service.create_session(
app_name=APP_NAME, user_id=session_id, session_id=session_id, state={}
)
logging.info(f"ADK Session created for {session_id}.")
# Start agent communication task
await run_adk_agent_session(websocket, ct_server_params, session_id)
except WebSocketDisconnect:
# This might be redundant if run_adk_agent_session handles it,
# but good for logging the endpoint's perspective.
logging.info(f"WebSocket endpoint for {session_id} detected disconnect.")
finally:
logging.info(f"WebSocket endpoint for session {session_id} is concluding.")
app.mount("/", StaticFiles(directory=STATIC_DIR, html=True), name="static")