#!/usr/bin/env python3 """ gemini_agent_server.py Server HTTP mínimo (FastAPI) que expone un endpoint /query para recibir un prompt, opcionalmente enviar ese prompt a la API de Gemini (si GEMINI_API_KEY está configurada), interpretar la instrucción y ejecutar la acción usando `gemini_log_agent`. - Instalar dependencias: python3 -m pip install --user fastapi uvicorn requests - Ejecutar: export GEMINI_API_KEY="YOUR_KEY" export PLAYWRIGHT_WS="ws://192.168.1.20:3003" python3 -m uvicorn packages.broadcast_panel.e2e.gemini_agent_server:app --host 0.0.0.0 --port 5001 Nota: el módulo se implementa para usarse en el workspace; si uvicorn no encuentra el módulo, ejecuta desde la raíz del repo: uvicorn packages.broadcast-panel.e2e.gemini_agent_server:app --reload """ from __future__ import annotations import os import json import traceback from typing import Optional from fastapi import FastAPI, HTTPException from pydantic import BaseModel # Import local agent from .gemini_log_agent import interpret_prompt, handle_action import requests app = FastAPI(title="Gemini Log Agent Server") GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY') PLAYWRIGHT_WS = os.environ.get('PLAYWRIGHT_WS', 'ws://192.168.1.20:3003') class QueryRequest(BaseModel): prompt: str use_llm: Optional[bool] = False lines: Optional[int] = 200 ws: Optional[str] = None class QueryResponse(BaseModel): ok: bool mapping: dict result: str @app.get('/health') def health(): return {"status": "ok", "playwright_ws": PLAYWRIGHT_WS, "llm": bool(GEMINI_API_KEY)} def call_gemini(prompt: str) -> Optional[str]: """Call Google Generative Language (Gemini) REST endpoint using API key if available. Returns the generated text or None on failure. Note: we use the text-bison endpoint (v1beta2) as a best-effort; if the network fails we return None and fallback to local heuristics. """ if not GEMINI_API_KEY: return None try: url = f"https://generativelanguage.googleapis.com/v1beta2/models/text-bison-001:generateText?key={GEMINI_API_KEY}" payload = { "prompt": { "text": prompt }, "temperature": 0.2, "maxOutputTokens": 256 } headers = { 'Content-Type': 'application/json' } resp = requests.post(url, json=payload, headers=headers, timeout=15) if resp.status_code != 200: return None body = resp.json() # Response shape: {'candidates':[{'content':'...'}], ...} if 'candidates' in body and len(body['candidates'])>0: return body['candidates'][0].get('content') # Older shapes may have 'output' or 'result' if 'output' in body and isinstance(body['output'], list) and len(body['output'])>0: return body['output'][0].get('content') if 'result' in body and isinstance(body['result'], dict): return body['result'].get('content') return None except Exception: return None @app.post('/query', response_model=QueryResponse) def query(req: QueryRequest): try: prompt = req.prompt use_llm = bool(req.use_llm) lines = int(req.lines or 200) ws = req.ws or PLAYWRIGHT_WS if use_llm and GEMINI_API_KEY: gen = call_gemini(prompt) if gen: # Use the generated text to form a mapping prompt (pass-through) interpreted = interpret_prompt(gen) else: interpreted = interpret_prompt(prompt) else: interpreted = interpret_prompt(prompt) # Attach ws info if available for run_session if interpreted.get('action') == 'run_session': interpreted['ws'] = ws result = handle_action(interpreted, lines=lines) return QueryResponse(ok=True, mapping=interpreted, result=result) except Exception as e: traceback.print_exc() raise HTTPException(status_code=500, detail=str(e))