KI-Chat: Web-Recherche ueber Ollama Web Search
Der Assistent kannte bisher nur die Datenbank. Fragen wie "Was muss ich ueber die Firma wissen?" konnte er damit nicht beantworten - Groesse, Produkte, News, Kultur stehen nirgends in der Bewerbung. Zwei neue Werkzeuge (web_suche, web_seite_lesen) ueber die Web-Search-API von ollama.com. Sie authentifizieren sich mit demselben OLLAMA_API_KEY, den der Benutzer fuer Chat und Dokumente ohnehin hinterlegt hat: keine zusaetzliche Konfiguration. Anders als /api/chat haengt die Websuche nicht an OLLAMA_HOST - wer rein lokal chattet, hat schlicht keine Websuche, der Rest laeuft unveraendert weiter. - Tool-Runden 4 -> 8: Bewerbung holen, suchen, Seiten lesen, antworten lief vorher ins Limit, statt zu antworten. - Antworten der Websuche hart gedeckelt, sie wandern sonst in jeder weiteren Runde erneut ins Kontextfenster. - Status-Label zeigt, wonach gesucht wird - sonst sieht der Nutzer nicht, ob die richtige Firma erwischt wurde. - Nackte URLs im Chat sind klickbar: Belege nennt das Modell meist als reine URL, nicht als Markdown-Link. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,7 @@ const {
|
||||
DOKUMENT_TYPEN, normalizeDokumente,
|
||||
} = require('./lib/documents');
|
||||
const chat = require('./lib/chat');
|
||||
const websuche = require('./lib/websuche');
|
||||
const promptStore = require('./lib/prompts');
|
||||
const designStore = require('./lib/design');
|
||||
const mailer = require('./lib/mailer');
|
||||
@@ -3932,6 +3933,35 @@ initializeDatabase().then(async () => {
|
||||
parameters: { type: 'object', properties: {} },
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'web_suche',
|
||||
description: 'Sucht im Internet und liefert Treffer mit Titel, URL und Auszug. Nutze dies für alles, was NICHT in der Bewerbungsdatenbank steht: Hintergrund zu einer Firma (Größe, Produkte, Standorte, Kultur, aktuelle News, Geschäftsberichte, Bewertungen), Gehaltsspannen, Branchen- oder Technologiefragen, Vorbereitung auf ein Vorstellungsgespräch. Hole dir Firmenname/Stelle zuerst per bewerbung_detail aus der Datenbank und suche dann gezielt danach.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: { type: 'string', description: 'Suchanfrage in natürlicher Sprache, möglichst konkret (z. B. „Muster GmbH München Mitarbeiterzahl Produkte 2026").' },
|
||||
max_results: { type: 'integer', description: 'Anzahl Treffer (Standard 5, max 10).' },
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'web_seite_lesen',
|
||||
description: 'Lädt eine konkrete Webseite und liefert ihren Text. Nutze dies, wenn ein Treffer aus web_suche vertieft werden soll (z. B. die „Über uns"-/Karriereseite der Firma) oder wenn der Nutzer bzw. die Bewerbung eine URL nennt (quelle_url der Stellenanzeige).',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string', description: 'Vollständige URL inkl. https://' },
|
||||
},
|
||||
required: ['url'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Tool labels shown in the UI while a tool call is in flight.
|
||||
@@ -3940,8 +3970,23 @@ initializeDatabase().then(async () => {
|
||||
list_bewerbungen: 'Bewerbungen werden geladen…',
|
||||
bewerbung_detail: 'Bewerbungsdetails werden geladen…',
|
||||
kommende_termine: 'Termine werden geladen…',
|
||||
web_suche: 'Im Internet wird recherchiert…',
|
||||
web_seite_lesen: 'Webseite wird gelesen…',
|
||||
};
|
||||
|
||||
// Bei der Web-Recherche zeigt das Label mit an, wonach gesucht bzw. was geladen
|
||||
// wird — sonst steht der Nutzer vor einem stummen „recherchiert…", ohne zu
|
||||
// sehen, ob der Assistent die richtige Firma erwischt hat.
|
||||
function chatToolLabel(name, args) {
|
||||
const basis = CHAT_TOOL_LABELS[name] || name;
|
||||
const a = args || {};
|
||||
if (name === 'web_suche' && a.query) return `Im Internet wird recherchiert: „${String(a.query).slice(0, 80)}"`;
|
||||
if (name === 'web_seite_lesen' && a.url) {
|
||||
try { return `Webseite wird gelesen: ${new URL(String(a.url)).hostname}`; } catch (e) { /* Label ohne Host */ }
|
||||
}
|
||||
return basis;
|
||||
}
|
||||
|
||||
// Execute one tool call against the database. Returns a JSON-serialisable
|
||||
// value that is fed back to the model as the tool result.
|
||||
async function executeChatTool(name, args) {
|
||||
@@ -4015,6 +4060,15 @@ initializeDatabase().then(async () => {
|
||||
})),
|
||||
};
|
||||
}
|
||||
// Web-Recherche (Ollama Web Search) — Fehler werden als Tool-Ergebnis
|
||||
// zurückgegeben (runChat fängt sie ohnehin ab), damit das Modell einen
|
||||
// Ausfall der Websuche dem Nutzer sagen kann, statt den Chat abzubrechen.
|
||||
if (name === 'web_suche') {
|
||||
return websuche.suche(a.query, a.max_results);
|
||||
}
|
||||
if (name === 'web_seite_lesen') {
|
||||
return websuche.seiteLesen(a.url);
|
||||
}
|
||||
return { error: 'unbekanntes Werkzeug: ' + name };
|
||||
}
|
||||
|
||||
@@ -4156,7 +4210,7 @@ initializeDatabase().then(async () => {
|
||||
tools: CHAT_TOOLS,
|
||||
signal: controller.signal,
|
||||
onToken: (delta) => send({ type: 'token', content: delta }),
|
||||
onToolCall: (name) => send({ type: 'tool', name, label: CHAT_TOOL_LABELS[name] || name }),
|
||||
onToolCall: (name, args) => send({ type: 'tool', name, label: chatToolLabel(name, args) }),
|
||||
executeTool: executeChatTool,
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user