跳转到正文

check-examples-sequence-live.ts ​

源文件: scripts/check-examples-sequence-live.ts · 下载原文件 · 示例使用说明

ts
/** Real-model acceptance checks for the sequence examples. Run explicitly with .env. */
import assert from "node:assert/strict";
import { randomBytes, randomInt } from "node:crypto";
import { writeFile } from "node:fs/promises";
import { parseArgs } from "node:util";
import { createDitto, loadRuntimeConfigFile } from "@codesoul-co/ditto/runtime";
import { createContextWorker } from "@codesoul-co/ditto/worker/context";
import { createInferWorker, type NodeResult, type SampleOutput, type Usage } from "@codesoul-co/ditto/worker/infer";
import { createInteractionWorker, type InteractionOutputInput } from "@codesoul-co/ditto/worker/interaction";
import { pipelineGraph } from "../examples/control-flow/sequence/pipeline.ts";
import { dependenciesGraph } from "../examples/control-flow/sequence/dependencies.ts";
import { runStages } from "../examples/control-flow/sequence/stages.ts";
import { runBatch } from "../examples/control-flow/sequence/batch.ts";

const { values } = parseArgs({ options: {
  provider: { type: "string" },
  report: { type: "string", default: ".examples-sequence-live-results.json" },
} });
const config = loadRuntimeConfigFile("ditto.yaml", process.env);
const providers = values.provider?.split(",") ?? (config.model ? [config.model.provider] : []);
assert.ok(providers.length && providers.every(name => name && config.providers[name]), "Select a configured provider with --provider or DITTO_WORKER_INFER_MODEL_PROVIDER");

function completed(result: NodeResult<SampleOutput>): SampleOutput {
  const httpStatus = result.error?.code === "PROVIDER_HTTP_ERROR"
    ? result.error.message.match(/HTTP \d{3}/)?.[0] : undefined;
  assert.equal(result.status, "success", `Model call failed: ${result.error?.code ?? result.status}${httpStatus ? ` (${httpStatus})` : ""}`);
  assert.ok(result.output, "Missing model output");
  assert.equal(result.output.finishReason, "stop", "Model must complete without truncation or actions");
  assert.equal(result.output.message.role, "assistant");
  assert.equal(typeof result.output.message.content, "string");
  return result.output;
}

function answer(result: NodeResult<SampleOutput>): unknown {
  const sample = completed(result);
  return JSON.parse(String(sample.message.content).trim().replace(/^```(?:json)?\s*/, "").replace(/\s*```$/, ""));
}

interface CaseReport {
  provider: string;
  model: string;
  example: string;
  status: "passed" | "failed";
  durationMs: number;
  expected: unknown;
  executionId?: string;
  executionIds?: string[];
  usage?: Usage;
  actual?: unknown;
  modelCalls?: number;
  deliveries?: number;
  error?: string;
}
interface WorkflowResult {
  samples: readonly NodeResult<SampleOutput>[];
  actual: unknown;
  finalContent: unknown;
}
const startedAt = new Date().toISOString();
const results: CaseReport[] = [];
async function save() {
  await writeFile(values.report!, JSON.stringify({
    startedAt, checkedAt: new Date().toISOString(),
    transport: "configured HTTP model providers", providers,
    passed: results.filter(result => result.status === "passed").length,
    failed: results.filter(result => result.status === "failed").length,
    results,
  }, null, 2) + "\n");
}

for (const provider of providers) {
  const modelName = config.providers[provider]!.model
    ?? (config.model?.provider === provider ? config.model.model : undefined);
  assert.ok(modelName, `Missing model for provider ${provider}`);
  const model = { provider, model: modelName };
  const deliveries: InteractionOutputInput[] = [];
  // Only built-in Workers and the configured HTTP providers; no model doubles.
  const runtime = createDitto({ config, workers: [
    createContextWorker({ policy: config.context.policy ?? {} }),
    createInferWorker(),
    createInteractionWorker({ output: { async deliver(input) {
      deliveries.push(input);
      return { deliveryId: input.deliveryId, status: "accepted" };
    } } }),
  ] });

  const runCase = async (example: string, expected: unknown, run: () => Promise<NodeResult<SampleOutput> | WorkflowResult>) => {
    deliveries.length = 0;
    const start = Date.now();
    const report: CaseReport = { provider, model: modelName, example, status: "failed", durationMs: 0, expected };
    console.log(JSON.stringify({ provider, model: modelName, example, event: "started" }));
    try {
      const value = await run();
      const outcome: WorkflowResult = "samples" in value ? value : {
        samples: [value], actual: answer(value), finalContent: completed(value).message.content,
      };
      report.executionIds = outcome.samples.map(sample => sample.executionId);
      if (report.executionIds[0]) report.executionId = report.executionIds[0];
      const usage: Usage = {};
      for (const sample of outcome.samples) {
        const output = completed(sample);
        for (const key of ["inputTokens", "outputTokens", "totalTokens", "reasoningTokens", "cachedInputTokens"] as const) {
          if (output.usage?.[key] !== undefined) usage[key] = (usage[key] ?? 0) + output.usage[key];
        }
      }
      if (Object.keys(usage).length) report.usage = usage;
      report.modelCalls = outcome.samples.length;
      report.actual = outcome.actual;
      report.deliveries = deliveries.length;
      assert.deepEqual(report.actual, expected, "Model answer must match the generated input");
      assert.deepEqual(deliveries.at(-1)?.message.content, outcome.finalContent, "Final delivery must contain the validated model result");
      report.status = "passed";
    } catch (error) {
      report.error = error instanceof Error ? error.message : "Unknown failure";
    } finally {
      report.durationMs = Date.now() - start;
      results.push(report);
      await save();
      console.log(JSON.stringify(report));
    }
  };

  try {
    const code = `PICKUP-${randomBytes(6).toString("hex")}`;
    const count = randomInt(2, 50);
    await runCase("pipeline", { code, count }, async () => {
      const plan = pipelineGraph
        .node("answered", "INFER.REASONING.SAMPLE", ["selected"], (_input, { selected }) => ({
          model,
          messages: [
            { role: "system", content: 'Read the supplied records. Return ONLY JSON with code (string) and count (number) for the pickup. Do not invent missing values.' },
            { role: "user", content: JSON.stringify(selected.context.items) },
          ],
        }))
        .node("delivered", "INTERACTION.OUTPUT", ["answered"], (_input, { answered }) => {
          const output = completed(answered);
          return { deliveryId: code, message: { role: "assistant", content: String(output.message.content) } };
        });
      const output = await runtime.run(plan, {
        items: [
          { id: "pickup", content: `Pickup code: ${code}. Pickup count: ${count}.` },
          { id: "unrelated", content: "Office opens at nine. Reference code: IGNORE-000; count: 999." },
        ],
        query: "pickup", limit: 1,
      });
      assert.deepEqual(output.selected.selectedItemIds, ["pickup"]);
      assert.equal(output.delivered.status, "accepted");
      assert.equal(deliveries.length, 1);
      return output.answered;
    });

    const originalCode = `OLD-${randomBytes(6).toString("hex")}`;
    const currentCode = `NEW-${randomBytes(6).toString("hex")}`;
    const currentCount = randomInt(51, 100);
    await runCase("dependencies", { originalCode, currentCode, currentCount }, async () => {
      const plan = dependenciesGraph
        .node("answered", "INFER.REASONING.SAMPLE", ["loaded", "selected", "delivered"], (_input, { loaded, selected, delivered }) => {
          assert.equal(delivered.status, "accepted", "Context delivery must finish before model interpretation");
          return { model, messages: [
            { role: "system", content: 'Compare the original and current records. Return ONLY JSON with originalCode (string), currentCode (string), currentCount (number). Preserve the codes exactly; use the current count.' },
            { role: "user", content: JSON.stringify({ original: loaded.items, current: selected.context.items }) },
          ] };
        })
        .node("final", "INTERACTION.OUTPUT", ["answered"], (_input, { answered }) => {
          const output = completed(answered);
          return { deliveryId: currentCode, message: { role: "assistant", content: String(output.message.content) } };
        });
      const input = {
        items: [{ id: "shipment", content: `Shipment code: ${originalCode}. Shipment count: 1.` }],
        additions: [{ id: "shipment", content: `Shipment code: ${currentCode}. Shipment count: ${currentCount}.` }],
        query: "shipment", limit: 1, deliveryId: `${currentCode}:context`,
      };
      const originalInput = structuredClone(input);
      const output = await runtime.run(plan, input);
      assert.deepEqual(input, originalInput);
      assert.deepEqual(output.loaded.items, originalInput.items);
      assert.deepEqual(output.updated.items, originalInput.additions);
      assert.deepEqual(output.selected.selectedItemIds, ["shipment"]);
      assert.deepEqual(deliveries[0]?.message.content, {
        originalItemIds: ["shipment"], selectedItemIds: ["shipment"],
        selectedContent: originalInput.additions.map(item => item.content),
      });
      assert.equal(output.final.status, "accepted");
      assert.equal(deliveries.length, 2);
      return output.answered;
    });

    const order = {
      code: `ORDER-${randomBytes(6).toString("hex")}`,
      quantity: randomInt(2, 10), unitPriceCents: randomInt(100, 1000),
    };
    await runCase("stages", { code: order.code, totalCents: order.quantity * order.unitPriceCents }, async () => {
      const output = await runStages(runtime, {
        id: order.code, model,
        text: `Order code ${order.code}: ${order.quantity} notebooks at ${order.unitPriceCents} cents each.`,
      });
      assert.deepEqual(output.order, order, "Preparation must extract the supplied order");
      assert.deepEqual(answer(output.prepared.extracted), order);
      assert.equal(output.completed.delivered.status, "accepted");
      assert.equal(deliveries.length, 1);
      const actual = answer(output.completed.summarized);
      return { samples: [output.prepared.extracted, output.completed.summarized], actual, finalContent: actual };
    });

    const records = Array.from({ length: 3 }, (_, index) => ({
      id: `item-${index}`, code: `BATCH-${randomBytes(6).toString("hex")}`, quantity: randomInt(1, 50),
    }));
    const expectedBatch = { items: records, totalQuantity: records.reduce((sum, item) => sum + item.quantity, 0) };
    await runCase("batch", expectedBatch, async () => {
      const input = {
        model, deliveryId: `batch-${randomBytes(6).toString("hex")}`,
        items: records.map(record => ({ id: record.id, text: `Pickup code ${record.code}; quantity ${record.quantity}.` })),
      };
      const snapshot = structuredClone(input);
      const output = await runBatch(runtime, input);
      assert.deepEqual(input, snapshot);
      assert.equal(output.samples.length, records.length);
      assert.equal(output.receipt.status, "accepted");
      assert.equal(deliveries.length, 1, "Batch must deliver one combined summary");
      const actual = { items: output.results, totalQuantity: output.results.reduce((sum, item) => sum + item.quantity, 0) };
      return { samples: output.samples, actual, finalContent: actual };
    });
  } finally {
    await runtime.close();
  }
}
assert.equal(results.length, providers.length * 4, "Every provider must run all four examples");
if (results.some(result => result.status === "failed")) process.exitCode = 1;

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