navi-1
===============

Модульная агентная система с REST API и WebSocket. FastAPI бэкенд, Ollama LLM, Vue 3 + Pinia клиент.

Лицензия: [MIT](LICENSE)

## Запуск

```bash
# 1. Клонировать
 git clone <repo> && cd navi-1

# 2. Зависимости
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

# 3. База данных (PostgreSQL 15+ с pgvector)
# См. "База данных" ниже — создай БД, пользователя и включи расширения.

# 4. Конфигурация
cp .env.example .env  # отредактируй DATABASE_URL, OLLAMA_HOST и др.

# 5. Запуск
uvicorn navi.main:app --reload --reload-dir navi --port 8000
```

Клиент: `http://localhost:8000`  
Swagger UI: `http://localhost:8000/docs`  
Debug panel: `http://localhost:8000/debug`

## Быстрый старт (REST)

```bash
# 1. Создать сессию с профилем "secretary"
curl -X POST http://localhost:8000/sessions \
  -H "Content-Type: application/json" \
  -d '{"profile_id": "secretary"}'

# 2. Отправить сообщение (замени <session_id>)
curl -X POST http://localhost:8000/sessions/<session_id>/messages \
  -H "Content-Type: application/json" \
  -d '{"content": "Найди последние новости про Python 3.13"}'
```

## WebSocket

```
ws://localhost:8000/ws/sessions/<session_id>
```

Клиент отправляет:
```json
{"type": "message", "content": "...", "images": [...], "files": [...]}
```

Сервер стримит события в порядке:
```
stream_start
plan_ready          (если profile.planning_enabled — план до начала выполнения)
thinking_delta*     (reasoning chunks)
thinking_end
turn_thinking       (reasoning из tool-calling турна, целиком)
tool_started        (начало вызова инструмента — pending card в UI)
tool_call           (инструмент завершён — результат)
stream_delta*       (финальный текстовый ответ)
stream_end
context_compressed  (опционально — после сжатия контекста)
```

Остановить генерацию: `POST /sessions/<session_id>/stop`

Полная спецификация: [`docs/websocket.md`](docs/websocket.md) и [`docs/api.md`](docs/api.md)

## Navi Code — терминальный режим

Локальный вариант Navi без авторизации, управляемый из терминала:

```bash
# 1. Скопировать конфиг для терминального режима
cp .env.navi_code.example .env

# 2. Запустить сервер (см. раздел "Запуск" выше)

# 3. В другом терминале
navi-code
# или one-shot
navi-code "перепиши функцию на async/await"
```

- Профиль по умолчанию: `navi_code`.
- Без авторизации: `NAVI_AUTH_ENABLED=false`.
- CLI сохраняет `session_id` в `~/.navi_code/state.json`.
- **Navi работает с проектом в текущей директории:** клиент автоматически передаёт серверу рабочую папку, из которой запущен `navi-code`. Относительные пути в `filesystem`, `terminal` и `code_exec` разрешаются относительно неё.

### Установка для запуска из любой директории

После `pip install -e .` консольная команда `navi-code` появляется в PATH внутри активированного venv. Если хочешь вызывать `navi-code` из любой папки без активации venv, есть два варианта:

**Вариант 1 — symlink на wrapper (рекомендуется для постоянного использования):**
```bash
ln -s /path/to/navi-1/bin/navi-code ~/.local/bin/navi-code
# или любая другая директория из $PATH
```

**Вариант 2 — editable install даёт entry point:**
```bash
.venv/bin/pip install -e .
# затем можно symlink и сам entry point:
ln -s /path/to/navi-1/.venv/bin/navi-code ~/.local/bin/navi-code
```

Wrapper в `bin/navi-code` сам находит `.venv` рядом с репозиторием и запускает клиент, передавая текущую директорию серверу.

Подробнее: [`docs/navi_code.md`](docs/navi_code.md) и [`docs/navi_code_cli.md`](docs/navi_code_cli.md).

## Структура

```
navi/
├── main.py            # FastAPI app, роутеры, startup hooks
├── config.py          # настройки через .env (pydantic-settings)
├── exceptions.py      # доменные исключения
├── llm/               # LLM бэкенды: ollama.py, openai_backend.py
├── tools/             # встроенные инструменты (~20 шт.)
├── profiles/          # профили агентов: secretary, server_admin, developer, tool_developer, discuss, modeler_3d, navi_code
├── core/              # Agent, registry, session, compressor, events
├── memory/            # долгосрочная память (PostgreSQL + pgvector)
├── workers/           # post-turn workers (CompressionWorker, MemoryWorker)
└── api/               # роуты и WebSocket handler

tools/                 # пользовательские инструменты (auto-discovered)
├── enabled.json       # список инструментов, активных во всех профилях
├── _template.py       # шаблон формата
├── get_current_datetime.py
├── gmail.py
└── weather.py

clients/               # клиенты
└── terminal/          # CLI-клиент Navi Code (navi-code)

manuals/               # markdown-мануалы для tool_manual
docs/                  # архитектурная документация
persona.txt            # глобальная личность и инструкции агента
persona_navi_code.txt  # персона для терминального режима
```

## Профили

| ID | Назначение | Температура | Планирование |
|----|-----------|-------------|--------------|
| `secretary` | Исследования, написание текстов, повседневные задачи | 0.65 | ✓ |
| `server_admin` | Администрирование серверов, мониторинг, инфраструктура | 0.3 | ✓ |
| `developer` | Разработка, анализ кода, архитектура | 0.45 | ✓ |
| `tool_developer` | Написание, тестирование и отладка пользовательских инструментов | 0.35 | ✓ |
| `discuss` | Свободное обсуждение, мозговой штурм, лёгкие беседы | 0.85 | — |
| `modeler_3d` | 3D-моделирование для 3D-печати (OpenSCAD → STL) | 0.35 | ✓ |
| `navi_code` | Локальный терминальный кодинг-ассистент | 0.35 | ✓ |

`tool_developer` — единственный профиль с `reload_tools`, `delete_tool` и `test_tool`.

## Расширение

**Пользовательский инструмент** — переключись в профиль `developer` и попроси Нави создать инструмент. Она напишет файл в `tools/`, протестирует через `test_tool` и перезагрузит через `reload_tools`. Либо вручную создай `tools/my_tool.py`:

```python
name = "my_tool"
description = "Когда и зачем использовать."
parameters = {"type": "object", "properties": {...}, "required": [...]}

async def execute(params: dict) -> str:
    return "результат"
```

Добавить во все профили: занести имя в `tools/enabled.json`.

**Новый профиль** — создай `navi/profiles/my_profile.py`, добавь в `ALL_PROFILES` в `__init__.py`.

**Новый LLM бэкенд** — реализуй `LLMBackend` из `navi/llm/base.py`, зарегистрируй в `build_default_registries()` в `navi/core/registry.py`.

## База данных

Нави требует **PostgreSQL 15+** с расширениями `pgvector` и (опционально) `pg_trgm`.

### Установка PostgreSQL и расширений

**Ubuntu / Debian:**
```bash
sudo apt install postgresql-15 postgresql-15-pgvector
sudo -u postgres psql -c "CREATE DATABASE navidb;"
sudo -u postgres psql -c "CREATE USER navi WITH PASSWORD 'navipass';"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE navidb TO navi;"
sudo -u postgres psql navidb -c "CREATE EXTENSION IF NOT EXISTS vector;"
sudo -u postgres psql navidb -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
```

**Arch Linux:**
```bash
sudo pacman -S postgresql pgvector
# Аналогичные команды для создания БД и пользователя
```

**Docker:**
```bash
docker run -d \
  --name navi-postgres \
  -e POSTGRES_USER=navi \
  -e POSTGRES_PASSWORD=navipass \
  -e POSTGRES_DB=navidb \
  -p 5432:5432 \
  ankane/pgvector:latest
# pgvector и pg_trgm уже включены в образ ankane/pgvector
```

### Переменные окружения

```dotenv
DATABASE_URL=postgresql://navi:navipass@localhost:5432/navidb
```

Если `pg_trgm` не установлен, приложение запустится, но GIN-индексы для текстового поиска не будут созданы (semantic search через pgvector продолжит работать).

## Конфигурация (.env)

Ключевые переменные (полный список: [`docs/config.md`](docs/config.md)):

```dotenv
# LLM
OLLAMA_HOST=http://localhost:11434
OLLAMA_API_KEY=
OLLAMA_DEFAULT_MODEL=gemma4:31b-cloud
OLLAMA_NUM_CTX=65536
OLLAMA_THINK=true
OLLAMA_REQUEST_TIMEOUT=30

# OpenAI (опционально, если используешь OpenAI backend)
OPENAI_API_KEY=
OPENAI_MODEL=gpt-4
# OPENAI_BASE_URL=https://api.openai.com/v1

# База данных (обязательно — PostgreSQL)
DATABASE_URL=postgresql://user:pass@host:5432/db

# Персона
NAVI_PERSONA_FILE=persona.txt
```

Для прямого доступа к Ollama Cloud:

```dotenv
OLLAMA_HOST=https://ollama.com
OLLAMA_API_KEY=your_api_key
```

Также укажи cloud-модель в `navi/profiles/*/config.json`, потому что профили могут переопределять `OLLAMA_DEFAULT_MODEL`.
