llm-api/plan_router.py aktualisiert
All checks were successful
Deploy Trainer_LLM to llm-node / deploy (push) Successful in 2s
All checks were successful
Deploy Trainer_LLM to llm-node / deploy (push) Successful in 2s
This commit is contained in:
parent
798e103eb8
commit
81473e20eb
|
|
@ -1,9 +1,10 @@
|
|||
#-*- coding: utf-8 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
plan_router.py – v0.9.0 (WP-15)
|
||||
plan_router.py – v0.10.0 (WP-15)
|
||||
|
||||
Minimal-CRUD für Plan-Templates & Pläne (POST/GET) + Idempotenz via Fingerprint.
|
||||
Keine bestehenden API-Signaturen geändert. Qdrant-Client-Stil wie exercise_router.
|
||||
Erweiterung ggü. v0.9.0: optionale Section-Felder ideal/supplement + materialisierte
|
||||
Facettenfelder für Qdrant-Indizes; robustere Payload→Model-Konvertierung.
|
||||
"""
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
|
|
@ -33,6 +34,8 @@ class TemplateSection(BaseModel):
|
|||
name: str
|
||||
target_minutes: int
|
||||
must_keywords: List[str] = []
|
||||
ideal_keywords: List[str] = [] # NEU
|
||||
supplement_keywords: List[str] = [] # NEU
|
||||
forbid_keywords: List[str] = []
|
||||
capability_targets: Dict[str, int] = {}
|
||||
|
||||
|
|
@ -145,6 +148,14 @@ def _get_by_field(collection: str, key: str, value: Any) -> Optional[Dict[str, A
|
|||
payload.setdefault("id", str(pts[0].id))
|
||||
return payload
|
||||
|
||||
|
||||
def _as_model(model_cls, payload: Dict[str, Any]):
|
||||
"""Filtert unbekannte Felder heraus (Pydantic v1/v2 kompatibel)."""
|
||||
fields = getattr(model_cls, "model_fields", None) or getattr(model_cls, "__fields__", {})
|
||||
allowed = set(fields.keys())
|
||||
data = {k: payload[k] for k in payload.keys() if k in allowed}
|
||||
return model_cls(**data)
|
||||
|
||||
# -----------------
|
||||
# Endpoints
|
||||
# -----------------
|
||||
|
|
@ -152,10 +163,22 @@ def _get_by_field(collection: str, key: str, value: Any) -> Optional[Dict[str, A
|
|||
def create_plan_template(t: PlanTemplate):
|
||||
_ensure_collection(PLAN_TEMPLATE_COLLECTION)
|
||||
payload = t.model_dump()
|
||||
# Normalisierung
|
||||
payload["goals"] = _norm_list(payload.get("goals"))
|
||||
for s in payload.get("sections", []) or []:
|
||||
sections = payload.get("sections", []) or []
|
||||
for s in sections:
|
||||
s["must_keywords"] = _norm_list(s.get("must_keywords") or [])
|
||||
s["ideal_keywords"] = _norm_list(s.get("ideal_keywords") or []) # NEU
|
||||
s["supplement_keywords"] = _norm_list(s.get("supplement_keywords") or []) # NEU
|
||||
s["forbid_keywords"] = _norm_list(s.get("forbid_keywords") or [])
|
||||
|
||||
# Materialisierte Facettenfelder (stabile KEYWORD-Indizes in Qdrant)
|
||||
payload["section_names"] = _norm_list([s.get("name", "") for s in sections])
|
||||
payload["section_must_keywords"] = _norm_list([kw for s in sections for kw in (s.get("must_keywords") or [])])
|
||||
payload["section_ideal_keywords"] = _norm_list([kw for s in sections for kw in (s.get("ideal_keywords") or [])])
|
||||
payload["section_supplement_keywords"] = _norm_list([kw for s in sections for kw in (s.get("supplement_keywords") or [])])
|
||||
payload["section_forbid_keywords"] = _norm_list([kw for s in sections for kw in (s.get("forbid_keywords") or [])])
|
||||
|
||||
vec = _embed(_template_embed_text(t))
|
||||
qdrant.upsert(collection_name=PLAN_TEMPLATE_COLLECTION, points=[PointStruct(id=str(t.id), vector=vec, payload=payload)])
|
||||
return t
|
||||
|
|
@ -167,7 +190,7 @@ def get_plan_template(tpl_id: str):
|
|||
found = _get_by_field(PLAN_TEMPLATE_COLLECTION, "id", tpl_id)
|
||||
if not found:
|
||||
raise HTTPException(status_code=404, detail="not found")
|
||||
return PlanTemplate(**found)
|
||||
return _as_model(PlanTemplate, found)
|
||||
|
||||
|
||||
@router.post("/plan", response_model=Plan)
|
||||
|
|
@ -180,7 +203,7 @@ def create_plan(p: Plan):
|
|||
# Idempotenz
|
||||
existing = _get_by_field(PLAN_COLLECTION, "fingerprint", p.fingerprint)
|
||||
if existing:
|
||||
return Plan(**existing)
|
||||
return _as_model(Plan, existing)
|
||||
|
||||
# Normalisieren
|
||||
p.goals = _norm_list(p.goals)
|
||||
|
|
@ -206,4 +229,4 @@ def get_plan(plan_id: str):
|
|||
found["created_at"] = datetime.fromisoformat(found["created_at"])
|
||||
except Exception:
|
||||
pass
|
||||
return Plan(**found)
|
||||
return _as_model(Plan, found)
|
||||
Loading…
Reference in New Issue
Block a user