
第 3 篇槽位注册如何让 Norp 认识你定义的“新插口”3.1 从问题出发上一篇你学会了把组件放进注册表reg.register_tool(weather, WeatherTool()) reg.register_model(my_model, MyModel())然后呢你要用它们得通过槽位npa(tools[weather], modelmy_model)问题来了model、tools、session、sandbox……这些槽位名字是哪里来的为什么npa()认model但不认my_custom_slot答案是这些名字来自一张槽位表SLOT_SPECS。npa()启动时先把你的参数名跟这张表做匹配参数名在表里 → 当作槽位值处理参数名不在表里 → 当作任务参数透传比如max_steps、task_timeout默认情况下槽位表里有 18 个内置名字。你想让npa(my_custom_slot...)被当作槽位识别就得往这张表里加一条记录。这就是“槽位注册”——给 Norp 定义一个新的可填参数。3.2 槽位的本质是什么一个槽位 三个东西的组合要素含义举例名字npa()的关键字参数名model、tools、vector_store语义字符串值怎么解释是模块地址还是注册表名还是字面值动作值非空时做什么注册到注册表订阅钩子写入extras内置的 18 个槽位已经在框架启动时注册好了。你现在要做的是把自己定义的新插口也加进去。3.3 注册一个槽位最小写法注册槽位用register_slotfrom norpagent.arch import SlotSpec, register_slot register_slot(SlotSpec( nameaudit_tag, string_semanticsliteral, applier_apply_audit_tag, ))然后就可以用了npa(audit_tagrelease-1)这四个字段是最小集合name槽位名也就是npa()的参数名string_semantics字符串值的解释方式applier值非空时执行什么动作注册后npa(audit_tagrelease-1)的行为是参数audit_tag被识别为槽位不走任务参数透传装配器把release-1literal 语义原样保留传给applierapplier拿到值执行你定义的逻辑3.4 string_semantics字符串的四种解释方式这是槽位注册里最需要想清楚的一点。string_semantics决定了一个字符串进入这个槽位后怎么解释。有四种选择语义含义举例address字符串 模块地址myapp.models:create→ 加载模块并实例化name字符串 注册表组件名openai_compat→ 去注册表查这个名字name_or_address先按注册表名查查不到再按地址加载npa(modelsomething)literal字符串 字面值但“形如地址”的按地址加载high是级别myapp.sec:build是地址选择哪种语义取决于你的槽位要什么类型的值你的槽位要传一个模块地址 → 用address你的槽位要传一个已注册组件的名字 → 用name你的槽位既要支持已注册名又要支持外部地址 → 用name_or_address你的槽位主要传字面值级别、路径、目录偶尔传地址 → 用literal3.5 applier值非空时做什么applier是槽位的“执行逻辑”。它的签名是def applier(reg, layer, value, params, ctx): # reg: 注册表实例 # layer: 架构层实例 # value: 解析后的槽位值字符串已按 string_semantics 解释过 # params: 运行时参数 dict # ctx: 四个可变容器 passctx里有四个你可以操作的东西容器用途ctx[components]声明预设组件{kind: name}AgentRuntime 会据此构建组件ctx[extras]往引擎上挂额外对象通过engine.extras[槽位名]取用ctx[overrides]改写预设的字段比如覆盖preset.modelctx[meta]记录需要清理的对象热重载时用于退订applier 的核心职责把“槽位值”翻译成“系统动作”。光有值没用你得告诉框架怎么用这个值。3.6 完整示例开发一个“向量检索”槽位目标新增一个vector_store槽位。用户传值后自动注册为通用组件工具通过ctx.component(vector_store)取用。from norpagent.arch import SlotSpec, register_slot def apply_vector_store(reg, layer, value, params, ctx): # 1. value 可能是实例、工厂、或已解析的模块 factory value if callable(value) else (lambda vvalue: v) # 2. 注册为通用组件_arch_vector 是内部名字不冲突 reg.register_component(vector_store, _arch_vector, factory) # 3. 写入预设组件声明 → AgentRuntime 构建 ctx.components ctx[components][vector_store] _arch_vector # 4. 同时挂到 extras引擎侧可直接取用 ctx[extras][vector_store] value register_slot(SlotSpec( namevector_store, description向量检索组件, string_semanticsliteral, applierapply_vector_store, remount_rebuild_agentTrue, # 热替换后重建 AgentRuntime ))使用npa(vector_storeMyVectorStore()) # 或 npa(vector_storemyapp.vector:create) # 或 npa(vector_store{backend: pg, index: ./idx})工具侧取用store ctx.component(vector_store)3.7 remount_rebuild_agent热替换后要不要重建自定义槽位分为两类组件型只往注册表/注册表里挂了点东西下一次run()就能生效。这种不需要重建。装配型改写了预设的components或overrides或者替换了引擎核心组件。这种必须重建才能生效。remount_rebuild_agent告诉框架热替换这个槽位后要不要触发AgentRuntime热重建。False默认只更新extras/注册表不重建引擎True热替换后立即重建AgentRuntime新组件立即生效vector_store例子改写了components所以设True。3.8 重入安全热替换时别叠罗汉一个关键问题applier会在多个时机被重复调用启动时装配每次npa.remount(vector_store...)每次npa.remount()触发全量重新装配如果applier每次都在reg.bus上挂一个新订阅那订阅会越叠越多同一个事件触发十几次。这是 bug。解法用ctx[meta]记录旧对象重挂前先清理。内置的hooks槽位、security槽位、plugins槽位都遵循这个模式。框架热重载时会先调用卸载逻辑再重新装配保证事件订阅不叠加组件注册不堆积沙箱/会话等资源被正确释放def apply_my_slot(reg, layer, value, params, ctx): # 如果有旧订阅先退订 meta ctx.get(meta, {}) old meta.get(my_slot_subscription) if old: reg.bus.unsubscribe(old) meta.pop(my_slot_subscription, None) # 挂新订阅 def handler(event): print(event) reg.bus.subscribe(handler, on_content) meta[my_slot_subscription] handler这样每次重挂都先清理再挂新干净利落。3.9 检查槽位注册是否成功注册自定义槽位后用layer.describe()看装配清单eng npa.current() print(eng.layer.describe())输出里能看到vector_store - 地址 myapp.vector:create VectorStore 实例 audit_tag - 直接值 release-1 str如果看不到说明槽位没注册上或者注册时机晚于npa()启动。3.10 内置槽位受保护18 个内置槽位名是受保护的async_loop, agent_runtime, model, tools, session, sandbox, scheduler, context_store, project_manager, hooks, security, plugins, frontend, ui, preset, logger, storage, error_handler不能覆盖它们的规格不能注销它们。它们的值随时可以npa.remount热替换但“插口本身”是框架结构的一部分不可删除。3.11 你现在能做的事情读完这篇你已经能理解槽位 名字 语义 动作用register_slot注册自定义槽位选择合适的string_semantics写applier把槽位值翻译成系统动作理解remount_rebuild_agent什么时候用 True用ctx[meta]保证热重载不叠加订阅用layer.describe()检查槽位是否生效下一篇你会学到地址解析——字符串myapp.models:create是如何变成一个可调用对象的。