Skip to content

第 2 课:把操作交给资源 ​

Notes 同时有 read 和 append,读取节点却只需要前者。直接把整个 Notes 交给每个节点,会让每处业务都依赖实体的全部方法。资源让我们把“实体拥有内容”与“这段逻辑可以执行什么操作”分别表达。

先定义两种操作 ​

js
const reading = new ResourceOffer({
  methods: {
    read(notes, id) {
      const note = notes.read(id);
      onRead(note); // 仅在真实读取成功后输出宿主日志。
      return note;
    },
  },
});
const writing = new ResourceOffer({
  methods: {
    append(notes, value) {
      const note = notes.append(value);
      onWrite(note);
      return note;
    },
  },
});

两个 ResourceOffer 分别开放 read 与 append。方法的第一个参数 notes 是绑定到的业务对象,后面的 id 或 value 才来自这次操作。Offer 描述操作,创建 Offer 本身不读取、不写入便签。

为什么再包一层已有方法?这里能明确每种能力的范围,也能组合宿主行为:读取成功后才调用 onRead,追加成功后才调用 onWrite。它们是示例主动安排的日志回调;这些日志帮助我们核对任务,不等同于内核记录系统。

两个回调都发生在实体方法返回之后。尤其 onWrite 被调用时,便签已经追加了。如果以后把回调改成可能失败的工作,就必须理解这个先后关系,不能因为回调出错便认定写入没有发生。

申请操作,再绑定实体 ​

js
const readNote = new Node({
  deps: { notes: reading.request() },
  run: (work) => work.resources.notes.read(work.input.id),
});
const writeNote = new Node({
  deps: { notes: writing.request() },
  run: (work) => work.resources.notes.append(work.input),
});

先只看 deps。reading.request() 声明读节点对这一种 Offer 的请求,writing.request() 则属于写节点。notes 是节点给依赖起的局部名字,因而两个节点都能写 work.resources.notes,却各自得到不同的操作集合。

请求还需要实际供应。下面装配代码的前两次 register,把同一个 Notes 分别绑定到两种 Offer:

js
const resources = new Resources()
  .register(new Resource(notes), reading)
  .register(new Resource(notes), writing)
  .register(new Resource(exchanges), ModelExchangeResource)
  .register(new Resource({ exchanges, channel: interactions.bind(contract), ...(provider.inspectRequest ? { inspection: provider } : {}) }), ToolModelResource);
const runner = new Runner(new Scene(assistant), resources, interactions).build();
return { runner, interactions };

new Resource(notes) 把宿主对象纳入资源绑定;register(..., reading) 和 register(..., writing) 提供可匹配的能力。这里创建了两个包装,底层仍是同一个 notes,因此写入后能从同一集合读回。其余两次登记是模型所需能力,第 5 课再解释。

依赖和登记要一起读:deps 说明节点需要什么,Resources 说明宿主提供什么。只起一个相同的属性名,并不能凭空取得操作;也不能因为宿主有写能力,就推导所有后代都应获得它。本层依赖与子树供应的完整规则见资源契约。

练习:沿读路径找一次接线 ​

打开 assistant.mjs,依次找到 reading、读节点的 reading.request()、register(new Resource(notes), reading),然后运行:

sh
node examples/notes-assistant.mjs

预期仍是两次读取和一次写入。对着输出,指出每条日志在哪个 Offer 中产生,以及该节点能调用哪个操作。这次练习不需要改代码:能完整追到这三个位置,才算理解接线。

接下来把注意力从依赖移到 run,看输入和结果如何流动:第 3 课:让 Node 完成一次工作。