Files
2026-08-21 16:00:13 +08:00

128 lines
3.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""解析模型返回为内驱力草稿(纯函数,无 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))