commit 09984d49faa86d51e7f9e2169775aeff31f9cc07 Author: Bruno Charest Date: Fri Jun 19 22:46:32 2026 -0400 feat: JARVIS v3 Phase 1 — WebSocket relay vers Hermes A.L.E.X.A. - Serveur WebSocket direct avec websockets 16.0 - Interface HTML/CSS/JS dark theme avec chat - Relay vers API Hermes sur port 8642 - Historique de conversation (20 messages) - Clé API via Infisical - Testé et fonctionnel sur openclaw1.dev.home:7081 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f7b5c47 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__/ +*.pyc +venv/ +.env +*.log +*.pid diff --git a/_check.sh b/_check.sh new file mode 100644 index 0000000..b7962f3 --- /dev/null +++ b/_check.sh @@ -0,0 +1,9 @@ +#!/bin/bash +cd ~/workspace/jarvis-pipecat +echo "=== head ===" +head -30 bot.py +echo "=== line count ===" +wc -l bot.py +echo "=== import test ===" +./venv/bin/python -c "import bot" 2>&1 +echo "exit: $?" diff --git a/_check_out.txt b/_check_out.txt new file mode 100644 index 0000000..8b796c1 --- /dev/null +++ b/_check_out.txt @@ -0,0 +1,36 @@ +=== head === +#!/usr/bin/env python3 +""" +JARVIS v3 PoC — Pipecat bot avec API Hermes +============================================ +Phase 1 : bot texte via WebSocket +Version corrigée pour Pipecat 1.4.0 — utilise LLMContext + LLMContextFrame +""" + +import json +import os +import subprocess +import sys + +from fastapi import FastAPI, WebSocket +from fastapi.responses import HTMLResponse +import uvicorn + +from pipecat.frames.frames import ( + Frame, + InputTransportMessageFrame, + LLMContextFrame, + LLMFullResponseEndFrame, + LLMFullResponseStartFrame, + LLMTextFrame, + OutputTransportMessageFrame, + OutputTransportMessageUrgentFrame, + StartFrame, +) +from pipecat.pipeline.pipeline import Pipeline +from pipecat.pipeline.runner import PipelineRunner +=== line count === +354 bot.py +=== import test === +2026-06-19 17:14:16.253 | INFO | pipecat::28 - ᓚᘏᗢ Pipecat 1.4.0 (Python 3.13.5 (main, May 5 2026, 21:05:52) [GCC 14.2.0]) ᓚᘏᗢ +exit: 0 diff --git a/_debug.py b/_debug.py new file mode 100644 index 0000000..4db907d --- /dev/null +++ b/_debug.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +import pipecat.frames.frames as pf +for name in sorted(dir(pf)): + if 'LLM' in name or 'Context' in name or 'Message' in name: + print(name) diff --git a/_debug2.py b/_debug2.py new file mode 100644 index 0000000..412f6fe --- /dev/null +++ b/_debug2.py @@ -0,0 +1,16 @@ +import pipecat.frames.frames as pf +# LLMContextFrame +import inspect +print("=== LLMContextFrame ===") +print(inspect.getsource(pf.LLMContextFrame)) +print() +print("=== LLMMessagesAppendFrame ===") +print(inspect.getsource(pf.LLMMessagesAppendFrame)) +print() +print("=== LLMMessagesUpdateFrame ===") +print(inspect.getsource(pf.LLMMessagesUpdateFrame)) +print() +# Also look at LLMContext aggregator +from pipecat.processors.aggregators.llm_context import LLMContext +print("=== LLMContext.__init__ ===") +print(inspect.getsource(LLMContext.__init__)) diff --git a/_debug_out.txt b/_debug_out.txt new file mode 100644 index 0000000..127a2ec --- /dev/null +++ b/_debug_out.txt @@ -0,0 +1,28 @@ +2026-06-19 17:09:12.173 | INFO | pipecat::28 - ᓚᘏᗢ Pipecat 1.4.0 (Python 3.13.5 (main, May 5 2026, 21:05:52) [GCC 14.2.0]) ᓚᘏᗢ +FunctionCallFromLLM +InputTransportMessageFrame +LLMAssistantPushAggregationFrame +LLMConfigureOutputFrame +LLMContextAssistantTimestampFrame +LLMContextAssistantTurnFrame +LLMContextFrame +LLMContextSummaryRequestFrame +LLMContextSummaryResultFrame +LLMEnablePromptCachingFrame +LLMFullResponseEndFrame +LLMFullResponseStartFrame +LLMMarkerFrame +LLMMessagesAppendFrame +LLMMessagesTransformFrame +LLMMessagesUpdateFrame +LLMRunFrame +LLMSetToolChoiceFrame +LLMSetToolsFrame +LLMSummarizeContextFrame +LLMTextFrame +LLMThoughtEndFrame +LLMThoughtStartFrame +LLMThoughtTextFrame +LLMUpdateSettingsFrame +OutputTransportMessageFrame +OutputTransportMessageUrgentFrame diff --git a/_debug_out2.txt b/_debug_out2.txt new file mode 100644 index 0000000..dd881e5 --- /dev/null +++ b/_debug_out2.txt @@ -0,0 +1,74 @@ +2026-06-19 17:09:30.215 | INFO | pipecat::28 - ᓚᘏᗢ Pipecat 1.4.0 (Python 3.13.5 (main, May 5 2026, 21:05:52) [GCC 14.2.0]) ᓚᘏᗢ +=== LLMContextFrame === +@dataclass +class LLMContextFrame(Frame): + """Frame containing a universal LLM context. + + Used as a signal to LLM services to ingest the provided context and + generate a response based on it. + + Parameters: + context: The LLM context containing messages, tools, and configuration. + """ + + context: LLMContext + + +=== LLMMessagesAppendFrame === +@dataclass +class LLMMessagesAppendFrame(DataFrame): + """Frame containing LLM messages to append to current context. + + A frame containing a list of LLM messages that need to be added to the + current context. + + Parameters: + messages: List of context messages to append. + run_llm: Whether the context update should be sent to the LLM. + """ + + messages: list[LLMContextMessage] + run_llm: bool | None = None + + +=== LLMMessagesUpdateFrame === +@dataclass +class LLMMessagesUpdateFrame(DataFrame): + """Frame containing LLM messages to replace current context. + + A frame containing a list of new LLM messages to replace the current + context LLM messages. + + Parameters: + messages: List of context messages to replace current context. + run_llm: Whether the context update should be sent to the LLM. + """ + + messages: list[LLMContextMessage] + run_llm: bool | None = None + + +=== LLMContext.__init__ === + def __init__( + self, + messages: list[LLMContextMessage] | None = None, + tools: ToolsSchema | list[FunctionSchema | DirectFunction] | NotGiven = NOT_GIVEN, + tool_choice: LLMContextToolChoice | NotGiven = NOT_GIVEN, + ): + """Initialize the LLM context. + + Args: + messages: Initial list of conversation messages. + tools: Available tools for the LLM to use. May be a ``ToolsSchema`` + or a plain list of direct functions and/or ``FunctionSchema`` + objects (normalized to a ``ToolsSchema`` internally). Any tool + that carries a handler — a direct function, or a + ``FunctionSchema`` with its ``handler`` set — is registered with + the LLM service automatically, so no separate + ``register_function`` call is needed. + tool_choice: Tool selection strategy for the LLM. + """ + self._messages: list[LLMContextMessage] = messages if messages else [] + self._tools: ToolsSchema | NotGiven = LLMContext._normalize_and_validate_tools(tools) + self._tool_choice: LLMContextToolChoice | NotGiven = tool_choice + diff --git a/_get_key.py b/_get_key.py new file mode 100644 index 0000000..7352b01 --- /dev/null +++ b/_get_key.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 +import os +# Read the raw environ of the hermes gateway process +with open('/proc/1359/environ', 'rb') as f: + data = f.read() +for var in data.split(b'\x00'): + if var: + decoded = var.decode('utf-8', errors='replace') + if any(k in decoded.lower() for k in ['key', 'secret', 'token', 'api_server']): + print(decoded) diff --git a/_key_out.txt b/_key_out.txt new file mode 100644 index 0000000..e69de29 diff --git a/_show.py b/_show.py new file mode 100644 index 0000000..1ffc129 --- /dev/null +++ b/_show.py @@ -0,0 +1,5 @@ +with open('/home/openclaw/workspace/jarvis-pipecat/bot.py') as f: + lines = f.readlines() +for i, line in enumerate(lines, 1): + if 67 <= i <= 100 or 110 <= i <= 175: + print(f'{i}: {line}', end='') diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..51da2e6 --- /dev/null +++ b/bot.py @@ -0,0 +1,172 @@ +#!/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 """ + + +JARVIS v3 + +

⚡ JARVIS v3 — Pipecat PoC

+
🔴 Déconnecté
+
+""" + +# ─── FastAPI ───────────────────────────────────────────────── +app = FastAPI(title="JARVIS v3 PoC", version="0.1.0") + +@app.get("/") +async def root(): + return HTMLResponse(content=HTML_PAGE) + +@app.websocket("/ws") +async def ws_endpoint(websocket: WebSocket): + transport = FastAPIWebsocketTransport( + websocket=websocket, + params=FastAPIWebsocketParams( + add_wav_header=False, + session_timeout=None, + # allowed_origins vide = autorise toutes les origines + ), + ) + + relay = SimpleLLMRelay(SYSTEM_PROMPT) + llm = OpenAILLMService( + api_key=_hermes_key, + base_url=HERMES_URL, + settings=OpenAILLMService.Settings( + model="Hermes Agent", temperature=0.8, max_tokens=300, + ), + ) + + pipeline = Pipeline(processors=[transport.input(), relay, llm, transport.output()]) + task = PipelineTask(pipeline) + + @transport.event_handler("on_client_connected") + async def on_connect(transport, client): + print(f"✅ Connecté") + await transport.output().send_message( + pframes.OutputTransportMessageFrame( + message="👋 Bonjour Bruno ! Je suis JARVIS v3. Pose-moi une question !" + ) + ) + + @transport.event_handler("on_client_disconnected") + async def on_disconnect(transport, client): + print(f"👋 Déconnecté") + await task.cancel() + + runner = PipelineRunner() + await runner.add_workers(task) + try: + await runner.run() + except Exception as e: + print(f"Pipeline error: {e}") + +# ─── Main ──────────────────────────────────────────────────── +if __name__ == "__main__": + if not _hermes_key: + print("❌ Pas de clé API Hermes"); sys.exit(1) + print(f"🔑 {_hermes_key[:15]}...") + print(f"🌐 {HERMES_URL}") + print(f"🚀 http://0.0.0.0:{PORT}") + print(f" ws://0.0.0.0:{PORT}/ws") + uvicorn.run(app, host="0.0.0.0", port=PORT, log_level="info") diff --git a/bot_direct.py b/bot_direct.py new file mode 100644 index 0000000..6fa8e3e --- /dev/null +++ b/bot_direct.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +""" +JARVIS v3 Phase 1 — Relay WebSocket ↔ Hermes API +================================================== +PoC minimal : WebSocket texte → API Hermes → réponse texte. +Pas de Pipecat — intégration directe pour valider le concept. +""" +import asyncio +import json +import os +import subprocess +import sys +import httpx +from websockets.asyncio.server import serve +from websockets.http11 import Response, Headers + +PORT = int(os.environ.get("JARVIS_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/chat/completions" + +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." +) + +# ─── Page HTML ─────────────────────────────────────────────── +HTML_PAGE = """ + + + + +JARVIS v3 — Phase 1 PoC + + + +

⚡ JARVIS v3 — Phase 1 (Hermes direct)

+
🔴 Déconnecté
+
+
+ + +
+ + + +""" + +# ─── HTTP Handler ──────────────────────────────────────────── +async def http_handler(websocket): + """Serve the test page over raw HTTP (before upgrade).""" + # This is handled by serve()'s process_request + pass + +async def process_request(connection, request): + """Serve HTML for HTTP requests, upgrade for WebSocket.""" + if request.path == "/" or request.path == "/test.html": + headers = Headers([("Content-Type", "text/html; charset=utf-8")]) + return Response(200, "OK", headers, HTML_PAGE.encode()) + return None # Let WebSocket handler take over + +# ─── WebSocket Handler ─────────────────────────────────────── +async def ws_handler(websocket): + """Handle WebSocket connection: relay messages to Hermes API.""" + messages = [{"role": "system", "content": SYSTEM_PROMPT}] + client = httpx.AsyncClient(timeout=60.0) + + try: + # Send welcome + await websocket.send(json.dumps({ + "type": "welcome", + "content": "👋 Bonjour Bruno ! Je suis JARVIS v3 (Phase 1). Pose-moi une question !" + })) + print("✅ Client connecté") + + async for raw in websocket: + try: + data = json.loads(raw) + text = data.get("text", "").strip() + except: + text = raw.strip() + + if not text: + continue + + print(f"👤 {text[:80]}") + messages.append({"role": "user", "content": text}) + # Keep last 20 messages + system prompt + if len(messages) > 21: + messages = [messages[0]] + messages[-20:] + + # Send thinking indicator + await websocket.send(json.dumps({"type": "thinking"})) + + # Call Hermes API + try: + resp = await client.post( + HERMES_URL, + headers={ + "Authorization": f"Bearer {HERMES_KEY}", + "Content-Type": "application/json", + }, + json={ + "model": "Hermes Agent", + "messages": messages, + "temperature": 0.8, + "max_tokens": 300, + "stream": False, + }, + ) + resp.raise_for_status() + body = resp.json() + reply = body["choices"][0]["message"]["content"] + + messages.append({"role": "assistant", "content": reply}) + print(f"🤖 {reply[:80]}") + + await websocket.send(json.dumps({ + "type": "response", + "content": reply, + })) + + except Exception as e: + print(f"❌ API error: {e}") + await websocket.send(json.dumps({ + "type": "error", + "content": f"Erreur API: {str(e)[:200]}", + })) + + except Exception as e: + print(f"👋 Déconnecté: {e}") + finally: + await client.aclose() + + +# ─── Main ──────────────────────────────────────────────────── +async def main(): + if not HERMES_KEY: + print("❌ Pas de clé API Hermes"); sys.exit(1) + + print(f"🔑 {HERMES_KEY[:15]}...") + print(f"🌐 {HERMES_URL}") + print(f"🚀 JARVIS v3 PoC → http://0.0.0.0:{PORT}") + print(f" WebSocket → ws://0.0.0.0:{PORT}/ws") + + async with serve(ws_handler, "0.0.0.0", PORT, process_request=process_request): + print(f"✅ Serveur prêt sur le port {PORT}") + await asyncio.get_running_loop().create_future() # run forever + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/check_api.py b/check_api.py new file mode 100644 index 0000000..50c6ec7 --- /dev/null +++ b/check_api.py @@ -0,0 +1,16 @@ +from pipecat.processors.aggregators.llm_context import OpenAIContextAggregatorPair +import inspect + +print("OpenAIContextAggregatorPair:") +for name, method in inspect.getmembers(OpenAIContextAggregatorPair, inspect.isfunction): + if not name.startswith("_"): + sig = inspect.signature(method) + print(f" {name}{sig}") + +print() +# Also check what other classes are exported +import pipecat.processors.aggregators.llm_context as m +print("All public symbols:") +for x in dir(m): + if not x.startswith("_"): + print(f" {x}") diff --git a/check_api2.py b/check_api2.py new file mode 100644 index 0000000..4926fff --- /dev/null +++ b/check_api2.py @@ -0,0 +1,11 @@ +import pipecat.processors.aggregators.llm_context as m +import inspect + +print("Has OpenAILLMContext:", hasattr(m, "OpenAILLMContext")) +print("All OpenAI classes:", [x for x in dir(m) if "OpenAI" in x or "Context" in x]) + +# Try to import from the module's namespace +for name in dir(m): + if name.startswith("OpenAI"): + cls = getattr(m, name) + print(f" {name}: {type(cls)}") diff --git a/quick_test.py b/quick_test.py new file mode 100644 index 0000000..35240e7 --- /dev/null +++ b/quick_test.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Quick test: start bot, hit WS, see result.""" +import subprocess, os, json, time, sys, asyncio, threading, signal + +os.chdir("/home/openclaw/workspace/jarvis-pipecat") + +# Get key +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) +os.environ["HERMES_API_KEY"] = r.stdout.strip() +os.environ["PYTHONUNBUFFERED"] = "1" + +# Import bot (this triggers the key check print) +import bot + +# Start uvicorn in thread +import uvicorn +def run_server(): + uvicorn.run(bot.app, host="0.0.0.0", port=7081, log_level="info") + +t = threading.Thread(target=run_server, daemon=True) +t.start() + +# Wait for server +for i in range(20): + try: + import urllib.request + urllib.request.urlopen("http://localhost:7081/", timeout=1) + break + except: + time.sleep(0.3) +else: + print("FAIL: Server didn't start") + sys.exit(1) + +# Test WS +async def test(): + import websockets + try: + async with websockets.connect("ws://localhost:7081/ws", open_timeout=5) as ws: + msg = await asyncio.wait_for(ws.recv(), timeout=5) + data = json.loads(msg) + print(f"WELCOME: {data.get('message', msg)[:120]}") + + await ws.send("Bonjour! Quelle heure est-il?") + print("SENT question") + + resp = await asyncio.wait_for(ws.recv(), timeout=20) + data2 = json.loads(resp) + content = data2.get("content", str(data2)) + print(f"REPLY: {content[:300]}") + print("\n✅✅✅ SUCCESS! JARVIS v3 works! ✅✅✅") + return True + except Exception as e: + print(f"WS FAIL: {type(e).__name__}: {e}") + import traceback + traceback.print_exc() + return False + +result = asyncio.run(test()) +sys.exit(0 if result else 1) diff --git a/run_bot.py b/run_bot.py new file mode 100644 index 0000000..5b4dca9 --- /dev/null +++ b/run_bot.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Starts JARVIS v3 bot with Hermes API key from Infisical.""" +import json, subprocess, os, sys + +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=20, +) +hermes_key = r.stdout.strip() +if not hermes_key: + print(f"ERROR: {r.stderr}", file=sys.stderr) + sys.exit(1) + +print(f"Key: {hermes_key[:15]}...") +os.chdir("/home/openclaw/workspace/jarvis-pipecat") +os.environ["HERMES_API_KEY"] = hermes_key +os.environ["PYTHONUNBUFFERED"] = "1" +# PAS de PIPECAT_ALLOWED_ORIGINS = laisse le défaut (liste vide = tout autorisé) +if "PIPECAT_ALLOWED_ORIGINS" in os.environ: + del os.environ["PIPECAT_ALLOWED_ORIGINS"] +os.execvp("./venv/bin/python", ["./venv/bin/python", "bot.py"]) diff --git a/run_bot.sh b/run_bot.sh new file mode 100644 index 0000000..775dc85 --- /dev/null +++ b/run_bot.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e +pkill -f "bot.py" 2>/dev/null +sleep 1 + +TOKEN=$(python3 -c "import json; print(json.load(open('/opt/infisical/admin-credentials.json'))['token'])") +HERMES_KEY=$(/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 "$TOKEN" 2>/dev/null) + +export HERMES_API_KEY="$HERMES_KEY" +cd /home/openclaw/workspace/jarvis-pipecat +exec ./venv/bin/python bot.py diff --git a/run_direct.py b/run_direct.py new file mode 100644 index 0000000..159a6b0 --- /dev/null +++ b/run_direct.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +"""Launcher pour JARVIS v3 Phase 1 (direct WebSocket).""" +import json, subprocess, os, sys + +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=20, +) +hermes_key = r.stdout.strip() +if not hermes_key: + print(f"ERROR: {r.stderr}", file=sys.stderr); sys.exit(1) + +os.chdir("/home/openclaw/workspace/jarvis-pipecat") +os.environ["HERMES_API_KEY"] = hermes_key +os.execvp("./venv/bin/python", ["./venv/bin/python", "bot_direct.py"]) diff --git a/run_test.py b/run_test.py new file mode 100644 index 0000000..af2236a --- /dev/null +++ b/run_test.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""Script de test complet pour JARVIS v3 Pipecat.""" +import subprocess, os, json, time, sys, asyncio + +# 1. Récupérer la clé API Hermes depuis Infisical +print("🔑 Récupération clé API Hermes...") +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=20, + env={"PATH": "/usr/local/bin:/usr/bin:/bin"} + ) + hermes_key = r.stdout.strip() + if not hermes_key: + print(f"❌ Erreur Infisical: {r.stderr}") + sys.exit(1) + print(f"✅ Clé: {hermes_key[:15]}...") +except Exception as e: + print(f"❌ Erreur: {e}") + sys.exit(1) + +# 2. Lancer le bot +print("\n🚀 Lancement du bot Pipecat...") +os.environ["HERMES_API_KEY"] = hermes_key +proc = subprocess.Popen( + ["./venv/bin/python", "bot.py"], + cwd="/home/openclaw/workspace/jarvis-pipecat", + env={**os.environ, "HERMES_API_KEY": hermes_key}, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True, bufsize=1 +) + +# 3. Attendre que le serveur démarre +print("⏳ Attente démarrage serveur...") +import select +start = time.time() +while time.time() - start < 15: + ready, _, _ = select.select([proc.stdout], [], [], 0.5) + if ready: + line = proc.stdout.readline() + if line: + print(f" {line.rstrip()}") + if "Uvicorn running" in line or "Application startup complete" in line: + break + # Vérifier si le port écoute + try: + import socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) + s.connect(("localhost", 7081)) + s.close() + print("✅ Port 7081 ouvert!") + break + except: + pass + time.sleep(0.5) + +if proc.poll() is not None: + print("❌ Le bot a crashé au démarrage!") + sys.exit(1) + +print("✅ Bot démarré!") + +# 4. Test HTTP +print("\n🌐 Test HTTP GET /") +import urllib.request +resp = urllib.request.urlopen("http://localhost:7081/", timeout=5) +content = resp.read().decode() +if "JARVIS" in content: + print("✅ Page HTML OK") +else: + print(f"⚠️ Page: {content[:100]}") + +# 5. Test WebSocket +print("\n🔌 Test WebSocket...") +try: + import websockets +except ImportError: + subprocess.run(["./venv/bin/pip", "install", "websockets"], check=True) + import websockets + +async def ws_test(): + async with websockets.connect("ws://localhost:7081/ws") as ws: + # Recevoir message de bienvenue + msg = await asyncio.wait_for(ws.recv(), timeout=5) + print(f"👋 Welcome: {msg[:150]}") + + # Envoyer une question + await ws.send("Quelle heure est-il actuellement?") + print("📤 Question envoyée") + + # Attendre la réponse + try: + msg = await asyncio.wait_for(ws.recv(), timeout=15) + print(f"🤖 Réponse: {msg[:300]}") + print("\n✅✅✅ TEST RÉUSSI! JARVIS v3 répond via Pipecat! ✅✅✅") + except asyncio.TimeoutError: + print("⚠️ Timeout: pas de réponse du LLM en 15s") + print(" (le LLM peut prendre plus de temps)") + +asyncio.run(ws_test()) + +print("\n📊 Serveur toujours en cours d'exécution...") +print(f" 🌐 http://openclaw1.dev.home:7081/") +print(f" 🔌 ws://openclaw1.dev.home:7081/ws") +print(f" PID: {proc.pid}") diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..aed005f --- /dev/null +++ b/start.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# ~/workspace/jarvis-pipecat/start.sh +# Lancement JARVIS v3 Pipecat + +set -e +cd "$(dirname "$0")" + +# Récupération clé API Hermes depuis Infisical +echo "🔐 Récupération clé API Hermes..." +HERMES_API_KEY=$( + ./venv/bin/python3 -c " +import json, subprocess +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'], + env={'INFISICAL_TOKEN': creds['token'], 'PATH': '/usr/local/bin:/usr/bin:/bin'}, + capture_output=True, text=True, timeout=15, +) +print(r.stdout.strip()) +" +) + +if [ -z "$HERMES_API_KEY" ]; then + echo "❌ Impossible de récupérer la clé API Hermes" + exit 1 +fi + +export HERMES_API_KEY +export JARVIS_PIPECAT_PORT="${JARVIS_PIPECAT_PORT:-7081}" +# Désactiver la vérification d'origine pour le PoC (client websockets n'envoie pas d'Origin) +export PIPECAT_ALLOWED_ORIGINS="http://localhost:7081,http://0.0.0.0:7081" + +echo "🔑 Clé: ${HERMES_API_KEY:0:15}..." +echo "🚀 JARVIS v3 Pipecat → http://0.0.0.0:${JARVIS_PIPECAT_PORT}" +exec ./venv/bin/python3 bot.py diff --git a/test_aggregators.py b/test_aggregators.py new file mode 100644 index 0000000..41032c0 --- /dev/null +++ b/test_aggregators.py @@ -0,0 +1,27 @@ + +from pipecat.processors.aggregators.llm_context import ( + LLMUserContextAggregator, + LLMAssistantContextAggregator, +) +import inspect + +print("=== LLMUserContextAggregator ===") +sig = inspect.signature(LLMUserContextAggregator.__init__) +for n, p in sig.parameters.items(): + if n == 'self': continue + default = "REQUIRED" if p.default is inspect.Parameter.empty else str(p.default)[:60] + print(f" {n}: {default}") + +print() +print("=== LLMAssistantContextAggregator ===") +sig2 = inspect.signature(LLMAssistantContextAggregator.__init__) +for n, p in sig2.parameters.items(): + if n == 'self': continue + default = "REQUIRED" if p.default is inspect.Parameter.empty else str(p.default)[:60] + print(f" {n}: {default}") + +# Check if they need a context object +print() +print("=== LLMUserContextAggregator base classes ===") +for cls in LLMUserContextAggregator.__mro__: + print(f" {cls.__name__}") diff --git a/test_all_imports.py b/test_all_imports.py new file mode 100644 index 0000000..c819106 --- /dev/null +++ b/test_all_imports.py @@ -0,0 +1,35 @@ + +# Test all imports needed for the bot +from pipecat.frames.frames import OutputTransportMessageFrame +print("✅ OutputTransportMessageFrame") + +from pipecat.pipeline.pipeline import Pipeline +print("✅ Pipeline") + +from pipecat.pipeline.runner import PipelineRunner +print("✅ PipelineRunner") + +from pipecat.pipeline.task import PipelineTask +print("✅ PipelineTask") + +from pipecat.processors.aggregators.llm_context import OpenAILLMContext +print("✅ OpenAILLMContext") + +from pipecat.services.openai.llm import OpenAILLMService +print("✅ OpenAILLMService") + +from pipecat.transports.websocket.fastapi import ( + FastAPIWebsocketParams, + FastAPIWebsocketTransport, +) +print("✅ FastAPIWebsocketTransport") + +# Test create_context_aggregator +import inspect +if hasattr(OpenAILLMService, 'create_context_aggregator'): + print("✅ create_context_aggregator exists") +else: + print("❌ create_context_aggregator NOT found - checking parent...") + for cls in OpenAILLMService.__mro__: + if hasattr(cls, 'create_context_aggregator') and 'create_context_aggregator' in cls.__dict__: + print(f" Found in {cls.__name__}") diff --git a/test_api.py b/test_api.py new file mode 100644 index 0000000..953f49b --- /dev/null +++ b/test_api.py @@ -0,0 +1,30 @@ + +import pipecat.processors.aggregators.llm_context as ctx_mod +import inspect + +# Check OpenAIContextAggregatorPair +print("=== OpenAIContextAggregatorPair ===") +cls = ctx_mod.OpenAIContextAggregatorPair +sig = inspect.signature(cls.__init__) +print(f"__init__ params: {list(sig.parameters.keys())}") + +# Check OpenAILLMContextAggregatorPair +print() +print("=== OpenAILLMContextAggregatorPair ===") +cls2 = ctx_mod.OpenAILLMContextAggregatorPair +sig2 = inspect.signature(cls2.__init__) +print(f"__init__ params: {list(sig2.parameters.keys())}") + +# Check methods +print() +print("=== OpenAIContextAggregatorPair methods ===") +for name in dir(ctx_mod.OpenAIContextAggregatorPair): + if not name.startswith('_'): + print(f" {name}") + +# Check LLMUserContextAggregator +print() +print("=== LLMUserContextAggregator __init__ ===") +cls3 = ctx_mod.LLMUserContextAggregator +sig3 = inspect.signature(cls3.__init__) +print(f"params: {list(sig3.parameters.keys())}") diff --git a/test_complet.py b/test_complet.py new file mode 100644 index 0000000..e049631 --- /dev/null +++ b/test_complet.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""Test complet JARVIS v3 Pipecat : démarre le bot, teste WebSocket.""" +import subprocess, os, json, time, sys, asyncio, signal + +# 1. Kill existing bots +print("🧹 Nettoyage...") +subprocess.run(["pkill", "-f", "bot.py"], capture_output=True) +time.sleep(0.5) + +# 2. Get Hermes key +print("🔑 Récupération clé API...") +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=20, + env={"PATH": "/usr/local/bin:/usr/bin:/bin"} +) +hermes_key = r.stdout.strip() +if not hermes_key: + print(f"❌ Erreur Infisical: {r.stderr}") + sys.exit(1) +print(f"✅ Clé: {hermes_key[:12]}...") + +# 3. Start bot +print("🚀 Démarrage du bot...") +os.chdir("/home/openclaw/workspace/jarvis-pipecat") +bot_env = os.environ.copy() +bot_env["HERMES_API_KEY"] = hermes_key +bot_env["PYTHONUNBUFFERED"] = "1" + +bot_proc = subprocess.Popen( + ["./venv/bin/python", "bot.py"], + env=bot_env, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True, bufsize=1 +) + +# 4. Wait for server +print("⏳ Attente démarrage...") +import select, socket +start = time.time() +ready = False +while time.time() - start < 15: + # Check port + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) + s.connect(("localhost", 7081)) + s.close() + ready = True + break + except: + pass + + # Check for crash + if bot_proc.poll() is not None: + out = bot_proc.stdout.read() if bot_proc.stdout else "" + print(f"❌ Bot crashed!") + print(f"Output: {out[-500:]}") + sys.exit(1) + + time.sleep(0.3) + +if not ready: + print("❌ Timeout: serveur pas prêt") + bot_proc.kill() + sys.exit(1) + +print("✅ Port 7081 ouvert!") + +# Allow extra startup time for pipeline init +time.sleep(2) + +# 5. Test WS +print("🔌 Test WebSocket...") +try: + import websockets +except ImportError: + subprocess.run(["./venv/bin/pip", "install", "websockets"], check=True) + import websockets + +async def test_ws(): + uri = "ws://localhost:7081/ws" + async with websockets.connect(uri, open_timeout=10) as ws: + # Welcome message + msg = await asyncio.wait_for(ws.recv(), timeout=10) + try: + data = json.loads(msg) + print(f"👋 Welcome: {data.get('message', data.get('content', msg))[:120]}") + except: + print(f"👋 Welcome (raw): {msg[:120]}") + + # Send question + await ws.send("Bonjour JARVIS, quelle heure est-il actuellement?") + print("📤 Question envoyée") + + # Wait for response + for i in range(3): + try: + resp = await asyncio.wait_for(ws.recv(), timeout=30) + try: + data = json.loads(resp) + print(f"🤖 Réponse [{i+1}]: {json.dumps(data, ensure_ascii=False)[:300]}") + if data.get("content"): + print("\n✅✅✅ SUCCÈS! JARVIS v3 répond via Pipecat! ✅✅✅") + return True + except: + print(f"🤖 Raw [{i+1}]: {resp[:200]}") + except asyncio.TimeoutError: + print(f"⏰ Timeout - pas de réponse après 30s (message {i+1})") + break + return False + +success = asyncio.run(test_ws()) + +# 6. Report +print(f"\n{'='*50}") +print(f"🌐 URL: http://openclaw1.dev.home:7081/") +print(f"🔌 WS: ws://openclaw1.dev.home:7081/ws") +print(f"📋 Bot PID: {bot_proc.pid}") +if success: + print("✅ TEST RÉUSSI!") +else: + print("⚠️ Test partiel - vérifie le navigateur") + +# Kill bot (clean exit) +bot_proc.send_signal(signal.SIGTERM) +try: + bot_proc.wait(timeout=5) +except: + bot_proc.kill() +print("🛑 Bot arrêté") diff --git a/test_ctx.py b/test_ctx.py new file mode 100644 index 0000000..b3f0909 --- /dev/null +++ b/test_ctx.py @@ -0,0 +1,12 @@ + +import pipecat.processors.aggregators.llm_context as ctx_mod +print("Module imported OK") +print("Public symbols:", [x for x in dir(ctx_mod) if not x.startswith('_')]) + +# Try to access classes +for name in ['OpenAILLMContext', 'OpenAILLMContextAggregatorPair', 'LLMContext']: + if hasattr(ctx_mod, name): + cls = getattr(ctx_mod, name) + print(f' {name}: {cls}') + else: + print(f' {name}: NOT FOUND') diff --git a/test_ctx_method.py b/test_ctx_method.py new file mode 100644 index 0000000..aaa6360 --- /dev/null +++ b/test_ctx_method.py @@ -0,0 +1,23 @@ + +from pipecat.services.openai.llm import OpenAILLMService +import inspect + +# Get the create_context_aggregator method +method = getattr(OpenAILLMService, 'create_context_aggregator', None) +if method: + print("Method found!") + print(f"Type: {type(method)}") + sig = inspect.signature(method) + print(f"Signature: {sig}") + # Try to see what params it takes + for name, param in sig.parameters.items(): + print(f" {name}: {param.annotation if param.annotation is not inspect.Parameter.empty else 'Any'} = {param.default if param.default is not inspect.Parameter.empty else 'REQUIRED'}") +else: + print("Method NOT found!") + + # Check if it's created by a metaclass or __init_subclass__ + for cls in OpenAILLMService.__mro__: + if hasattr(cls, 'create_context_aggregator'): + print(f" Found in {cls.__name__}") + m = getattr(cls, 'create_context_aggregator') + print(f" Type: {type(m)}") diff --git a/test_dict_access.py b/test_dict_access.py new file mode 100644 index 0000000..f3a70da --- /dev/null +++ b/test_dict_access.py @@ -0,0 +1,17 @@ +import pipecat.processors.aggregators.llm_context as m + +# Access directly from __dict__ +user_cls = m.__dict__.get('LLMUserContextAggregator') +asst_cls = m.__dict__.get('LLMAssistantContextAggregator') + +print(f"LLMUserContextAggregator: {user_cls}") +print(f"LLMAssistantContextAggregator: {asst_cls}") + +if user_cls: + import inspect + sig = inspect.signature(user_cls.__init__) + print(f"User params: {list(sig.parameters.keys())}") + +if asst_cls: + sig = inspect.signature(asst_cls.__init__) + print(f"Asst params: {list(sig.parameters.keys())}") diff --git a/test_frames.py b/test_frames.py new file mode 100644 index 0000000..227df66 --- /dev/null +++ b/test_frames.py @@ -0,0 +1,13 @@ +import pipecat.frames.frames as fm + +# Try module attribute access +try: + cls = fm.LLMMessagesFrame + print(f"LLMMessagesFrame via module: {cls}") +except AttributeError as e: + print(f"ERROR via module: {e}") + +# Try __dict__ access +for key in sorted(fm.__dict__.keys()): + if 'LLM' in key and 'Frame' in key: + print(f" fm.{key}: {type(fm.__dict__[key]).__name__}") diff --git a/test_getattr.py b/test_getattr.py new file mode 100644 index 0000000..66bc628 --- /dev/null +++ b/test_getattr.py @@ -0,0 +1,8 @@ +import pipecat.processors.aggregators.llm_context as m +try: + Ctx = m.OpenAILLMContext + print(f"OpenAILLMContext: {Ctx}") +except Exception as e: + print(f"ERROR: {type(e).__name__}: {e}") + import traceback + traceback.print_exc() diff --git a/test_import.py b/test_import.py new file mode 100644 index 0000000..031311b --- /dev/null +++ b/test_import.py @@ -0,0 +1,6 @@ +import traceback +try: + from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext + print("SUCCESS:", OpenAILLMContext) +except Exception as e: + traceback.print_exc() diff --git a/test_imports.py b/test_imports.py new file mode 100644 index 0000000..953c37a --- /dev/null +++ b/test_imports.py @@ -0,0 +1,19 @@ + +import traceback, sys +tests = [ + ("pipecat.pipeline.pipeline", "Pipeline"), + ("pipecat.pipeline.runner", "PipelineRunner"), + ("pipecat.pipeline.task", "PipelineTask"), + ("pipecat.processors.aggregators.llm_context", "OpenAILLMContext"), + ("pipecat.services.openai.llm", "OpenAILLMService"), + ("pipecat.transports.websocket.fastapi", "FastAPIWebsocketTransport"), + ("pipecat.transports.websocket.fastapi", "FastAPIWebsocketParams"), + ("pipecat.frames.frames", "OutputTransportMessageFrame"), +] +for mod, cls in tests: + try: + m = __import__(mod, fromlist=[cls]) + getattr(m, cls) + print(f" ✅ {mod}.{cls}") + except Exception as e: + print(f" ❌ {mod}.{cls}: {e}") diff --git a/test_lazy.py b/test_lazy.py new file mode 100644 index 0000000..06c3c17 --- /dev/null +++ b/test_lazy.py @@ -0,0 +1,15 @@ + +import sys +import pipecat.processors.aggregators.llm_context as m + +# Try to trigger __getattr__ if it's lazy +print("Module:", m) +print("Module file:", getattr(m, '__file__', 'none')) + +# Check if __getattr__ is defined +print("Has __getattr__:", hasattr(m, '__getattr__')) + +# Try to access via module __dict__ +for key in sorted(m.__dict__.keys()): + if not key.startswith('_'): + print(f" m.{key}: {type(m.__dict__[key]).__name__}") diff --git a/test_llm.py b/test_llm.py new file mode 100644 index 0000000..7c0b2a5 --- /dev/null +++ b/test_llm.py @@ -0,0 +1,19 @@ + +from pipecat.services.openai.llm import OpenAILLMService +import inspect + +# Check if create_context_aggregator exists +print("Has create_context_aggregator:", hasattr(OpenAILLMService, "create_context_aggregator")) + +# Check all methods +methods = [m for m in dir(OpenAILLMService) if not m.startswith('_')] +print("Methods:", methods) + +# Check the class signature +sig = inspect.signature(OpenAILLMService.__init__) +print() +print("__init__ params:") +for name, param in sig.parameters.items(): + if name not in ('self', 'kwargs'): + default = "REQUIRED" if param.default is inspect.Parameter.empty else str(param.default)[:50] + print(f" {name}: {default}") diff --git a/test_llm_ctx.py b/test_llm_ctx.py new file mode 100644 index 0000000..58cda39 --- /dev/null +++ b/test_llm_ctx.py @@ -0,0 +1,11 @@ + +# Try importing LLMContextAggregatorPair +from pipecat.processors.aggregators.llm_context import LLMContextAggregatorPair +print("LLMContextAggregatorPair imported OK!") +import inspect +sig = inspect.signature(LLMContextAggregatorPair.__init__) +print("__init__ params:", list(sig.parameters.keys())) + +# Check if it has user() and assistant() methods +print() +print("Methods:", [m for m in dir(LLMContextAggregatorPair) if not m.startswith('_')]) diff --git a/test_m_attr.py b/test_m_attr.py new file mode 100644 index 0000000..f666588 --- /dev/null +++ b/test_m_attr.py @@ -0,0 +1,24 @@ + +import pipecat.processors.aggregators.llm_context as m + +# Access via module attribute +try: + UserCtx = m.LLMUserContextAggregator + print(f"LLMUserContextAggregator accessed: {UserCtx}") +except AttributeError as e: + print(f"ERROR accessing LLMUserContextAggregator: {e}") + +try: + AsstCtx = m.LLMAssistantContextAggregator + print(f"LLMAssistantContextAggregator accessed: {AsstCtx}") +except AttributeError as e: + print(f"ERROR accessing LLMAssistantContextAggregator: {e}") + +# Try the module's __getattr__ +print() +print("Testing __getattr__...") +try: + x = m.OpenAILLMContext + print(f"OpenAILLMContext: {x}") +except Exception as e: + print(f"OpenAILLMContext ERROR: {type(e).__name__}: {e}") diff --git a/test_openai_ctx.py b/test_openai_ctx.py new file mode 100644 index 0000000..ff71330 --- /dev/null +++ b/test_openai_ctx.py @@ -0,0 +1,15 @@ + +# Test 1: Access via module getattr +import pipecat.processors.aggregators.llm_context as m +try: + Ctx = m.OpenAILLMContext + print(f"✅ OpenAILLMContext accessed: {Ctx}") +except Exception as e: + print(f"❌ OpenAILLMContext: {e}") + +# Test 2: from import +try: + from pipecat.processors.aggregators.llm_context import OpenAILLMContext + print("✅ from-import works!") +except ImportError as e: + print(f"❌ from-import: {e}") diff --git a/ws_test.py b/ws_test.py new file mode 100644 index 0000000..8cfe149 --- /dev/null +++ b/ws_test.py @@ -0,0 +1,34 @@ + +import asyncio +import websockets +import json +import sys + +async def test(): + print("🔌 Connexion WebSocket à ws://localhost:7081/ws...") + try: + async with websockets.connect('ws://localhost:7081/ws', open_timeout=10) as ws: + print("✅ Connecté!") + + # Receive welcome message + try: + msg = await asyncio.wait_for(ws.recv(), timeout=15) + print(f"📩 Message de bienvenue: {msg[:300]}") + except asyncio.TimeoutError: + print("⚠️ Pas de message de bienvenue (normal si pas d'event handler)") + + # Send a question + print("📤 Envoi: 'Quelle heure est-il ?'") + await ws.send("Quelle heure est-il ?") + + # Get response + try: + response = await asyncio.wait_for(ws.recv(), timeout=60) + print(f"📩 Réponse: {response[:500]}") + except asyncio.TimeoutError: + print("❌ Timeout: pas de réponse du LLM en 60s") + + except Exception as e: + print(f"❌ Erreur: {e}") + +asyncio.run(test()) diff --git a/ws_test2.py b/ws_test2.py new file mode 100644 index 0000000..efd156d --- /dev/null +++ b/ws_test2.py @@ -0,0 +1,50 @@ + +import asyncio +import websockets +import json +import time + +async def test(): + t0 = time.time() + print("🔌 Connexion WebSocket à ws://localhost:7081/ws...") + try: + async with websockets.connect('ws://localhost:7081/ws', open_timeout=10) as ws: + t_connect = time.time() - t0 + print(f"✅ Connecté en {t_connect:.1f}s!") + + # Receive welcome message + try: + msg = await asyncio.wait_for(ws.recv(), timeout=15) + print(f"📩 Bienvenue: {msg[:200]}") + except asyncio.TimeoutError: + print("⚠️ Pas de message de bienvenue") + + # Send a question + t1 = time.time() + print("📤 Envoi: 'Quelle heure est-il ?'") + await ws.send("Quelle heure est-il ?") + + # Get response + try: + response = await asyncio.wait_for(ws.recv(), timeout=90) + t_resp = time.time() - t1 + print(f"📩 Réponse ({t_resp:.1f}s): {response[:500]}") + except asyncio.TimeoutError: + print("❌ Timeout: pas de réponse du LLM en 90s") + + # Send second question + t2 = time.time() + print("📤 Envoi: 'Quel temps fait-il aujourd''hui ?'") + await ws.send("Quel temps fait-il aujourd'hui ?") + + try: + response2 = await asyncio.wait_for(ws.recv(), timeout=90) + t_resp2 = time.time() - t2 + print(f"📩 Réponse 2 ({t_resp2:.1f}s): {response2[:500]}") + except asyncio.TimeoutError: + print("❌ Timeout: pas de réponse 2 en 90s") + + except Exception as e: + print(f"❌ Erreur: {type(e).__name__}: {e}") + +asyncio.run(test())