Metadata-Version: 2.4
Name: local-agent-chat
Version: 0.1.1
Summary: Local chat UI with persistent history and a sandboxed ReAct agent
Author: dev-sergeev
License-Expression: MIT
Project-URL: Homepage, https://github.com/dev-sergeev/local-agent-chat
Project-URL: Documentation, https://github.com/dev-sergeev/local-agent-chat#readme
Project-URL: Issues, https://github.com/dev-sergeev/local-agent-chat/issues
Project-URL: Source, https://github.com/dev-sergeev/local-agent-chat
Keywords: chat,llm,chainlit,react-agent,localchat
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: <3.14,>=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: chainlit<2.12,>=2.11
Requires-Dist: langchain<2,>=1.2
Requires-Dist: langchain-openai<2,>=1.6
Requires-Dist: langchain-gigachat<0.6,>=0.5.1
Requires-Dist: aiosqlite<1,>=0.20
Requires-Dist: SQLAlchemy<3,>=2.0
Requires-Dist: PyYAML<7,>=6
Requires-Dist: python-dotenv<2,>=1
Provides-Extra: test
Requires-Dist: pytest<10,>=8; extra == "test"
Requires-Dist: pytest-asyncio<2,>=1; extra == "test"
Requires-Dist: python-socketio[client]<6,>=5.11; extra == "test"
Requires-Dist: requests<3,>=2; extra == "test"
Requires-Dist: ruff<1,>=0.13; extra == "test"
Dynamic: license-file

# LocalChat

Однопользовательский Chainlit UI и обычный ReAct-агент на LangChain: модель вызывает инструменты чтения загруженных файлов и формирует ответ. История диалога, сводка контекста и файлы сохраняются локально.

Агент предоставляет ровно четыре инструмента: `ls`, `read_file`, `glob`, `grep`. Их виртуальный `/` — файлы текущего чата. Выход в файловую систему хоста, другие чаты, запись файлов, shell и выполнение кода недоступны. Субагентов, планировщика, файловой выгрузки контекста и общей долговременной памяти нет.

Редактирование исторического запроса удаляет его прежний ответ и последующее продолжение. Контекст вместе со сводкой и файлами восстанавливается перед изменяемым запросом, после чего агент отвечает заново. Ошибка или Stop восстанавливают прежнее состояние. Одинаковый текст правки сохраняет весь диалог.

## Установка и первый запуск

Требуются Python **3.12–3.13**, Linux (или WSL2) и модель с tool calling: OpenAI-compatible API или GigaChat. Пакет содержит UI, переводы и все ресурсы приложения; клонировать репозиторий для запуска не нужно. Windows без WSL не поддерживается из-за требований файловой песочницы. macOS пока не входит в проверяемые платформы.

Установите приложение из PyPI через [pipx](https://pipx.pypa.io/stable/installation/):

```bash
pipx install --python python3.12 local-agent-chat
localchat init
localchat run
```

`init` запросит модель, адрес API и ключ или готовый access token. Запускайте команды в папке, где хотите хранить настройки и историю. Для OpenAI-compatible endpoint имя модели имеет вид `openai:<model-id>`, например `openai:deepseek/deepseek-v4-flash-0731` для OpenRouter. Для GigaChat используйте `gigachat:GigaChat-2` и base URL его API. Ключ вводится без отображения в терминале; секрет сессии создаётся автоматически. `run` покажет адрес UI, по умолчанию **http://127.0.0.1:8765/**. Остановка — `Ctrl+C`; `--open-browser` открывает браузер автоматически.

Можно также установить собранный wheel: `pipx install --python python3.12 ./dist/local_agent_chat-0.1.1-py3-none-any.whl`. Альтернатива pipx — `python3.12 -m venv .venv`, активация окружения и `python -m pip install local-agent-chat`.

Для локального API без ключа:

```bash
localchat init --no-input --model openai:your-model \
  --base-url http://127.0.0.1:8000/v1 --no-api-key
```

Для автоматической настройки с ключом передайте его в `OPENAI_API_KEY` и используйте те же аргументы без `--no-api-key`. `--api-key-env MY_PROVIDER_KEY` выбирает другое имя переменной; `--no-streaming` отключает потоковый ответ. Полная справка: `localchat init --help`, `localchat run --help`.

Для GigaChat можно выполнить интерактивную настройку:

```bash
localchat init --model gigachat:GigaChat-2 --base-url https://api.giga.chat/v1
localchat run
```

Введите **готовый access token**, а не OAuth-ключ `credentials`. Для `--no-input` заранее задайте `GIGACHAT_ACCESS_TOKEN`; `--api-key-env COMPANY_TOKEN` выбирает другое имя переменной. OAuth и автоматическое обновление токена не выполняются. Если токен истёк, обновите его в `.env` (или в окружении, если оно переопределяет файл) и перезапустите приложение. `--no-api-key` доступен только для локального OpenAI-compatible API. Проверка TLS остаётся включённой; для собственного центра сертификации SDK поддерживает `GIGACHAT_CA_BUNDLE_FILE` с абсолютным путём к сертификатам.

## Настройки, данные и обновление

В текущей рабочей папке создаются:

- `.env` — параметры запуска и токены, права нового файла `0600`.
- `models.yaml` — профили моделей: идентификатор, base URL, streaming и имя переменной токена; секреты здесь не хранятся.
- `.local-agent-chat/` — SQLite, история, память агента и вложения. Технические логи выводятся в консоль.

`APP_DATA_DIR=.local-agent-chat` и `MODEL_PROFILES_FILE=models.yaml` сохраняются относительными путями. `--config-dir PATH` или `LOCALCHAT_CONFIG_DIR` выбирает другой каталог конфигурации; его каталог данных по умолчанию также находится рядом с `.env`. `--data-dir PATH` или `APP_DATA_DIR` переопределяет каталог данных. Относительные пути разрешаются относительно каталога конфигурации. Приоритет: аргументы запуска, затем переменные окружения, затем `.env`, затем значения по умолчанию. `XDG_CONFIG_HOME` и `XDG_DATA_HOME` больше не выбирают расположение данных LocalChat.

Повторный `init` не перезаписывает настройки. Для смены модели или ключа отредактируйте конфигурацию и перезапустите приложение. Для нескольких моделей добавьте профили в `models.yaml` с уникальными `id`, например:

```yaml
models:
  - id: openrouter
    label: DeepSeek
    model: openai:deepseek/deepseek-v4-flash-0731
    base_url: https://openrouter.ai/api/v1
    api_key_env: OPENAI_API_KEY
  - id: giga
    label: GigaChat 2
    model: gigachat:GigaChat-2
    base_url: https://api.giga.chat/v1
    api_key_env: GIGACHAT_ACCESS_TOKEN
```

Токены обоих профилей задаются в `.env`. Модели используются для ответа, инструментов, суммаризации и названий чатов.

Обновление: `pipx upgrade local-agent-chat` или `python -m pip install --upgrade local-agent-chat`. Перед обновлением или переносом папки остановите процесс. Папку с `.env`, `models.yaml` и `.local-agent-chat/` можно скопировать целиком и запустить в новом месте; относительные пути продолжат работать. Абсолютные пути нужно изменить отдельно. Два процесса не могут одновременно открыть один каталог данных, даже на разных портах.

Если запуск сообщает об отсутствующей конфигурации, выполните `localchat init`. По умолчанию сервер начинает с порта `8765`; если он занят, пробует следующие до `65535`. `localchat run --port 9000` меняет начальный порт. Фактический порт показывается при запуске. Ошибки модели отображаются в UI и терминале; используйте endpoint с поддержкой tool calling и подходящим бюджетом контекста.

## JupyterHub и VS Code proxy

В новом `.env` записывается `APP_ROOT_PATH=auto`. При наличии `JUPYTERHUB_SERVICE_PREFIX` сервер автоматически использует `<prefix>/vscode/proxy/<фактический порт>`. Вне JupyterHub префикс пустой, приложение открывается напрямую.

Также поддерживается точная запись:

```dotenv
APP_ROOT_PATH="${JUPYTERHUB_SERVICE_PREFIX%/}/vscode/proxy/$APP_PORT"
```

Этот шаблон раскрывается самим CLI после выбора свободного порта; без JupyterHub он даёт `/vscode/proxy/<порт>`. Остальные значения `.env` читаются буквально, shell-команды не выполняются. Явный `--root-path /custom/path` или статический путь в `.env` используется без замены порта. `--root-path ''` либо `APP_ROOT_PATH=''` отключает префикс даже в JupyterHub.

Открывайте UI по публичному адресу прокси. Прямой порт рассчитан на локальное использование; внешний доступ требует аутентификации прокси, см. [Security](https://github.com/dev-sergeev/local-agent-chat/blob/main/SECURITY.md).

Прежние системные каталоги автоматически не переносятся. Для старой установки используйте `localchat run --config-dir ~/.config/localchat`, сохранив прежний `APP_DATA_DIR` в её `.env`. Для перехода на локальную папку остановите сервис, скопируйте `.env`, `models.yaml` и содержимое прежнего каталога данных в `.local-agent-chat/`, затем задайте относительный `APP_DATA_DIR`. Пустой старый `APP_ROOT_PATH` сохраняет прямой запуск; замените его на `auto` для автоматического прокси.

`./scripts/run.sh` делегирует запуск Python CLI из текущей рабочей папки и больше не выполняет `.env` как shell-скрипт. Для совместимости можно задать `ENV_FILE=/path/to/.env`; для другого расположения используйте `--config-dir`. Запуск из исходников и проверки описаны в [Contributing](https://github.com/dev-sergeev/local-agent-chat/blob/main/CONTRIBUTING.md), выпуск — в [инструкции публикации](https://github.com/dev-sergeev/local-agent-chat/blob/main/docs/publishing.md).

## Контекст и лимиты

Параметры находятся в `AgentConfig` и могут задаваться через `.env`:

| Переменная | По умолчанию | Назначение |
|---|---:|---|
| `AGENT_CONTEXT_TOKENS` | 16000 | Бюджет окна с резервом под ответ и инструменты |
| `AGENT_SUMMARY_TRIGGER_TOKENS` | 10000 | Порог суммаризации сообщений |
| `AGENT_KEEP_TOKENS` | 3000 | Бюджет последних сообщений, сохраняемых дословно |
| `AGENT_SUMMARY_TOKENS` | 1000 | Максимальный ответ модели суммаризации |
| `AGENT_MAX_OUTPUT_TOKENS` | 2000 | Максимальный ответ основной модели |
| `AGENT_MAX_MODEL_CALLS` | 12 | Максимум обращений основной модели за один запрос |

Укажите бюджет не больше реального окна выбранной модели. Счётчик использует консервативную оценку для текста и tool calls, поскольку у совместимых endpoint не всегда доступен точный токенизатор. Конфигурация проверяет запас для ответа, сводки и схем инструментов.

Перед обращением к основной модели штатный `SummarizationMiddleware` заменяет старую часть контекста сводкой и сохраняет недавние сообщения, не разрывая tool-call/result пары. Предыдущая сводка включается в следующую. Длинный импортированный контекст суммируется порциями: старые сообщения не отбрасываются из-за стандартного лимита summarizer в 4000 токенов. Сводка не показывается как ответ пользователю и не заменяет полную историю UI.

В `summary_options` профиля можно передать параметры модели только для суммаризации. В примере OpenRouter reasoning выключен через `extra_body.reasoning.enabled: false`: иначе небольшой лимит ответа может целиком уйти на reasoning и оставить пустую сводку. См. [параметры reasoning OpenRouter](https://openrouter.ai/docs/guides/best-practices/reasoning-tokens). Для другого провайдера задайте поддерживаемые им параметры. Лимиты ответа и streaming управляются агентом.

Пустая или обрезанная сводка, пустой ответ, ошибка модели и превышение бюджета последнего запроса завершают операцию с откатом. Слишком большой запрос следует разделить на части. Вывод одного файлового инструмента ограничен примерно 6000 символами; `read_file` поддерживает постраничное чтение, `grep` ищет буквальную строку.

## Надёжность и хранение

OpenAI SDK и адаптер GigaChat повторяют отдельные transient HTTP-запросы; полный ход агента и инструменты не переигрываются. Настройки: `LLM_MAX_RETRIES=3`, `LLM_REQUEST_TIMEOUT_SECONDS=60`, `LLM_STREAM_CHUNK_TIMEOUT_SECONDS=120`, `LLM_STREAM_RETRIES=1`, `LLM_AUXILIARY_TIMEOUT_SECONDS=30`. Дополнительная попытка streaming допустима только до первого полученного chunk. Для суммаризации действует та же политика без вложенного `with_retry`.

В `APP_DATA_DIR` находятся:

- `chainlit.sqlite3`: сообщения, шаги инструментов, вложения, названия; постоянный `stepOrder` сохраняет порядок даже при одинаковых временных метках.
- `runtime-history.sqlite3`: только актуальные завершённые запросы/ответы и ссылки на предшествующее состояние.
- `checkpoints.sqlite3`: выбор модели, текущий контекст и снимки контекста перед запросами. Граф ReAct между вызовами не хранит внутреннее состояние.
- `sandboxes/`: файлы и снимки файлов; `blobs/`: сохранённые вложения Chainlit.

Прежние чаты импортируются из актуальных запросов и ответов. Старые вызовы инструментов и внутренние графы не выполняются. Редактирование старого запроса восстанавливает предшествующую часть видимой истории. Старые режимы доступа исчезают; все чаты получают только чтение своей песочницы. Прежний индекс межчатового поиска удаляется, сохранённый `memory/MEMORY.md` больше не читается агентом. Внутренние старые LangGraph-таблицы могут оставаться в существующей базе как неиспользуемые данные.

UI принимает до 20 файлов по 100 MiB; объём активных файлов чата ограничен 1 GiB. Коллизии имён получают суффикс. Удаление чата удаляет его активные данные и снимки нового контекста.

## Где менять код

| Задача | Файл |
|---|---|
| Сборка агента и цикл выполнения | `local_agent_chat/agent_execution.py` |
| Суммаризация и бюджет контекста | `local_agent_chat/agent_context.py` |
| Хранение контекста и откат | `local_agent_chat/agent_memory.py` |
| Четыре инструмента чтения | `local_agent_chat/sandbox_tools.py` |
| System prompt и заголовки | `local_agent_chat/prompts.py` |
| Настройки и provider retry | `local_agent_chat/settings.py`, `local_agent_chat/llm_retry.py`, `local_agent_chat/providers.py` |
| Координация истории и UI | `local_agent_chat/runtime.py`, `local_agent_chat/chainlit_data.py`, `local_agent_chat/app.py` |

[Архитектура](https://github.com/dev-sergeev/local-agent-chat/blob/main/docs/architecture.md), [термины](https://github.com/dev-sergeev/local-agent-chat/blob/main/CONTEXT.md), [ограничения доступа](https://github.com/dev-sergeev/local-agent-chat/blob/main/SECURITY.md), [разработка](https://github.com/dev-sergeev/local-agent-chat/blob/main/CONTRIBUTING.md), [результаты проверок](https://github.com/dev-sergeev/local-agent-chat/blob/main/docs/react-validation.md).
