mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-17 16:09:40 +02:00
feat(research): add mana-research service — Phase 1 + 2
New Bun/Hono service on port 3068 that bundles many web-research providers behind a unified interface for side-by-side comparison. All eval runs persist in research.* (mana_platform) so quality can be reviewed later. Providers (Phase 1+2): search: searxng, duckduckgo, brave, tavily, exa, serper extract: readability (via mana-search), jina-reader, firecrawl Endpoints: POST /v1/search, /v1/search/compare — single + fan-out POST /v1/extract, /v1/extract/compare — single + fan-out GET /v1/runs, /v1/runs/:id — history POST /v1/runs/:run/results/:id/rate — manual eval GET /v1/providers, /v1/providers/health — catalog + readiness Auto-routing: when `provider` is omitted, queries are classified via regex (fast path, 0ms) with optional mana-llm fallback, then routed to the first available provider for that query type (news → tavily, academic → exa, semantic → exa, etc.). Credits: server-key calls go through mana-credits reserve → commit/refund so failed provider calls don't charge the user. BYO-keys supported via research.provider_configs (UI arrives in Phase 4). Cache: Redis with graceful degradation (1h TTL for search, 24h for extract). Pay-per-use APIs only — no subscription-gated providers. Docs: docs/plans/mana-research-service.md + docs/reports/web-research-capabilities.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
004fc0b2fd
commit
2bdb48bdd1
56 changed files with 4431 additions and 298 deletions
|
|
@ -26,7 +26,8 @@
|
|||
> - mana-mail `3042`
|
||||
> - mana-sync `3050`
|
||||
> - mana-credits `3061`, mana-user `3062`, mana-subscriptions `3063`,
|
||||
> mana-analytics `3064`, mana-events `3065`
|
||||
> mana-analytics `3064`, mana-events `3065`, mana-research `3068`
|
||||
> (new 2026-04-17, Bun/Hono, public: `research.mana.how`)
|
||||
>
|
||||
> **Not deployed:** `mana-voice-bot` (default port `3024`, no scheduled
|
||||
> task, no cloudflared route, no launchd plist).
|
||||
|
|
|
|||
580
docs/plans/mana-research-service.md
Normal file
580
docs/plans/mana-research-service.md
Normal file
|
|
@ -0,0 +1,580 @@
|
|||
# Mana Research Service — Plan
|
||||
|
||||
## Status (2026-04-17)
|
||||
|
||||
Planung, noch kein Code. Neuer Service `services/mana-research` (Bun/Hono, Port 3068), der 16+ Web-Research-Provider hinter einer einheitlichen Schnittstelle bündelt. Ziel: Vergleichbarkeit zwischen Anbietern + beste verfügbare Research-Qualität je nach Query-Typ.
|
||||
|
||||
**Vorausgegangene Analyse:** [`docs/reports/web-research-capabilities.md`](../reports/web-research-capabilities.md)
|
||||
**Verwandtes Modul:** [`docs/plans/news-research-module.md`](./news-research-module.md) (das bestehende RSS-Modul, wird in Phase 3 auf den neuen Service migriert)
|
||||
|
||||
## Ziel
|
||||
|
||||
Eine zentrale Research-Schicht, die:
|
||||
|
||||
1. **Viele Anbieter bündelt** hinter einer gemeinsamen Schnittstelle (Search, Extract, Agent).
|
||||
2. **Side-by-Side-Vergleich** erlaubt (gleiche Query → N Provider parallel, normalisierte + rohe Ergebnisse in PostgreSQL).
|
||||
3. **Auto-Routing** nach Query-Typ macht (News → Tavily, Paper → Exa, komplexe Frage → Perplexity, …).
|
||||
4. **Pay-per-use-only**: keine Services mit Monats-Abo, nur API-Call-basierte Abrechnung.
|
||||
5. **mana-credits** integriert ist — jeder Call kostet je nach Provider + Operation.
|
||||
6. **Hybrid-Keys** unterstützt — Server-Keys als Default (charged via credits), BYO-Keys pro User optional (kein Credit-Verbrauch).
|
||||
7. **Frontend Research Lab** liefert — UI zum Eingeben von Queries, Auswahl von Providern, Side-by-Side-Review, manuelles Rating.
|
||||
|
||||
## Abgrenzung
|
||||
|
||||
- **`mana-search`** (Go, Port 3021) bleibt als SearXNG+Readability-Primitive bestehen und wird *ein* Provider im neuen Service. Keine Ablösung, nur Umfassung.
|
||||
- **`mana-crawler`** (Go, Port 3023) bleibt als Deep-Crawl-Tool separat. Wird nicht Teil der Research-Pipeline.
|
||||
- **`news-research`-Modul** im Frontend und die `/api/v1/news-research/*`-Routen in mana-api bleiben zunächst parallel bestehen und werden in Phase 3 als dünner Adapter auf den neuen Service umgestellt.
|
||||
- **`mana-ai`** behält seinen eigenen NewsResearchClient, wird in Phase 3 auf `mana-research` umgestellt.
|
||||
|
||||
---
|
||||
|
||||
## 1. Architektur
|
||||
|
||||
```
|
||||
apps/mana web / apps/api / mana-ai
|
||||
│
|
||||
▼
|
||||
mana-research (3068)
|
||||
│
|
||||
┌──────────────────┼──────────────────┐
|
||||
▼ ▼ ▼
|
||||
SearchProvider ExtractProvider ResearchAgent
|
||||
│ │ │
|
||||
┌──────────┼──────────┐ │ ┌──────────┼──────────┐
|
||||
│ │ │ │ │ │ │
|
||||
SearXNG Brave Tavily Readab Perplex Claude OpenAI
|
||||
DDG Exa Serper Jina Sonar Web-Search Responses
|
||||
Mojeek SerpAPI Firecr Gemini DR (async)
|
||||
ScrapBee Ground
|
||||
|
||||
┌──────────────────────────────────┐
|
||||
│ Redis Cache (query+provider) │
|
||||
│ Postgres research.{eval_runs, │
|
||||
│ eval_results, │
|
||||
│ provider_configs, │
|
||||
│ provider_stats} │
|
||||
│ mana-credits (cost tracking) │
|
||||
│ mana-llm (query classification) │
|
||||
└──────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Service-Grenze
|
||||
|
||||
- **Eigener Hono-Server** in `services/mana-research/src/server.ts`.
|
||||
- **Auth:** `@mana/shared-hono` authMiddleware wie alle anderen Services. Interne Service-to-Service-Calls (mana-ai) nutzen weiterhin den vorhandenen Muster ohne User-Auth.
|
||||
- **CORS-Origins:** `*.mana.how` + `localhost:5173` — wird in Phase 1 gesetzt.
|
||||
- **Dependencies:** `mana-llm` (für Query-Klassifikation), `mana-credits` (für Cost-Tracking), `mana-search` (als Provider gewrappt), PostgreSQL (`mana_platform`), Redis (Cache).
|
||||
|
||||
### Provider-Interfaces (TypeScript, exportiert aus `packages/shared-research`)
|
||||
|
||||
Neues Shared-Package `@mana/shared-research` für Typen, die vom Frontend (research-lab Modul) und Backend (mana-research + mana-api) gemeinsam genutzt werden.
|
||||
|
||||
```ts
|
||||
// Gemeinsame Metadaten jedes Provider-Calls
|
||||
export interface ProviderMeta {
|
||||
provider: ProviderId; // 'brave' | 'tavily' | ...
|
||||
category: 'search' | 'extract' | 'agent';
|
||||
latencyMs: number;
|
||||
costCredits: number; // via creditsPerCall mapping
|
||||
cacheHit: boolean;
|
||||
billingMode: 'server-key' | 'byo-key';
|
||||
errorCode?: string; // wenn success=false
|
||||
}
|
||||
|
||||
export interface SearchHit {
|
||||
url: string;
|
||||
title: string;
|
||||
snippet: string;
|
||||
publishedAt?: string;
|
||||
author?: string;
|
||||
score?: number; // provider-eigenes Ranking
|
||||
content?: string; // wenn Provider direkt Text liefert (Tavily)
|
||||
providerRaw: unknown; // roh für Debug
|
||||
}
|
||||
|
||||
export interface ExtractedContent {
|
||||
url: string;
|
||||
title: string;
|
||||
content: string; // Markdown oder plain text
|
||||
excerpt?: string;
|
||||
author?: string;
|
||||
siteName?: string;
|
||||
publishedAt?: string;
|
||||
wordCount: number;
|
||||
providerRaw: unknown;
|
||||
}
|
||||
|
||||
export interface AgentAnswer {
|
||||
query: string;
|
||||
answer: string; // synthetisierte Antwort, Markdown
|
||||
citations: Array<{ url: string; title: string; snippet?: string }>;
|
||||
followUpQueries?: string[];
|
||||
tokenUsage?: { input: number; output: number };
|
||||
providerRaw: unknown;
|
||||
}
|
||||
|
||||
export interface SearchProvider {
|
||||
id: ProviderId;
|
||||
capabilities: {
|
||||
webSearch: boolean;
|
||||
newsSearch: boolean;
|
||||
scholarSearch: boolean;
|
||||
semanticSearch: boolean;
|
||||
contentInResults: boolean; // true wenn Tavily-style
|
||||
};
|
||||
search(query: string, opts: SearchOptions): Promise<{
|
||||
results: SearchHit[];
|
||||
meta: ProviderMeta;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ExtractProvider {
|
||||
id: ProviderId;
|
||||
capabilities: {
|
||||
jsRendering: boolean;
|
||||
pdfSupport: boolean;
|
||||
markdownOutput: boolean;
|
||||
};
|
||||
extract(url: string, opts: ExtractOptions): Promise<{
|
||||
content: ExtractedContent;
|
||||
meta: ProviderMeta;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ResearchAgent {
|
||||
id: ProviderId;
|
||||
capabilities: {
|
||||
multiStep: boolean;
|
||||
async: boolean;
|
||||
withCitations: boolean;
|
||||
};
|
||||
research(query: string, opts: AgentOptions): Promise<{
|
||||
answer: AgentAnswer;
|
||||
meta: ProviderMeta;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
### Service-Modi (HTTP-Endpoints)
|
||||
|
||||
| Endpoint | Zweck |
|
||||
|---|---|
|
||||
| `POST /v1/search` | Single-Provider-Search. Body: `{ query, provider?, options }`. Wenn provider fehlt → Auto-Router. |
|
||||
| `POST /v1/search/compare` | Fan-Out an N Provider, speichert Run in `research.eval_runs`. Body: `{ query, providers: [...], options }`. |
|
||||
| `POST /v1/extract` | Single-Provider-Extract. Body: `{ url, provider? }`. |
|
||||
| `POST /v1/extract/compare` | Fan-Out für Extract. |
|
||||
| `POST /v1/research` | Agent-Mode. Query → synthesierte Antwort + Zitate. Body: `{ query, agent?, options }`. |
|
||||
| `POST /v1/research/compare` | Fan-Out an N Agents. |
|
||||
| `GET /v1/providers` | Liste aller registrierten Provider + Capabilities + aktuelle Kosten. |
|
||||
| `GET /v1/providers/health` | Welche Provider sind aktuell erreichbar / haben gültige Keys? |
|
||||
| `GET /v1/runs` | User's gespeicherte Eval-Runs (paginiert). |
|
||||
| `GET /v1/runs/:id` | Einzelner Run mit allen Results. |
|
||||
| `POST /v1/runs/:id/results/:resultId/rate` | User-Rating für Eval. |
|
||||
| `GET /health`, `/metrics` | Standard |
|
||||
|
||||
---
|
||||
|
||||
## 2. Provider-Inventar
|
||||
|
||||
Alle Provider nur mit **Pay-per-use**-Abrechnung, kein fester Monatsbetrag nötig.
|
||||
|
||||
### 2.1 Search Providers (6)
|
||||
|
||||
| Provider | Stärke | Kosten-Modell | Key nötig? | `costCredits` (geschätzt) |
|
||||
|---|---|---|---|---|
|
||||
| `searxng` | Self-hosted, unabhängig | Infrastruktur only | Nein | 0 |
|
||||
| `brave` | Unabhängiger Index, Privacy | $5/1k Queries (PAYG) | Ja | 5 (≈ $0.005) |
|
||||
| `tavily` | Agent-optimiert, liefert Content + Answer | Credit-Packs, kein Abo-Zwang ($30 = 4k credits, persistent) | Ja | 8 |
|
||||
| `exa` | Semantische/Embedding-basierte Suche, Papers | $0.001-0.01/Query PAYG | Ja | 3–10 je nach Modus |
|
||||
| `serper` | Google-SERP als JSON (inkl. People-Also-Ask, Knowledge Panel) | $0.30-1/1k (PAYG) | Ja | 1 |
|
||||
| `duckduckgo` | Free Instant-Answer API | Kostenlos, rate-limited | Nein | 0 |
|
||||
|
||||
Optional später: `mojeek` (unabhängiger Index, PAYG), `serpapi` (Alternative zu Serper, höhere Coverage $5/1k).
|
||||
|
||||
### 2.2 Extract Providers (5)
|
||||
|
||||
| Provider | Stärke | Kosten-Modell | Key nötig? | `costCredits` |
|
||||
|---|---|---|---|---|
|
||||
| `readability` | Mozilla Readability via `mana-search` | Kostenlos (self-hosted) | Nein | 0 |
|
||||
| `jina-reader` | `r.jina.ai`, extrem robust, Markdown out | Free-Tier 1M tokens/Monat, dann $0.02/1M tokens | Optional (höheres Rate-Limit mit Key) | 1 |
|
||||
| `firecrawl` | JS-Rendering via Playwright, Batch-Crawls | PAYG Credits (oder self-host via Docker, dann 0) | Ja (bzw. intern für self-host) | 10 |
|
||||
| `scrapingbee` | Proxy + JS-Render | PAYG Credits | Ja | 8 |
|
||||
| `crawl4ai` | Self-hosted OSS (Python + Playwright) | Infrastruktur only | Nein | 0 |
|
||||
|
||||
### 2.3 Research Agents (5)
|
||||
|
||||
| Provider | Stärke | Kosten-Modell | Key nötig? | `costCredits` |
|
||||
|---|---|---|---|---|
|
||||
| `perplexity-sonar` | Beste Plug-and-Play-Research-API, 4 Modelle (`sonar`, `sonar-pro`, `sonar-reasoning`, `sonar-deep-research`) | Token-based PAYG ($1-15/1M input + search fees) | Ja | 50–500 je nach Modell |
|
||||
| `claude-web-search` | Claude-API mit server-seitigem `web_search`-Tool | Token-based + $10/1k searches | Ja (Anthropic) | 100–300 |
|
||||
| `openai-responses` | OpenAI Responses API mit `web_search_preview`-Tool | Token-based + per-tool-call | Ja (OpenAI) | 100–300 |
|
||||
| `gemini-grounding` | Gemini + Google Search Grounding | Token-based + per-grounding-query | Ja (Google) | 80–200 |
|
||||
| `openai-deep-research` | Async, autonomer Multi-Step-Agent | Pay-per-task (~$0.10-1/task) | Ja | 1000+ (async via Job-Queue) |
|
||||
|
||||
**Preise sind Stand 2026-04-17** und werden zentral in `services/mana-research/src/providers/pricing.ts` gepflegt und im Service-Startup aus einer JSON-Datei gezogen (updatebar ohne Redeploy).
|
||||
|
||||
---
|
||||
|
||||
## 3. Daten-Modell
|
||||
|
||||
Neues PostgreSQL-Schema `research` in `mana_platform`. Drizzle-Schema in `services/mana-research/src/db/schema.ts`.
|
||||
|
||||
```ts
|
||||
export const researchSchema = pgSchema('research');
|
||||
|
||||
// Ein Run = eine Query, ein oder mehrere Provider
|
||||
export const evalRuns = researchSchema.table('eval_runs', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: text('user_id'), // null für Service-to-Service
|
||||
query: text('query').notNull(),
|
||||
queryType: text('query_type'), // 'news' | 'general' | 'semantic' | 'academic' | 'code'
|
||||
mode: text('mode').notNull(), // 'single' | 'compare' | 'auto'
|
||||
category: text('category').notNull(),// 'search' | 'extract' | 'agent'
|
||||
providersRequested: text('providers_requested').array().notNull(),
|
||||
billingMode: text('billing_mode').notNull(), // 'server-key' | 'byo-key' | 'mixed'
|
||||
totalCostCredits: integer('total_cost_credits').notNull().default(0),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
});
|
||||
|
||||
// Ein Result = Antwort von genau einem Provider
|
||||
export const evalResults = researchSchema.table('eval_results', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
runId: uuid('run_id').notNull().references(() => evalRuns.id, { onDelete: 'cascade' }),
|
||||
providerId: text('provider_id').notNull(),
|
||||
success: boolean('success').notNull(),
|
||||
latencyMs: integer('latency_ms').notNull(),
|
||||
costCredits: integer('cost_credits').notNull().default(0),
|
||||
billingMode: text('billing_mode').notNull(),
|
||||
cacheHit: boolean('cache_hit').notNull().default(false),
|
||||
rawResponse: jsonb('raw_response'), // Provider-spezifisch, für Debug
|
||||
normalizedResult: jsonb('normalized_result'),// SearchHit[] | ExtractedContent | AgentAnswer
|
||||
errorCode: text('error_code'),
|
||||
errorMessage: text('error_message'),
|
||||
userRating: integer('user_rating'), // 1-5, null wenn nicht bewertet
|
||||
userNotes: text('user_notes'),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
});
|
||||
|
||||
// Per-User Provider-Config (API-Keys, Limits)
|
||||
export const providerConfigs = researchSchema.table('provider_configs', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: text('user_id'), // null = server-wide default
|
||||
providerId: text('provider_id').notNull(),
|
||||
apiKeyEncrypted: text('api_key_encrypted'),// verschlüsselt via shared-crypto (KEK-wrapped, AES-GCM)
|
||||
enabled: boolean('enabled').notNull().default(true),
|
||||
dailyBudgetCredits: integer('daily_budget_credits'),
|
||||
monthlyBudgetCredits: integer('monthly_budget_credits'),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
||||
}, (t) => ({
|
||||
userProvider: uniqueIndex('user_provider_unique').on(t.userId, t.providerId),
|
||||
}));
|
||||
|
||||
// Aggregierte Stats für Admin-Dashboard & Auto-Router
|
||||
export const providerStats = researchSchema.table('provider_stats', {
|
||||
providerId: text('provider_id').notNull(),
|
||||
day: date('day').notNull(),
|
||||
totalCalls: integer('total_calls').notNull().default(0),
|
||||
totalLatencyMs: bigint('total_latency_ms', { mode: 'number' }).notNull().default(0),
|
||||
totalCostCredits:integer('total_cost_credits').notNull().default(0),
|
||||
successCount: integer('success_count').notNull().default(0),
|
||||
errorCount: integer('error_count').notNull().default(0),
|
||||
avgUserRating: real('avg_user_rating'),
|
||||
ratingCount: integer('rating_count').notNull().default(0),
|
||||
}, (t) => ({
|
||||
pk: primaryKey({ columns: [t.providerId, t.day] }),
|
||||
}));
|
||||
```
|
||||
|
||||
**Encryption:** `providerConfigs.apiKeyEncrypted` wird via `shared-crypto` (existierendes AES-GCM + KEK-Pattern) verschlüsselt. Eintrag in `apps/mana/apps/web/src/lib/data/crypto/registry.ts` nötig, falls Client-Cache der Keys kommt — sonst serverseitig über `mana-auth` KEK.
|
||||
|
||||
---
|
||||
|
||||
## 4. Credits-Integration
|
||||
|
||||
**Prinzip:** Jeder Provider-Call hat festen `costCredits`-Preis (siehe §2). Bei Server-Key-Mode → Credits werden via `mana-credits` vom User abgezogen. Bei BYO-Key → 0 Credits, aber wir loggen den Call trotzdem (Usage-Transparenz).
|
||||
|
||||
### Flow pro Call
|
||||
|
||||
```ts
|
||||
// services/mana-research/src/middleware/credits.ts
|
||||
async function chargeForCall(userId, providerId, operation) {
|
||||
const config = await loadUserConfig(userId, providerId);
|
||||
const isByoKey = !!config?.apiKeyEncrypted;
|
||||
|
||||
if (isByoKey) {
|
||||
return { billingMode: 'byo-key', costCredits: 0, apiKey: decrypt(config.apiKeyEncrypted) };
|
||||
}
|
||||
|
||||
// Server-Key-Mode: Credits prüfen + reservieren
|
||||
const price = PROVIDER_PRICING[providerId][operation]; // aus pricing.ts
|
||||
const balance = await manaCredits.getBalance(userId);
|
||||
if (balance < price) throw new HTTPException(402, 'Insufficient credits');
|
||||
|
||||
// Soft-Reserve (atomic decrement)
|
||||
await manaCredits.reserve(userId, price, `research:${providerId}:${operation}`);
|
||||
|
||||
return { billingMode: 'server-key', costCredits: price, apiKey: SERVER_KEYS[providerId] };
|
||||
}
|
||||
|
||||
// Nach erfolgreichem Call: commit (bei Fehler: refund)
|
||||
async function finalizeCharge(userId, reservationId, success) {
|
||||
if (success) await manaCredits.commit(reservationId);
|
||||
else await manaCredits.refund(reservationId);
|
||||
}
|
||||
```
|
||||
|
||||
### mana-credits API (zu verifizieren / ggf. zu erweitern)
|
||||
|
||||
Erwartete Endpoints:
|
||||
- `GET /api/v1/credits/balance?userId=...`
|
||||
- `POST /api/v1/credits/reserve` `{ userId, amount, reason }` → `{ reservationId }`
|
||||
- `POST /api/v1/credits/commit` `{ reservationId }`
|
||||
- `POST /api/v1/credits/refund` `{ reservationId }`
|
||||
- `GET /api/v1/credits/usage?userId=...&since=...&filter=research:*`
|
||||
|
||||
**Falls** `mana-credits` diese Granularität noch nicht hat → Task in Phase 1 ergänzen: Reserve/Commit/Refund-Pattern einführen.
|
||||
|
||||
### Budget-Enforcement
|
||||
|
||||
Pro User konfigurierbar in `providerConfigs.dailyBudgetCredits` / `monthlyBudgetCredits`. Default: unlimited (nur von `mana-credits`-Balance limitiert). UI in Settings → "Research Providers" (Phase 4).
|
||||
|
||||
---
|
||||
|
||||
## 5. BYO-Keys (Hybrid-Modus)
|
||||
|
||||
### UX
|
||||
|
||||
Settings-Seite `/settings/research-providers` (Phase 4):
|
||||
- Liste aller Provider mit Toggle "Server-Key nutzen" vs. "Eigener Key"
|
||||
- Eingabefeld für API-Key (wird nicht angezeigt, nur `••••••`)
|
||||
- Pro Provider optional tägliches/monatliches Budget setzen
|
||||
|
||||
### Storage
|
||||
|
||||
- Keys verschlüsselt via bestehender `shared-crypto`-Pipeline (AES-GCM-256, KEK aus `mana-auth`)
|
||||
- Tabelle `research.provider_configs` mit `userId` + `providerId` unique constraint
|
||||
- Beim Call: wenn User-Config existiert → verwende deren Key → `billingMode = 'byo-key'` → kein Credit-Verbrauch
|
||||
|
||||
### Server-Keys
|
||||
|
||||
- Zentral in Env-Vars: `BRAVE_API_KEY`, `TAVILY_API_KEY`, `EXA_API_KEY`, `SERPER_API_KEY`, `PERPLEXITY_API_KEY`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_GENAI_API_KEY`, `FIRECRAWL_API_KEY`, `SCRAPINGBEE_API_KEY`, `JINA_API_KEY`
|
||||
- Einheitlich via `services/mana-research/src/config.ts`
|
||||
- In `.env.development` dokumentiert + `scripts/generate-env.mjs` ergänzt
|
||||
|
||||
---
|
||||
|
||||
## 6. Query-Klassifikation & Auto-Router
|
||||
|
||||
Für `mode: 'auto'`: Query wird via `mana-llm` (gemma3:4b) klassifiziert → bester Provider je Typ.
|
||||
|
||||
```ts
|
||||
// services/mana-research/src/router/classify.ts
|
||||
export type QueryType = 'news' | 'general' | 'semantic' | 'academic' | 'code' | 'conversational';
|
||||
|
||||
const ROUTE_MAP: Record<QueryType, ProviderId[]> = {
|
||||
news: ['tavily', 'brave', 'searxng'], // Tavily first (News-strong)
|
||||
general: ['brave', 'tavily', 'serper'],
|
||||
semantic: ['exa', 'tavily'], // Exa für "find similar to"
|
||||
academic: ['exa', 'searxng'], // Exa findet Papers am besten
|
||||
code: ['exa', 'serper', 'searxng'],
|
||||
conversational: ['perplexity-sonar', 'claude-web-search'],// agent mode für Frage-Antwort
|
||||
};
|
||||
|
||||
// Fallback-Chain: wenn Primary fehlschlägt, nächster in Liste
|
||||
```
|
||||
|
||||
Klassifikation ist optional und fällt bei LLM-Timeout auf `'general'` zurück.
|
||||
|
||||
---
|
||||
|
||||
## 7. Phasen-Plan
|
||||
|
||||
### Phase 1 — Foundation + Core Providers ✅ (2026-04-17)
|
||||
|
||||
**Ziel:** Service läuft mit 4 Search-Providern + grundlegender Cache, Credits-Integration, `/search` + `/search/compare` Endpoints.
|
||||
|
||||
- [ ] Service-Scaffold `services/mana-research/` (Bun/Hono + Drizzle + `@mana/shared-hono`)
|
||||
- [ ] Shared-Package `packages/shared-research` für Provider-Typen
|
||||
- [ ] DB-Migration: `research` schema (4 Tabellen)
|
||||
- [ ] Provider-Adapter:
|
||||
- `SearXNGProvider` (wrapt `mana-search`)
|
||||
- `BraveSearchProvider`
|
||||
- `TavilyProvider`
|
||||
- `DuckDuckGoProvider` (als kostenloser Fallback)
|
||||
- [ ] `POST /v1/search` + `POST /v1/search/compare`
|
||||
- [ ] Redis-Cache (key: `research:${category}:${provider}:${sha256(query+opts)}`, TTL 1h)
|
||||
- [ ] Credits-Middleware mit Reserve/Commit/Refund
|
||||
- [ ] `pricing.ts` + `PROVIDER_PRICING`-Map
|
||||
- [ ] Docker-Compose-Eintrag (`docker-compose.yml` + `docker-compose.macmini.yml`)
|
||||
- [ ] Port-Eintrag in [`docs/PORT_SCHEMA.md`](../PORT_SCHEMA.md)
|
||||
- [ ] Env-Vars: `BRAVE_API_KEY`, `TAVILY_API_KEY` in `.env.development`
|
||||
- [ ] `services/mana-research/CLAUDE.md`
|
||||
- [ ] Eintrag im Root-CLAUDE.md "Active services"
|
||||
- [ ] **Falls nötig:** Erweiterung `mana-credits` um Reserve/Commit/Refund
|
||||
- [ ] Integration-Tests: `tests/search-providers.spec.ts` (Mock-HTTP)
|
||||
|
||||
### Phase 2 — Extraction + semantische Suche ✅ (2026-04-17)
|
||||
|
||||
- [x] Provider-Adapter:
|
||||
- `ReadabilityProvider` (wrapt `mana-search /extract`)
|
||||
- `JinaReaderProvider` (zero-auth, optionaler Key für höheres Rate-Limit)
|
||||
- `FirecrawlProvider` (PAYG + self-hostbar via `FIRECRAWL_API_URL`)
|
||||
- ~~`ScrapingBeeProvider`~~ — deferred: liefert raw HTML, braucht zusätzlichen Readability-Pass. Wird als Phase-3-Addition behandelt.
|
||||
- `ExaProvider` (semantische Suche in Phase 2 gelandet)
|
||||
- `SerperProvider` (Google SERP als JSON)
|
||||
- [x] `POST /v1/extract` + `POST /v1/extract/compare`
|
||||
- [x] Query-Klassifikation: `classify.ts` mit Regex-Fast-Path + optionalem `mana-llm`-Call (`useLlmClassifier: true`)
|
||||
- [x] Auto-Router in `POST /v1/search` (wenn `provider` nicht gesetzt) + Auto-Router für Extract
|
||||
- [x] Provider-Health-Check-Endpoint `GET /v1/providers/health`
|
||||
- [x] Run-Listen-Endpoints bereits in Phase 1 geliefert
|
||||
- [x] ~~Nightly-Job~~: Live-Aggregation im `addResult()`-Pfad via `onConflictDoUpdate` genügt für Phase 2.
|
||||
|
||||
### Phase 3 — Research Agents + mana-ai Migration (≈ 1–2 Wochen)
|
||||
|
||||
- [ ] Provider-Adapter:
|
||||
- `PerplexitySonarProvider` (4 Modelle: sonar, sonar-pro, sonar-reasoning, sonar-deep-research)
|
||||
- `ClaudeWebSearchProvider` (via Anthropic SDK + tool-use)
|
||||
- `OpenAIResponsesProvider` (via OpenAI SDK + `web_search_preview` tool)
|
||||
- `GeminiGroundingProvider` (via google-genai SDK mit Search-Grounding)
|
||||
- `OpenAIDeepResearchProvider` — **async**, via BullMQ/inline Job-Queue, Response-Endpoint `GET /v1/research/tasks/:id`
|
||||
- [ ] `POST /v1/research` + `POST /v1/research/compare`
|
||||
- [ ] Auto-Router für `conversational`-Queries → Agent-Mode
|
||||
- [ ] `mana-llm` um Anthropic- und OpenAI-Provider erweitern (nur für Claude/OpenAI Agents; restlicher LLM-Workflow bleibt Ollama-first)
|
||||
- [ ] **Migration:** `apps/api/src/modules/news-research/routes.ts` wird zum dünnen Adapter auf `mana-research`
|
||||
- [ ] **Migration:** `services/mana-ai/src/planner/news-research-client.ts` ruft jetzt `mana-research` direkt statt `mana-api`
|
||||
- [ ] **Migration:** `research_news`-Tool bekommt Option `depth: 'shallow' | 'deep'`; `deep` ruft Agent-Mode
|
||||
- [ ] Altes `mana-api/news-research/*` bleibt als Backward-Compat, logged Deprecation-Warning
|
||||
|
||||
### Phase 4 — Research Lab UI (≈ 1 Woche)
|
||||
|
||||
**Neues Frontend-Modul** `apps/mana/apps/web/src/lib/modules/research-lab/` (tier: `beta`).
|
||||
|
||||
- [ ] Routes:
|
||||
- `/research-lab` — Main: Query-Input + Provider-Multi-Select + Compare-Button
|
||||
- `/research-lab/runs` — Liste gespeicherter Runs mit Filter (query type, datum, provider)
|
||||
- `/research-lab/runs/[id]` — Side-by-Side-View: Columns pro Provider, Ratings, Notes, Export
|
||||
- [ ] Module-Store: `researchLab.svelte.ts` (Runs, Results, aktueller Vergleich)
|
||||
- [ ] Component: `ProviderPicker.svelte` (gruppiert nach Kategorie, zeigt Kosten + Capabilities)
|
||||
- [ ] Component: `ResultCard.svelte` (normalisierter Result + Rating-UI + "raw" Toggle)
|
||||
- [ ] Component: `ComparisonGrid.svelte`
|
||||
- [ ] Settings-Integration: `/settings/research-providers` für BYO-Keys + Budgets
|
||||
- [ ] Tool-Schema erweitert: `research_news` mit neuen Options, neues `deep_research` Tool (propose-policy wegen höherer Kosten)
|
||||
- [ ] Tier-Gate: `beta`+ für Research Lab, `research_news` bleibt `public`+
|
||||
- [ ] Registrierung in `apps/mana/apps/web/src/lib/app-registry/apps.ts`
|
||||
- [ ] Seed-Handler für Demo-Runs in `apps/mana/apps/web/src/lib/data/seed-registry.ts`
|
||||
|
||||
---
|
||||
|
||||
## 8. Offene Fragen
|
||||
|
||||
- [ ] **`mana-credits` API-Shape**: hat der Service schon Reserve/Commit/Refund oder nur Debit? Muss geprüft werden in Phase 1 Task 0.
|
||||
- [ ] **Rate-Limiting**: separate per-User Rate-Limits pro Provider (damit ein Power-User nicht das Quota für alle aufbraucht)? Meine Tendenz: via `provider_configs.dailyBudgetCredits` reicht als Soft-Limit.
|
||||
- [ ] **Export-Format für Eval-Runs**: JSON-Download reicht, oder brauchen wir CSV/Markdown-Export für manuelle Analyse?
|
||||
- [ ] **Ollama als Agent?** Wir könnten lokale LLMs mit Tool-Use-Capabilities (z.B. Llama 3.1 tool calling + eigener search-tool-loop) als "self-hosted agent" anbieten — wäre konsistent mit Self-Hosting-Positioning. Phase 5-Kandidat.
|
||||
- [ ] **Caching-TTL pro Provider**: News sollten kürzer cachen als akademische Papers. Default 1h, aber konfigurierbar pro Provider-Config.
|
||||
- [ ] **Kosten-Dashboard**: wann bauen wir ein Admin-Dashboard für `provider_stats`? Phase 4 oder später?
|
||||
|
||||
---
|
||||
|
||||
## 9. Verzeichnisstruktur (Vorschlag)
|
||||
|
||||
```
|
||||
services/mana-research/
|
||||
├── CLAUDE.md
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
├── Dockerfile
|
||||
├── drizzle.config.ts
|
||||
├── src/
|
||||
│ ├── server.ts # Hono bootstrap
|
||||
│ ├── config.ts # Env-Vars
|
||||
│ ├── db/
|
||||
│ │ ├── schema.ts
|
||||
│ │ └── migrations/
|
||||
│ ├── providers/
|
||||
│ │ ├── index.ts # registerAll()
|
||||
│ │ ├── types.ts # re-export @mana/shared-research
|
||||
│ │ ├── pricing.ts # PROVIDER_PRICING map
|
||||
│ │ ├── search/
|
||||
│ │ │ ├── searxng.ts
|
||||
│ │ │ ├── brave.ts
|
||||
│ │ │ ├── tavily.ts
|
||||
│ │ │ ├── exa.ts
|
||||
│ │ │ ├── serper.ts
|
||||
│ │ │ └── duckduckgo.ts
|
||||
│ │ ├── extract/
|
||||
│ │ │ ├── readability.ts
|
||||
│ │ │ ├── jina-reader.ts
|
||||
│ │ │ ├── firecrawl.ts
|
||||
│ │ │ └── scrapingbee.ts
|
||||
│ │ └── agent/
|
||||
│ │ ├── perplexity-sonar.ts
|
||||
│ │ ├── claude-web-search.ts
|
||||
│ │ ├── openai-responses.ts
|
||||
│ │ ├── gemini-grounding.ts
|
||||
│ │ └── openai-deep-research.ts
|
||||
│ ├── routes/
|
||||
│ │ ├── search.ts
|
||||
│ │ ├── extract.ts
|
||||
│ │ ├── research.ts
|
||||
│ │ ├── runs.ts
|
||||
│ │ └── providers.ts
|
||||
│ ├── middleware/
|
||||
│ │ ├── credits.ts # Reserve/Commit/Refund
|
||||
│ │ ├── byo-keys.ts # Key-Resolution
|
||||
│ │ └── cache.ts # Redis wrapper
|
||||
│ ├── router/
|
||||
│ │ ├── classify.ts # Query → QueryType via mana-llm
|
||||
│ │ └── auto-route.ts # QueryType → Provider-Chain
|
||||
│ ├── storage/
|
||||
│ │ ├── runs.ts # persist + retrieve
|
||||
│ │ └── stats.ts # aggregation job
|
||||
│ └── clients/
|
||||
│ ├── mana-llm.ts
|
||||
│ ├── mana-credits.ts
|
||||
│ └── mana-auth.ts
|
||||
└── tests/
|
||||
├── providers/
|
||||
└── integration/
|
||||
|
||||
packages/shared-research/
|
||||
├── package.json
|
||||
└── src/
|
||||
├── index.ts
|
||||
├── types.ts # SearchHit, ExtractedContent, AgentAnswer, ProviderMeta
|
||||
├── options.ts # SearchOptions, ExtractOptions, AgentOptions
|
||||
└── ids.ts # ProviderId union
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Risiken
|
||||
|
||||
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
|
||||
|---|---|---|---|
|
||||
| Provider-Pricing ändert sich | Hoch | Mittel | `pricing.ts` updatebar ohne Redeploy, Warnung wenn Kosten > X |
|
||||
| Provider-Key wird gebannt (z.B. Brave bei zu viel Traffic) | Mittel | Hoch | Multi-Provider-Fallback in Auto-Router, Health-Check |
|
||||
| `mana-credits` Reserve/Refund-Pattern nicht ready | Mittel | Hoch | Phase 1 Task 0: verifizieren + ggf. implementieren vor allem anderen |
|
||||
| Kosten-Explosion durch Fan-Out-Compare | Mittel | Hoch | Hartes Limit auf `providers.length` (max 5), UI-Warnung mit Kosten-Preview |
|
||||
| BYO-Key-Leak durch Logs | Niedrig | Sehr hoch | Keys nur verschlüsselt in DB, Logger-Filter für bekannte Key-Patterns |
|
||||
| LLM-Klassifikation falsch → schlechter Auto-Route | Mittel | Niedrig | User kann immer explizit Provider wählen, Klassifikation ist nur Hint |
|
||||
|
||||
---
|
||||
|
||||
## 11. Verwandte Dateien
|
||||
|
||||
**Heute existierende Research-Pfade (werden in Phase 3 umgestellt):**
|
||||
- `apps/api/src/lib/search.ts` — `webSearch()` Wrapper
|
||||
- `apps/api/src/modules/news-research/routes.ts` — `/discover`, `/search`, `/extract`
|
||||
- `services/mana-ai/src/planner/news-research-client.ts`
|
||||
- `services/mana-ai/src/cron/tick.ts:282` — Pre-Planning Research Step
|
||||
- `apps/mana/apps/web/src/lib/modules/news-research/tools.ts:48` — `research_news` Tool
|
||||
- `services/mana-search/` — bleibt, wird SearXNG+Readability-Provider
|
||||
- `packages/shared-rss/` — bleibt unverändert
|
||||
|
||||
**Neu zu erstellen:**
|
||||
- `services/mana-research/**`
|
||||
- `packages/shared-research/**`
|
||||
- `apps/mana/apps/web/src/lib/modules/research-lab/**`
|
||||
- `apps/mana/apps/web/src/routes/(app)/research-lab/**`
|
||||
324
docs/reports/web-research-capabilities.md
Normal file
324
docs/reports/web-research-capabilities.md
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
# Web-Recherche im Mana-System — Bestandsaufnahme & Vergleich
|
||||
|
||||
**Datum:** 2026-04-17
|
||||
**Scope:** Wie recherchiert das System heute im Internet, wie gut ist das, und welche Alternativen gibt es?
|
||||
|
||||
---
|
||||
|
||||
## 1. TL;DR
|
||||
|
||||
Mana hat eine **vollständig selbstgehostete, RSS-zentrierte Recherche-Pipeline**:
|
||||
|
||||
```
|
||||
User / AI-Mission
|
||||
↓
|
||||
research_news Tool (Frontend) bzw. NewsResearchClient (mana-ai Backend)
|
||||
↓
|
||||
mana-api /api/v1/news-research/{discover,search,extract}
|
||||
↓
|
||||
mana-search (Go, SearXNG-Proxy) + @mana/shared-rss (RSS + Readability)
|
||||
↓
|
||||
SearXNG (lokal, Port 8080) → Google / Bing / DDG / Brave / Wikipedia / arXiv / GitHub / ...
|
||||
```
|
||||
|
||||
**Stärken:** Null API-Kosten, voller Datenschutz, kein Vendor-Lock-in, robuste Inhaltsextraktion (Mozilla Readability), RSS-First-Ansatz liefert strukturierte, zitierbare Quellen.
|
||||
|
||||
**Schwächen:** Kein semantisches Ranking (nur Keyword-TF), keine agentische Multi-Step-Recherche, keine Paywall-/JS-Heavy-Site-Handling, keine Cross-Source-Synthese, SearXNG ist fragil gegen Provider-Blocks (Google blockt ihn regelmäßig). Ergebnisqualität liegt etwa bei **30–50 %** dessen, was Perplexity/Exa/OpenAI Deep Research heute liefern.
|
||||
|
||||
**Empfehlung:** Hybrid-Modell. Self-Hosting als Default behalten, aber eine **optionale API-Bridge** (Tavily, Exa, oder Brave Search API) als Fallback bzw. "Premium-Recherche" einbauen. Siehe §6.
|
||||
|
||||
---
|
||||
|
||||
## 2. Was das System HEUTE kann
|
||||
|
||||
### 2.1 Service-Landschaft
|
||||
|
||||
| Service | Port | Sprache | Rolle |
|
||||
|---|---|---|---|
|
||||
| `mana-search` | 3021 | Go | SearXNG-Proxy + Readability-Extract (zentral) |
|
||||
| `mana-crawler` | 3023 | Go | Deep-Crawls mit Depth/Selector-Support (**wird nicht** von Research genutzt) |
|
||||
| `mana-api` | 3060 | Bun/Hono | Orchestriert News-Research-Endpoints |
|
||||
| `mana-ai` | 3067 | Bun | Background Mission Runner mit Pre-Planning Research Step |
|
||||
| `mana-llm` | 3025 | Python | LLM-Gateway (Ollama-First, Gemini-Fallback) |
|
||||
| `news-ingester` | 3066 | Bun | Cron-basiertes Vorab-Pooling kuratierter RSS-Quellen (passiv) |
|
||||
| SearXNG | 8080 | — | Meta-Such-Frontend über ~15 Engines |
|
||||
|
||||
### 2.2 Die vier Primitive
|
||||
|
||||
**1. Web-Suche** (`mana-search` → SearXNG)
|
||||
- File: `services/mana-search/internal/search/searxng.go`
|
||||
- Kategorien: `general` (Google, Bing, DDG, Brave, Wikipedia), `news` (Google/Bing News), `science` (arXiv, Scholar, PubMed), `it` (GitHub, StackOverflow, NPM, MDN)
|
||||
- Redis-Cache (TTL 1h), Prometheus-Metriken, Graceful-Degradation
|
||||
- **Limitation:** SearXNG wird von Google regelmäßig per CAPTCHA geblockt; Fallback auf DDG/Bing
|
||||
|
||||
**2. Inhaltsextraktion** (`mana-search` + `@mana/shared-rss`)
|
||||
- go-shiori/go-readability (Go-Port von Mozilla Readability) im Service
|
||||
- `@mozilla/readability` + jsdom in der shared-rss Lib
|
||||
- Bulk-Extract bis 20 URLs parallel (`POST /api/v1/extract/bulk`)
|
||||
- Redis-Cache 24h, Max-Length 50k chars
|
||||
- **Limitation:** Kein JS-Rendering (Playwright fehlt in Research-Pfad), Paywalls werden nicht umgangen, keine PDF-Extraktion
|
||||
|
||||
**3. RSS-Feed-Discovery** (`packages/shared-rss`)
|
||||
- File: `packages/shared-rss/src/discover.ts`
|
||||
- Strategy: `<link rel="alternate">` scannen → Fallback auf Common-Paths (`/feed`, `/rss.xml`, `/atom.xml`, `/feeds/posts/default`, ...)
|
||||
- `rss-parser` für RSS 1.0/2.0 und Atom
|
||||
- **Stärke:** Trifft strukturierte Inhalte direkt von der Quelle (keine HTML-Extraktion nötig)
|
||||
|
||||
**4. AI-Research-Tool** (`research_news`)
|
||||
- File: `apps/mana/apps/web/src/lib/modules/news-research/tools.ts:48`
|
||||
- Policy: `auto` (Tool läuft ohne User-Approval)
|
||||
- Pipeline: Query → `discover` (Web-Search nach "query rss feed") → `search` (Top 10 Feeds parsen + rank) → formatter Markdown-Context
|
||||
- Ranking: Keyword-Frequency (title +3, excerpt +2, content +1) + Recency-Boost (<24h: +2, <7d: +1)
|
||||
- **Limitation:** Kein BM25, keine Embeddings, keine Query-Expansion, keine Cross-Source-Deduplication
|
||||
|
||||
### 2.3 Integration in AI-Missions
|
||||
|
||||
Der `mana-ai` Tick-Runner hat einen **Pre-Planning-Step** (`services/mana-ai/src/cron/tick.ts:282`):
|
||||
|
||||
```typescript
|
||||
if (RESEARCH_TRIGGER.test(m.objective) || RESEARCH_TRIGGER.test(m.conceptMarkdown)) {
|
||||
const research = await nrc.research(m.objective, { language: 'de', limit: 8 });
|
||||
resolvedInputs.push({
|
||||
id: '__web-research__',
|
||||
module: 'news-research',
|
||||
table: 'web',
|
||||
title: `Web-Research: "${m.objective.slice(0, 60)}"`,
|
||||
content: research.contextMarkdown,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Regex triggert auf: `recherchier|research|news|finde|suche|aktuelle|neueste|today|history|historisch|on this day`. Ergebnis wird als ResolvedInput in den Planner-Prompt injiziert → Planner sieht echte URLs + Excerpts, soll nur diese zitieren.
|
||||
|
||||
### 2.4 LLM-Integration (mana-llm)
|
||||
|
||||
Router unterstützt: Ollama (primary, lokal), Google Gemini (Fallback), OpenRouter, Groq, Together.
|
||||
|
||||
**Wichtig:** Weder OpenAI noch Anthropic sind konfiguriert. Das heißt:
|
||||
- **Kein** Claude `web_search` / `fetch_url` Tool-Use
|
||||
- **Kein** OpenAI Browsing / Deep Research
|
||||
- **Kein** Gemini Grounding mit Google Search
|
||||
- Die gesamte Research-Logik ist **explizit in mana-api kodiert** — kein LLM macht hier eigenständig Tool-Calls ins Web.
|
||||
|
||||
---
|
||||
|
||||
## 3. Qualitätseinschätzung
|
||||
|
||||
### Bewertungsmatrix (1 = schwach, 5 = state-of-the-art)
|
||||
|
||||
| Dimension | Score | Begründung |
|
||||
|---|---|---|
|
||||
| **Coverage** (wie viel vom Web) | 2/5 | Nur Quellen mit RSS + was SearXNG liefert (<30 % typisch für News, <5 % für Fachwissen) |
|
||||
| **Recency** | 3/5 | RSS-Feeds meist ≤ 1h Verzögerung, SearXNG liefert tagesaktuell. Aber: Pre-Pool (`news-ingester`) ist 15-min-Cron |
|
||||
| **Inhaltsqualität** | 4/5 | Mozilla Readability ist best-in-class OSS-Extraktion. Schwach bei Paywalls, JS-SPAs, PDFs |
|
||||
| **Semantisches Ranking** | 1/5 | Nur TF + Recency-Boost. Keine Embeddings, kein BM25, keine Reranker |
|
||||
| **Multi-Hop-Recherche** | 1/5 | Ein Shot: Query → Feeds → Artikel. Keine Iteration, kein "ich hab X gefunden, jetzt suche ich Y" |
|
||||
| **Synthese & Zusammenfassung** | 2/5 | Wird dem LLM überlassen (gemma3:4b). Keine spezialisierten Research-Prompts, keine Quellen-Cross-Validierung |
|
||||
| **Grounding / Zitierbarkeit** | 4/5 | URLs + Excerpts werden strukturiert übergeben; Planner-Prompt warnt explizit gegen URL-Halluzination |
|
||||
| **Latenz** | 2/5 | Discover + Search + Extract in Serie: 3–15 s typisch. Bei Timeouts: deutlich langsamer |
|
||||
| **Kosten** | 5/5 | Null API-Kosten, nur Infrastruktur |
|
||||
| **Datenschutz** | 5/5 | Keine Queries verlassen die eigene Infra (bis auf den SearXNG → Google/Bing Roundtrip, der aber über SearXNG anonymisiert ist) |
|
||||
| **Robustheit** | 2/5 | SearXNG wird von Google regelmäßig geblockt, Fallbacks sind vorhanden, aber Qualität fällt dann stark |
|
||||
|
||||
**Gesamt-Score: ~2.8/5** — solide für das, was es ist (ein RSS-getriebener News-Aggregator mit AI-Integration), aber weit entfernt von dem, was moderne Research-Agents (Perplexity Sonar, Claude mit `web_search`, OpenAI Deep Research, Gemini Deep Research) leisten.
|
||||
|
||||
### Konkrete Qualitäts-Gaps
|
||||
|
||||
1. **Kein semantisches Matching.** Query "Auswirkungen von Zinssenkungen auf Immobilienmarkt" → findet nur Artikel, die genau diese Keywords haben, nicht solche mit "EZB senkt Leitzins, Hypotheken werden billiger".
|
||||
2. **Kein Multi-Step.** "Was sagt Studie X zu Thema Y?" erfordert: 1) Studie finden, 2) Autoren identifizieren, 3) Original-Paper lesen, 4) Kritikpunkte recherchieren. System macht nur Schritt 1–2 rudimentär.
|
||||
3. **Keine Inhalts-Synthese.** Wenn 8 Quellen dasselbe Ereignis berichten, werden 8 Texte ans LLM gegeben — keine automatische Dedup oder Consensus-Extraktion.
|
||||
4. **Paywall-Problem.** FAZ, NYT, Spiegel+ etc. liefern nur Teaser. Kein Bypass (wäre rechtlich auch problematisch).
|
||||
5. **Kein PDF/Paper-Access.** arXiv-Links werden gelistet, aber das PDF wird nicht geladen/extrahiert.
|
||||
6. **Keine Wissensgraph-Anbindung.** Entities (Personen, Firmen, Orte) werden nicht extrahiert oder verknüpft.
|
||||
|
||||
---
|
||||
|
||||
## 4. Marktübersicht: Alternativen & Ergänzungen
|
||||
|
||||
### 4.1 Search APIs (ersetzen SearXNG)
|
||||
|
||||
| API | Stärken | Schwächen | Preis (Stand 2026) |
|
||||
|---|---|---|---|
|
||||
| **Brave Search API** | Unabhängiger Index (kein Google-Relay), gute Privacy-Story, gutes Preis/Leistung | Kleinere Coverage als Google | $3–5 / 1k Queries |
|
||||
| **Tavily** | Explizit für LLM-Agents gebaut, liefert extrahierten Content statt nur Links, eingebaute Answer-Synthese | Black-Box-Ranking | $0.008 / Query (Basic), ab $30/Monat |
|
||||
| **Exa** (früher Metaphor) | Semantische Suche auf Embedding-Basis, "find me similar to this URL", beste Coverage für akademische/technische Quellen | Nicht optimal für News | $0.001–0.01 / Query |
|
||||
| **Serper / SerpAPI** | Google-SERP als JSON (inkl. Knowledge Panels, People-Also-Ask, Shopping, Images) | Nur Google-Relay (nicht unabhängig) | $0.30–1 / 1k Queries |
|
||||
| **You.com API** | Multi-Engine + integriertes LLM | Kleiner | $10–50/Monat |
|
||||
| **Kagi Search API** | Bester Qualitäts-Index laut Reviews | Teuer, Wartelisten | $25/Monat Basic |
|
||||
| **Bing Web Search API** | Große Coverage, stabil | Wird 2025 eingestellt (Microsoft deprecation) | — |
|
||||
| **Google Custom Search** | Offizieller Google-Zugang | 100 Queries/Tag gratis, dann teuer; Custom-Engine-Setup nötig | $5 / 1k ab 101. Query |
|
||||
|
||||
**Empfehlung:** **Tavily** für Agentic-Research (gibt bereits extrahierten Content + Answer), **Brave Search API** für Privacy-freundlichen Default, **Exa** für semantische / Paper-Recherche.
|
||||
|
||||
### 4.2 Extraction & Scraping APIs (ersetzen/ergänzen Readability)
|
||||
|
||||
| API | Stärken | Schwächen | Preis |
|
||||
|---|---|---|---|
|
||||
| **Firecrawl** | JS-Rendering via Playwright, LLM-ready Markdown, Crawl-Jobs, Sitemap, Schema-Extraktion | Selbst-hostbar (OSS), oder Cloud | $16–99/Monat, oder OSS gratis |
|
||||
| **Jina Reader** (`r.jina.ai`) | Free-Tier großzügig, `https://r.jina.ai/<URL>` gibt Markdown | Rate-Limits ohne Key | Free + $0.02/1M tokens |
|
||||
| **Diffbot** | KI-basierte Extraktion, strukturierte Entities, Knowledge-Graph | Teuer | $300+/Monat |
|
||||
| **ScrapingBee / ScraperAPI** | Proxy + JS-Render | Generisch, nicht LLM-optimiert | $49–299/Monat |
|
||||
| **Crawl4AI** (OSS) | Playwright + LLM-friendly Markdown, lokal self-hostbar, Python | Selbst betreiben | Free |
|
||||
| **Trafilatura** (OSS Python lib) | Best-in-class Text-Extraktion (besser als Readability laut Benchmarks) | Nur Library, kein Service | Free |
|
||||
|
||||
**Empfehlung für Mana:** **Jina Reader** als drop-in Replacement für Readability-Timeouts (1 Zeile HTTP-Call, extrem robust). Oder **Firecrawl self-hosted** für volle Kontrolle + JS-Support.
|
||||
|
||||
### 4.3 All-in-One Research Agents (ersetzen die gesamte Pipeline)
|
||||
|
||||
| Service | Was es tut | Preis | Hinweis |
|
||||
|---|---|---|---|
|
||||
| **Perplexity Sonar API** | Endpoint: "frage eine Frage, bekomm Antwort + Zitate". Multi-Step-Research eingebaut. `sonar-large-online`, `sonar-small-online`, `sonar-pro`, `sonar-reasoning` | $1–5 / 1M tokens + $5 / 1k searches | Beste "plug&play" Research-API am Markt |
|
||||
| **OpenAI Deep Research API** | Async Jobs die 5–30 Min laufen, autonome Agentic-Research, Report als Ergebnis | $10+ / Task | Premium, nicht Echtzeit |
|
||||
| **Gemini Deep Research** | Ähnlich OpenAI DR, in Gemini Advanced / Vertex AI | In Abos enthalten | Nur über Gemini-API |
|
||||
| **Claude mit `web_search` Tool** | Claude-API hat server-seitiges Web-Search Tool seit 2025 | $10 / 1k searches + Token-Kosten | Integriert sich nahtlos in existierende Agents |
|
||||
| **You.com Research API** | Ähnlich Perplexity | $ pro Query | Kleiner Anbieter |
|
||||
|
||||
**Empfehlung:** **Perplexity Sonar** ist der direkteste Ersatz für das, was Mana heute tut — mit deutlich höherer Qualität. **Claude `web_search`** ist die natürlichste Integration, wenn Claude sowieso schon als LLM genutzt wird (aktuell nicht der Fall in Mana, siehe §2.4).
|
||||
|
||||
### 4.4 Open-Source Research-Frameworks
|
||||
|
||||
| Framework | Was es ist | Eignung für Mana |
|
||||
|---|---|---|
|
||||
| **Perplexica** | Self-hosted Perplexity-Clone (SearXNG + LLM + UI) | **Sehr hohe** Übereinstimmung mit Mana-Stack; könnte als Vorbild für eine verbesserte Research-UX dienen |
|
||||
| **GPT Researcher** | LangChain-basiert, führt autonome Multi-Step-Research durch | Passt nicht direkt zu Mana's Tool-Architektur, aber Konzepte übertragbar |
|
||||
| **SearXNG + LiteLLM + Self-Extract** | Was Mana im Wesentlichen schon hat | — |
|
||||
| **llm.datasette.io + mwmbl** | Extrem minimalistisch, OSS-Index | Zu klein für Produktivnutzung |
|
||||
| **Open WebUI + Tool-Servers** | UI-Layer für LLMs mit Tool-Ecosystem | Orthogonal |
|
||||
| **crawl4ai + rag-stack** | Python-Pipelines für Deep-Research | Nur als inspirativer Input |
|
||||
|
||||
---
|
||||
|
||||
## 5. Vergleichs-Matrix: Mana heute vs. Alternativen
|
||||
|
||||
| Dimension | Mana (heute) | + Tavily | + Perplexity Sonar | Claude `web_search` | OpenAI DR |
|
||||
|---|---|---|---|---|---|
|
||||
| Coverage | 2/5 | 4/5 | 5/5 | 5/5 | 5/5 |
|
||||
| Semantisches Ranking | 1/5 | 4/5 | 5/5 | 5/5 | 5/5 |
|
||||
| Multi-Hop | 1/5 | 2/5 (mit Agent-Loop) | 4/5 | 5/5 | 5/5 |
|
||||
| Synthese & Zitate | 2/5 | 4/5 | 5/5 | 5/5 | 5/5 |
|
||||
| Latenz | 2/5 (3–15s) | 4/5 (<2s) | 4/5 (2–8s) | 3/5 (5–30s) | 1/5 (5–30 min) |
|
||||
| Datenschutz | 5/5 | 3/5 | 2/5 | 3/5 | 2/5 |
|
||||
| Kosten | 5/5 | 4/5 | 3/5 | 3/5 | 2/5 |
|
||||
| Self-Hosting möglich | 5/5 | 0/5 | 0/5 | 0/5 | 0/5 |
|
||||
| Produktionsreife | 3/5 | 5/5 | 5/5 | 5/5 | 5/5 |
|
||||
|
||||
---
|
||||
|
||||
## 6. Empfehlungen — vom Minimum zum Maximum
|
||||
|
||||
### 6.1 Quick-Wins ohne externe APIs (Null zusätzliche Kosten)
|
||||
|
||||
1. **BM25-Ranking** statt Keyword-Frequency. 1 Tag Arbeit, 20–30 % Qualitätssprung.
|
||||
- File: `apps/api/src/modules/news-research/routes.ts:160` (`scoreAndRank`)
|
||||
2. **Query-Expansion via lokalem LLM.** Vor dem Search-Call: `gemma3:4b` generiert 2–3 Varianten der Query → mehr Coverage. ~0.5 s Latenz extra.
|
||||
3. **Embedding-Dedup.** Vor dem Ranking: MiniLM-Embeddings (via `@mana/local-llm` oder mana-llm) für Cluster-Dedup gleicher News. Halbiert Redundanz.
|
||||
4. **PDF-Extraktion** für arXiv & Paper-Links. `pdf-parse` npm-lib, ~2 h Arbeit.
|
||||
5. **SearXNG hardenen.** `settings.yml` reviewen, Engines auf stabile (Brave, DDG, Qwant) fokussieren, Google deprioritisieren.
|
||||
6. **Playwright-Fallback** in `mana-crawler` für JS-heavy Sites (ist da, aber nicht im Research-Pfad). Nutzen, wenn Readability leeren Text zurückgibt.
|
||||
|
||||
**Aufwand gesamt:** ~3–5 Tage, Qualitätssprung von 2.8/5 auf ~3.5/5.
|
||||
|
||||
### 6.2 Hybrid-Modell (empfohlen für Release)
|
||||
|
||||
**Architektur:** `mana-search` behält SearXNG als Default, aber akzeptiert einen optionalen Provider-Switch:
|
||||
|
||||
```typescript
|
||||
// apps/api/src/lib/search.ts
|
||||
async function webSearch(opts) {
|
||||
const provider = opts.provider ?? env.DEFAULT_SEARCH_PROVIDER; // 'searxng' | 'brave' | 'tavily'
|
||||
switch (provider) {
|
||||
case 'tavily': return tavilySearch(opts); // agentic, gibt Content + Answer
|
||||
case 'brave': return braveSearch(opts); // privacy-freundlich, stabil
|
||||
case 'searxng': return searxngSearch(opts); // self-hosted, free
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Tier-Mapping** (nutzt existierendes `requiredTier`-System):
|
||||
- `guest` / `public` → SearXNG (self-hosted, free)
|
||||
- `beta` / `alpha` / `founder` → Tavily/Brave (Premium-Recherche)
|
||||
|
||||
**Konkret:**
|
||||
- Neues `research_news_deep` Tool mit `auto`-Policy für Premium-Tiers
|
||||
- SearXNG bleibt für alles andere
|
||||
- Env-Var: `TAVILY_API_KEY`, `BRAVE_SEARCH_API_KEY` (beide optional)
|
||||
|
||||
**Kosten:** ~$30–100/Monat für moderate Nutzung (geschätzt 5k–20k Queries).
|
||||
|
||||
**Qualitätssprung:** 2.8/5 → 4.2/5.
|
||||
|
||||
### 6.3 Max-Quality-Setup (wenn Tier-Premium-Feature)
|
||||
|
||||
Für zahlende Nutzer oder besonders wichtige Missionen:
|
||||
|
||||
1. **Perplexity Sonar API** als Default-Agent (`sonar-pro` für Standard, `sonar-reasoning` für Deep-Dives).
|
||||
2. **Jina Reader** als Extraction-Fallback für alles, was Readability nicht schafft.
|
||||
3. **Exa** für akademische/technische Queries (detektiert über Query-Klassifikation).
|
||||
4. **Langchain-style Multi-Step-Agent** in `mana-ai`: Query → initial search → identify gaps → follow-up searches → synthesis. Mit Token-Budget (bereits vorhanden).
|
||||
|
||||
**Kosten:** ~$200–500/Monat für intensive Nutzung.
|
||||
|
||||
**Qualitätssprung:** 4.2/5 → 4.7/5 (auf Augenhöhe mit Perplexity-UI).
|
||||
|
||||
### 6.4 Was NICHT gemacht werden sollte
|
||||
|
||||
- **Nicht** OpenAI Deep Research als Default einbauen — zu langsam (5–30 min), zu teuer, zu wenig Kontrolle.
|
||||
- **Nicht** SearXNG komplett rauswerfen — bleibt der beste Free-Tier-Default und wichtig für Privacy-Positioning.
|
||||
- **Nicht** `mana-crawler` zum News-Fetcher umfunktionieren — er ist für Tiefen-Scans gebaut, falsches Tool.
|
||||
- **Nicht** Paywall-Umgehung einbauen — rechtlich riskant, schadet dem Ruf.
|
||||
|
||||
---
|
||||
|
||||
## 7. Datenschutz & "Self-Hosting"-Positioning
|
||||
|
||||
Mana positioniert sich als **Self-Hosted & Independent**. Eine Hybrid-Lösung ist damit vereinbar, wenn:
|
||||
|
||||
1. Default-Verhalten = 100 % self-hosted (SearXNG).
|
||||
2. Externe APIs sind **opt-in** pro User (Settings → "Premium-Recherche aktivieren").
|
||||
3. API-Keys sind **pro User** oder **pro Tier** — nicht geshared.
|
||||
4. Transparente Anzeige im UI: *"Diese Recherche wurde via Tavily durchgeführt (externer Dienst)"*.
|
||||
5. Dokumentation in `docs/TECH_STACK_INDEPENDENCE.md` entsprechend erweitern.
|
||||
|
||||
Alternativ: **Nur Brave Search API** (unabhängiger Index, Privacy-Fokus) als einzige externe Option — behält die "unabhängig vom Big-Tech-Stack"-Story.
|
||||
|
||||
---
|
||||
|
||||
## 8. Nächste Schritte (konkret)
|
||||
|
||||
**Phase 1 — Quick-Wins** (1 Woche)
|
||||
- [ ] BM25 in `apps/api/src/modules/news-research/routes.ts:160`
|
||||
- [ ] Query-Expansion via `gemma3:4b` im `research_news`-Pfad
|
||||
- [ ] SearXNG-`settings.yml` reviewen + dokumentieren
|
||||
- [ ] Playwright-Fallback in Extraction-Chain (nur wenn Readability leer)
|
||||
|
||||
**Phase 2 — Hybrid-Provider** (1–2 Wochen)
|
||||
- [ ] `webSearch()` Abstraktion mit Provider-Switch in `apps/api/src/lib/search.ts`
|
||||
- [ ] Brave Search API als Fallback-Provider
|
||||
- [ ] Tier-Gating in `packages/shared-branding/src/mana-apps.ts`
|
||||
- [ ] UI: Settings → "Premium-Recherche"
|
||||
|
||||
**Phase 3 — Agentic Research** (3–4 Wochen)
|
||||
- [ ] Multi-Step-Loop in `mana-ai` mit Follow-Up-Queries
|
||||
- [ ] Embedding-Dedup + semantisches Clustering
|
||||
- [ ] Perplexity Sonar als optionaler `research_deep` Tool
|
||||
- [ ] Zitations-UI im Frontend (Inline-Footnotes in AI-Antworten)
|
||||
|
||||
---
|
||||
|
||||
## 9. Referenzen im Code
|
||||
|
||||
| Funktion | Datei | Zeile |
|
||||
|---|---|---|
|
||||
| Web-Search Client | `apps/api/src/lib/search.ts` | `webSearch()` |
|
||||
| News-Research Routes | `apps/api/src/modules/news-research/routes.ts` | 1–220 |
|
||||
| SearXNG Integration | `services/mana-search/internal/search/searxng.go` | — |
|
||||
| Readability Extract | `services/mana-search/internal/extract/extractor.go` | — |
|
||||
| RSS-Discovery | `packages/shared-rss/src/discover.ts` | 1–129 |
|
||||
| Research-Tool (Frontend) | `apps/mana/apps/web/src/lib/modules/news-research/tools.ts` | 48–102 |
|
||||
| Pre-Planning Research Step | `services/mana-ai/src/cron/tick.ts` | 282–297 |
|
||||
| LLM Router | `services/mana-llm/src/providers/router.py` | — |
|
||||
| News Ingester | `services/news-ingester/src/sources.ts` | — |
|
||||
|
||||
---
|
||||
|
||||
**Zusammenfassung in einem Satz:** Mana hat eine solide, self-hosted RSS+SearXNG+Readability-Pipeline, die bei ~60 % der Use-Cases ausreicht — für echten agentischen Research-Qualitätsanspruch braucht es entweder Phase-1-Optimierungen (BM25, Query-Expansion, Embedding-Dedup) oder eine optionale Brücke zu Brave/Tavily/Perplexity Sonar als Premium-Feature.
|
||||
Loading…
Add table
Add a link
Reference in a new issue