From 721bf3d6d87fd9c5f371005341c0ca77d05edb68ae274fbb124761b251599737 Mon Sep 17 00:00:00 2001 From: Komisar Date: Fri, 20 Feb 2026 02:24:44 +0300 Subject: [PATCH] Itration 0 from deepseek --- .gitignore | 179 ++----------------------- README.md | 22 ++- client/client.py | 54 ++++++++ docker-compose.yml | 111 +++++++++++++++ info.txt | 32 +++++ services/orchestrator/app.py | 26 ++++ services/orchestrator/dockerfile | 10 ++ services/orchestrator/requirements.txt | 5 + 8 files changed, 268 insertions(+), 171 deletions(-) create mode 100644 client/client.py create mode 100644 docker-compose.yml create mode 100644 info.txt create mode 100644 services/orchestrator/app.py create mode 100644 services/orchestrator/dockerfile create mode 100644 services/orchestrator/requirements.txt diff --git a/.gitignore b/.gitignore index 3996e81..9d0ddd0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,176 +1,21 @@ -# ---> Python -# Byte-compiled / optimized / DLL files +# Python __pycache__/ *.py[cod] -*$py.class - -# C extensions *.so - -# Distribution / packaging .Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -#uv.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/latest/usage/project/#working-with-version-control -.pdm.toml -.pdm-python -.pdm-build/ - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv env/ venv/ -ENV/ -env.bak/ -venv.bak/ +*.egg-info/ +dist/ +build/ -# Spyder project settings -.spyderproject -.spyproject +# Docker +*.log +.env -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -# Ruff stuff: -.ruff_cache/ - -# PyPI configuration file -.pypirc +# Аудио файлы (если будут временные) +*.wav +*.mp3 +# Эмбеддинги (но embeddings.json мы пока храним в репозитории для простоты) +# services/speaker-id/embeddings.json diff --git a/README.md b/README.md index bda6035..388cc79 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,21 @@ -# ai1 +# Джарвис (ai1) -DS AI +Локальный голосовой ассистент с идентификацией по голосу и поддержкой русского языка. -test +## Архитектура -ещё один тест +Микросервисы на Docker: +- `vad` – определение голосовой активности (Silero VAD) +- `asr` – распознавание речи и диаризация (WhisperX) +- `speaker-id` – идентификация говорящего (SpeechBrain ECAPA-TDNN) +- `orchestrator` – маршрутизация и инструменты +- `tts` – синтез речи (CosyVoice / Piper) +- `redis` – шина событий + +## Запуск + +1. Убедитесь, что Docker Desktop с WSL2 работает и NVIDIA Container Toolkit настроен. +2. Склонируйте репозиторий. +3. Выполните: + ```bash + docker-compose up --build diff --git a/client/client.py b/client/client.py new file mode 100644 index 0000000..bcbdad4 --- /dev/null +++ b/client/client.py @@ -0,0 +1,54 @@ +import asyncio +import sounddevice as sd +import numpy as np +import websockets +import json +import requests + +# Настройки +VAD_URL = "ws://localhost:8001/audio-stream" +TTS_URL = "http://localhost:8005/synthesize" +SAMPLE_RATE = 16000 +BLOCK_SIZE = 1600 # 100 мс при 16 кГц + +async def audio_stream(): + """Захватывает аудио с микрофона и отправляет в VAD сервис.""" + async with websockets.connect(VAD_URL) as websocket: + print("Подключен к VAD сервису. Говорите...") + + def callback(indata, frames, time, status): + """Отправляет аудио-блок в WebSocket.""" + audio_bytes = indata.tobytes() + asyncio.run_coroutine_threadsafe( + websocket.send(audio_bytes), loop + ) + + stream = sd.InputStream( + samplerate=SAMPLE_RATE, + channels=1, + dtype='int16', + blocksize=BLOCK_SIZE, + callback=callback + ) + + with stream: + while True: + await asyncio.sleep(0.1) + +def play_audio(audio_data): + """Воспроизводит аудио (полученное от TTS).""" + audio_array = np.frombuffer(audio_data, dtype=np.int16) + sd.play(audio_array, samplerate=SAMPLE_RATE) + sd.wait() + +async def main(): + # Запускаем поток аудио в фоне + asyncio.create_task(audio_stream()) + + # Основной цикл не нужен, так как всё работает через WebSocket + await asyncio.Future() # бесконечное ожидание + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(main()) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2c16393 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,111 @@ +version: '3.8' + +services: + redis: + image: redis:7-alpine + container_name: jarvis-redis + restart: unless-stopped + ports: + - "6379:6379" + networks: + - jarvis-net + + vad: + build: ./services/vad + container_name: jarvis-vad + restart: unless-stopped + ports: + - "8001:8000" # API для приема аудио + environment: + - REDIS_HOST=redis + - REDIS_PORT=6379 + depends_on: + - redis + networks: + - jarvis-net + # VAD работает на CPU, deploy не нужен + + asr: + build: ./services/asr + container_name: jarvis-asr + restart: unless-stopped + ports: + - "8002:8000" + environment: + - REDIS_HOST=redis + - REDIS_PORT=6379 + depends_on: + - redis + networks: + - jarvis-net + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: 1 + capabilities: [gpu] + + speaker-id: + build: ./services/speaker-id + container_name: jarvis-speaker-id + restart: unless-stopped + ports: + - "8003:8000" + volumes: + - ./services/speaker-id/embeddings.json:/app/embeddings.json + environment: + - REDIS_HOST=redis + - REDIS_PORT=6379 + depends_on: + - redis + networks: + - jarvis-net + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: 1 + capabilities: [gpu] # может работать и на CPU, но GPU ускорит + + orchestrator: + build: ./services/orchestrator + container_name: jarvis-orchestrator + restart: unless-stopped + ports: + - "8004:8000" + environment: + - REDIS_HOST=redis + - REDIS_PORT=6379 + depends_on: + - redis + networks: + - jarvis-net + # CPU, GPU не нужен + + tts: + build: ./services/tts + container_name: jarvis-tts + restart: unless-stopped + ports: + - "8005:8000" + environment: + - REDIS_HOST=redis + - REDIS_PORT=6379 + depends_on: + - redis + networks: + - jarvis-net + # Для iGPU потребуется дополнительная конфигурация, пока оставим на CPU + # deploy: + # resources: + # reservations: + # devices: + # - driver: intel + # count: 1 + # capabilities: [gpu] + +networks: + jarvis-net: + driver: bridge diff --git a/info.txt b/info.txt new file mode 100644 index 0000000..7b38d88 --- /dev/null +++ b/info.txt @@ -0,0 +1,32 @@ +ai1/ +├── .gitignore +├── README.md +├── docker-compose.yml +├── services/ +│ ├── vad/ +│ │ ├── Dockerfile +│ │ ├── requirements.txt +│ │ └── app.py +│ ├── asr/ +│ │ ├── Dockerfile +│ │ ├── requirements.txt +│ │ └── app.py +│ ├── speaker-id/ +│ │ ├── Dockerfile +│ │ ├── requirements.txt +│ │ ├── app.py +│ │ └── embeddings.json +│ ├── orchestrator/ +│ │ ├── Dockerfile +│ │ ├── requirements.txt +│ │ ├── app.py +│ │ └── tools/ +│ │ └── time_tool.py +│ └── tts/ +│ ├── Dockerfile +│ ├── requirements.txt +│ └── app.py +├── client/ +│ └── client.py +└── redis/ + └── (файлы Redis не нужны, образ стандартный) diff --git a/services/orchestrator/app.py b/services/orchestrator/app.py new file mode 100644 index 0000000..4dbe11e --- /dev/null +++ b/services/orchestrator/app.py @@ -0,0 +1,26 @@ +from fastapi import FastAPI +import redis +import os +import json + +app = FastAPI() + +redis_host = os.getenv("REDIS_HOST", "localhost") +redis_port = int(os.getenv("REDIS_PORT", 6379)) +r = redis.Redis(host=redis_host, port=redis_port, decode_responses=True) + +@app.get("/health") +async def health(): + return {"status": "ok", "service": "orchestrator"} + +@app.post("/process") +async def process_text(request: dict): + # Здесь будет логика обработки текста и вызова инструментов + text = request.get("text", "") + user = request.get("user", "guest") + print(f"Processing from {user}: {text}") + + # Простой ответ для теста + response = "Я получил ваше сообщение, но пока не умею отвечать." + + return {"response": response} diff --git a/services/orchestrator/dockerfile b/services/orchestrator/dockerfile new file mode 100644 index 0000000..bad8a0b --- /dev/null +++ b/services/orchestrator/dockerfile @@ -0,0 +1,10 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/services/orchestrator/requirements.txt b/services/orchestrator/requirements.txt new file mode 100644 index 0000000..69de3dc --- /dev/null +++ b/services/orchestrator/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.115.0 +uvicorn[standard]==0.30.1 +redis==5.0.4 +pydantic==2.8.2 +python-dotenv==1.0.1