#!/usr/bin/env python3 """ JARVIS v3 PoC — Pipecat + Hermes API ===================================== Bot WebSocket texte : transport → LLM → transport Sans agrégateur — notre processeur custom gère tout. """ import json, os, subprocess, sys from fastapi import FastAPI, WebSocket from fastapi.responses import HTMLResponse import uvicorn import pipecat.frames.frames as pframes from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.task import PipelineTask from pipecat.processors.frame_processor import FrameProcessor, FrameDirection from pipecat.services.openai.llm import OpenAILLMService from pipecat.transports.websocket.fastapi import ( FastAPIWebsocketParams, FastAPIWebsocketTransport, ) PORT = int(os.environ.get("JARVIS_PIPECAT_PORT", "7081")) # ─── Clé API ───────────────────────────────────────────────── def _get_hermes_key(): key = os.environ.get("HERMES_API_KEY", "") or os.environ.get("HERMES_ALEXA_KEY", "") if key: return key try: creds = json.load(open("/opt/infisical/admin-credentials.json")) r = subprocess.run( ["/home/openclaw/.local/bin/infisical", "secrets", "get", "API_SERVER_KEY", "--domain", "https://secret.dracodev.net", "--projectId", "192c1eed-6666-41ee-80df-a4636a2da0ad", "--env", "prod", "--path", "/hermes-claw", "--plain", "--token", creds["token"]], capture_output=True, text=True, timeout=15) return r.stdout.strip() except Exception as e: print(f"⚠️ Infisical: {e}"); return "" _hermes_key = _get_hermes_key() HERMES_URL = "http://localhost:8642/v1" SYSTEM_PROMPT = ( "Tu es JARVIS, l'assistant vocal de Bruno. " "Parle en français, sois concis (3 phrases max), chaleureux et utile. " "Pas de markdown. Réponds comme si tu parlais à l'oral." ) # ─── Processeur custom ultra-simple ────────────────────────── class SimpleLLMRelay(FrameProcessor): """ Capture les messages texte du transport et les envoie au LLM via LLMContextFrame. Les réponses du LLM passent directement au transport. """ def __init__(self, system_prompt: str): super().__init__() self._messages = [{"role": "system", "content": system_prompt}] async def process_frame(self, frame, direction): await self.push_frame(frame, direction) if isinstance(frame, pframes.InputTransportMessageFrame): text = frame.message if isinstance(text, str) and text.strip(): self._messages.append({"role": "user", "content": text}) if len(self._messages) > 21: self._messages = [self._messages[0]] + self._messages[-20:] print(f"👤 {text[:80]}") ctx = pframes.LLMContextFrame(context=list(self._messages)) await self.push_frame(ctx) # ─── Page HTML ─────────────────────────────────────────────── HTML_PAGE = open(os.path.join(os.path.dirname(__file__), "test.html"), "r").read() if os.path.exists("test.html") else """