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:
Dieter Schlüter 2026-06-29 21:43:24 +02:00
commit c36b884aab
4 changed files with 105 additions and 1 deletions

View file

@ -11,6 +11,7 @@ from dataclasses import dataclass, field
import httpx
from app.metrics import metrics
from app.providers.llm.base import lang_instruction
logger = logging.getLogger(__name__)
@ -58,6 +59,11 @@ class SonarTool:
{"role": "user", "content": query.strip()}]
async def run(self, query: str, language: str | None = None) -> ToolResult:
result = await self._search(query, language)
metrics.inc("sonar_calls_total", {"status": "ok" if result.ok else "error"})
return result
async def _search(self, query: str, language: str | None = None) -> ToolResult:
if not query or not query.strip():
return ToolResult(text=_NO_DATA, ok=False)
if not self.api_key: