Полностью локальный голосовой ассистент на 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>
90 lines
3.9 KiB
Python
90 lines
3.9 KiB
Python
"""
|
||
Агент на базе 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
|