M8:天工阁、叩心与辰正——受托任务后台并行、叩心四约束、时态感知投影
- 天工阁域 domains/tiangong:backstage_tasks(委托包+验收标准+结果证据);有限分解(parent 链)与受控并行(各自后台 Run);子任务全终态级联父任务进入待验收;按证据 accept/reject - kernel.start_backstage_run(kind=background_task):loop 后台分支不占前台席位、与前台对话并行;断言放宽容纳无人物功能任务;结果只经回调回呈任务——无身份写入路径 - 叩心域 domains/kouxin:knocking_candidates(event/time_anchor/manual 触发 + 草稿 + 裁决快照);四约束裁决(总开关→静默窗口【本地时区判定】→24h 频率→预算),dispatch 未过 429 且快照留痕;候选默认不自动发送;expire_stale 过期推进不触碰缘契 - 辰正 domains/zhengchen:temporal_context() 复用 time_sense 计算核(快照/农历/季节),产出【时态感知】投影段接入镜树生成 prompt(客观时间语境,不附情绪臆测——守一纪律) - 退役:modules/goal_orchestrator|planner|perception|big_cycle、application/planning、services/governance|goal_persist、candidates 表链(imports 候选抽取 API 面)、goals/plans/agent_roster/perception_channels/big_cycles/旧候选表(迁移 009);前端 admin goals/plans/agents/perception/world 页与六组件;governance 只留 GenerationPreferences;admin 治理面收敛为司命+百工两枢;pivots 健康以域表计数重建 - 验收(test_m8_tiangong 五用例):后台任务无身份写入(记忆/魂籍/缘契零新增);后台与前台并行;分解级联;叩心四约束逐项;不回应不降低关系(缘契维度/状态/版本零变化) - 四套件:后端 345、前端 203、BFF 4、神识 5 包全绿(2 个预存环境失败不变);迁移 009 净库干净
This commit is contained in:
1 parent
b419e0afde
commit
c63b75152a
68 files changed
+1205
-4750
No files matched your search
@@ -1 +0,0 @@
|
||||
"""大周天演化候选领域纯逻辑(造消息 / 解析候选草稿,禁止 IO)。"""
|
||||
@@ -1,114 +0,0 @@
|
||||
"""解析模型返回为演化候选草稿(纯函数,无 IO)。
|
||||
|
||||
模型可能返回纯 JSON、markdown 代码块或夹杂解释文本;本模块容错提取首个 JSON
|
||||
数组,并归一化为演化候选草稿。``evidence`` 序号由 application 映射为记忆 ID。
|
||||
|
||||
缺省字段取**安全悲观默认**:``level=L4``(必须人工确认)、``auto_apply_allowed=False``,
|
||||
避免模型漏字段时误自动应用高风险演化。
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class EvolutionCandidateDraft:
|
||||
type: str
|
||||
title: str
|
||||
proposal: str
|
||||
level: str = "L4"
|
||||
scope: str = "character_scope"
|
||||
source: str = "big_cycle"
|
||||
delta: dict[str, Any] = field(default_factory=dict)
|
||||
confidence: float = 0.5
|
||||
benefit_score: float = 0.5
|
||||
risk_score: float = 0.5
|
||||
drift_score: float = 0.0
|
||||
auto_apply_allowed: bool = False
|
||||
evidence: list[int] = field(default_factory=list)
|
||||
|
||||
|
||||
def parse_evolution_candidates(raw: str) -> list[EvolutionCandidateDraft]:
|
||||
drafts: list[EvolutionCandidateDraft] = []
|
||||
for item in _extract_json_array(raw):
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
type_ = str(item.get("type", "")).strip()
|
||||
title = str(item.get("title", "")).strip()
|
||||
proposal = str(item.get("proposal", "")).strip()
|
||||
if not type_ or not title or not proposal:
|
||||
continue
|
||||
drafts.append(
|
||||
EvolutionCandidateDraft(
|
||||
type=type_,
|
||||
title=title,
|
||||
proposal=proposal,
|
||||
level=_level(item.get("level")),
|
||||
scope=_scope(item.get("scope")),
|
||||
source=str(item.get("source", "big_cycle")).strip() or "big_cycle",
|
||||
delta=_dict(item.get("delta")),
|
||||
confidence=_clamp_float(item.get("confidence"), 0.5),
|
||||
benefit_score=_clamp_float(item.get("benefit_score"), 0.5),
|
||||
risk_score=_clamp_float(item.get("risk_score"), 0.5),
|
||||
drift_score=_clamp_float(item.get("drift_score"), 0.0),
|
||||
auto_apply_allowed=bool(item.get("auto_apply_allowed", False)),
|
||||
evidence=_int_list(item.get("evidence")),
|
||||
)
|
||||
)
|
||||
return drafts
|
||||
|
||||
|
||||
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 []
|
||||
|
||||
|
||||
_VALID_LEVELS = {"L0", "L1", "L2", "L3", "L4"}
|
||||
_VALID_SCOPES = {"character_scope", "user_scope", "task_scope", "global_scope"}
|
||||
|
||||
|
||||
def _level(value: Any) -> str:
|
||||
text = str(value or "").strip().upper()
|
||||
return text if text in _VALID_LEVELS else "L4"
|
||||
|
||||
|
||||
def _scope(value: Any) -> str:
|
||||
text = str(value or "").strip()
|
||||
return text if text in _VALID_SCOPES else "character_scope"
|
||||
|
||||
|
||||
def _dict(value: Any) -> dict[str, Any]:
|
||||
return value if isinstance(value, dict) else {}
|
||||
|
||||
|
||||
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))
|
||||
|
||||
|
||||
def _int_list(value: Any) -> list[int]:
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
result: list[int] = []
|
||||
for item in value:
|
||||
try:
|
||||
result.append(int(item))
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
return result
|
||||
@@ -1,78 +0,0 @@
|
||||
"""大周天演化候选 prompt 构建(纯函数,无 IO)。
|
||||
|
||||
接收 ``EvaluationSnapshot``(由 application 阶段从 ORM 组装的态势快照),产出
|
||||
``(system, user)`` 消息对,喂给模型网关。模型返回的 JSON 由 ``extract`` 模块解析。
|
||||
|
||||
枚举取值以 [契约字典](../../docs/contracts/contract-glossary.md) §12 与
|
||||
[自我演化机制设计](../../docs/domain/core-domains.md) §11 为准。
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from app.modules.evolution import LEVELS as EVOLUTION_LEVELS
|
||||
from app.modules.evolution import SCOPES as EVOLUTION_SCOPES
|
||||
from app.modules.evolution import TYPES as EVOLUTION_TYPES
|
||||
|
||||
EVOLUTION_TYPES = EVOLUTION_TYPES
|
||||
EVOLUTION_LEVELS = EVOLUTION_LEVELS
|
||||
EVOLUTION_SCOPES = EVOLUTION_SCOPES
|
||||
|
||||
|
||||
@dataclass
|
||||
class EvaluationSnapshot:
|
||||
"""大周天自我评估态势快照(application 组装,供候选生成消费)。"""
|
||||
|
||||
character_id: str
|
||||
memory_count: int
|
||||
recent_memories: list[tuple[str, str]] # (title, content)
|
||||
personality_summary: str
|
||||
world_entity_count: int
|
||||
conclusion: str
|
||||
evidence_ids: list[str] = field(default_factory=list) # 记忆 ID,供 evidence 序号映射
|
||||
cumulative_drift: float = 0.0 # 最近大周天的累积漂移值
|
||||
drift_budget_remaining: float = 0.0 # 剩余漂移预算
|
||||
|
||||
|
||||
def build_evolution_extraction_messages(
|
||||
snapshot: EvaluationSnapshot,
|
||||
) -> tuple[str, str]:
|
||||
system = (
|
||||
"你是魂镜的大周天自我演化引擎。基于本轮自我评估的态势快照,生成轻量、可解释、"
|
||||
"可回滚的演化候选——只调整人格表达、程序记忆、工具策略等轻量参数,"
|
||||
"不改变核心使命、安全边界或高敏关系判断。"
|
||||
f"type 取自:{', '.join(EVOLUTION_TYPES)}。"
|
||||
f"level 取自:{', '.join(EVOLUTION_LEVELS)}"
|
||||
"(L0-L3 低风险可半自动,L4 核心人格/使命/安全边界必须人工确认)。"
|
||||
f"scope 取自:{', '.join(EVOLUTION_SCOPES)}。"
|
||||
'以 JSON 数组返回,每个元素:{"type": str, "source": "big_cycle", '
|
||||
'"level": str, "scope": str, "title": str, "proposal": str, '
|
||||
'"delta": 对象, "confidence": 0~1, "benefit_score": 0~1, '
|
||||
'"risk_score": 0~1, "auto_apply_allowed": bool, "evidence": [记忆序号]}。'
|
||||
"delta 为具体增量(如人格字段覆盖对象、程序记忆 title/content)。"
|
||||
"evidence 为下方记忆摘要的序号([0]/[1]…)列表。"
|
||||
"仅在态势充分支撑时提议,宁缺毋滥。若本轮无需演化返回 []。"
|
||||
"只返回 JSON 数组,不要解释或 markdown。"
|
||||
)
|
||||
user = (
|
||||
f"记忆总数:{snapshot.memory_count}\n"
|
||||
f"世界实体数:{snapshot.world_entity_count}\n"
|
||||
f"自我评估结论:{snapshot.conclusion or '(无明显态势)'}\n"
|
||||
f"当前人格摘要:\n{snapshot.personality_summary or '(尚未形成)'}\n"
|
||||
f"近期记忆摘要:\n{_format_memories(snapshot.recent_memories)}"
|
||||
)
|
||||
# 人格权重向量:将漂移信息注入 prompt,引导 LLM 保守生成
|
||||
if snapshot.cumulative_drift > 0:
|
||||
user += (
|
||||
f"\n\n⚠️ 人格漂移警告:累积漂移 {snapshot.cumulative_drift:.2f} / "
|
||||
f"预算 {snapshot.drift_budget_remaining + snapshot.cumulative_drift:.1f}。"
|
||||
f"剩余漂移预算 {snapshot.drift_budget_remaining:.1f}。"
|
||||
f"请尽可能保守,减少高风险 proposal,优先微调而非大幅改动。"
|
||||
)
|
||||
return system, user
|
||||
|
||||
|
||||
def _format_memories(memories: list[tuple[str, str]]) -> str:
|
||||
if not memories:
|
||||
return "(暂无)"
|
||||
lines = [f"[{idx}] {title}:{content}" for idx, (title, content) in enumerate(memories)]
|
||||
return "\n".join(lines)
|
||||
@@ -1,69 +0,0 @@
|
||||
"""太一枢(Goal Orchestrator)领域逻辑。
|
||||
|
||||
v1 落地 §7.1 最小闭环 + §7.2 预留扩展:
|
||||
- 从用户消息提取会话目标写入 ``run.goal``,并注入运行契约。
|
||||
- 活跃目标按优先级排序(``prioritize_active_goals``),供运行时注入。
|
||||
|
||||
目标持久化体系(``goals`` 表)、调度与冲突裁决属 §7.2 / §7.3。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.modules.goal_orchestrator.extract import (
|
||||
extract_session_goal,
|
||||
format_goal_directive,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"extract_session_goal",
|
||||
"format_goal_directive",
|
||||
"prioritize_active_goals",
|
||||
"complete_session_goal",
|
||||
]
|
||||
|
||||
|
||||
def prioritize_active_goals(
|
||||
goals: list[dict],
|
||||
) -> list[dict]:
|
||||
"""活跃目标优先级排序。
|
||||
|
||||
按以下规则返回排序后的目标列表(最高优先级在前):
|
||||
1. ``priority`` 越小越优先(低数值 = 高优先级)
|
||||
2. ``priority`` 相同时 ``progress`` 越低越优先(未完成的优先)
|
||||
3. 层级越靠前越优先(L0 > L1 > L2 > L3 > L4)
|
||||
|
||||
Args:
|
||||
goals: 活跃目标字典列表,每个字典包含 ``title`` / ``level`` / ``priority`` / ``progress``。
|
||||
|
||||
Returns:
|
||||
排序后的目标列表。
|
||||
"""
|
||||
LEVEL_ORDER = {"L0": 0, "L1": 1, "L2": 2, "L3": 3, "L4": 4}
|
||||
|
||||
def _sort_key(g: dict) -> tuple:
|
||||
priority = g.get("priority")
|
||||
if priority is None:
|
||||
priority = 100
|
||||
progress = g.get("progress", 0) or 0
|
||||
level = LEVEL_ORDER.get(str(g.get("level", "")), 99)
|
||||
return (0 if priority is not None else 1, priority, level, progress)
|
||||
|
||||
return sorted(goals, key=_sort_key)
|
||||
|
||||
|
||||
def complete_session_goal(
|
||||
goal: dict,
|
||||
) -> dict:
|
||||
"""将会话目标标记为完成。
|
||||
|
||||
Args:
|
||||
goal: 会话目标字典,包含 ``id`` / ``title`` / ``level`` / ``status``。
|
||||
|
||||
Returns:
|
||||
更新后的目标字典(status=completed, progress=1.0)。
|
||||
"""
|
||||
return {
|
||||
**goal,
|
||||
"status": "completed",
|
||||
"progress": 1.0,
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
"""太一会话目标提取(纯函数,无 IO)。
|
||||
|
||||
从用户消息提取会话目标(L2)。v1 采用规则提取——用户消息本身即目标的最直接
|
||||
表达,清理与截断后写入 ``run.goal`` 并注入运行契约。LLM 提取为后续增强。
|
||||
"""
|
||||
|
||||
_MAX_GOAL_CHARS = 120
|
||||
|
||||
|
||||
def extract_session_goal(content: str) -> str:
|
||||
"""从用户消息内容提取会话目标摘要。
|
||||
|
||||
规则:去除首尾空白,超长截断到 ``_MAX_GOAL_CHARS``;空内容返回空串。
|
||||
"""
|
||||
goal = (content or "").strip()
|
||||
if not goal:
|
||||
return ""
|
||||
return goal[:_MAX_GOAL_CHARS]
|
||||
|
||||
|
||||
def format_goal_directive(goal: str) -> str:
|
||||
"""把会话目标格式化为运行契约的 system instruction 片段;空目标返回空串。"""
|
||||
if not goal:
|
||||
return ""
|
||||
return f"当前会话目标:{goal}"
|
||||
@@ -1 +0,0 @@
|
||||
"""感知领域模块(纯逻辑,无 IO)。"""
|
||||
@@ -1 +0,0 @@
|
||||
"""视觉感知领域模块。"""
|
||||
@@ -1,93 +0,0 @@
|
||||
"""
|
||||
OCR 结果后处理(纯逻辑,无 IO)。
|
||||
|
||||
对 OCR 返回的原始结果做结构化解析、置信度过滤、阅读顺序修正。
|
||||
不依赖任何外部资源,可独立测试。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def filter_low_confidence(
|
||||
blocks: list[dict[str, Any]],
|
||||
threshold: float = 0.5,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""过滤低置信度结果。
|
||||
|
||||
Args:
|
||||
blocks: 原始 OCR 块列表。
|
||||
threshold: 置信度阈值(默认 0.5)。
|
||||
|
||||
Returns:
|
||||
过滤后的块列表。
|
||||
"""
|
||||
return [b for b in blocks if b.get("confidence", 0) >= threshold]
|
||||
|
||||
|
||||
def merge_adjacent_lines(
|
||||
blocks: list[dict[str, Any]],
|
||||
y_tolerance: float = 10.0,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""合并同一行的相邻块。
|
||||
|
||||
Args:
|
||||
blocks: OCR 块列表(需含 bbox 坐标)。
|
||||
y_tolerance: Y 轴容差(px),同一行最大差异。
|
||||
|
||||
Returns:
|
||||
合并后的块列表。
|
||||
"""
|
||||
if not blocks:
|
||||
return []
|
||||
|
||||
sorted_blocks = sorted(blocks, key=lambda b: (_center_y(b), _center_x(b)))
|
||||
|
||||
merged: list[dict[str, Any]] = []
|
||||
current = sorted_blocks[0]
|
||||
|
||||
for block in sorted_blocks[1:]:
|
||||
if abs(_center_y(block) - _center_y(current)) <= y_tolerance:
|
||||
current["text"] += block["text"]
|
||||
current["confidence"] = max(current["confidence"], block["confidence"])
|
||||
else:
|
||||
merged.append(current)
|
||||
current = block
|
||||
|
||||
merged.append(current)
|
||||
return merged
|
||||
|
||||
|
||||
def extract_text(
|
||||
blocks: list[dict[str, Any]],
|
||||
separator: str = "",
|
||||
) -> str:
|
||||
"""从 OCR 块中提取纯文本。
|
||||
|
||||
Args:
|
||||
blocks: OCR 块列表。
|
||||
separator: 块之间的分隔符。
|
||||
|
||||
Returns:
|
||||
拼接后的文本。
|
||||
"""
|
||||
return separator.join(b["text"] for b in blocks)
|
||||
|
||||
|
||||
def _center_y(block: dict[str, Any]) -> float:
|
||||
"""获取块的中心 Y 坐标。"""
|
||||
pts = block.get("bbox", {}).get("points", block.get("bbox", []))
|
||||
if pts and isinstance(pts, list) and len(pts) > 0:
|
||||
ys = [p[1] for p in pts if isinstance(p, (list, tuple)) and len(p) >= 2]
|
||||
return sum(ys) / len(ys) if ys else 0.0
|
||||
return 0.0
|
||||
|
||||
|
||||
def _center_x(block: dict[str, Any]) -> float:
|
||||
"""获取块的中心 X 坐标。"""
|
||||
pts = block.get("bbox", {}).get("points", block.get("bbox", []))
|
||||
if pts and isinstance(pts, list) and len(pts) > 0:
|
||||
xs = [p[0] for p in pts if isinstance(p, (list, tuple)) and len(p) >= 2]
|
||||
return sum(xs) / len(xs) if xs else 0.0
|
||||
return 0.0
|
||||
@@ -1,166 +0,0 @@
|
||||
"""洛书枢 deliberative 规划器领域模块。
|
||||
|
||||
纯数据结构和纯函数(禁止 IO),定义显式计划的数据模型与解析逻辑。
|
||||
IO 相关编排在 ``app/application/planning/`` 中完成。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlanStep:
|
||||
"""计划步骤。"""
|
||||
|
||||
id: int
|
||||
title: str
|
||||
description: str
|
||||
tool: str | None = None
|
||||
risk_level: str = "L2"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeliberativePlan:
|
||||
"""显式计划。"""
|
||||
|
||||
title: str
|
||||
steps: list[PlanStep]
|
||||
assumptions: list[str] = field(default_factory=list)
|
||||
risks: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
def parse_plan(raw: str) -> DeliberativePlan | None:
|
||||
"""解析 LLM 返回的 JSON 计划文本。
|
||||
|
||||
Args:
|
||||
raw: LLM 原始输出(可能含 ```json 围栏)。
|
||||
|
||||
Returns:
|
||||
解析成功的 DeliberativePlan,失败返回 None。
|
||||
"""
|
||||
text = raw.strip()
|
||||
matched = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, re.DOTALL)
|
||||
if matched:
|
||||
text = matched.group(1)
|
||||
else:
|
||||
brace_start = text.find("{")
|
||||
brace_end = text.rfind("}")
|
||||
if brace_start != -1 and brace_end > brace_start:
|
||||
text = text[brace_start : brace_end + 1]
|
||||
try:
|
||||
data = json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
if not isinstance(data, dict):
|
||||
return None
|
||||
title = str(data.get("title", "")).strip()
|
||||
raw_steps = data.get("steps", [])
|
||||
if not title or not isinstance(raw_steps, list) or not raw_steps:
|
||||
return None
|
||||
steps: list[PlanStep] = []
|
||||
for i, s in enumerate(raw_steps):
|
||||
if not isinstance(s, dict):
|
||||
continue
|
||||
step_title = str(s.get("title", "")).strip()
|
||||
if not step_title:
|
||||
continue
|
||||
tool_raw = str(s.get("tool", "")).strip()
|
||||
tool = tool_raw if tool_raw and tool_raw != "None" else None
|
||||
steps.append(
|
||||
PlanStep(
|
||||
id=s.get("id", i + 1),
|
||||
title=step_title,
|
||||
description=str(s.get("description", "")).strip(),
|
||||
tool=tool,
|
||||
risk_level=str(s.get("risk_level", "L2")).upper(),
|
||||
)
|
||||
)
|
||||
if not steps:
|
||||
return None
|
||||
return DeliberativePlan(
|
||||
title=title,
|
||||
steps=steps,
|
||||
assumptions=[str(a).strip() for a in data.get("assumptions", []) if a],
|
||||
risks=[str(r).strip() for r in data.get("risks", []) if r],
|
||||
)
|
||||
|
||||
|
||||
def build_planning_prompt(
|
||||
request: str,
|
||||
*,
|
||||
goal_directive: str = "",
|
||||
available_tools: list[str] | None = None,
|
||||
memory_context: str = "",
|
||||
) -> str:
|
||||
"""构建规划 prompt。
|
||||
|
||||
当请求明显需要多步骤(调用多个工具、查询多处信息等)时,
|
||||
要求 LLM 先生成一个显式计划,再按计划逐步执行。
|
||||
|
||||
Args:
|
||||
request: 用户请求文本。
|
||||
goal_directive: 当前活跃目标描述。
|
||||
available_tools: 可用工具列表。
|
||||
memory_context: 相关记忆上下文。
|
||||
|
||||
Returns:
|
||||
规划 prompt 文本。
|
||||
"""
|
||||
tools_str = ""
|
||||
if available_tools:
|
||||
tools_str = "\n可用工具:\n" + "\n".join(f"- {t}" for t in available_tools)
|
||||
return (
|
||||
"请判断以下用户请求是否需要多步计划。如果需要,请输出 JSON 格式的计划。\n\n"
|
||||
f"用户请求:{request}\n"
|
||||
f"{f'当前目标:{goal_directive}' if goal_directive else ''}"
|
||||
f"{tools_str}"
|
||||
f"{f'\n上下文记忆:{memory_context}' if memory_context else ''}"
|
||||
"\n\n"
|
||||
"输出格式(如需多步计划):\n"
|
||||
'```json\n{\n "title": "计划标题",\n'
|
||||
' "assumptions": ["假设1", "假设2"],\n'
|
||||
' "risks": ["风险1", "风险2"],\n'
|
||||
' "steps": [\n'
|
||||
' {"id": 1, "title": "步骤1", "description": "说明", '
|
||||
'"tool": "工具名(可选)", "risk_level": "L2"}\n'
|
||||
" ]\n"
|
||||
"}\n```\n\n"
|
||||
"如果不需要多步计划(简单问题),只回复:NO_PLAN_NEEDED"
|
||||
)
|
||||
|
||||
|
||||
def format_plan_for_prompt(plan: DeliberativePlan) -> str:
|
||||
"""将计划格式化为 system prompt 可用文本。"""
|
||||
lines = [f"## 计划:{plan.title}\n"]
|
||||
if plan.assumptions:
|
||||
lines.append("假设:")
|
||||
lines.extend(f"- {a}" for a in plan.assumptions)
|
||||
lines.append("")
|
||||
if plan.risks:
|
||||
lines.append("风险:")
|
||||
lines.extend(f"- {r}" for r in plan.risks)
|
||||
lines.append("")
|
||||
lines.append("执行步骤:")
|
||||
for step in plan.steps:
|
||||
tool_info = f" [工具:{step.tool}]" if step.tool else ""
|
||||
lines.append(f"{step.id}. {step.title}{tool_info}")
|
||||
if step.description:
|
||||
lines.append(f" {step.description}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def should_plan(request: str) -> bool:
|
||||
"""判断请求是否需要多步计划。
|
||||
|
||||
简单规则:包含需要多工具的关键词时返回 True。
|
||||
"""
|
||||
triggers = [
|
||||
"搜索", "查找", "研究", "调研", "分析", "比较",
|
||||
"整理", "总结", "汇总", "规划", "安排", "计划",
|
||||
"search", "research", "analyze", "compare",
|
||||
"summarize", "plan", "organize",
|
||||
]
|
||||
return any(t in request for t in triggers)
|
||||
Reference in new issue
Block a user