Initial commit: Cosmo voice assistant

Полностью локальный голосовой ассистент на Python.

Стек:
- Wake word: openWakeWord (onnxruntime)
- STT: RealtimeSTT + faster-whisper + Silero VAD (CUDA)
- LLM-агент: smolagents ToolCallingAgent + Ollama qwen2.5:7b
- TTS: Silero V4 (torch.hub) + sounddevice
- Shell: Git Bash (Windows) / bash (macOS)

Поддерживает Windows и macOS. Агент с памятью и tool calling —
находит программы самостоятельно, запоминает пути, выполняет
произвольные shell-команды.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
d.klimov
2026-04-10 15:58:12 +03:00
commit 6010816f1d
23 changed files with 1969 additions and 0 deletions

89
cosmo/agent.py Normal file
View File

@@ -0,0 +1,89 @@
"""
Агент на базе smolagents + Ollama.
Использует ToolCallingAgent — LLM вызывает инструменты через JSON tool calling.
Продолжает работу пока задача не решена (до max_steps).
"""
import os
import platform
from loguru import logger
from smolagents import ToolCallingAgent, LiteLLMModel
from cosmo.memory import Memory
# Выбираем инструменты под текущую платформу
if platform.system() == "Darwin" or os.environ.get("COSMO_PLATFORM") == "mac":
from cosmo.tools_mac import ALL_TOOLS, set_memory
_PLATFORM_NOTE = "macOS. Используй bash, 'open -a AppName' для запуска приложений, mdfind для поиска файлов."
else:
from cosmo.tools import ALL_TOOLS, set_memory
_PLATFORM_NOTE = "Windows. Используй Git Bash, 'start' для запуска приложений."
SYSTEM_PROMPT = f"""Ты — Cosmo, умный голосовой ассистент. Платформа: {_PLATFORM_NOTE}
Правила:
1. Используй инструменты для выполнения задач — не выдумывай результаты
2. Если первая попытка не сработала — пробуй другой подход, не сдавайся
3. Перед поиском программы — проверь память (memory_get), может путь уже известен
4. Если нашёл путь к программе — сохрани в память (memory_set) чтобы не искать повторно
5. Отвечай коротко на русском языке — пользователь слушает голосом, не читает
Факты из памяти о пользователе и системе:
{memory_facts}
"""
class Agent:
def __init__(self, config: dict, memory: Memory):
self.memory = memory
self._cfg = config["ollama"]
# Передаём память в инструменты
set_memory(memory)
model_id = f"ollama/{self._cfg['model']}"
logger.info(f"Инициализирую smolagents с моделью {model_id}")
self._model = LiteLLMModel(
model_id=model_id,
api_base=self._cfg["base_url"],
temperature=self._cfg.get("temperature", 0.2),
max_tokens=self._cfg.get("max_tokens", 1024),
)
self._agent = ToolCallingAgent(
tools=ALL_TOOLS,
model=self._model,
max_steps=self._cfg.get("max_agent_steps", 10),
verbosity_level=1,
)
logger.info("Агент готов")
def run(self, user_input: str) -> str:
"""
Обработать команду пользователя.
Возвращает финальный текст ответа для TTS.
"""
logger.info(f"Агент: '{user_input}'")
# Сохраняем в историю
self.memory.add_message("user", user_input)
# Формируем промпт с текущей памятью
system = SYSTEM_PROMPT.format(memory_facts=self.memory.facts_as_text())
# smolagents принимает задачу и опциональный системный промпт
try:
result = self._agent.run(
user_input,
additional_args={"system_prompt_override": system},
)
response = str(result).strip() if result else "Готово"
except Exception as e:
logger.error(f"Ошибка агента: {e}")
response = "Произошла ошибка при выполнении команды"
self.memory.add_message("assistant", response)
logger.info(f"Агент ответил: '{response}'")
return response