chore: remove Mistral/Ollama legacy references after Gemini migration
Rename mistral.go → llm_enricher.go and mistral_test.go → llm_enricher_test.go; update all test function names and stale model strings (mistral-large-latest → gemini-2.5-flash-lite); drop Ollama block from .env; mark superseded planning specs; update provider references in planning docs and CLAUDE.md to Google Gemini.
This commit is contained in:
@@ -26,7 +26,7 @@ Monorepo at `gitlab.com/vikingowl/marktvogt.de`. Components are regular director
|
||||
| Mobile | Flutter |
|
||||
| Auth | Custom (Go libs), E-Mail+PW / Magic Link / OAuth / 2FA |
|
||||
| Payments | Stripe Connect |
|
||||
| LLM | OpenRouter |
|
||||
| LLM | Google Gemini |
|
||||
| CI/CD | GitLab CI (gitlab.com) — evaluation vs Woodpecker; sister project infinity-tales still on Woodpecker |
|
||||
| Hosting | Kubernetes (itsh.dev) |
|
||||
| Monitoring | Prometheus, Loki, Grafana, Sentry |
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
CLI that grades discovery's AI-backed components against labelled fixtures.
|
||||
Two modes:
|
||||
|
||||
- `-mode similarity` (default) — `MistralSimilarityClassifier` on pair-
|
||||
- `-mode similarity` (default) — `SimilarityClassifier` on pair-
|
||||
labelled fixtures. Reports precision / recall / F1 / accuracy + a
|
||||
confidence calibration table.
|
||||
- `-mode category` — `MistralLLMEnricher`'s `category` output on row-
|
||||
- `-mode category` — `ProviderLLMEnricher`'s `category` output on row-
|
||||
labelled fixtures. Reports accuracy + a per-label confusion matrix.
|
||||
|
||||
File-based cache keeps reruns free. Each mode has its own cache key shape,
|
||||
@@ -18,7 +18,7 @@ so switching modes doesn't churn entries.
|
||||
|
||||
```
|
||||
export AI_API_KEY=...
|
||||
export AI_MODEL_COMPLEX=mistral-large-latest
|
||||
export AI_MODEL_COMPLEX=gemini-2.5-flash-lite
|
||||
|
||||
go run ./backend/cmd/discovery-eval \
|
||||
-mode similarity \
|
||||
@@ -34,7 +34,7 @@ Exit code is 1 when `F1 < threshold` (0 = gating disabled).
|
||||
|
||||
```
|
||||
export AI_API_KEY=...
|
||||
export AI_MODEL_COMPLEX=mistral-large-latest
|
||||
export AI_MODEL_COMPLEX=gemini-2.5-flash-lite
|
||||
|
||||
go run ./backend/cmd/discovery-eval \
|
||||
-mode category \
|
||||
|
||||
@@ -166,7 +166,7 @@ func TestMerge_BaseWinsOverOverlay(t *testing.T) {
|
||||
Description: "Ein großer Markt.",
|
||||
InputTokens: 100,
|
||||
OutputTokens: 50,
|
||||
Model: "mistral-large-latest",
|
||||
Model: "gemini-2.5-flash-lite",
|
||||
Sources: Sources{
|
||||
"plz": ProvenanceLLM,
|
||||
"venue": ProvenanceLLM,
|
||||
|
||||
@@ -23,7 +23,7 @@ func (s *stubScraper) Fetch(_ context.Context, url string) (string, error) {
|
||||
return s.responses[url], nil
|
||||
}
|
||||
|
||||
func TestMistralEnrich_HappyPath(t *testing.T) {
|
||||
func TestLLMEnricher_HappyPath(t *testing.T) {
|
||||
scraper := &stubScraper{responses: map[string]string{
|
||||
"https://a.example/markt": "Ein Mittelaltermarkt mit Ritterspielen und Markttreiben.",
|
||||
"https://b.example/info": "Sa-So jeweils 10-18 Uhr.",
|
||||
@@ -79,7 +79,7 @@ func TestMistralEnrich_HappyPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMistralEnrich_AllScrapesFail(t *testing.T) {
|
||||
func TestLLMEnricher_AllScrapesFail(t *testing.T) {
|
||||
scraper := &stubScraper{errs: map[string]error{
|
||||
"https://a.example": errors.New("timeout"),
|
||||
"https://b.example": errors.New("404"),
|
||||
@@ -99,7 +99,7 @@ func TestMistralEnrich_AllScrapesFail(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMistralEnrich_SomeScrapesFailStillCallsLLM(t *testing.T) {
|
||||
func TestLLMEnricher_SomeScrapesFailStillCallsLLM(t *testing.T) {
|
||||
// One URL fails, one succeeds — the LLM should still run with partial
|
||||
// context, not fail because of the one bad source.
|
||||
scraper := &stubScraper{
|
||||
@@ -124,7 +124,7 @@ func TestMistralEnrich_SomeScrapesFailStillCallsLLM(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMistralEnrich_EmptyFieldsNoProvenance(t *testing.T) {
|
||||
func TestLLMEnricher_EmptyFieldsNoProvenance(t *testing.T) {
|
||||
// LLM returns empty strings for fields it can't support. Those fields
|
||||
// must NOT appear in Sources — an empty provenance is misleading.
|
||||
scraper := &stubScraper{responses: map[string]string{"https://a.example": "Content."}}
|
||||
@@ -148,7 +148,7 @@ func TestMistralEnrich_EmptyFieldsNoProvenance(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMistralEnrich_CapsURLsAtFive(t *testing.T) {
|
||||
func TestLLMEnricher_CapsURLsAtFive(t *testing.T) {
|
||||
// Supply 7 URLs; only the first 5 should be fetched.
|
||||
urls := []string{"u1", "u2", "u3", "u4", "u5", "u6", "u7"}
|
||||
responses := map[string]string{}
|
||||
@@ -31,7 +31,7 @@ func TestSimilarityPairKey_DifferentInputsDifferentKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMistralSimilarity_HappyPath(t *testing.T) {
|
||||
func TestSimilarityClassifier_HappyPath(t *testing.T) {
|
||||
stub := &stubProvider{
|
||||
content: `{"same_market":true,"confidence":0.82,"reason":"Gleicher Name, gleiche Stadt, gleiches Jahr."}`,
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func TestMistralSimilarity_HappyPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMistralSimilarity_ClampsConfidence(t *testing.T) {
|
||||
func TestSimilarityClassifier_ClampsConfidence(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
raw string
|
||||
@@ -89,15 +89,15 @@ func TestMistralSimilarity_ClampsConfidence(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMistralSimilarity_PropagatesPass2Error(t *testing.T) {
|
||||
c := NewSimilarityClassifier(&stubProvider{err: errors.New("mistral down")})
|
||||
func TestSimilarityClassifier_PropagatesPass2Error(t *testing.T) {
|
||||
c := NewSimilarityClassifier(&stubProvider{err: errors.New("provider down")})
|
||||
_, err := c.Classify(context.Background(), SimilarityRow{}, SimilarityRow{})
|
||||
if err == nil {
|
||||
t.Fatal("expected error; got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMistralSimilarity_RejectsBadJSON(t *testing.T) {
|
||||
func TestSimilarityClassifier_RejectsBadJSON(t *testing.T) {
|
||||
c := NewSimilarityClassifier(&stubProvider{content: "not json at all"})
|
||||
_, err := c.Classify(context.Background(), SimilarityRow{}, SimilarityRow{})
|
||||
if err == nil {
|
||||
|
||||
@@ -696,7 +696,7 @@ func TestRunLLMEnrichOne_HappyPath(t *testing.T) {
|
||||
Category: catMittelaltermarkt,
|
||||
Description: "Ein großer Markt in der Altstadt.",
|
||||
Sources: enrich.Sources{"category": enrich.ProvenanceLLM, "description": enrich.ProvenanceLLM},
|
||||
Model: "mistral-large-latest",
|
||||
Model: "gemini-2.5-flash-lite",
|
||||
InputTokens: 500,
|
||||
OutputTokens: 80,
|
||||
}
|
||||
@@ -841,7 +841,7 @@ func TestRunLLMEnrichOne_LLMErrorMarksFailed(t *testing.T) {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
llm := &stubLLMEnricher{err: errors.New("mistral down")}
|
||||
llm := &stubLLMEnricher{err: errors.New("provider down")}
|
||||
svc := NewService(repo, nil, noopLinkVerifier{}, noopMarketCreator{}, nil, llm, nil)
|
||||
|
||||
_, err := svc.RunLLMEnrichOne(context.Background(), rowID)
|
||||
@@ -993,7 +993,7 @@ func TestClassifySimilarPair_LLMErrorPropagates(t *testing.T) {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
sim := &stubSimilarityClassifier{err: errors.New("mistral 500")}
|
||||
sim := &stubSimilarityClassifier{err: errors.New("provider 500")}
|
||||
svc := NewService(repo, nil, noopLinkVerifier{}, noopMarketCreator{}, nil, nil, sim)
|
||||
|
||||
_, err := svc.ClassifySimilarPair(context.Background(), aID, bID)
|
||||
|
||||
@@ -37,7 +37,7 @@ Workflow:
|
||||
|
||||
## Entscheidungen
|
||||
|
||||
- [x] **LLM-Provider**: OpenRouter (viele Modelle verfuegbar, einfach austauschbar)
|
||||
- [x] **LLM-Provider**: Google Gemini (gemini-2.5-flash-lite default)
|
||||
- [x] **Bild-Upload**: Ja, auch Bilder und Flyer-Scans werden per LLM geparsed
|
||||
- [x] **Push bei Aenderungen**: Ja, User mit Erinnerung fuer diesen Markt werden benachrichtigt
|
||||
- [x] **PDF-Export**: Ja, Programm als PDF exportierbar (fuer Aushang vor Ort)
|
||||
|
||||
@@ -45,7 +45,7 @@ Internes Dashboard fuer Plattform-Betreiber. Nicht oeffentlich zugaenglich.
|
||||
- Programm-Parsing (PDF/Bild)
|
||||
- Bild-Moderation
|
||||
- Natuerliche Sprache Suche (spaeter)
|
||||
- Provider: OpenRouter (Modell-Dropdown)
|
||||
- Provider: Google Gemini (gemini-2.5-flash-lite default)
|
||||
- Fallback-Modell konfigurierbar
|
||||
- Token-Verbrauch / Kosten-Uebersicht pro Bereich
|
||||
- API-Key-Verwaltung
|
||||
|
||||
@@ -38,7 +38,7 @@ Ziel: Veranstalter uebernehmen ihre Eintraege, erstellen eigene Maerkte.
|
||||
- Markt beanspruchen ("Das ist mein Markt")
|
||||
- Markt anlegen / bearbeiten
|
||||
- Kontaktdaten hinterlegen
|
||||
- Programm-Verwaltung (manuell + LLM-Parsing via OpenRouter)
|
||||
- Programm-Verwaltung (manuell + LLM-Parsing via Google Gemini)
|
||||
- Mitarbeiter-Sub-Rolle (granulare Berechtigungen)
|
||||
- Wunschliste (gesuchte Kategorien/Epochen)
|
||||
|
||||
@@ -125,7 +125,7 @@ Ziel: Interne Verwaltung und Moderation.
|
||||
- Admin-Rollen (Super-Admin, Moderator, Support)
|
||||
- Role-Assume (Impersonate)
|
||||
- Moderation (Bilder, Bewertungen, Chat)
|
||||
- LLM-Konfiguration (Modell pro Bereich, OpenRouter)
|
||||
- LLM-Konfiguration (Modell pro Bereich, Google Gemini)
|
||||
- Monitoring (Grafana-Einbettung, Plattform-Statistiken)
|
||||
- Audit Log
|
||||
- Admin-Benachrichtigungen (Push, E-Mail, Matrix Webhook)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
> **SUPERSEDED 2026-04-25**: Replaced by Gemini-only migration (commit 3ddfd87). Kept for historical context. See `backend/internal/pkg/ai/gemini.go` for the implementation.
|
||||
|
||||
# Pluggable AI Provider + Local Research Orchestrator — Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
> **SUPERSEDED 2026-04-25**: Replaced by Gemini-only migration (commit 3ddfd87). Kept for historical context. See `backend/internal/pkg/ai/gemini.go` for the implementation.
|
||||
|
||||
# Spec: Pluggable AI Provider + Local Research Orchestrator
|
||||
|
||||
- **Date:** 2026-04-24
|
||||
|
||||
Reference in New Issue
Block a user