2026-08-11 00:54:05 +08:00
|
|
|
|
# ============================================================
|
|
|
|
|
|
# 内置算法库注册表(moduleId: core-algolib, 可重生 ✅)
|
|
|
|
|
|
# plan.md §9.5:算法收敛为带注册表的可重生资产(契约/黄金测试/适用边界);
|
|
|
|
|
|
# 与 core-skills(外部算法 SkillRegistry)区分——本表只登记**内置**算法,
|
|
|
|
|
|
# SkillRegistry 管外部可插拔算法(HTTP/local 端点)。
|
|
|
|
|
|
# 资产分类(矩阵 86 行口径,A-E 五类):
|
|
|
|
|
|
# A=启发式(调度规则/构造启发式) B=精确(CP-SAT/MILP)
|
|
|
|
|
|
# C=元启发(GA/LNS/SA/NSGA-II) D=ML(预测器/代理模型)
|
|
|
|
|
|
# E=集成(混合引擎/评估链:HYBRID、敏感性、鲁棒性、参数优化)
|
|
|
|
|
|
# 注:plan.md §9.5.1 的五类与任务矩阵 86 行分类略有差异,本表以
|
|
|
|
|
|
# 任务矩阵口径为准(元启发独立成 C,ML 归 D,E 为集成/评估链)。
|
|
|
|
|
|
# ============================================================
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import importlib
|
|
|
|
|
|
import threading
|
|
|
|
|
|
import time
|
|
|
|
|
|
from collections.abc import Callable
|
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
|
|
|
|
|
|
# 类别定义:A-E 五类资产的用途与适用边界(查询/健康检查展示用)
|
|
|
|
|
|
CATEGORY_LABELS: dict[str, str] = {
|
|
|
|
|
|
"A": "启发式:调度规则/构造启发式(EDD/SPT/ATC/贪心插入),毫秒级,作初始解与兜底",
|
|
|
|
|
|
"B": "精确:CP-SAT/MILP 等精确或近似优化,秒~分钟级,规模与时限需匹配",
|
|
|
|
|
|
"C": "元启发:GA/LNS/SA/NSGA-II,非确定性算法需固定随机种子才可复现",
|
|
|
|
|
|
"D": "ML:预测器/代理模型(工时回归/交期概率/需求时序),需历史数据、输出带置信区间",
|
|
|
|
|
|
"E": "集成:混合引擎与评估链(RULE+CP HYBRID、敏感性分析、蒙特卡洛鲁棒性、参数优化)",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 默认随机种子(注册表级):非确定性内置算法复现的统一种子源
|
|
|
|
|
|
DEFAULT_SEED = 20260802
|
|
|
|
|
|
|
|
|
|
|
|
# 健康检查历史保留条数
|
|
|
|
|
|
_HEALTH_HISTORY_KEEP = 20
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AlgorithmManifest(BaseModel):
|
|
|
|
|
|
"""算法注册表条目(plan.md §9.5.2 AlgorithmManifest 的内置实现形态)。
|
|
|
|
|
|
|
|
|
|
|
|
每个算法自描述:编排器/LLM 据此选型与调用;golden_tests 是重生验收。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
algo_id: str # 算法唯一 ID,如 "rule.delivery_first"
|
|
|
|
|
|
name: str # 展示名
|
|
|
|
|
|
category: str # A/B/C/D/E(对应 CATEGORY_LABELS)
|
|
|
|
|
|
description: str = "" # 一句话用途
|
|
|
|
|
|
scale_limit: str = "" # 适用规模声明,如 "<=50k 工单"
|
|
|
|
|
|
time_budget: str = "" # 典型时限,如 "5s~5min"
|
|
|
|
|
|
input_schema: dict[str, Any] = Field(default_factory=dict) # 输入契约(JSON Schema 形态)
|
|
|
|
|
|
output_schema: dict[str, Any] = Field(default_factory=dict) # 输出契约(含证据字段)
|
|
|
|
|
|
golden_tests: list[str] = Field(default_factory=list) # 黄金测试用例路径(重生验收)
|
|
|
|
|
|
deterministic: bool = True # 是否确定性(GA/SA 需固定种子)
|
|
|
|
|
|
random_seed: int | None = None # 随机种子(非确定性算法的复现锚点)
|
|
|
|
|
|
version: str = "1.0" # 语义化版本
|
|
|
|
|
|
regen_strategy: str = "manual" # 重生策略:llm / manual / hybrid
|
|
|
|
|
|
available: bool = True # 内置实现是否就绪(False=目录资产未落地)
|
|
|
|
|
|
entrypoint: str = "" # 取用入口:引擎名(RULE/CP/GA/HYBRID)、
|
|
|
|
|
|
# "ENGINE:STRATEGY" 或 "module.path:attr"
|
|
|
|
|
|
health_fn: str | Callable[[], dict[str, Any]] = "" # 可选健康探针(点路径或可调用对象)
|
|
|
|
|
|
|
|
|
|
|
|
def validate_manifest(self) -> list[str]:
|
|
|
|
|
|
"""元数据完整性校验,返回问题列表(空=通过)。"""
|
|
|
|
|
|
problems: list[str] = []
|
|
|
|
|
|
if not self.algo_id or not self.algo_id.strip():
|
|
|
|
|
|
problems.append("algo_id 缺失")
|
|
|
|
|
|
if not self.name or not self.name.strip():
|
|
|
|
|
|
problems.append("name 缺失")
|
|
|
|
|
|
if self.category not in CATEGORY_LABELS:
|
|
|
|
|
|
problems.append(f"category 非法:{self.category!r}(须为 A/B/C/D/E)")
|
|
|
|
|
|
if not isinstance(self.input_schema, dict) or not isinstance(self.output_schema, dict):
|
|
|
|
|
|
problems.append("input_schema/output_schema 必须是 dict")
|
|
|
|
|
|
if not isinstance(self.golden_tests, list):
|
|
|
|
|
|
problems.append("golden_tests 必须是 list")
|
|
|
|
|
|
if not isinstance(self.deterministic, bool):
|
|
|
|
|
|
problems.append("deterministic 必须是 bool")
|
|
|
|
|
|
if not self.version or not str(self.version).strip():
|
|
|
|
|
|
problems.append("version 缺失")
|
|
|
|
|
|
if self.regen_strategy not in ("llm", "manual", "hybrid"):
|
|
|
|
|
|
problems.append(f"regen_strategy 非法:{self.regen_strategy!r}")
|
|
|
|
|
|
if not self.deterministic and self.random_seed is None:
|
|
|
|
|
|
problems.append("非确定性算法必须声明 random_seed(复现锚点)")
|
|
|
|
|
|
return problems
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _resolve_health_fn(
|
|
|
|
|
|
spec: str | Callable[[], dict[str, Any]],
|
|
|
|
|
|
) -> Callable[[], dict[str, Any]] | None:
|
|
|
|
|
|
"""把 health_fn 规范解析为可调用对象(点路径或原对象)。"""
|
|
|
|
|
|
if callable(spec):
|
|
|
|
|
|
return spec
|
|
|
|
|
|
if isinstance(spec, str) and ":" in spec:
|
|
|
|
|
|
mod_path, attr = spec.rsplit(":", 1)
|
|
|
|
|
|
try:
|
|
|
|
|
|
fn = getattr(importlib.import_module(mod_path), attr)
|
|
|
|
|
|
return fn if callable(fn) else None
|
|
|
|
|
|
except (ImportError, AttributeError):
|
|
|
|
|
|
return None
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _builtin_catalog() -> list[AlgorithmManifest]:
|
|
|
|
|
|
"""内置算法目录:只登记仓库内真实存在的实现(available=True),
|
|
|
|
|
|
未落地的规划资产(MILP/ML 预测器)以 available=False 登记,
|
|
|
|
|
|
健康检查如实报告未就绪,避免把目录当成果(LNS 已随 round-36 交付转为可用)。"""
|
|
|
|
|
|
rule_tests = ["tests/golden/test_planning.py", "tests/golden/test_schedule_wizard.py"]
|
|
|
|
|
|
cp_tests = ["tests/golden/test_cp_engine.py"]
|
|
|
|
|
|
kpi_out = {
|
|
|
|
|
|
"scheduleVersion": {"id": "int", "versionNo": "str", "status": "str"},
|
|
|
|
|
|
"productionOrders": "array<生产订单>",
|
|
|
|
|
|
"workOrders": "array<工单>",
|
|
|
|
|
|
"conflicts": "array<冲突>",
|
|
|
|
|
|
"kpi": {"tardiness": "float(小时)", "conflicts": "int",
|
|
|
|
|
|
"utilization": "float", "changeoverMin": "float"},
|
|
|
|
|
|
}
|
|
|
|
|
|
rule_in = {
|
|
|
|
|
|
"salesOrders": "array<订单>", "lines": "array<产线>",
|
|
|
|
|
|
"strategy": "RULE 策略模板", "planningHorizonDays": "int",
|
|
|
|
|
|
"constraints": "dict<bool 约束开关>",
|
|
|
|
|
|
}
|
|
|
|
|
|
items = [
|
2026-09-03 13:14:04 +08:00
|
|
|
|
# ---- A. Optimize Python-native dispatch rules ----
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="optimize.edd", name="Optimize EDD 最早交期",
|
|
|
|
|
|
category="A", description="Optimize V2 适配器:按最早交期派工",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=["tests/golden/test_optimize_engine.py"], deterministic=True,
|
|
|
|
|
|
entrypoint="OPTIMIZE:EDD", regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="optimize.spt", name="Optimize SPT 最短工时",
|
|
|
|
|
|
category="A", description="Optimize V2 适配器:短工时优先",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=["tests/golden/test_optimize_engine.py"], deterministic=True,
|
|
|
|
|
|
entrypoint="OPTIMIZE:SPT", regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="optimize.priority", name="Optimize PRIORITY 优先级",
|
|
|
|
|
|
category="A", description="Optimize V2 适配器:订单优先级优先",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=["tests/golden/test_optimize_engine.py"], deterministic=True,
|
|
|
|
|
|
entrypoint="OPTIMIZE:PRIORITY", regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="optimize.fifo", name="Optimize FIFO 先来先服务",
|
|
|
|
|
|
category="A", description="Optimize V2 适配器:按释放时间派工",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=["tests/golden/test_optimize_engine.py"], deterministic=True,
|
|
|
|
|
|
entrypoint="OPTIMIZE:FIFO", regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="optimize.lpt", name="Optimize LPT 最长工时",
|
|
|
|
|
|
category="A", description="Optimize V2 适配器:长工时优先",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=["tests/golden/test_optimize_engine.py"], deterministic=True,
|
|
|
|
|
|
entrypoint="OPTIMIZE:LPT", regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="optimize.cr", name="Optimize CR 临界比",
|
|
|
|
|
|
category="A", description="Optimize V2 适配器:交期紧迫度优先",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=["tests/golden/test_optimize_engine.py"], deterministic=True,
|
|
|
|
|
|
entrypoint="OPTIMIZE:CR", regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="optimize.atc", name="Optimize ATC 逾期成本",
|
|
|
|
|
|
category="A", description="Optimize V2 适配器:逾期成本代理排序",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=["tests/golden/test_optimize_engine.py"], deterministic=True,
|
|
|
|
|
|
entrypoint="OPTIMIZE:ATC", regen_strategy="manual",
|
|
|
|
|
|
),
|
2026-08-11 00:54:05 +08:00
|
|
|
|
# ---- A. 启发式(RULE 引擎各策略模板)----
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="rule.delivery_first", name="EDD 最早交期",
|
|
|
|
|
|
category="A", description="交期优先派工(单机 1||Lmax 最优,Jackson 定理)",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=list(rule_tests), deterministic=True,
|
|
|
|
|
|
entrypoint="RULE:DELIVERY_FIRST", regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="rule.fifo", name="FIFO 先来先服务",
|
|
|
|
|
|
category="A", description="按订单下达时间排序",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=list(rule_tests), deterministic=True,
|
|
|
|
|
|
entrypoint="RULE:FIFO", regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="rule.comprehensive", name="COMPREHENSIVE 综合策略",
|
|
|
|
|
|
category="A", description="等级权重+优先级+交期综合排序(默认派工策略)",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=list(rule_tests), deterministic=True,
|
|
|
|
|
|
entrypoint="RULE:COMPREHENSIVE", regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="rule.capacity_balance", name="CAPACITY_BALANCE 产能均衡",
|
|
|
|
|
|
category="A", description="按等级+优先级+累计占用均衡选线",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=list(rule_tests), deterministic=True,
|
|
|
|
|
|
entrypoint="RULE:CAPACITY_BALANCE", regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="rule.changeover_min", name="CHANGEOVER_MIN 换型最小化",
|
|
|
|
|
|
category="A", description="贪心最近邻最小化换型(SC-07)",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=["tests/golden/test_changeover.py"], deterministic=True,
|
|
|
|
|
|
entrypoint="RULE:CHANGEOVER_MIN", regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="rule.cost_first", name="COST_FIRST 成本优先",
|
|
|
|
|
|
category="A", description="以换型耗时为成本代理排序(SC-07 备选)",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=["tests/golden/test_changeover.py"], deterministic=True,
|
|
|
|
|
|
entrypoint="RULE:COST_FIRST", regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="rule.campaign", name="CAMPAIGN 战役合并",
|
|
|
|
|
|
category="A", description="同产品交期窗口战役合并,按换型最小化排战役序(SC-08)",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="毫秒级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=["tests/golden/test_campaign.py"], deterministic=True,
|
|
|
|
|
|
entrypoint="RULE:CAMPAIGN", regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
# ---- B. 精确 ----
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="cp_sat", name="CP-SAT 约束规划",
|
|
|
|
|
|
category="B", description="OR-Tools CP-SAT 详排求优(IntervalVar+NoOverlap+Cumulative)",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="5s~5min",
|
|
|
|
|
|
input_schema={**rule_in, "timeLimitSeconds": "float(秒,anytime)"},
|
|
|
|
|
|
output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=list(cp_tests), deterministic=True,
|
|
|
|
|
|
entrypoint="CP", regen_strategy="hybrid",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="milp", name="MILP 混合整数规划",
|
|
|
|
|
|
category="B", description="S1 集团产能平衡与小规模基准校核(析取建模,Manne 1960)",
|
|
|
|
|
|
scale_limit="<=2k 工单", time_budget="分钟级",
|
|
|
|
|
|
input_schema=rule_in, output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=[], deterministic=True,
|
|
|
|
|
|
entrypoint="", available=False, regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
# ---- C. 元启发 ----
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="ga", name="遗传算法 GA",
|
|
|
|
|
|
category="C", description="种群/代数演化搜索近优解",
|
|
|
|
|
|
scale_limit="<=10k 工单", time_budget="秒~分钟级",
|
|
|
|
|
|
input_schema={**rule_in, "population": "int", "generations": "int"},
|
|
|
|
|
|
output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=["tests/golden/test_ga_engine.py"], deterministic=False,
|
|
|
|
|
|
random_seed=DEFAULT_SEED, entrypoint="GA", regen_strategy="hybrid",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="lns", name="LNS/ALNS 大邻域搜索",
|
|
|
|
|
|
category="C", description="插单局部修复:固定窗口最小扰动,超阈值升级全量重排(§9.11,round-36 交付)",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="<30s",
|
|
|
|
|
|
input_schema={**rule_in, "frozenWindowHours": "float", "maxAffectedOrders": "int", "disturbanceTolerance": "float"},
|
|
|
|
|
|
output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=["tests/golden/test_rush_lns.py"], deterministic=False,
|
|
|
|
|
|
random_seed=DEFAULT_SEED, entrypoint="server.aps_domain.lns:lns_local_repair",
|
|
|
|
|
|
available=True, regen_strategy="hybrid",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="nsga2", name="NSGA-II 多目标遗传",
|
|
|
|
|
|
category="C", description="多目标帕累托解集:快速非支配排序+拥挤度+精英保留,3 目标(总延迟/容量冲突/负载均衡)(§9.7,round-40 交付)",
|
|
|
|
|
|
scale_limit="<=10k 工单", time_budget="分钟级",
|
|
|
|
|
|
input_schema={**rule_in, "objectives": "array<目标键>", "population": "int", "generations": "int"},
|
|
|
|
|
|
output_schema={**kpi_out, "paretoSet": "array<方案>"},
|
|
|
|
|
|
golden_tests=["tests/golden/test_nsga2_engine.py"], deterministic=False,
|
|
|
|
|
|
random_seed=DEFAULT_SEED, entrypoint="server.engines.nsga2_engine:solve_nsga2",
|
|
|
|
|
|
available=True, regen_strategy="hybrid",
|
|
|
|
|
|
),
|
|
|
|
|
|
# ---- D. ML(预测器/代理模型,当前目录资产,未落地实现)----
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="ml.worktime_regression", name="实际工时回归",
|
|
|
|
|
|
category="D", description="LightGBM/XGBoost 工时估计(§9.1a OR 输入参数估准)",
|
|
|
|
|
|
scale_limit="需历史报工数据", time_budget="训练级",
|
|
|
|
|
|
input_schema={"features": "dict", "history": "array<报工记录>"},
|
|
|
|
|
|
output_schema={"estimate": "float", "confidenceInterval": "[low, high]"},
|
|
|
|
|
|
golden_tests=[], deterministic=False,
|
|
|
|
|
|
random_seed=DEFAULT_SEED, entrypoint="", available=False, regen_strategy="llm",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="ml.due_date_survival", name="交期达成概率",
|
|
|
|
|
|
category="D", description="生存分析:订单交期达成概率",
|
|
|
|
|
|
scale_limit="需历史订单数据", time_budget="训练级",
|
|
|
|
|
|
input_schema={"order": "dict", "history": "array<历史订单>"},
|
|
|
|
|
|
output_schema={"deliveryProbability": "float", "confidenceInterval": "[low, high]"},
|
|
|
|
|
|
golden_tests=[], deterministic=False,
|
|
|
|
|
|
random_seed=DEFAULT_SEED, entrypoint="", available=False, regen_strategy="llm",
|
|
|
|
|
|
),
|
|
|
|
|
|
# ---- E. 集成(混合引擎与评估链)----
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="hybrid", name="HYBRID 规则+CP 混合",
|
|
|
|
|
|
category="E", description="RULE 热启动构造初始解 + CP-SAT 局部改良",
|
|
|
|
|
|
scale_limit="<=50k 工单", time_budget="5s~5min",
|
|
|
|
|
|
input_schema={**rule_in, "timeLimitSeconds": "float(秒,anytime)"},
|
|
|
|
|
|
output_schema=kpi_out,
|
|
|
|
|
|
golden_tests=list(cp_tests), deterministic=False,
|
|
|
|
|
|
random_seed=DEFAULT_SEED, entrypoint="HYBRID", regen_strategy="hybrid",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="eval.sensitivity_tornado", name="敏感性 Tornado 分析",
|
|
|
|
|
|
category="E", description="OAT 单因子扰动排序(SC-06,跑 Explore 沙盒)",
|
|
|
|
|
|
scale_limit="按需", time_budget="秒级/因子",
|
|
|
|
|
|
input_schema={"world": "dict", "strategy": "str"},
|
|
|
|
|
|
output_schema={"rows": "array<因子摆幅>", "monteCarlo": "dict", "markdown": "str"},
|
|
|
|
|
|
golden_tests=["tests/golden/test_sensitivity.py"], deterministic=True,
|
|
|
|
|
|
entrypoint="server.aps_domain.sensitivity:run_sensitivity",
|
|
|
|
|
|
regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="eval.monte_carlo_robustness", name="蒙特卡洛鲁棒性",
|
|
|
|
|
|
category="E", description="固定种子情景仿真,鲁棒性评分(§9.9)",
|
|
|
|
|
|
scale_limit="按需", time_budget="后台批量",
|
|
|
|
|
|
input_schema={"world": "dict", "trials": "int", "seed": "int"},
|
|
|
|
|
|
output_schema={"robustness": "float", "distribution": "dict", "seed": "int"},
|
|
|
|
|
|
golden_tests=["tests/golden/test_sensitivity.py"], deterministic=True,
|
|
|
|
|
|
random_seed=DEFAULT_SEED,
|
|
|
|
|
|
entrypoint="server.aps_domain.robustness:run_monte_carlo",
|
|
|
|
|
|
regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
AlgorithmManifest(
|
|
|
|
|
|
algo_id="opt.parameter_optimizer", name="参数优化闭环",
|
|
|
|
|
|
category="E", description="回放→灰度→验证→退化自动回滚(§9.8)",
|
|
|
|
|
|
scale_limit="按需", time_budget="版本级",
|
|
|
|
|
|
input_schema={"world": "dict", "candidates": "array<参数字典>"},
|
|
|
|
|
|
output_schema={"experiments": "array<实验记录>", "summary": "dict"},
|
|
|
|
|
|
golden_tests=["tests/golden/test_param_opt.py"], deterministic=True,
|
|
|
|
|
|
random_seed=DEFAULT_SEED,
|
|
|
|
|
|
entrypoint="server.agent_core.param_opt:ParameterOptimizer",
|
|
|
|
|
|
regen_strategy="manual",
|
|
|
|
|
|
),
|
|
|
|
|
|
]
|
|
|
|
|
|
return items
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AlgorithmRegistry:
|
|
|
|
|
|
"""内置算法注册表(内存态,无文件持久化——内置资产随代码版本演进)。
|
|
|
|
|
|
|
|
|
|
|
|
与 core-skills 的 SkillRegistry 区分:SkillRegistry 管**外部**算法
|
|
|
|
|
|
(manifest.json + HTTP 端点),本注册表管**内置**算法(引擎/策略/评估链)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, seed: int = DEFAULT_SEED) -> None:
|
|
|
|
|
|
self._lock = threading.Lock()
|
|
|
|
|
|
self._seed = int(seed)
|
|
|
|
|
|
self._algos: dict[str, AlgorithmManifest] = {}
|
|
|
|
|
|
self.health_history: dict[str, list[dict[str, Any]]] = {}
|
|
|
|
|
|
for m in _builtin_catalog():
|
|
|
|
|
|
self._algos[m.algo_id] = m
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------- 注册 / 注销 ----------------
|
|
|
|
|
|
def register(self, manifest: AlgorithmManifest | dict[str, Any]) -> dict[str, Any]:
|
|
|
|
|
|
"""注册或更新(upsert)一个内置算法。元数据不完整直接拒绝。"""
|
|
|
|
|
|
m = manifest if isinstance(manifest, AlgorithmManifest) else AlgorithmManifest(**manifest)
|
|
|
|
|
|
problems = m.validate_manifest()
|
|
|
|
|
|
if problems:
|
|
|
|
|
|
raise ValueError("算法元数据不完整:" + "; ".join(problems))
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
self._algos[m.algo_id] = m
|
|
|
|
|
|
return m.model_dump()
|
|
|
|
|
|
|
|
|
|
|
|
def unregister(self, algo_id: str) -> bool:
|
|
|
|
|
|
"""注销算法(内置资产不建议注销;测试/热替换用)。"""
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
return self._algos.pop(algo_id, None) is not None
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------- 查询 ----------------
|
|
|
|
|
|
def get(self, algo_id: str) -> dict[str, Any] | None:
|
|
|
|
|
|
m = self._algos.get(algo_id)
|
|
|
|
|
|
return m.model_dump() if m else None
|
|
|
|
|
|
|
|
|
|
|
|
def list(self) -> list[dict[str, Any]]:
|
|
|
|
|
|
return [m.model_dump() for m in self._algos.values()]
|
|
|
|
|
|
|
|
|
|
|
|
def query(
|
|
|
|
|
|
self,
|
|
|
|
|
|
*,
|
|
|
|
|
|
category: str | None = None,
|
|
|
|
|
|
available: bool | None = None,
|
|
|
|
|
|
keywords: str = "",
|
|
|
|
|
|
max_time_budget: str | None = None,
|
|
|
|
|
|
) -> list[dict[str, Any]]:
|
|
|
|
|
|
"""按类别/可用性/关键词/时限声明查询(编排器选型入口)。"""
|
|
|
|
|
|
kw = keywords.strip().lower()
|
|
|
|
|
|
out: list[dict[str, Any]] = []
|
|
|
|
|
|
for m in self._algos.values():
|
|
|
|
|
|
if category and m.category != category:
|
|
|
|
|
|
continue
|
|
|
|
|
|
if available is not None and m.available != available:
|
|
|
|
|
|
continue
|
|
|
|
|
|
if kw and kw not in (m.name + m.description + m.algo_id).lower():
|
|
|
|
|
|
continue
|
|
|
|
|
|
if max_time_budget and not self._budget_le(m.time_budget, max_time_budget):
|
|
|
|
|
|
continue
|
|
|
|
|
|
out.append(m.model_dump())
|
|
|
|
|
|
out.sort(key=lambda x: (x["category"], x["algo_id"]))
|
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
|
def categories(self) -> dict[str, str]:
|
|
|
|
|
|
return dict(CATEGORY_LABELS)
|
|
|
|
|
|
|
|
|
|
|
|
def version(self, algo_id: str) -> dict[str, Any] | None:
|
|
|
|
|
|
"""版本查询:返回 {algo_id, version, regen_strategy, available}。"""
|
|
|
|
|
|
m = self._algos.get(algo_id)
|
|
|
|
|
|
if not m:
|
|
|
|
|
|
return None
|
|
|
|
|
|
return {
|
|
|
|
|
|
"algo_id": m.algo_id,
|
|
|
|
|
|
"version": m.version,
|
|
|
|
|
|
"regen_strategy": m.regen_strategy,
|
|
|
|
|
|
"available": m.available,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def next_seed(self, algo_id: str) -> int:
|
|
|
|
|
|
"""随机种子服务:确定性算法返回声明的种子(无则 None 语义用 0);
|
|
|
|
|
|
非确定性算法返回按 algo_id 稳定的种子(同注册表种子源可复现)。"""
|
|
|
|
|
|
m = self._algos.get(algo_id)
|
|
|
|
|
|
if not m:
|
|
|
|
|
|
return self._seed
|
|
|
|
|
|
if m.deterministic:
|
|
|
|
|
|
return m.random_seed if m.random_seed is not None else 0
|
|
|
|
|
|
return m.random_seed if m.random_seed is not None else (self._seed + sum(map(ord, algo_id)))
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------- 健康检查 ----------------
|
|
|
|
|
|
def health(self, algo_id: str | None = None) -> list[dict[str, Any]]:
|
|
|
|
|
|
"""健康检查:元数据完整性 + 就绪状态 + 可选探针;记录历史(最近 N 条)。"""
|
|
|
|
|
|
targets = [self._algos[algo_id]] if algo_id and algo_id in self._algos else ([] if algo_id else list(self._algos.values()))
|
|
|
|
|
|
out: list[dict[str, Any]] = []
|
|
|
|
|
|
for m in targets:
|
|
|
|
|
|
status = self._probe(m)
|
|
|
|
|
|
record = {"ts": time.strftime("%Y-%m-%d %H:%M:%S"), **status}
|
|
|
|
|
|
hist = self.health_history.setdefault(m.algo_id, [])
|
|
|
|
|
|
hist.append(record)
|
|
|
|
|
|
del hist[:-_HEALTH_HISTORY_KEEP]
|
|
|
|
|
|
out.append({
|
|
|
|
|
|
"algo_id": m.algo_id,
|
|
|
|
|
|
"name": m.name,
|
|
|
|
|
|
"category": m.category,
|
|
|
|
|
|
"version": m.version,
|
|
|
|
|
|
"available": m.available,
|
|
|
|
|
|
**status,
|
|
|
|
|
|
})
|
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
|
def history(self, algo_id: str) -> list[dict[str, Any]]:
|
|
|
|
|
|
return list(self.health_history.get(algo_id) or [])
|
|
|
|
|
|
|
|
|
|
|
|
def _probe(self, m: AlgorithmManifest) -> dict[str, Any]:
|
|
|
|
|
|
"""单算法探针:元数据→可用性→入口→自定义探针,逐级短路报告。"""
|
|
|
|
|
|
started = time.time()
|
|
|
|
|
|
problems = m.validate_manifest()
|
|
|
|
|
|
if problems:
|
|
|
|
|
|
return {"ok": False, "latencyMs": 0, "detail": "元数据不完整: " + "; ".join(problems)}
|
|
|
|
|
|
if not m.available:
|
|
|
|
|
|
return {"ok": False, "latencyMs": 0, "detail": "目录资产未落地(available=False)"}
|
|
|
|
|
|
detail = "builtin"
|
|
|
|
|
|
if m.entrypoint:
|
|
|
|
|
|
ok, err = self._check_entrypoint(m.entrypoint)
|
|
|
|
|
|
if not ok:
|
|
|
|
|
|
return {"ok": False, "latencyMs": 0, "detail": f"入口不可达: {err}"}
|
|
|
|
|
|
detail = f"entrypoint ok ({m.entrypoint})"
|
|
|
|
|
|
if m.health_fn:
|
|
|
|
|
|
fn = _resolve_health_fn(m.health_fn)
|
|
|
|
|
|
if fn is None:
|
|
|
|
|
|
return {"ok": False, "latencyMs": 0, "detail": "health_fn 不可解析"}
|
|
|
|
|
|
try:
|
|
|
|
|
|
r = fn() or {}
|
|
|
|
|
|
if not isinstance(r, dict) or "ok" not in r:
|
|
|
|
|
|
return {"ok": False, "latencyMs": 0, "detail": "health_fn 返回格式非法"}
|
|
|
|
|
|
latency = int((time.time() - started) * 1000)
|
|
|
|
|
|
return {"ok": bool(r["ok"]), "latencyMs": latency,
|
|
|
|
|
|
"detail": str(r.get("detail") or detail)}
|
|
|
|
|
|
except Exception as exc: # noqa: BLE001 - 健康探针必须兜底任意异常(异常即不健康)
|
|
|
|
|
|
return {"ok": False, "latencyMs": 0, "detail": f"health_fn 异常: {exc}"}
|
|
|
|
|
|
return {"ok": True, "latencyMs": int((time.time() - started) * 1000), "detail": detail}
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _check_entrypoint(entrypoint: str) -> tuple[bool, str]:
|
|
|
|
|
|
"""入口可达性:引擎名 / ENGINE:STRATEGY / module.path:attr。"""
|
|
|
|
|
|
if ":" not in entrypoint:
|
|
|
|
|
|
return True, "" # 纯引擎名(RULE/CP/GA/HYBRID)由 get_engine 工厂保证
|
2026-09-03 13:14:04 +08:00
|
|
|
|
if entrypoint.startswith(("RULE:", "OPTIMIZE:")):
|
2026-08-11 00:54:05 +08:00
|
|
|
|
from server.engines import get_engine
|
|
|
|
|
|
try:
|
2026-09-03 13:14:04 +08:00
|
|
|
|
get_engine(entrypoint.split(":", 1)[0])
|
2026-08-11 00:54:05 +08:00
|
|
|
|
return True, ""
|
|
|
|
|
|
except (ImportError, AttributeError, RuntimeError, ValueError, TypeError) as exc:
|
|
|
|
|
|
return False, str(exc)
|
|
|
|
|
|
mod_path, attr = entrypoint.rsplit(":", 1)
|
|
|
|
|
|
try:
|
|
|
|
|
|
getattr(importlib.import_module(mod_path), attr)
|
|
|
|
|
|
return True, ""
|
|
|
|
|
|
except (ImportError, AttributeError) as exc:
|
|
|
|
|
|
return False, str(exc)
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _budget_le(declared: str, limit: str) -> bool:
|
|
|
|
|
|
"""时限声明比较(粗粒度:解析首段数值,如 "5s~5min" → 5min)。"""
|
|
|
|
|
|
def parse(spec: str) -> float:
|
|
|
|
|
|
text = spec.strip().lower()
|
|
|
|
|
|
if not text:
|
|
|
|
|
|
return 0.0
|
|
|
|
|
|
num = ""
|
|
|
|
|
|
for ch in text:
|
|
|
|
|
|
if ch.isdigit() or ch == ".":
|
|
|
|
|
|
num += ch
|
|
|
|
|
|
elif num:
|
|
|
|
|
|
break
|
|
|
|
|
|
try:
|
|
|
|
|
|
val = float(num)
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
return 0.0
|
|
|
|
|
|
if "ms" in text:
|
|
|
|
|
|
return val / 1000.0
|
|
|
|
|
|
if "s" in text:
|
|
|
|
|
|
return val
|
|
|
|
|
|
if "min" in text:
|
|
|
|
|
|
return val * 60
|
|
|
|
|
|
if "h" in text:
|
|
|
|
|
|
return val * 3600
|
|
|
|
|
|
return val
|
|
|
|
|
|
return parse(declared) <= parse(limit) if parse(limit) > 0 else True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_registry: AlgorithmRegistry | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_algolib() -> AlgorithmRegistry:
|
|
|
|
|
|
"""单例获取(与 get_skills 对齐)。"""
|
|
|
|
|
|
global _registry
|
|
|
|
|
|
if _registry is None:
|
|
|
|
|
|
_registry = AlgorithmRegistry()
|
|
|
|
|
|
return _registry
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def reset_algolib_registry() -> None:
|
|
|
|
|
|
"""测试用:丢弃单例,下次按默认目录重建。"""
|
|
|
|
|
|
global _registry
|
|
|
|
|
|
_registry = None
|