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, без эмодзи." MAX_HISTORY = int(os.getenv("MAX_HISTORY", "20")) RESET_PATTERNS = re.compile( r"(начни|начать|создай|открой|давай).{0,10}(новую|новый|чистую|чистый).{0,10}(сессию|сессия|диалог|разговор|чат)" r"|" r"(сбрось|очисти|обнови).{0,10}(сессию|диалог|разговор|чат|историю|контекст)", re.IGNORECASE, ) 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) cfg = AGENTS.get(agent_id, AGENTS["cosmo"]) gateway_url = cfg["gateway_url"] session = cfg["session"] agent = cfg["agent"] try: resp = session.post( f"{gateway_url}/v1/chat/completions", headers={"x-openclaw-model": cfg["voice_model"]}, json={ "model": agent, "stream": True, "messages": conv.messages, "max_tokens": 150, }, stream=True, timeout=60, ) resp.raise_for_status() except requests.ConnectionError: log.exception("Gateway недоступен") msg = "Не могу связаться с сервером, попробуй ещё раз." print(f"⚠️ {msg}") play_error_sound() speak(msg, agent_id) return msg except requests.Timeout: log.exception("Gateway таймаут") msg = "Сервер не ответил вовремя, попробуй ещё раз." print(f"⚠️ {msg}") play_error_sound() speak(msg, agent_id) return msg except requests.HTTPError: log.exception(f"Gateway HTTP ошибка {resp.status_code}") msg = "Ошибка сервера, попробуй ещё раз." print(f"⚠️ Gateway {resp.status_code}: {resp.text}") play_error_sound() speak(msg, agent_id) return msg full_text = "" buffer = "" try: for line in resp.iter_lines(): if not line or line == b"data: [DONE]": continue if line.startswith(b"data: "): try: chunk = json.loads(line[6:]) delta = chunk["choices"][0]["delta"].get("content", "") if not delta: continue full_text += delta buffer += delta last_punct = find_sentence_end(buffer, min_len=60) if last_punct > -1: sentence = clean_for_speech(buffer[:last_punct + 1]) if sentence.strip(): print(f"🔊 Говорю: {sentence}") speak(sentence, agent_id) buffer = buffer[last_punct + 1:].lstrip() except (json.JSONDecodeError, KeyError, IndexError): continue except Exception as e: log.exception("Ошибка при чтении стрима") print(f"⚠️ Стрим прервался: {e}") # Остаток if buffer.strip(): sentence = clean_for_speech(buffer) if sentence: speak(sentence, agent_id) if not full_text: msg = "Не получил ответ, попробуй ещё раз." speak(msg, agent_id) return msg result = clean_for_speech(full_text) conv.add_assistant(full_text) return result