feat(llm): Koreferenz-Vorstufe + Tool-/Sonar-Metriken (Weg 2, Schritt 5)
Schliesst die im Tool-Calling-Eval isolierte Restkante (nl + Pronomen-aus-
History, z. B. "Leeft hij nog?" -> "Leeft Rutger Hauer nog?") und macht die
Web-Suche metrisch beobachtbar.
- Decontextualizer (app/pipeline/decontextualizer.py): loest Pronomen der
letzten Aeusserung anhand des Verlaufs auf. Bewusst gegated (kurze
Folgefrage MIT Pronomen UND History) -> kein Extra-Call im Normalfall;
best effort (bei Fehler Original behalten).
- ToolCallingLLM nutzt die Vorstufe vor complete()/stream() und zaehlt
tool_calls_total{tool=...}.
- SonarTool zaehlt sonar_calls_total{status=ok|error}.
- Verdrahtung: Registry openrouter-tools reicht den Decontextualizer durch.
Damit ist v1 von Weg 2 vollstaendig. Tests: 290 gruen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
703a695bed
commit
c36b884aab
4 changed files with 105 additions and 1 deletions
|
|
@ -17,6 +17,7 @@ from datetime import date
|
|||
|
||||
import httpx
|
||||
|
||||
from app.metrics import metrics
|
||||
from app.providers.llm.base import LLMProvider, lang_instruction, with_lang_reminder
|
||||
from app.providers.llm.openrouter import SYSTEM_PROMPT as PERSONA_PROMPT
|
||||
|
||||
|
|
@ -126,7 +127,8 @@ class _StreamAcc:
|
|||
class ToolCallingLLM(LLMProvider):
|
||||
def __init__(self, api_key: str, model: str, tools: list,
|
||||
knowledge_cutoff: str = "fall 2024", max_rounds: int = 3,
|
||||
max_retries: int = 4, temperature: float = 0.3):
|
||||
max_retries: int = 4, temperature: float = 0.3,
|
||||
decontextualizer=None):
|
||||
self.api_key = (api_key or "").strip()
|
||||
self.model = (model or "").strip()
|
||||
self.tools = {t.name: t for t in tools}
|
||||
|
|
@ -134,6 +136,13 @@ class ToolCallingLLM(LLMProvider):
|
|||
self.max_rounds = max(1, max_rounds)
|
||||
self.max_retries = max(1, max_retries)
|
||||
self.temperature = temperature
|
||||
self.decontextualizer = decontextualizer
|
||||
|
||||
async def _resolve_text(self, text: str, history, language) -> str:
|
||||
"""Koreferenz-Vorstufe (gegated) — löst Pronomen aus der History auf."""
|
||||
if self.decontextualizer is not None and history:
|
||||
return await self.decontextualizer.run(text, history, language)
|
||||
return text
|
||||
|
||||
def _initial_messages(self, text: str, history, language) -> list[dict]:
|
||||
if not self.api_key:
|
||||
|
|
@ -181,6 +190,7 @@ class ToolCallingLLM(LLMProvider):
|
|||
"""Führt einen Tool-Aufruf aus und liefert den tool-Message-Inhalt."""
|
||||
fn = tool_call.get("function", {})
|
||||
name = fn.get("name", "")
|
||||
metrics.inc("tool_calls_total", {"tool": name or "unknown"})
|
||||
tool = self.tools.get(name)
|
||||
if tool is None:
|
||||
logger.warning("Unbekanntes Tool angefragt: %r", name)
|
||||
|
|
@ -196,6 +206,7 @@ class ToolCallingLLM(LLMProvider):
|
|||
|
||||
async def complete(self, text: str, history: list[dict] | None = None,
|
||||
session_id: str | None = None, language: str | None = None) -> str:
|
||||
text = await self._resolve_text(text, history, language)
|
||||
messages = self._initial_messages(text, history, language)
|
||||
|
||||
for _ in range(self.max_rounds):
|
||||
|
|
@ -228,6 +239,7 @@ class ToolCallingLLM(LLMProvider):
|
|||
durch. `on_tool_start(language)` (optional) feuert, bevor ein Tool läuft —
|
||||
Aufhänger für den ephemeren Filler (Schritt 4b).
|
||||
"""
|
||||
text = await self._resolve_text(text, history, language)
|
||||
messages = self._initial_messages(text, history, language)
|
||||
|
||||
for _ in range(self.max_rounds):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue