Files

8.3 KiB

Phase 7: Redo and Optimize LLM Integration - Discussion Log

Audit trail only. Do not use as input to planning, research, or execution agents. Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.

Date: 2026-06-02 Phase: 07-redo-and-optimize-llm-integration Areas discussed: Structured Output, Provider Factory Refactor, Retry & Resilience, Context Window Strategy, LMStudio Fix, New Providers


Structured Output

Option Description Selected
JSON mode everywhere All OpenAI-compat providers use response_format={"type":"json_object"}. Anthropic uses native JSON output. Keep regex parser as fallback only.
JSON schema / tool_use Define strict Pydantic schema, use OpenAI/Anthropic tool_use. Guaranteed conformance but local models hit-or-miss.
Keep current regex parsing Improve error handling but keep current fragile approach.

User's choice: JSON mode everywhere Notes: None.

Anthropic strategy sub-question

Option Description Selected
Native tool_use schema Define ClassificationResult as Anthropic tool. Schema-guaranteed, no parsing needed.
Prompt-enforced JSON mode Explicit system prompt + strip fences. Simpler but not schema-enforced.
You decide Leave Anthropic strategy to researcher/planner.

User's choice: You decide Notes: Researcher should pick the most reliable option consistent with the JSON-mode-everywhere principle.


Provider Factory Refactor

Client lifecycle sub-question

User's choice: Free text — "I want you to pick the best method for each provider. Especially when adding new providers which can happen after you research it but even later down the road, new providers can be added and the best method for each provider needs to be elaborated." Notes: Researcher must investigate and document optimal client lifecycle per provider type so that the pattern is clear for future additions. This is captured as a Claude's Discretion item (D-07).

Configuration storage sub-question

Option Description Selected
Pydantic ProviderConfig models Typed config models; get_provider() accepts ProviderConfig not raw dict. ✓ (implied)
Keep raw dict with validation Add Pydantic step before passing dict. Less refactoring.
Move config into Settings All provider config in config.py Settings env-var driven.

User's choice: Free text — "I want all settings inside the admin settings page and only default values inside any .env or other files. I want the settings to be as interactive as possible." Notes: This means a new system_settings DB table storing all AI provider configuration. Admin panel gets an AI Providers section. Env vars serve as startup defaults only.

DB storage sub-question

Option Description Selected
New system_settings table in DB Key-value/JSON config table, admin writes via API. API keys encrypted. Multi-instance safe.
Extend existing users table / per-user only Keep settings purely per-user. No global defaults.
JSON config file on disk Admin writes JSON file mounted into container. Breaks multi-instance.

User's choice: New system_settings table in DB Notes: None.


Retry & Resilience

Option Description Selected
Celery task auto-retry with backoff Raise retryable exception; 3x retry at 30s/90s/270s. Uses existing Celery infra.
tenacity in-process retry Wrap provider calls in tenacity. Blocks worker thread during wait.
No retry — better error logging Fail-fast but structured logs. User manually re-triggers.

User's choice: Celery task auto-retry with backoff Notes: None.

User visibility sub-question

Option Description Selected
Show status badge + manual re-classify button Document card shows 'Classification failed' badge + Re-analyze button calls POST /api/documents/{id}/classify
Silent auto-retry only Only Celery retries. No UI feedback if all fail.
Already exists Re-analyze button already in DocumentView.

User's choice: Show status badge + manual re-classify button Notes: None.


Context Window Strategy

Option Description Selected
Per-provider token limit + smart truncation Each provider declares context size. Truncation: first 60% + last 40% of limit. MAX_AI_CHARS becomes per-provider.
Chunk + merge (multi-call) Split doc into N chunks, classify each, merge. More accurate but 2-5x API calls.
Keep 8,000 char limit globally Keep current behavior. Simple but inaccurate for long docs.

User's choice: Per-provider token limit + smart truncation Notes: None.


LMStudio Fix

Failure mode sub-question

Option Description Selected
Classification returns empty/wrong topics API call succeeds but model returns non-JSON. Fixed by JSON mode.
Connection refused / not reachable Docker can't reach host.docker.internal:1234.
Model name mismatch Hardcoded 'gemma-4-e4b-it' doesn't match loaded model.
Other / not sure Leave to researcher.

User's choice: "Classification failed: Connection error." Notes: Connection error is the reported symptom.

OS sub-question

Option Description Selected
macOS or Windows host.docker.internal works automatically.
Linux Requires extra_hosts: ["host.docker.internal:host-gateway"] — almost certainly root cause.
Both / uncertain Fix for all OSes.

User's choice: Linux Notes: Root cause confirmed. Fix: add extra_hosts: ["host.docker.internal:host-gateway"] to backend and celery-worker in docker-compose.yml.


New Providers

Which providers to add

Option Description Selected
Google Gemini OpenAI-compat endpoint, zero new deps.
Mistral Native SDK or OpenAI-compat.
Groq OpenAI-compat, fastest inference.
xAI/Grok + DeepSeek + OpenRouter All OpenAI-compat, just base_url + api_key.

User's choice: All four selected. Notes: None.

Implementation approach

Option Description Selected
One GenericOpenAIProvider class Single GenericOpenAIProvider(base_url, api_key, model, context_chars). Named presets are factory shortcuts.
Separate class per provider Individual files for each. Explicit but repetitive.
You decide Leave to planner.

User's choice: One GenericOpenAIProvider class Notes: All OpenAI-compat providers (Groq, Grok, DeepSeek, OpenRouter, Gemini-compat, Mistral-compat) use this one class.

Mistral SDK vs compat

Option Description Selected
OpenAI-compat (simpler) Use GenericOpenAIProvider with Mistral base_url. Zero new dependencies.
Native mistralai SDK Full Mistral features (embeddings, FIM) but adds dependency.
You decide Leave to researcher.

User's choice: OpenAI-compat (simpler) Notes: Native SDK can be added later if Mistral-specific features are needed.


Claude's Discretion

  • Anthropic structured output strategy — researcher to determine tool_use schema vs response_format vs prompt-enforcement. Must be consistent with JSON-mode-everywhere principle.
  • Client lifecycle per provider — researcher to investigate and document optimal instantiation pattern (singleton, pooled, per-request) per provider type, such that future providers can follow the documented pattern without re-researching.

Deferred Ideas

  • Chunk + merge multi-call classification — 2-5x API cost; revisit when provider layer is stable.
  • Native mistralai SDK — not needed for classification; revisit when embedding-based retrieval is added.
  • Token counting (tiktoken or provider tokenizers) — per-provider context_chars approximation sufficient for now.
  • Streaming classification — Celery background task makes streaming non-trivial without websockets.