"""解析模型返回为内驱力草稿(纯函数,无 IO)。 好奇问题可内嵌次生目标([内驱力生成器设计] ../../docs/domain/core-domains.md §6:好奇问题→转化次生目标)。缺省取**安全悲观默认**:``risk_level=L4``、 ``source=reflection``,避免模型漏字段时生成高风险探索。 """ import json import re from dataclasses import dataclass, field from typing import Any @dataclass class SubgoalDraft: title: str description: str expected_information_gain: float = 0.5 interruption_cost: float = 0.5 risk_level: str = "L4" @dataclass class CuriosityDraft: question: str source: str = "reflection" related_entities: list[str] = field(default_factory=list) expected_value: float = 0.5 urgency: float = 0.5 risk_level: str = "L4" subgoals: list[SubgoalDraft] = field(default_factory=list) _VALID_SOURCES = { "world_uncertainty", "memory_gap", "perception_change", "goal_tension", "reflection", "time_rhythm", } _VALID_LEVELS = {"L0", "L1", "L2", "L3", "L4"} def parse_curiosities(raw: str) -> list[CuriosityDraft]: drafts: list[CuriosityDraft] = [] for item in _extract_json_array(raw): if not isinstance(item, dict): continue question = str(item.get("question", "")).strip() if not question: continue drafts.append( CuriosityDraft( question=question, source=_source(item.get("source")), related_entities=_str_list(item.get("related_entities")), expected_value=_clamp_float(item.get("expected_value"), 0.5), urgency=_clamp_float(item.get("urgency"), 0.5), risk_level=_level(item.get("risk_level")), subgoals=_parse_subgoals(item.get("subgoals")), ) ) return drafts def _parse_subgoals(value: Any) -> list[SubgoalDraft]: if not isinstance(value, list): return [] result: list[SubgoalDraft] = [] for item in value: if not isinstance(item, dict): continue title = str(item.get("title", "")).strip() description = str(item.get("description", "")).strip() if not title or not description: continue result.append( SubgoalDraft( title=title, description=description, expected_information_gain=_clamp_float(item.get("expected_information_gain"), 0.5), interruption_cost=_clamp_float(item.get("interruption_cost"), 0.5), risk_level=_level(item.get("risk_level")), ) ) return result def _extract_json_array(raw: str) -> list[Any]: text = raw.strip() matched = re.search(r"```(?:json)?\s*(\[.*?\])\s*```", text, re.DOTALL) if matched: text = matched.group(1) else: start, end = text.find("["), text.rfind("]") if start != -1 and end > start: text = text[start : end + 1] try: data = json.loads(text) except json.JSONDecodeError: return [] return data if isinstance(data, list) else [] def _source(value: Any) -> str: text = str(value or "").strip() return text if text in _VALID_SOURCES else "reflection" def _level(value: Any) -> str: text = str(value or "").strip().upper() return text if text in _VALID_LEVELS else "L4" def _str_list(value: Any) -> list[str]: if not isinstance(value, list): return [] return [str(v) for v in value if v is not None] def _clamp_float(value: Any, default: float) -> float: try: num = float(value) except (TypeError, ValueError): return default return max(0.0, min(1.0, num))