- Updated provenance priorities and introduced a mapping from internal provenance values to EdgeDTO-compliant literals. - Added a new function `normalize_provenance` to standardize internal provenance strings. - Enhanced the `_edge` function to include an `is_internal` flag and provenance normalization. - Modified the `EdgeDTO` model to include a new `source_hint` field for detailed provenance information and an `is_internal` flag for intra-note edges. - Reduced the provenance options in `EdgeDTO` to valid literals, improving data integrity.
183 lines
6.3 KiB
Python
183 lines
6.3 KiB
Python
"""
|
|
FILE: app/models/dto.py
|
|
DESCRIPTION: Pydantic-Modelle (DTOs) für Request/Response Bodies. Definiert das API-Schema.
|
|
VERSION: 0.7.1 (WP-25: Stream-Tracing Support)
|
|
STATUS: Active
|
|
DEPENDENCIES: pydantic, typing, uuid
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
from pydantic import BaseModel, Field
|
|
from typing import List, Literal, Optional, Dict, Any
|
|
import uuid
|
|
|
|
# WP-25: Erweiterte Kanten-Typen gemäß neuer decision_engine.yaml
|
|
EdgeKind = Literal[
|
|
"references", "references_at", "backlink", "next", "prev",
|
|
"belongs_to", "depends_on", "related_to", "similar_to",
|
|
"caused_by", "derived_from", "based_on", "solves", "blocks",
|
|
"uses", "guides", "enforced_by", "implemented_in", "part_of",
|
|
"experienced_in", "impacts", "risk_of"
|
|
]
|
|
|
|
|
|
# --- Basis-DTOs ---
|
|
|
|
class NodeDTO(BaseModel):
|
|
"""Darstellung eines Knotens (Note oder Chunk) im API-Graph."""
|
|
id: str
|
|
type: Literal["note", "chunk"]
|
|
title: Optional[str] = None
|
|
note_id: Optional[str] = None
|
|
tags: Optional[List[str]] = None
|
|
in_degree: Optional[int] = None
|
|
out_degree: Optional[int] = None
|
|
score: Optional[float] = None
|
|
section_title: Optional[str] = None
|
|
section_path: Optional[str] = None
|
|
path: Optional[str] = None
|
|
|
|
|
|
class EdgeDTO(BaseModel):
|
|
"""Darstellung einer Kante im API-Graph."""
|
|
id: str
|
|
kind: str
|
|
source: str
|
|
target: str
|
|
weight: float
|
|
direction: Literal["out", "in", "undirected"] = "out"
|
|
# WP-26 v1.0: Provenance auf valide Literale reduziert (EdgeDTO-Constraint)
|
|
# Detail-Informationen werden über source_hint transportiert
|
|
provenance: Optional[Literal["explicit", "rule", "smart", "structure"]] = "explicit"
|
|
# WP-26 v1.0: Neues Feld für Detail-Informationen zur Herkunft
|
|
source_hint: Optional[Literal[
|
|
"callout", "wikilink", "inline_rel", "schema_default", "note_scope",
|
|
"note_zone", "belongs_to", "order", "backlink", "edge_defaults", "global_pool"
|
|
]] = None
|
|
confidence: float = 1.0
|
|
target_section: Optional[str] = None
|
|
# WP-26 v1.0: Flag für Intra-Note-Edges
|
|
is_internal: Optional[bool] = None
|
|
|
|
|
|
# --- Request Models ---
|
|
|
|
class QueryRequest(BaseModel):
|
|
"""
|
|
Request für /query. Unterstützt Multi-Stream Isolation via filters.
|
|
WP-24c v4.1.0: Erweitert um Section-Filtering und Scope-Awareness.
|
|
"""
|
|
mode: Literal["semantic", "edge", "hybrid"] = "hybrid"
|
|
query: Optional[str] = None
|
|
query_vector: Optional[List[float]] = None
|
|
top_k: int = 10
|
|
expand: Dict = {"depth": 1, "edge_types": ["references", "belongs_to", "prev", "next", "depends_on", "related_to"]}
|
|
filters: Optional[Dict] = None
|
|
ret: Dict = {"with_paths": True, "with_notes": True, "with_chunks": True}
|
|
explain: bool = False
|
|
|
|
# WP-22/25: Dynamische Gewichtung der Graphen-Highways
|
|
boost_edges: Optional[Dict[str, float]] = None
|
|
|
|
# WP-24c v4.1.0: Section-Filtering für präzise Section-Links
|
|
target_section: Optional[str] = None
|
|
|
|
|
|
class FeedbackRequest(BaseModel):
|
|
"""User-Feedback zu einem spezifischen Treffer oder der Gesamtantwort."""
|
|
query_id: str = Field(..., description="ID der ursprünglichen Suche")
|
|
node_id: str = Field(..., description="ID des bewerteten Treffers oder 'generated_answer'")
|
|
score: int = Field(..., ge=1, le=5, description="1 (Irrelevant) bis 5 (Perfekt)")
|
|
comment: Optional[str] = None
|
|
|
|
|
|
class ChatRequest(BaseModel):
|
|
"""Request für /chat (WP-25 Einstieg)."""
|
|
message: str = Field(..., description="Die Nachricht des Users")
|
|
conversation_id: Optional[str] = Field(None, description="ID für Chat-Verlauf")
|
|
top_k: int = 5
|
|
explain: bool = False
|
|
|
|
|
|
# --- Explanation Models ---
|
|
|
|
class ScoreBreakdown(BaseModel):
|
|
"""Aufschlüsselung der Score-Komponenten nach der WP-22 Formel."""
|
|
semantic_contribution: float
|
|
edge_contribution: float
|
|
centrality_contribution: float
|
|
raw_semantic: float
|
|
raw_edge_bonus: float
|
|
raw_centrality: float
|
|
node_weight: float
|
|
status_multiplier: float = 1.0
|
|
graph_boost_factor: float = 1.0
|
|
|
|
|
|
class Reason(BaseModel):
|
|
"""Ein semantischer Grund für das Ranking."""
|
|
# WP-25: 'status' hinzugefügt für Synchronität mit retriever.py
|
|
kind: Literal["semantic", "edge", "type", "centrality", "lifecycle", "status"]
|
|
message: str
|
|
score_impact: Optional[float] = None
|
|
details: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
class Explanation(BaseModel):
|
|
"""Container für alle Erklärungsdaten eines Treffers."""
|
|
breakdown: ScoreBreakdown
|
|
reasons: List[Reason]
|
|
related_edges: Optional[List[EdgeDTO]] = None
|
|
applied_intent: Optional[str] = None
|
|
applied_boosts: Optional[Dict[str, float]] = None
|
|
|
|
|
|
# --- Response Models ---
|
|
|
|
class QueryHit(BaseModel):
|
|
"""
|
|
Einzelnes Trefferobjekt.
|
|
WP-25: stream_origin hinzugefügt für Tracing und Feedback-Optimierung.
|
|
WP-24c v4.1.0: source_chunk_id für RAG-Kontext hinzugefügt.
|
|
"""
|
|
node_id: str
|
|
note_id: str
|
|
semantic_score: float
|
|
edge_bonus: float
|
|
centrality_bonus: float
|
|
total_score: float
|
|
paths: Optional[List[List[Dict]]] = None
|
|
source: Optional[Dict] = None
|
|
payload: Optional[Dict] = None
|
|
explanation: Optional[Explanation] = None
|
|
stream_origin: Optional[str] = Field(None, description="Name des Ursprungs-Streams")
|
|
source_chunk_id: Optional[str] = Field(None, description="Chunk-ID der Quelle (für RAG-Kontext)")
|
|
|
|
|
|
class QueryResponse(BaseModel):
|
|
"""Antwortstruktur für /query (wird von DecisionEngine Streams genutzt)."""
|
|
query_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
results: List[QueryHit]
|
|
used_mode: str
|
|
latency_ms: int
|
|
|
|
|
|
class GraphResponse(BaseModel):
|
|
"""Antwortstruktur für /graph/{note_id}."""
|
|
center_note_id: str
|
|
nodes: List[NodeDTO]
|
|
edges: List[EdgeDTO]
|
|
stats: Dict[str, int]
|
|
|
|
|
|
class ChatResponse(BaseModel):
|
|
"""
|
|
Antwortstruktur für /chat.
|
|
WP-25: 'intent' spiegelt nun die gewählte Strategie wider.
|
|
"""
|
|
query_id: str = Field(..., description="Traceability ID")
|
|
answer: str = Field(..., description="Generierte Antwort vom LLM")
|
|
sources: List[QueryHit] = Field(..., description="Die genutzten Quellen (alle Streams)")
|
|
latency_ms: int
|
|
intent: Optional[str] = Field("FACT", description="Die gewählte WP-25 Strategie")
|
|
intent_source: Optional[str] = Field("LLM_Router", description="Quelle der Intent-Erkennung") |