diff --git a/.env.example b/.env.example index ef66883..a96251d 100644 --- a/.env.example +++ b/.env.example @@ -34,3 +34,5 @@ FOLLOWUP_TIMEOUT=8 # Логирование LOG_FILE=errors.log + +VOICE_SESSION_KEY=agent:main:voice:home diff --git a/satellite/llm.py b/satellite/llm.py index 73bdb84..91955e6 100644 --- a/satellite/llm.py +++ b/satellite/llm.py @@ -2,21 +2,14 @@ import json import os import re import requests -from datetime import date from .config import AGENTS, log from .text import clean_for_speech, find_sentence_end from .tts import speak, play_error_sound -SYSTEM_PROMPT = ( - "Отвечай кратко, 1-2 предложения, без markdown, без эмодзи. " - "Ответ будет озвучен голосом, поэтому: " - "числа пиши прописью (двадцать три, а не 23), " - "единицы измерения пиши полностью (километров в час, а не км/ч), " - "не используй спецсимволы (+, -, /, %, °) — заменяй словами (плюс, минус, из, процентов, градусов). " - "Температуру пиши так: 'плюс девять градусов', а не '+9°C'." -) -MAX_HISTORY = int(os.getenv("MAX_HISTORY", "20")) +# Ключ голосовой сессии — Cosmo работает как полноценный агент +VOICE_SESSION_KEY = os.getenv("VOICE_SESSION_KEY", "agent:main:voice:home") + # "stream" — режем по предложениям (быстро, но рваная интонация) # "full" — собираем весь ответ, потом TTS (естественно, но пауза перед началом) TTS_MODE = os.getenv("TTS_MODE", "full") @@ -29,60 +22,34 @@ RESET_PATTERNS = re.compile( ) -class Conversation: - """Хранит историю сообщений — одна сессия на день""" - - def __init__(self, agent_id: str = "cosmo"): - self.agent_id = agent_id - self.created_date = date.today() - self.messages = [{"role": "system", "content": SYSTEM_PROMPT}] - - def is_expired(self) -> bool: - return date.today() != self.created_date - - def reset(self): - self.created_date = date.today() - self.messages = [{"role": "system", "content": SYSTEM_PROMPT}] - - def add_user(self, text: str): - self.messages.append({"role": "user", "content": text}) - self._trim() - - def add_assistant(self, text: str): - self.messages.append({"role": "assistant", "content": text}) - self._trim() - - def _trim(self): - if len(self.messages) > MAX_HISTORY + 1: - self.messages = [self.messages[0]] + self.messages[-(MAX_HISTORY):] - - def is_reset_command(text: str) -> bool: return bool(RESET_PATTERNS.search(text)) -def ask_agent_stream(text: str, conv: "Conversation | None" = None, agent_id: str = "cosmo") -> str: - if conv is None: - conv = Conversation(agent_id) - - conv.add_user(text) - +def ask_agent_stream(text: str, conv=None, agent_id: str = "cosmo") -> str: + """ + Отправляет запрос к OpenClaw gateway как полноценный агент. + История хранится на стороне gateway (session_key). + conv параметр сохранён для обратной совместимости, не используется. + """ cfg = AGENTS.get(agent_id, AGENTS["cosmo"]) gateway_url = cfg["gateway_url"] session = cfg["session"] agent = cfg["agent"] + session_key = cfg.get("session_key", VOICE_SESSION_KEY) + try: resp = session.post( f"{gateway_url}/v1/chat/completions", headers={ - "x-openclaw-model": cfg["voice_model"], - "x-openclaw-session-key": cfg["session_key"], + "x-ocplatform-model": cfg["voice_model"], + "x-openclaw-session-key": session_key, }, json={ "model": agent, "stream": True, - "messages": conv.messages, + "messages": [{"role": "user", "content": text}], "max_tokens": 150, }, stream=True, @@ -151,16 +118,13 @@ def ask_agent_stream(text: str, conv: "Conversation | None" = None, agent_id: st result = clean_for_speech(full_text) if TTS_MODE == "full": - # LLM уже доcтримил — озвучиваем весь ответ одним куском с цельной интонацией if result.strip(): print(f"🔊 Говорю: {result}") speak(result, agent_id) else: - # остаток буфера в stream-режиме if buffer.strip(): tail = clean_for_speech(buffer) if tail: speak(tail, agent_id) - conv.add_assistant(full_text) return result