mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-17 03:59:40 +02:00
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>
59 lines
1.5 KiB
TypeScript
59 lines
1.5 KiB
TypeScript
/**
|
|
* Redis cache wrapper. Graceful degradation — if Redis is down, cache methods
|
|
* return null (miss) and set() is a no-op so the service still works.
|
|
*/
|
|
|
|
import Redis from 'ioredis';
|
|
import { createHash } from 'node:crypto';
|
|
|
|
let redis: Redis | null = null;
|
|
|
|
export function initCache(redisUrl: string) {
|
|
if (redis) return redis;
|
|
redis = new Redis(redisUrl, {
|
|
lazyConnect: true,
|
|
maxRetriesPerRequest: 2,
|
|
enableOfflineQueue: false,
|
|
});
|
|
redis.on('error', (err) => {
|
|
console.warn('[cache] redis error:', err.message);
|
|
});
|
|
redis.connect().catch((err) => {
|
|
console.warn('[cache] connect failed, running without cache:', err.message);
|
|
});
|
|
return redis;
|
|
}
|
|
|
|
export function cacheKey(
|
|
category: string,
|
|
providerId: string,
|
|
query: string,
|
|
opts: unknown
|
|
): string {
|
|
const h = createHash('sha256');
|
|
h.update(providerId);
|
|
h.update('\0');
|
|
h.update(query);
|
|
h.update('\0');
|
|
h.update(JSON.stringify(opts ?? {}));
|
|
return `research:${category}:${providerId}:${h.digest('hex').slice(0, 32)}`;
|
|
}
|
|
|
|
export async function cacheGet<T>(key: string): Promise<T | null> {
|
|
if (!redis || redis.status !== 'ready') return null;
|
|
try {
|
|
const raw = await redis.get(key);
|
|
return raw ? (JSON.parse(raw) as T) : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function cacheSet<T>(key: string, value: T, ttlSeconds: number): Promise<void> {
|
|
if (!redis || redis.status !== 'ready') return;
|
|
try {
|
|
await redis.setex(key, ttlSeconds, JSON.stringify(value));
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|