#!/usr/bin/env python3 """ Pipecat Bot Template — OpenAILLMService avec API Hermes ======================================================== Template minimal pour un bot texte via WebSocket. Adapté de ~/workspace/jarvis-pipecat/bot.py Prérequis: pipecat-ai[websocket]>=1.4.0, openai, uvicorn, websockets """ import json, os, subprocess, sys from fastapi import FastAPI, WebSocket from fastapi.responses import HTMLResponse import uvicorn # ⚠️ Pipecat 1.4.0 : utiliser `import module as m` (from-import cassé) 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 # ─── Config ────────────────────────────────────────────────── PORT = int(os.environ.get("BOT_PORT", "7081")) HERMES_URL = os.environ.get("HERMES_API_URL", "http://localhost:8642/v1") HERMES_KEY = os.environ.get("HERMES_API_KEY", "") SYSTEM_PROMPT = "Tu es un assistant vocal concis. Parle en français, 3 phrases max." # ─── Processeur custom : texte → LLMMessagesFrame ──────────── class TextToLLMProcessor(FrameProcessor): """Remplace le context aggregator (buggé en 1.4.0).""" 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:] await self.push_frame(pframes.LLMMessagesFrame(messages=list(self._messages))) elif isinstance(frame, pframes.LLMFullResponseStartFrame): self._response_text = "" elif isinstance(frame, pframes.TextFrame): if hasattr(self, '_response_text'): self._response_text += frame.text elif isinstance(frame, pframes.LLMFullResponseEndFrame): if hasattr(self, '_response_text') and self._response_text.strip(): self._messages.append({"role": "assistant", "content": self._response_text.strip()}) del self._response_text # ─── App ───────────────────────────────────────────────────── app = FastAPI() @app.websocket("/ws") async def ws_endpoint(websocket: WebSocket): transport = FastAPIWebsocketTransport( websocket=websocket, params=FastAPIWebsocketParams(add_wav_header=False, session_timeout=None), ) llm = OpenAILLMService( api_key=HERMES_KEY, base_url=HERMES_URL, settings=OpenAILLMService.Settings(model="Hermes Agent", temperature=0.8, max_tokens=300, system_instruction=SYSTEM_PROMPT), ) proc = TextToLLMProcessor(SYSTEM_PROMPT) pipeline = Pipeline(processors=[transport.input(), proc, llm, transport.output()]) task = PipelineTask(pipeline) runner = PipelineRunner() await runner.run(task) if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=PORT)