shinkan-jinkendo/backend/planning_exercise_expectation.py

70 lines
2.7 KiB
Python
Raw Normal View History

"""
Preset „Nächste aus Kontext“: LLM leitet Erwartungsprofil aus Planungskontext ab.
Prompt: planning_exercise_expectation_profile (Migration 074)
"""
from __future__ import annotations
import logging
from typing import Any, Dict, Mapping, Optional, Tuple
from planning_exercise_intent import (
PlanningQueryIntentParsed,
_compact_json,
_load_compact_catalog,
_load_skills_catalog_compact,
parse_planning_query_intent_response,
)
from ai_prompt_runtime import AiPromptUnavailableError, load_and_render_ai_prompt
from openrouter_chat import (
effective_openrouter_model_for_prompt_row,
normalize_openrouter_env,
openrouter_chat_completion,
)
_logger = logging.getLogger("shinkan.planning_exercise_expectation")
def try_build_planning_expectation_from_context(
cur,
*,
heuristic_intent: str,
context_summary: Mapping[str, Any],
target_profile_summary: Mapping[str, Any],
) -> Tuple[Optional[PlanningQueryIntentParsed], bool]:
"""
LLM-Erwartungsprofil für preset_next / leere Anfrage mit Planungsbezug.
Returns (parsed overlay, applied).
"""
api_key, _ = normalize_openrouter_env()
if not api_key:
return None, False
variables = {
"heuristic_intent": heuristic_intent or "suggest_next",
"planning_context_json": _compact_json(dict(context_summary or {})),
"target_profile_json": _compact_json(dict(target_profile_summary or {})),
"skills_catalog_json": _compact_json(_load_skills_catalog_compact(cur)),
"focus_areas_catalog_json": _compact_json(_load_compact_catalog(cur, "focus_areas", "id")),
"training_types_catalog_json": _compact_json(_load_compact_catalog(cur, "training_types", "id")),
"style_directions_catalog_json": _compact_json(_load_compact_catalog(cur, "style_directions", "id")),
"target_groups_catalog_json": _compact_json(_load_compact_catalog(cur, "target_groups", "id")),
}
try:
prow, rendered = load_and_render_ai_prompt(cur, "planning_exercise_expectation_profile", variables)
model = effective_openrouter_model_for_prompt_row(prow)
raw = openrouter_chat_completion(api_key=api_key, model=model, user_content=rendered.text)
parsed = parse_planning_query_intent_response(raw)
if parsed.scenario not in ("preset_next", "continue_plan", "free_search"):
parsed = parsed.model_copy(update={"scenario": "preset_next"})
return parsed, True
except AiPromptUnavailableError:
return None, False
except Exception as exc:
_logger.warning("Planungs-Erwartungsprofil-LLM fehlgeschlagen: %s", exc)
return None, False
__all__ = ["try_build_planning_expectation_from_context"]