Skip to content
Merged
10 changes: 9 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ ANTHROPIC_API_KEY=change_me
AUTO_BI_ANTHROPIC_MODEL=claude-sonnet-5
AUTO_BI_ANTHROPIC_MAX_TOKENS=16000

# Direct Mistral API (opt-in alternative: AUTO_BI_LLM_PROVIDER=mistral)
MISTRAL_API_KEY=change_me
# Optional AUTO_BI-prefixed alias:
# AUTO_BI_MISTRAL_API_KEY=change_me
AUTO_BI_MISTRAL_MODEL=mistral-large-latest
AUTO_BI_MISTRAL_URL=https://api.mistral.ai
AUTO_BI_MISTRAL_MAX_TOKENS=16000

# GraceKelly LLM service (local, opt-in alternative: AUTO_BI_LLM_PROVIDER=gracekelly)
AUTO_BI_GRACEKELLY_URL=http://127.0.0.1:8011
AUTO_BI_GRACEKELLY_MODEL=claude-sonnet-5
Expand Down Expand Up @@ -120,7 +128,7 @@ AUTO_BI_LLM_BUDGET_DAY_MAX_SECONDS=0
AUTO_BI_LLM_BUDGET_DAY_MAX_COST_USD=0
# cost price table, USD per 1000 tokens, "model:in/out,..." (example rates — set yours;
# used only when a *_MAX_COST_USD limit above is set)
AUTO_BI_LLM_BUDGET_PRICES=claude-opus-4-8:0.005/0.025,claude-sonnet-5:0.003/0.015,claude-sonnet-4-6:0.003/0.015,claude-haiku-4-5:0.001/0.005
AUTO_BI_LLM_BUDGET_PRICES=claude-opus-4-8:0.005/0.025,claude-sonnet-5:0.003/0.015,claude-sonnet-4-6:0.003/0.015,claude-haiku-4-5:0.001/0.005,mistral-large-latest:0.0005/0.0015
# Fail-closed remote bind (P0-3): required to serve on non-loopback with auth off
# and without DEMO_AUTO_ONLY. Prefer AUTH_ENABLED=true on the public internet.
AUTO_BI_ALLOW_INSECURE_REMOTE=false
Expand Down
19 changes: 16 additions & 3 deletions .github/workflows/eval-live-sentinel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,15 @@ jobs:
id: gate
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
AUTO_BI_MISTRAL_API_KEY: ${{ secrets.AUTO_BI_MISTRAL_API_KEY }}
AUTO_BI_GRACEKELLY_URL: ${{ secrets.AUTO_BI_GRACEKELLY_URL }}
run: |
if [ -n "${ANTHROPIC_API_KEY}" ] || [ -n "${AUTO_BI_GRACEKELLY_URL}" ]; then
if [ -n "${MISTRAL_API_KEY}" ] || [ -n "${AUTO_BI_MISTRAL_API_KEY}" ] || [ -n "${ANTHROPIC_API_KEY}" ] || [ -n "${AUTO_BI_GRACEKELLY_URL}" ]; then
echo "run=true" >> "$GITHUB_OUTPUT"
else
echo "run=false" >> "$GITHUB_OUTPUT"
echo "No ANTHROPIC_API_KEY / AUTO_BI_GRACEKELLY_URL secret — sentinel skipped (offline replay still gates quality)."
echo "No Mistral / Anthropic / GraceKelly credential secret — sentinel skipped (offline replay still gates quality)."
fi

- name: Install uv
Expand All @@ -84,11 +86,22 @@ jobs:
if: steps.gate.outputs.run == 'true'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
AUTO_BI_MISTRAL_API_KEY: ${{ secrets.AUTO_BI_MISTRAL_API_KEY }}
AUTO_BI_LLM_PROVIDER: ${{ secrets.AUTO_BI_LLM_PROVIDER }}
AUTO_BI_GRACEKELLY_URL: ${{ secrets.AUTO_BI_GRACEKELLY_URL }}
AUTO_BI_GRACEKELLY_MODEL: ${{ secrets.AUTO_BI_GRACEKELLY_MODEL }}
AUTO_BI_MISTRAL_MODEL: ${{ secrets.AUTO_BI_MISTRAL_MODEL }}
run: |
export AUTO_BI_LLM_PROVIDER="${AUTO_BI_LLM_PROVIDER:-anthropic}"
if [ -z "${AUTO_BI_LLM_PROVIDER}" ]; then
if [ -n "${MISTRAL_API_KEY}" ] || [ -n "${AUTO_BI_MISTRAL_API_KEY}" ]; then
export AUTO_BI_LLM_PROVIDER="mistral"
elif [ -n "${ANTHROPIC_API_KEY}" ]; then
export AUTO_BI_LLM_PROVIDER="anthropic"
else
export AUTO_BI_LLM_PROVIDER="gracekelly"
fi
fi
uv run auto_bi eval \
--suite golden \
--llm-mode live \
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@

## [Unreleased]

### Added

- **Direct Mistral provider** — `AUTO_BI_LLM_PROVIDER=mistral` routes through
Mistral Chat Completions with standard `MISTRAL_API_KEY` (or the
`AUTO_BI_MISTRAL_API_KEY` alias), the shared structured-repair and budget
hooks, token-usage logging, safe HTTP errors, and live-sentinel secret
routing. Default model: `mistral-large-latest`.

- **Local BYOK runbook** — `docs/LOCAL_BYOK.md`: первый локальный запуск после
clone со своим `ANTHROPIC_API_KEY` (ClickHouse + Superset через Compose,
`uv run auto_bi serve`, health/ready, остановка и troubleshooting).

## [0.5.0] - 2026-07-29

### Fixed
Expand Down
25 changes: 16 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

Агент «запрос → дашборд» поверх DM-слоя DWH. Принимает запрос **текстом, drag&drop-раскладкой полей витрин или авто-обзором витрины** (детерминированный курируемый дашборд без LLM), уточняет детали только при реальных расхождениях с данными, честно предупреждает о не предусмотренных витриной паттернах (engine-aware **Feasibility Advisor** — вплоть до «это запрос на новую витрину»), строит дашборд в выбранной BI и возвращает ссылку.

**Скоуп v1 (RU-рынок, release-gated в CI):** ClickHouse (DM) + Apache Superset (BI). **v2 experimental:** Greengage/Greenplum (offline advisor/golden в CI; live DWH — operator stand) + Yandex DataLens (unit compile offline; live contract Mac-only, не default release gate). Универсальность — в швах (IR, адаптеры), не в имплементации.
**LLM:** прямой Anthropic Messages API (по умолчанию — нужен только `ANTHROPIC_API_KEY`); локальный сервис GraceKelly — документированная опция (`AUTO_BI_LLM_PROVIDER=gracekelly`, см. [USER_GUIDE §6](docs/USER_GUIDE.md#6-конфигурация-переменные-окружения)).
**Скоуп v1 (RU-рынок, release-gated в CI):** ClickHouse (DM) + Apache Superset (BI). **v2 experimental:** Greengage/Greenplum (offline advisor/golden в CI; live DWH — operator stand) + Yandex DataLens (offline compile contracts и повторный live contract **15/15** на Mac; не default release gate). Универсальность — в швах (IR, адаптеры), не в имплементации.
**LLM:** прямой Anthropic Messages API по умолчанию; прямой Mistral Chat Completions (`AUTO_BI_LLM_PROVIDER=mistral`, `MISTRAL_API_KEY`) и локальный сервис GraceKelly — документированные опции (см. [USER_GUIDE §6](docs/USER_GUIDE.md#6-конфигурация-переменные-окружения)).

## Демо

**Живое демо: <https://juliome20-auto-bi-demo.hf.space>** — публичная песочница (Hugging Face Space, один контейнер ClickHouse + Superset + Auto_BI): выберите витрину, соберите авто-обзор и откройте готовый дашборд в Superset без логина. Работает детерминированный путь без LLM; полный текстовый цикл — на видео ниже. Данные синтетические, всё пересоздаётся при рестарте (холодный старт ~3 мин).
**Поддерживаемый путь без стенда:** офлайн golden path
(`uv run python scripts/demo_golden_path.py`) — детерминированный IR/SQL/advisor
без DWH, BI и LLM. Полный локальный запуск со своим API key —
[docs/LOCAL_BYOK.md](docs/LOCAL_BYOK.md).

![Auto_BI — полный цикл: текст → уточнение → спецификация + advisor → сборка → дашборд Superset](docs/screenshots/demo.gif)

Expand All @@ -23,9 +26,14 @@

## Статус

**Phase 0–4 + бэклог адекватности дашбордов (B1–B4) закрыты.** Работает end-to-end: текст/поля → spec → валидация → сборка дашборда. v1-стек (ClickHouse + Superset) и v2-стек (Greenplum/Greengage интроспекция + advisor; self-hosted DataLens-адаптер) live-проверены; web UI с двумя режимами ввода, итерациями, Feasibility Advisor, заявками владельцу DM и панелью наблюдаемости.
**Phase 0–4 + бэклог адекватности дашбордов (B1–B4) закрыты.** Работает end-to-end: текст/поля → spec → валидация → сборка дашборда. v1-стек (ClickHouse + Superset) live-проверен на v0.5.0; v2 (Greenplum/Greengage advisor/golden; DataLens) — offline evidence/contracts, а DataLens Mac-only live contract повторно прошёл **15/15** на текущем self-hosted stand 2026-07-29 (experimental / non-default / non-closure; фактический seeded workbook задавался через `AUTO_BI_DATALENS_WORKBOOK_ID`); web UI с двумя режимами ввода, итерациями, Feasibility Advisor, заявками владельцу DM и панелью наблюдаемости.

**Актуальное состояние и residual roadmap** — [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md). История фаз — [docs/PLAN.md](docs/PLAN.md). Полный env inventory (generated) — [docs/ENV_REFERENCE.md](docs/ENV_REFERENCE.md).
Все пять вынесенных external live validations завершены exact evidence:
DataLens 15/15, direct Mistral sentinel 3/3, protected-tag rejection,
intentional Trivy failure before promotion и process-restart reconciliation.
Активной audit work не осталось.

**Актуальное состояние и closure evidence** — [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md). История фаз — [docs/PLAN.md](docs/PLAN.md). Полный env inventory (generated) — [docs/ENV_REFERENCE.md](docs/ENV_REFERENCE.md).

## Чем отличается

Expand Down Expand Up @@ -54,17 +62,15 @@ flowchart LR
Установка, команды CLI, web UI, конфигурация — [docs/USER_GUIDE.md](docs/USER_GUIDE.md).
Подключение новой витрины DWH за ≤ 1 ч — [docs/ONBOARDING_DWH.md](docs/ONBOARDING_DWH.md).

Local-first — три ступени:
Local-first — два поддерживаемых пути:

1. **Офлайн golden path** — без DWH, BI, LLM и API-ключа:

```bash
uv run python scripts/demo_golden_path.py
```

2. **HF Space** — детерминированный auto-only, пользовательский ключ не нужен; текстовый режим там намеренно недоступен (см. «Демо» выше).

3. **Полный локальный путь.** Скопируйте `.env.example` в `.env` (`cp .env.example .env`; PowerShell: `Copy-Item .env.example .env`). Задайте свой `ANTHROPIC_API_KEY` **или** `AUTO_BI_LLM_PROVIDER=gracekelly` и `AUTO_BI_GRACEKELLY_URL`. Для DWH/BI — `AUTO_BI_CH_HOST`, `AUTO_BI_CH_PASSWORD`, `AUTO_BI_SUPERSET_URL`, `AUTO_BI_SUPERSET_PASSWORD` (полный inventory — [docs/ENV_REFERENCE.md](docs/ENV_REFERENCE.md)). `docker compose up -d` поднимает **только ClickHouse и Superset**, не Auto_BI; агент локально: `auto_bi serve` → http://127.0.0.1:8200.
2. **Полный локальный путь.** Пошаговый Anthropic-пример — [docs/LOCAL_BYOK.md](docs/LOCAL_BYOK.md). Скопируйте `.env.example` в `.env` (`cp .env.example .env`; PowerShell: `Copy-Item .env.example .env`). Задайте свой `ANTHROPIC_API_KEY`; либо `AUTO_BI_LLM_PROVIDER=mistral` + `MISTRAL_API_KEY`; либо `AUTO_BI_LLM_PROVIDER=gracekelly` + `AUTO_BI_GRACEKELLY_URL`. Для DWH/BI — `AUTO_BI_CH_HOST`, `AUTO_BI_CH_PASSWORD`, `AUTO_BI_SUPERSET_URL`, `AUTO_BI_SUPERSET_PASSWORD` (полный inventory — [docs/ENV_REFERENCE.md](docs/ENV_REFERENCE.md)). `docker compose up -d` поднимает **только ClickHouse и Superset**, не Auto_BI; агент локально: `auto_bi serve` → http://127.0.0.1:8200.

```bash
pip install autobi-agent # или pip install -e . из корня репозитория
Expand All @@ -90,6 +96,7 @@ uv run python scripts/demo_golden_path.py
| Файл | Что внутри |
|---|---|
| [docs/USER_GUIDE.md](docs/USER_GUIDE.md) | Руководство пользователя: установка, команды CLI, web UI, два режима ввода, advisor, наблюдаемость, конфигурация |
| [docs/LOCAL_BYOK.md](docs/LOCAL_BYOK.md) | Первый локальный запуск со своим Anthropic API key: clone, `.env`, Compose (CH+Superset), `uv run auto_bi serve`, health/ready |
| [docs/ONBOARDING_DWH.md](docs/ONBOARDING_DWH.md) | Подключение нового DWH за ≤ 1 ч: доступы, `.env`, интроспекция, обогащение, проверка (ClickHouse + Greenplum) |
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Архитектура: скоуп, IR-first, семантическая модель с физическим слоем, агент, Feasibility Advisor, адаптеры, LLM-слой, решения D1–D10, риски |
| [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) | Деплой в проде: workers=1, reverse-proxy/TLS, готовность, docker-compose, бэкап SQLite, ротация логов, чеклист секретов |
Expand Down
20 changes: 12 additions & 8 deletions auto_bi/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -726,11 +726,13 @@ def bi_healthcheck() -> AdapterHealth:
return probe_health(adapter_for, TargetBI.SUPERSET)

def llm_healthcheck() -> AdapterHealth:
if settings.llm_provider.strip().lower() != "gracekelly":
# Anthropic is a hosted API with no separate process to be "up/down" locally,
# and an actual completion call would cost tokens on every readiness probe —
# report configured-and-constructible (already proven by make_llm below).
return AdapterHealth(ok=True, message="anthropic: no live check (avoids token cost)")
provider = settings.llm_provider.strip().lower()
if provider != "gracekelly":
# Hosted providers (anthropic, mistral, …) have no separate process to be
# "up/down" locally, and an actual completion call would cost tokens on every
# readiness probe — report configured-and-constructible (already proven by
# make_llm below).
return AdapterHealth(ok=True, message=f"{provider}: no live check (avoids token cost)")
import httpx

try:
Expand Down Expand Up @@ -1010,9 +1012,11 @@ def _render(title: str, report: EvalReport) -> None:
store = Store(settings.store_path)
live_llm = make_llm(settings, store=store)
provider = settings.llm_provider.strip().lower()
model_id = (
settings.gracekelly_model if provider == "gracekelly" else settings.anthropic_model
)
model_id = {
"gracekelly": settings.gracekelly_model,
"anthropic": settings.anthropic_model,
"mistral": settings.mistral_model,
}.get(provider, provider)
provider_detail = (
f"{settings.gracekelly_url}, {model_id}" if provider == "gracekelly" else model_id
)
Expand Down
47 changes: 37 additions & 10 deletions auto_bi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from functools import lru_cache
from logging import Logger

from pydantic import Field
from pydantic import AliasChoices, Field
from pydantic_settings import BaseSettings, SettingsConfigDict


Expand Down Expand Up @@ -154,14 +154,13 @@ class Settings(BaseSettings):
),
)

# LLM provider seam (llm/factory.py): "anthropic" (default — direct Anthropic Messages
# API, works out of the box with just an API key) or "gracekelly" (local orchestration
# service, documented opt-in — ARCHITECTURE §3.6).
# LLM provider seam (llm/factory.py): direct Anthropic (default), direct Mistral,
# or GraceKelly (local orchestration service).
llm_provider: str = Field(
default="anthropic",
description=(
'LLM backend selector: "anthropic" for direct API or "gracekelly" for the local'
" orchestration service."
'LLM backend selector: "anthropic" or "mistral" for direct API access, or'
' "gracekelly" for the local orchestration service.'
),
)

Expand Down Expand Up @@ -202,6 +201,33 @@ class Settings(BaseSettings):
),
)

# Direct Mistral chat-completions API. The client also accepts the standard
# MISTRAL_API_KEY process env when this AUTO_BI-prefixed field is empty.
mistral_api_key: str = Field(
default="",
validation_alias=AliasChoices(
"mistral_api_key",
"AUTO_BI_MISTRAL_API_KEY",
"MISTRAL_API_KEY",
),
description=(
"Mistral API key for direct calls; empty lets the client fall back to"
" MISTRAL_API_KEY."
),
)
mistral_model: str = Field(
default="mistral-large-latest",
description="Mistral model id used when llm_provider is mistral.",
)
mistral_url: str = Field(
default="https://api.mistral.ai",
description="Mistral API base URL; the client appends /v1/chat/completions.",
)
mistral_max_tokens: int = Field(
default=16000,
description="Maximum output tokens requested from direct Mistral chat completions.",
)

# plan_sol step 2 / audit P0-2: DWH values (top-N) leave the process only on
# explicit opt-in. Default false — clean install never sends samples to an
# external LLM. Set true only for public/internal classes after classification.
Expand Down Expand Up @@ -380,7 +406,7 @@ class Settings(BaseSettings):
),
)
# cost price table (USD per 1000 tokens), "model:in/out,...". List prices as of
# 2026-07-18; override for your provider contract. Used only when a *_max_cost_usd
# 2026-07-29; override for your provider contract. Used only when a *_max_cost_usd
# limit is set — an unlisted model prices at 0, so add yours before relying on a cap.
# Sonnet 5 carries a lower introductory rate through 2026-08-31; the table keeps the
# standard rate so the guard errs toward over-estimating spend, not under.
Expand All @@ -389,7 +415,8 @@ class Settings(BaseSettings):
"claude-opus-4-8:0.005/0.025,"
"claude-sonnet-5:0.003/0.015,"
"claude-sonnet-4-6:0.003/0.015,"
"claude-haiku-4-5:0.001/0.005"
"claude-haiku-4-5:0.001/0.005,"
"mistral-large-latest:0.0005/0.0015"
),
description=(
"USD-per-1k-token price table as model:in/out pairs; unlisted models price at 0 until"
Expand Down Expand Up @@ -533,8 +560,8 @@ def unknown_env_settings(environ: Mapping[str, str] | None = None) -> list[str]:
`extra="ignore"` silently drops typos — `AUTO_BI_AUTH_ENABLE=true` leaves auth OFF
with no trace. `serve` reports every returned name as a warning so a misspelled
security flag is visible in the log instead of silently inert. Compares against
`Settings.model_fields` plus any explicit string validation_alias (none today;
AliasChoices would need unpacking if ever introduced).
`Settings.model_fields` plus any explicit string validation_alias. Field-derived
`AUTO_BI_*` names remain recognised when a field also accepts non-prefixed aliases.
"""
env = os.environ if environ is None else environ
prefix = str(Settings.model_config.get("env_prefix", "")).upper()
Expand Down
Loading
Loading