fix: routes

This commit is contained in:
2026-03-10 11:34:01 +01:00
parent c804b051e0
commit 5144378c7e

View File

@@ -3,6 +3,11 @@ import { searchProducts } from '../../../src/lib/qdrant';
import redis from '../../../src/lib/redis';
import { z } from 'zod';
import * as Sentry from '@sentry/nextjs';
import { generateText } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
// @ts-ignore
import { createMcpTools } from '@mintel/payload-ai/tools/mcpAdapter';
export const dynamic = 'force-dynamic';
export const maxDuration = 60; // Max allowed duration (Vercel)
@@ -108,12 +113,9 @@ Das ECHTE KLZ Team:
.map((p: any) => p.payload?.content)
.join('\n\n');
const knowledgeDescriptions = searchResults
.filter((p) => p.payload?.type === 'knowledge')
.map((p: any) => p.payload?.content)
.join('\n\n');
contextStr = `KATALOG & PRODUKTE:\n${productDescriptions}\n\nKABELWISSEN (Handbuch):\n${knowledgeDescriptions}`;
if (productDescriptions) {
contextStr = `KATALOG & PRODUKTE:\n${productDescriptions}`;
}
foundProducts = searchResults
.filter((p) => (p.payload?.type === 'product' || !p.payload?.type) && p.payload?.data)
@@ -145,12 +147,14 @@ DEINE HAUPTAUFGABE: BERATEN, NICHT AUSFRAGEN!
- FRAGE NICHT nach abstrakten Dingen wie "Welchen Kabeltyp brauchst du?" -> DAS IST DEIN JOB, IHM DAS ZU SAGEN!
- FRAGE NICHT nach Längen oder genauen Trassen, es sei denn, der Kunde hat schon ganz klar gesagt, was er kaufen will.
- Biete aktiv Hilfe an: "Ich kann dir die passenden Querschnitte raussuchen, wenn du willst."
- Wenn technisches Wissen aus dem Kabelhandbuch benötigt wird, NUTZE UNBEDINGT eines der "kabelfachmann_*" Tools, anstatt zu raten oder zu behaupten du wüsstest es nicht! Das Tool weiss alles.
VORGEHEN:
1. Prüfe den KONTEXT auf passende Kabel für das Kundenprojekt.
2. Nenne direkt 1-2 passende Produktserien aus dem Kontext, die für diesen Fall Sinn machen.
3. Biete eine konkrete Hilfestellung an (z.B. Leitungsberechnung, Verfügbarkeitsprüfung) ODER stelle EINE einzige fachliche Rückfrage, um das Kabel weiter einzugrenzen (z.B. Alu oder Kupfer?).
4. Wenn das Projekt klar ist und die Kabeltypen besprochen sind, frag nach, ob ein Kollege (z.B. Micha) ein konkretes Angebot machen soll.
1. Prüfe den KONTEXT auf passende Katalog-Kabel für das Kundenprojekt.
2. Wenn du tiefgehendes Wissen zu einem Kabeltyp brauchst (z.B. Biegeradius, Normen, Querschnitte), rufe das Kabelfachmann-Tool auf.
3. Nenne direkt 1-2 passende Produktserien aus dem Kontext oder der Tool-Abfrage, die für diesen Fall Sinn machen.
4. Biete eine konkrete Hilfestellung an (z.B. Leitungsberechnung, Verfügbarkeitsprüfung) ODER stelle EINE einzige fachliche Rückfrage, um das Kabel weiter einzugrenzen (z.B. Alu oder Kupfer?).
5. Wenn das Projekt klar ist und die Kabeltypen besprochen sind, frag nach, ob ein Kollege (z.B. Micha) ein konkretes Angebot machen soll.
GRENZEN:
- PRIVAT-ANFRAGEN: B2B only. Private Hausinstallationen lehnen wir freundlich ab.
@@ -161,51 +165,42 @@ ${contextStr || 'Kein Katalogkontext verfügbar.'}
${teamContextStr}
`;
const mistralKey = process.env.MISTRAL_API_KEY;
if (!mistralKey) {
throw new Error('MISTRAL_API_KEY is not set');
const openrouterApiKey = process.env.OPENROUTER_API_KEY;
if (!openrouterApiKey) {
throw new Error('OPENROUTER_API_KEY is not set');
}
// DSGVO: Mistral AI API direkt (EU/Frankreich) statt OpenRouter (US)
const fetchRes = await fetch('https://api.mistral.ai/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${mistralKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'ministral-8b-latest',
temperature: 0.3,
max_tokens: MAX_RESPONSE_TOKENS,
messages: [
{ role: 'system', content: systemPrompt },
...cappedMessages.map((m: any) => ({
role: m.role,
content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
})),
],
}),
const openrouter = createOpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: openrouterApiKey,
});
if (!fetchRes.ok) {
const errBody = await fetchRes.text();
console.error('Mistral API Error:', errBody);
Sentry.captureException(new Error(`Mistral ${fetchRes.status}: ${errBody}`), {
tags: { context: 'ai-search-mistral' },
let mcpTools: Record<string, any> = {};
const mcpUrl = process.env.KABELFACHMANN_MCP_URL || 'http://host.docker.internal:3007/sse';
try {
const { tools } = await createMcpTools({
name: 'kabelfachmann',
url: mcpUrl
});
// Return user-friendly error based on status
const userMsg =
fetchRes.status === 429
? 'Der KI-Service ist gerade überlastet. Bitte versuche es in ein paar Sekunden erneut.'
: fetchRes.status >= 500
? 'Der KI-Service ist vorübergehend nicht erreichbar. Bitte versuche es gleich nochmal.'
: 'Es gab ein Problem mit der KI-Anfrage. Bitte versuche es erneut.';
return NextResponse.json({ error: userMsg }, { status: 502 });
mcpTools = tools;
} catch (e) {
console.warn('Failed to load MCP tools', e);
Sentry.captureException(e, { tags: { context: 'ai-search-mcp' } });
}
const data = await fetchRes.json();
const text = data.choices[0].message.content;
const { text } = await generateText({
model: openrouter('google/gemini-3.0-flash'),
system: systemPrompt,
messages: cappedMessages.map((m: any) => ({
role: m.role,
content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
})),
tools: mcpTools,
// @ts-ignore
maxSteps: 3, // Allow the model to call the tool and then respond
temperature: 0.3,
maxTokens: MAX_RESPONSE_TOKENS,
});
// Return the AI's answer along with any found products
return NextResponse.json({