# Mock LLM (OpenAI-compatible stub for CI / local processing) Tiny local Chat Completions server that exercises Descrybe’s real `processing.OpenAIClient` path **without** production API keys or Green Chat. | Item | Value | |------|--------| | Command | `apps/api/cmd/mock-llm` | | Default listen | `127.0.0.1:18767` | | Base URL | `http://127.0.0.1:18767/v1` | | Model id | `mock-llm` | | API key | `local-test` (placeholder; Completer requires non-empty) | | Responses | `processing.HeuristicCompleter` (deterministic JSON for enhance) | **Do not use the A1 tenant.** Prefer **`demo@descrybe.local`** → **Platform Demo** ([demo-user.md](demo-user.md), [safe-test-fixtures.md](safe-test-fixtures.md)). Related: [green-chat-llm.md](green-chat-llm.md) (real local/LAN model), [local-llm-tuning.md](local-llm-tuning.md), [e2e-processing.md](e2e-processing.md). --- ## Start ```powershell cd apps/api go run ./cmd/mock-llm -addr 127.0.0.1:18767 # defaults: key=local-test model=mock-llm # env overrides: MOCK_LLM_API_KEY, MOCK_LLM_MODEL ``` Health: `GET http://127.0.0.1:18767/healthz` → `{"status":"ok","service":"mock-llm",...}`. Smoke: ```powershell curl.exe -sS http://127.0.0.1:18767/v1/models -H "Authorization: Bearer local-test" # POST /v1/chat/completions with JSON messages (same shape as OpenAI) ``` Endpoints: | Method | Path | Notes | |--------|------|--------| | `GET` | `/healthz` | No auth | | `GET` | `/v1/models` | Bearer required | | `POST` | `/v1/chat/completions` | Bearer; assistant content from HeuristicCompleter | | `POST` | `/v1/embeddings` | Bearer; tiny fixed vector for role probes | Loopback is allowed by `OpenAIClient` dial policy (`localhost` / `127.0.0.1`). --- ## Config keys Process-env platform fallback (root `.env` — restart **api** + **worker** after change): ```env OPENAI_API_KEY=local-test OPENAI_BASE_URL=http://127.0.0.1:18767/v1 OPENAI_MODEL=mock-llm # optional tuning # PROCESSING_RPM=60 # PROCESSING_MAX_RETRIES=3 ``` Preferred for a single tenant: company admin → **`/integrations/ai`** → custom OpenAI-compatible: - Base URL: `http://127.0.0.1:18767/v1` - Model: `mock-llm` - API key: `local-test` `OPENAI_API_KEY` must be non-empty or the Completer stays disabled (`Enabled()`). Unset `OPENAI_*` → worker skips AI enhance (or uses heuristic only if explicitly wired). Mock-llm is for proving the **HTTP** Completer path. --- ## How processing tests should call it ### 1. Unit / package tests (no process) Prefer in-process stubs — already used: - `stubCompleter` in `processing/steps_test.go` - `HeuristicCompleter` for offline enhance shapes - `httptest.NewServer` + `NewOpenAIClient(..., srv.URL+"/v1", ...)` as in `aiprovider/platform_role_test.go` `go test ./cmd/mock-llm` covers health, auth, enhance JSON, and an `OpenAIClient` round-trip against the stub handlers. ### 2. Integration / E2E (worker + API) 1. Start mock-llm (above). 2. Set `OPENAI_*` **or** configure `/integrations/ai` on **Platform Demo** (not A1). 3. Ensure demo plan has `can_use_ai=true` and credits ([free-tier.md](free-tier.md)). 4. Run API + worker so the worker inherits env (or uses dashboard BYOK). 5. Enqueue `enhance_only` (or `full`) for a demo product: ```powershell # After CSRF + login as demo@descrybe.local (cookie jar): # POST /api/processing/jobs {"raw_product_ids":["…"],"processing_type":"enhance_only"} # Poll GET /api/processing/jobs/{id} until completed # Expect ai_enhance step done; gpt/step log shows OpenAI-compatible usage (not openai_not_configured) ``` Worker log should show OpenAI configured with `base=http://127.0.0.1:18767/v1 model=mock-llm`, not “platform OpenAI unset”. ### 3. CI - Default CI: keep unit tests with `stubCompleter` / `httptest` (no long-lived process). - Optional job: `go run ./cmd/mock-llm` in background → set `OPENAI_*` → run a narrow processing smoke. Do **not** commit real keys; use `local-test`. --- ## Translation + processing verification Two independent axes — do not confuse them: | Axis | What it controls | Where | |------|------------------|--------| | **UI locale** | Dashboard chrome strings | localStorage key descrybe-ui-locale (en es fr de it pt nl pl ja) — pps/web/src/lib/i18n/locales.ts | | **Content language** | AI/product titles & descriptions | Company /settings → Content Language (companies.language) — allowlist in content-languages.ts / company/language.go | ### A. Translation (UI + content language) Use **demo@descrybe.local** (not A1). 1. Web up (PUBLIC_API_URL / WEB_ORIGIN from root .env.example). 2. Login → open language switcher (or set localStorage.setItem("descrybe-ui-locale","fr") then reload) — expect French chrome, not raw amespace.key paths; missing keys fall back to English. 3. /settings → Content Language → pick e.g. de or sl → save — company payload / GET /api/me shows the new language. 4. Optional checks: web pm run check; Go company.ParseLanguage / IsAllowedLanguage. Root .env.example documents both locale lists (comments only — not process-env). ### B. Processing against mock-llm 1. cd apps/api && go run ./cmd/mock-llm -addr 127.0.0.1:18767 2. Uncomment mock OPENAI_* in a **private** root .env (placeholders only — see .env.example) **or** set /integrations/ai on Platform Demo. 3. Restart **api** + **worker** if using process env. 4. Smoke stub: curl.exe -sS http://127.0.0.1:18767/v1/models -H "Authorization: Bearer local-test" 5. Package tests: go test ./cmd/mock-llm -count=1 6. Enqueue enhance (Integration / E2E section above) on a **demo** product — poll until i_enhance is done. 7. Optional: change content language, re-run enhance, confirm job still completes (heuristic mock text is language-agnostic; real Green Chat honors content language in prompts). Checklist copy lives in root .env.example under **Locales** and **LLM test provider**. --- ## Failure modes (processing + mock-llm) Prefer a **temporary** probe (closed port / wrong key / httptest timeout) over changing demo BYOK. If you stop mock-llm, restore it before leaving the session. | Mode | How to induce (safe) | Completer / job behavior | User-facing note (after TruncateError) | |------|----------------------|--------------------------|----------------------------------------| | Provider down | Point client at closed port `127.0.0.1:18768`, **or** briefly stop mock-llm | `ai_enhance` **failed**; job item still completes with passthrough title/description | `AI provider unreachable — check base URL and that the service is running` | | Bad API key | `OPENAI_API_KEY=wrong-key` against live mock-llm (or `/integrations/ai` test with wrong key) | Non-retryable 401; enhance fails; passthrough copy | `AI provider rejected the API key` | | Timeout | Short `HTTPClient.Timeout` / `context.DeadlineExceeded` (unit: `TestRunSteps_enhanceViaHTTPLLMTimeout`) | Retries then fail; passthrough copy; step `failed` | `AI provider timed out — try again or check provider load` | | Upstream 5xx | Mock returns 502 + message | Retries then fail; note keeps provider message when present | e.g. `green-chat unavailable` (retry prefix stripped) | | Platform unset | Empty `OPENAI_API_KEY` and no company BYOK | Step **skipped** (not failed) | `ai_enhance: skipped (platform OpenAI unset; …)` | | Free plan | `AllowAI=false` | Step **skipped** | Free-plan upgrade note | Package coverage: `go test ./internal/processing -run 'HTTPLLM|mockLLM|TruncateError_classifies' -count=1`. **Do not** permanently change Platform Demo `/integrations/ai` or root `.env` `OPENAI_*` for failure probes — use closed-port clients, stubs, or a short mock-llm stop. ### Restore mock-llm (after a stop) ```powershell # Confirm down curl.exe -sS http://127.0.0.1:18767/healthz # Start again cd apps/api go run ./cmd/mock-llm -addr 127.0.0.1:18767 # Expect healthz status=ok; key/model defaults: local-test / mock-llm curl.exe -sS http://127.0.0.1:18767/healthz curl.exe -sS http://127.0.0.1:18767/v1/models -H "Authorization: Bearer local-test" ``` If api/worker were started with `OPENAI_BASE_URL=http://127.0.0.1:18767/v1` and `OPENAI_API_KEY=local-test`, no `.env` change is needed after restore. If you temporarily pointed a **non-demo** test company at a bad base URL, set it back to `http://127.0.0.1:18767/v1` + `local-test` (or clear BYOK to use platform env). --- ## What this is / is not | Is | Is not | |----|--------| | OpenAI Chat Completions wire format for local/CI | A real GGUF / Ollama / Green Chat model | | Deterministic enhance JSON via HeuristicCompleter | Substitute for production model quality | | Safe placeholder key `local-test` | Something to commit as a secret | For a real tiny local model, point the same `OPENAI_*` keys at Ollama/LM Studio/Green Chat (`OPENAI_BASE_URL=http://127.0.0.1:PORT/v1`) — see [green-chat-llm.md](green-chat-llm.md).