Metadata-Version: 2.4
Name: local-agent-chat
Version: 0.1.0
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: aiosqlite<1,>=0.20
Requires-Dist: SQLAlchemy<3,>=2.0
Requires-Dist: PyYAML<7,>=6
Requires-Dist: python-dotenv<2,>=1
Requires-Dist: platformdirs<5,>=4
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) и OpenAI-compatible модель с tool calling. Пакет содержит 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 и ключ. Для OpenAI-compatible endpoint имя модели имеет вид `openai:<model-id>`, например `openai:deepseek/deepseek-v4-flash-0731` для OpenRouter. Ключ вводится без отображения в терминале; секрет сессии создаётся автоматически. `run` покажет адрес UI, по умолчанию **http://127.0.0.1:8765/**. Остановка — `Ctrl+C`; `--open-browser` открывает браузер автоматически.

До первого релиза можно установить собранный wheel: `pipx install --python python3.12 ./dist/local_agent_chat-0.1.0-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`.

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

На Linux по умолчанию используются:

- `~/.config/localchat/.env` — параметры запуска и ключи, права нового файла `0600`.
- `~/.config/localchat/models.yaml` — профили моделей; ключи здесь не хранятся.
- `~/.local/share/localchat/` — SQLite, история и вложения.

Учитываются `XDG_CONFIG_HOME` и `XDG_DATA_HOME`. `--config-dir PATH` или `LOCALCHAT_CONFIG_DIR` выбирает другой каталог конфигурации, `--data-dir PATH` — каталог данных. Относительные пути из `.env` разрешаются относительно каталога конфигурации. Приоритет: аргументы запуска, затем переменные окружения, затем `.env`, затем значения по умолчанию. `.env` читается как данные: shell-команды и подстановки `${...}` не выполняются.

Повторный `init` не перезаписывает настройки. Для смены модели или ключа отредактируйте конфигурацию и перезапустите приложение. Обновление: `pipx upgrade local-agent-chat`; перед обновлением остановите процесс и сделайте резервную копию каталогов конфигурации и данных. Установка и переустановка пакета их не затрагивают. Два процесса не могут одновременно открыть один каталог данных.

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

## JupyterHub и существующая установка

Для прокси задайте **полный публичный префикс**:

```bash
localchat run --port 8765 \
  --root-path "${JUPYTERHUB_SERVICE_PREFIX%/}/vscode/proxy/8765"
```

Здесь переменную раскрывает shell перед вызовом команды. В `.env` нужно записать уже готовый путь, например `/user/alice/vscode/proxy/8765`. Неверный префикс может привести к белому экрану из-за неправильных адресов JavaScript. Проверяйте UI по публичному адресу прокси. Прямой порт рассчитан на локальное использование; внешний доступ требует аутентификации прокси, см. [Security](https://github.com/dev-sergeev/local-agent-chat/blob/main/SECURITY.md).

Старые `.env`, `models.yaml` и каталог данных можно использовать через `localchat run --config-dir /path/to/checkout`. Сценарий `./scripts/run.sh` сохранён для существующих checkout и по-прежнему обрабатывает `.env` как shell-файл, включая прежние подстановки переменных. В новых установках используйте команды `localchat`. Запуск из исходников и проверки описаны в [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` ищет буквальную строку.

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

Provider SDK повторяет отдельные 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` |
| Координация истории и 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).
