84 lines
3.7 KiB
Python
84 lines
3.7 KiB
Python
#!/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)
|