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

@@ -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 уже доримил — озвучиваем весь ответ одним куском с цельной интонацией
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