跳转到正文

预定义流程 API ​

English / 简体中文 · Runtime · API

这些函数从 @codesoul-co/ditto/runtime 导出,组合已有公开节点。完整代码见 flows.ts。运行 npm run example:runtime:flows 会执行 Skill、文本 RAG 和真实 README 工具读取;MCP/ReAct 函数需要调用者提供已连接客户端或模型配置,不会自动运行。

Context 流程 ​

前四个函数签名均为 (runtime: RuntimeClient, input) => Promise<...>,只有两个参数。RuntimeClient 要实现 invoke/emit,可传 DittoRuntime 或 WorkerContext。它们使用显式 Context,不自动保存 session 缓存;需要缓存时使用带 scope 的 CONTEXT 节点。

API / 输入输出与调用链
runRagFlow(runtime, {context,query?,corpus?,limit?,maxTokens?,options?}){output:ContextSelection,context:output.context};SELECT purpose=infer,strategy.kind=rag;需已注入 ragStrategy
runSkillFlow(runtime, {context?,sources}){output:Context,context};LOAD sources,有 context 再 UPDATE 合并;sources 为已解析 ContextSource[]
runToolCallFlow(runtime, {context,call}){output:ExternalResult,observation,context};TOOL → OBSERVE → UPDATE
runMcpFlow(runtime, {context,request:{operation:"discover",server?}}){output:{operation:"discover",capabilities},context};只发现,不生成 observation、不修改 context
runMcpFlow(runtime, {context,request:{operation:"invoke",server,call}}){output:{operation:"invoke",result},observation,context};MCP → OBSERVE → UPDATE;校验 callId

query/content 使用 MessageContent,corpus 使用 Reference,options 为 JsonObject;limit/maxTokens 为可选预算,具体校验见 CONTEXT。call 为 {id,name,arguments},arguments 必须是 JSON 对象。工具、MCP 客户端和相应 Sandbox 权限必须在执行端配置。业务 failed 结果仍可经过 OBSERVE 写入 Context,调用方自行判断是否继续;基础设施异常直接拒绝。

Skill 流程不下载、扫描或执行 SKILL.md,也不隐式检查 skills 权限。应用解析 sources,并在自己的解析/执行边界调用 sandbox.assert("skills", name)。默认 CONTEXT.LOAD 保存 Reference,不自动读取其目标;需要引用解析时显式使用 LOAD 的 resolveReferences 与 resolver。

以下是完整示例的导入和工具定义:

ts
import assert from "node:assert/strict";
import type { Context, ToolCall } from "@codesoul-co/ditto/contracts";
import type { RuntimeClient } from "@codesoul-co/ditto/worker";
import { createContextWorker, createRagStrategy } from "@codesoul-co/ditto/worker/context";
import { createInteractionWorker, type RegisteredTool } from "@codesoul-co/ditto/worker/interaction";
import type { ModelConfig } from "@codesoul-co/ditto/worker/infer";
import {
  createDitto, runMcpFlow, runRagFlow, runReactFlow, runSkillFlow, runToolCallFlow,
  type DittoRuntime,
} from "@codesoul-co/ditto/runtime";
ts
export const readTextTool: RegisteredTool = {
  name: "read_text", description: "Read a workspace text file", effects: ["read"],
  inputSchema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] },
  validate(args) { if (typeof args.path !== "string") throw new TypeError("path must be a string"); },
  async execute(args, ctx) {
    return { status: "success", content: await ctx.services.sandbox.readText(args.path as string, ctx.signal) };
  },
};
ts
export async function contextFlows() {
  const runtime = createDitto({
    sandbox: { read: true, tools: ["read_text"] },
    workers: [
      createContextWorker({ services: { ragStrategy: createRagStrategy({ retrieve: {
        async retrieve({ items, query }) {
          if (typeof query !== "string") throw new TypeError("This retriever accepts text queries");
          return items.filter(item => typeof item.content === "string"
            && item.content.toLowerCase().includes(query.toLowerCase())).map(item => ({ item }));
        },
      } }) } }),
      createInteractionWorker({ tools: [readTextTool] }),
    ],
  });
  try {
    const skill = await runSkillFlow(runtime, {
      context: { items: [{ id: "task", content: "Read the project README." }] },
      sources: [{ id: "review-skill", content: "Review correctness and tests." }],
    });
    const selected = await runRagFlow(runtime, { context: skill.context, query: "tests", limit: 1 });
    assert.deepEqual(selected.context.items.map(item => item.id), ["review-skill"]);
    const tool = await runToolCallFlow(runtime, {
      context: selected.context,
      call: { id: "read-1", name: "read_text", arguments: { path: "README.md" } },
    });
    assert.equal(tool.output.status, "success");
    assert.equal(tool.observation.message.role, "tool");
    assert.equal(tool.context.items.length, 2);
    return { selectedIds: selected.context.items.map(item => item.id), toolStatus: tool.output.status };
  } finally { await runtime.close(); }
}

示例使用当前条目的真实子串检索;向量/BM25/混合检索换成数据库或 RETRIEVAL Provider,流程接口不变。RAG 只返回选中的 Context,不生成答案。SELECT 返回 context/selectedItemIds/purpose;传入 INFER 前由应用映射为 messages,见 Context → Infer。

MCP 的两个分支:

ts
export async function mcpFlows(runtime: RuntimeClient, context: Context, server: string, call: ToolCall) {
  // The caller registers CONTEXT and an INTERACTION Worker with a connected MCP client.
  const discovered = await runMcpFlow(runtime, { context, request: { operation: "discover", server } });
  if (!discovered.output.capabilities.some(capability => capability.name === call.name && capability.server === server)) {
    throw new Error(`MCP capability not found: ${server}/${call.name}`);
  }
  const invoked = await runMcpFlow(runtime, {
    context: discovered.context, request: { operation: "invoke", server, call },
  });
  return { capabilities: discovered.output.capabilities, ...invoked };
}

发现到的 capabilities 是 schema 描述,不是 ExternalResult,不能直接传入 OBSERVE。MCP SDK 接入见 Interaction API。以上流程没有第三个 options 参数;从 Worker handler 传 ctx 时继承本地执行信号。需要按节点绑定 Worker、Graph 并发或显式 signal 时,将相同叶子组合成 graph,再使用 runtime.run(plan,input,options)。

ReAct ​

runReactFlow(runtime, input, options?) 接受提供 run/services 的 Runtime。input 为 SAMPLE 输入(model/messages/generation/actions/metadata)加 constraints;options 为 {graphId?,signal?,timeoutMs?}。需要 INFER 以及动作目标所对应的 Worker,不要求 CONTEXT Worker。模型只产生 ActionRequest;运行流程负责执行并把 Observation 反馈到下一次 SAMPLE。

ts
export async function reactFlow(runtime: DittoRuntime, model: ModelConfig) {
  // Register INFER and INTERACTION with readTextTool; configure the model provider and Sandbox.
  const result = await runReactFlow(runtime, {
    model, messages: [{ role: "user", content: "Read README.md and summarize this project." }],
    actions: [{ name: readTextTool.name, description: readTextTool.description!, inputSchema: readTextTool.inputSchema }],
    constraints: { maxSteps: 4, maxActionCalls: 2, maxTotalTokens: 16_000 },
  }, { graphId: "readme-agent", timeoutMs: 30_000 });
  if (result.status !== "completed") throw new Error(`ReAct stopped: ${result.stopReason}`);
  return result;
}

返回 ReactFlowResult:result、samples、observations、actionRequests、usage,以及 status(completed/partial/failed)、stopReason 和可选 error,不套 NodeResult。示例把非 completed 转为异常,应用也可展示 partial 并保留未解决动作。

约束优先级、动作 target(Tool/MCP/公开 Node)与停止原因见 ReAct 详细说明。本地信号传递到模型与动作 handler;HTTP/IPC 取消只结束调用端等待。Token 预算依据 Provider usage 限制后续采样,不是硬计费上限;流程不会自动重试外部动作。

Ditto · @codesoul-co/ditto · Node.js 24+