128 lines
3.7 KiB
Python
128 lines
3.7 KiB
Python
"""解析模型返回为内驱力草稿(纯函数,无 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))
|