插件 SDK 参考

本章面向已经完成入门插件的开发者,说明 SDK v1 的契约、生命周期和边界。示例优先使用扁平 plugin.yaml,它也是仓库内置插件采用的形式。

加载过程

一个目录插件按以下顺序进入系统:

  1. Registry 发现并解析 plugin.yaml,此时不导入代码。
  2. 校验插件 ID、SDK 版本、能力、依赖声明和入口格式。
  3. Install 保存已验证的包;Enable 才导入 entrypoint
  4. 创建 ScientificPlugin 实例,并把磁盘清单绑定为该已加载插件的权威 manifest。
  5. 首次执行前调用 start(),关闭 Runtime 时调用 stop()
  6. 每次能力调用经输入和产物引用验证后进入 execute()

CLI 在配置的 plugins 中直接使用目录时,会在创建运行期间完成发现和加载。桌面自定义 ZIP 则把 Install 与 Enable 分为两个明确步骤。

plugin.yaml

插件级字段

字段 必需 说明
name 稳定插件 ID;建议反向域式命名,如 lab.spectrometer
display_name UI 显示名;省略时由 ID 生成
version 插件版本;建议语义化版本
type 实现类型,如 domainevaluatoranalyzerreporter
description 一两句用户可读说明
category coredomain;domain 插件应同时写 domain
domain Domain 建议 classical_mechanicsaccelerator_physics
sdk_version 当前只支持字符串 "1"
entrypoint 可执行插件是 python.module:ClassName
dependencies 必须满足的依赖声明;不会自动安装
optional_dependencies 兼容旧清单的可选依赖声明
access_role research(默认)或 trusted_evaluator
trusted_metrics 返回指标是否具有可信评估权威,默认 false
required_artifacts 插件总体需要的科学 artifact kind
produced_artifacts 插件总体产出的科学 artifact kind
runtime_requirements JSON 对象,例如 GPU、内存或外部运行时说明
capabilities 至少一个且名称唯一的能力

插件 ID 和能力 ID 必须以字母开头,并只使用字母、数字、下划线、点、冒号或短横线。发布后不要复用一个 ID 表示不兼容的新含义。

dependencies 是展示与可用性检查的声明,不是安装脚本。插件不得在 import、start()execute() 中自行运行包管理器。

能力字段

字段 必需 说明
name 稳定、带命名空间的能力 ID
description 建议 Reasoner 与用户可读的操作定义
input_schema 普通输入名到简单类型的映射
required_inputs input_schema 中的必填键
input_guidance 嵌套字段、范围、单位和选择规则
artifact_inputs 逻辑输入名到所需产物说明的映射;"*" 可接收额外产物
required_artifact_inputs 必须提供的逻辑产物输入名
output_schema 输出各部分的简单形状说明
output_summary 建议 一句话说明结果和产物
metric_outputs 验收指标需要 可能返回的规范化指标名
example 强烈建议 一个小型且合法的输入样例
cost_hints 确定性资源提示,如 solver_call: 1
immutable_key_input 参与不可变范围选择的输入键
immutable_output_scope 对应的冻结输出范围标识
provides 高层语义标签,用于能力匹配
consumes_artifact_kinds 消费的科学 artifact kind
produces_artifact_kinds 产出的科学 artifact kind
scientific_roles numerical_simulationscientific_analysis

简单 schema 类型只有:

string  number  integer  boolean  object  array

这是能力边界检查,不是完整 JSON Schema。嵌套对象的字段、单位、范围与互斥规则写进 input_guidanceexample,并在插件中做领域验证。未知普通输入会被拒绝;Python 的 bool 也不会被当作 numberinteger

Artifact kind 与 ArtifactType

两者用途不同:

  • ArtifactType 是 Runtime 存储分类,例如 DATASETTRAINED_MODELPREDICTOR_OUTPUTANALYSIS_RESULTSCIENTIFIC_REPORT
  • artifact kind 是领域语义,例如 three_body_trajectoryevaluation_resultoscillator_assessment,通常放在科学 metadata 中并由能力清单声明。

一个分析文件可使用 ArtifactType.ANALYSIS_RESULT,同时具有领域 kind oscillator_assessment。不要为了每个领域概念扩展存储枚举。

ScientificPlugin

最小实现:

class MyPlugin(ScientificPlugin):
    @property
    def manifest(self) -> PluginManifest:
        ...

    async def execute(
        self,
        capability: str,
        inputs: JsonObject,
        context: PluginExecutionContext,
    ) -> PluginResult:
        ...

可选钩子:

async def start(self) -> None: ...
async def stop(self) -> None: ...
def create_reasoner(self) -> Reasoner | None: ...
def estimate_cost(
    self, capability: str, inputs: JsonObject
) -> dict[str, int | float]: ...

生命周期建议

  • 构造函数和 manifest 应快速、确定性且无网络副作用。
  • start() 可加载模型、建立本地连接或准备只读资源;应可安全调用一次。
  • execute() 不能依赖另一调用正在同时修改的全局状态。
  • stop() 即使部分初始化失败也应能安全释放资源。
  • 不要在这些钩子中修改研究合同、事件文件或 Runtime 的产物目录。

estimate_cost()

该方法在执行前运行,用于阻止明显超预算的调用。它必须快速、确定性且不执行真正实验。例如:

def estimate_cost(self, capability, inputs):
    if capability == "lab.solver.generate_dataset":
        return {
            "training_samples": int(inputs["num_samples"]),
            "solver_call": 1,
        }
    return {}

预估不是实际用量。完成后仍应在 PluginResult.resource_usage 报告可测量消耗。当前预算直接识别的常见用量包括训练样本;action 类型还会分别计入实验、训练运行和求解器调用。

PluginExecutionContext

属性 用途
run_id 当前运行 ID
action_id 当前动作 ID
workspace 当前执行可使用的工作目录
resolved_artifacts 逻辑输入名到经验证本地路径的映射
artifact_references 对应公开产物引用与 metadata
private_artifacts 仅 trusted evaluator 可见的私有路径
private_artifact_references 私有引用 metadata
artifact_graph 当前可见科学产物图,只读
read_artifact(id) 通过 Runtime 边界按 ID 读取并校验产物

优先使用 resolved_artifacts["logical_name"] 处理清单声明的直接输入。需要沿谱系查找时才使用 artifact_graphread_artifact()

不要:

  • 从 artifact URI 猜磁盘路径;
  • 扫描整个 data directory;
  • 打开其他 run 的目录;
  • workspace.parent 绕出工作区;
  • 在普通 research 插件中尝试读取 private 字段。

Runtime 会限制同运行、可见性和 hash,但插件仍要验证文件格式、大小、编码和领域 schema。

PluginResult

PluginResult(
    success=True,
    metrics={"rmse": 0.04},
    observations={"samples": 512},
    artifacts=(...),
    resource_usage={"training_samples": 512},
)

规则:

  • 成功结果不能同时有 error;失败结果必须有 error
  • metrics 的名称忽略大小写后必须唯一,值必须可作为数字持久化。
  • observations、metadata 和 resource usage 必须是 JSON 可序列化内容。
  • resource usage 不能是负数或布尔值。
  • 同一次返回中的产物文件名必须唯一。

预期的输入错误、求解失败或外部工具失败使用失败 PluginResult。真正的插件 bug 或不可恢复初始化错误可以抛异常,Runtime 会记录为执行失败;错误文本不得包含密钥、私有标签或大段输入数据。

PluginArtifact

二进制产物:

PluginArtifact(
    name="predictions.csv",
    artifact_type=ArtifactType.PREDICTOR_OUTPUT,
    content=csv_bytes,
    metadata={"schema": "predictions-v1"},
)

文本辅助构造器:

PluginArtifact.text(
    name="report.md",
    artifact_type=ArtifactType.SCIENTIFIC_REPORT,
    content=markdown,
    metadata={"format": "markdown"},
)

name 必须是普通文件名,不能含目录。多个新产物可用 parent_names 建立同一次结果内的谱系;父产物必须排在子产物之前:

artifacts=(
    PluginArtifact.text("data.json", ArtifactType.DATASET, data),
    PluginArtifact.text(
        "analysis.json",
        ArtifactType.ANALYSIS_RESULT,
        analysis,
        parent_names=("data.json",),
    ),
)

默认 visibility=ArtifactVisibility.RESEARCHEVALUATOR_PRIVATE 只应用于经过设计和审查的受信任评估流程,不能作为普通插件隐藏不希望用户看到的结果。

受信任评估器

清单字段为:

type: evaluator
access_role: trusted_evaluator
trusted_metrics: true

受信任评估器应满足更严格的设计:

  • 预测与公开评估特征通过普通 artifact inputs 传入;
  • 私有目标由 Runtime 根据不可变 evaluation_id 解析;
  • 样本 ID、shape、schema 和 evaluation identity 必须逐项匹配;
  • 输出只包含聚合指标、允许公开的诊断和评估产物;
  • 不能把私有标签复制进 observations、日志或研究可见产物;
  • 测试证明普通插件和 Reasoner 无法读取私有内容。

在清单中自称 trusted 并不能替代代码审查和部署信任。第三方评估器启用前必须按高权限代码审核。

Executor 插件

执行后端继承 ExecutorPlugin,核心方法是:

async def execute_job(
    self,
    job: JobSpec,
    context: PluginExecutionContext,
) -> JobResult:
    ...

可选实现 submit()poll()cancel()collect_outputs()collect_usage()cleanup(),用于异步或远程后端。execute() 已提供 job_spec 到这些生命周期方法的兼容转换。

一个 Executor 必须执行 JobSpec 中声明的命令、输入和输出协议,保留 stdout/stderr 与退出状态,支持取消,并确保输出仍位于工作区。远程调度器还要持久化外部 job ID,才能在进程重启后恢复轮询。

不要把 Executor 当成 Domain 插件:领域插件描述“做什么科学操作”,Executor 描述“在哪儿以及如何运行一个已声明作业”。

版本与兼容性

  • sdk_version 变化代表 SDK 契约变化;当前为 1
  • patch 版本用于不改变输入输出的 bug 修复。
  • minor 版本可增加向后兼容的可选字段或能力。
  • 删除能力、重命名 metric、改变必填输入或产物 schema 应升 major。
  • 已持久化运行依赖旧能力身份;不要就地改变旧版本语义。

若必须迁移,为新能力使用新 ID,并在发行说明中给出配置转换示例。能力别名只用于有意的兼容路由,不能掩盖不兼容变更。

ZIP 包限制

自定义插件包必须满足:

  • .zip 扩展名;
  • 压缩内容最多 10 MiB;
  • 解压内容最多 50 MiB;
  • 最多 512 个文件;
  • 一个明确的 plugin.yamlplugin.ymlplugin.json
  • 无绝对路径、.. 穿越、符号链接、加密、重复或冲突条目。

不要打包虚拟环境、模型缓存、Git 历史、测试输出或密钥。大型模型和数据应作为独立、可校验的部署资源,而不是塞进插件 ZIP。

推荐测试层次

  1. Manifest 测试:解析清单,核对能力 ID、metric producer 和 entrypoint。
  2. 纯单元测试:直接调用 execute(),覆盖正常值、边界值和错误输入。
  3. Artifact 测试:内容、metadata、父子关系和 hash 后读取。
  4. Registry 测试:从真实目录发现并加载,不依赖测试专用 import path。
  5. ZIP 测试:安装、启用、禁用;验证恶意路径与超限包被拒绝。
  6. 端到端运行:用很小预算创建合同并达到真实验收条件。
  7. 恢复测试:若有长作业,测试暂停、进程中断、恢复与取消。
  8. 权限测试:普通插件不能读取 evaluator-private 或其他 run 的产物。

代码检查可从以下命令开始:

ruff check path/to/plugin
pytest -q path/to/plugin/tests
scientific-agent --data-dir /tmp/plugin-e2e run plugin-example.yaml

找到 条结果:“

    没有找到匹配结果:“