feat: route voice requests through OpenClaw agent session

- Remove local Conversation history (now managed by gateway)
- Use x-openclaw-session-key for persistent agent sessions
- Agent now has full context: SOUL.md, MEMORY.md, tools
- Add VOICE_SESSION_KEY env var (default: agent:main:voice:home)
- Backward compatible: conv parameter kept for compatibility
This commit is contained in:
Cosmo
2026-04-13 20:12:01 +00:00
parent a836bbb848
commit d9d892664a
2 changed files with 16 additions and 50 deletions

View File

@@ -34,3 +34,5 @@ FOLLOWUP_TIMEOUT=8
# Логирование # Логирование
LOG_FILE=errors.log LOG_FILE=errors.log
VOICE_SESSION_KEY=agent:main:voice:home

View File

@@ -2,21 +2,14 @@ import json
import os import os
import re import re
import requests import requests
from datetime import date
from .config import AGENTS, log from .config import AGENTS, log
from .text import clean_for_speech, find_sentence_end from .text import clean_for_speech, find_sentence_end
from .tts import speak, play_error_sound from .tts import speak, play_error_sound
SYSTEM_PROMPT = ( # Ключ голосовой сессии — Cosmo работает как полноценный агент
"Отвечай кратко, 1-2 предложения, без markdown, без эмодзи. " VOICE_SESSION_KEY = os.getenv("VOICE_SESSION_KEY", "agent:main:voice:home")
"Ответ будет озвучен голосом, поэтому: "
"числа пиши прописью (двадцать три, а не 23), "
"единицы измерения пиши полностью (километров в час, а не км/ч), "
"не используй спецсимволы (+, -, /, %, °) — заменяй словами (плюс, минус, из, процентов, градусов). "
"Температуру пиши так: 'плюс девять градусов', а не '+9°C'."
)
MAX_HISTORY = int(os.getenv("MAX_HISTORY", "20"))
# "stream" — режем по предложениям (быстро, но рваная интонация) # "stream" — режем по предложениям (быстро, но рваная интонация)
# "full" — собираем весь ответ, потом TTS (естественно, но пауза перед началом) # "full" — собираем весь ответ, потом TTS (естественно, но пауза перед началом)
TTS_MODE = os.getenv("TTS_MODE", "full") 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: def is_reset_command(text: str) -> bool:
return bool(RESET_PATTERNS.search(text)) return bool(RESET_PATTERNS.search(text))
def ask_agent_stream(text: str, conv: "Conversation | None" = None, agent_id: str = "cosmo") -> str: def ask_agent_stream(text: str, conv=None, agent_id: str = "cosmo") -> str:
if conv is None: """
conv = Conversation(agent_id) Отправляет запрос к OpenClaw gateway как полноценный агент.
История хранится на стороне gateway (session_key).
conv.add_user(text) conv параметр сохранён для обратной совместимости, не используется.
"""
cfg = AGENTS.get(agent_id, AGENTS["cosmo"]) cfg = AGENTS.get(agent_id, AGENTS["cosmo"])
gateway_url = cfg["gateway_url"] gateway_url = cfg["gateway_url"]
session = cfg["session"] session = cfg["session"]
agent = cfg["agent"] agent = cfg["agent"]
session_key = cfg.get("session_key", VOICE_SESSION_KEY)
try: try:
resp = session.post( resp = session.post(
f"{gateway_url}/v1/chat/completions", f"{gateway_url}/v1/chat/completions",
headers={ headers={
"x-openclaw-model": cfg["voice_model"], "x-ocplatform-model": cfg["voice_model"],
"x-openclaw-session-key": cfg["session_key"], "x-openclaw-session-key": session_key,
}, },
json={ json={
"model": agent, "model": agent,
"stream": True, "stream": True,
"messages": conv.messages, "messages": [{"role": "user", "content": text}],
"max_tokens": 150, "max_tokens": 150,
}, },
stream=True, 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) result = clean_for_speech(full_text)
if TTS_MODE == "full": if TTS_MODE == "full":
# LLM уже доримил — озвучиваем весь ответ одним куском с цельной интонацией
if result.strip(): if result.strip():
print(f"🔊 Говорю: {result}") print(f"🔊 Говорю: {result}")
speak(result, agent_id) speak(result, agent_id)
else: else:
# остаток буфера в stream-режиме
if buffer.strip(): if buffer.strip():
tail = clean_for_speech(buffer) tail = clean_for_speech(buffer)
if tail: if tail:
speak(tail, agent_id) speak(tail, agent_id)
conv.add_assistant(full_text)
return result return result