Skip to main content
// Props
import { ClassifyAndRoute } from "smithers-orchestrator";

type CategoryConfig = {
  agent: AgentLike;
  output?: OutputTarget;
  prompt?: (item: unknown) => string;
};

type ClassifyAndRouteProps = {
  id?: string;                                                       // prefix for auto-generated child task IDs; defaults to "classify-and-route"
  items: unknown | unknown[];
  categories: Record<string, AgentLike | CategoryConfig>;
  classifierAgent: AgentLike;
  classifierOutput: OutputTarget;
  routeOutput: OutputTarget;
  classificationResult?: { classifications: Array<{ category: string; itemId?: string }> } | null;
  maxConcurrency?: number;                                           // optional; unbounded when omitted
  skipIf?: boolean;
  children?: ReactNode;                                              // custom classifier prompt
};
const classification = ctx.outputMaybe(outputs.classification, {
  nodeId: "classify-and-route-classify",
});

<Workflow name="support-router">
  <ClassifyAndRoute
    items={ctx.input.tickets}
    categories={{ billing: billingAgent, support: supportAgent, sales: salesAgent }}
    classifierAgent={classifierAgent}
    classifierOutput={outputs.classification}
    routeOutput={outputs.handled}
    classificationResult={classification}
  />
</Workflow>;

Notes

  • Two-phase: first render classifies; pass the result back via classificationResult to mount route handlers.
  • Each entry’s category must match a key in categories; unknown categories are silently skipped.
  • Route tasks default to continueOnFail.

Source

The <ClassifyAndRoute> implementation and the files it imports, straight from the package source. This section is generated; edit the source, not this block.
// @smithers-type-exports-begin
/** @typedef {import("./ClassifyAndRouteProps.ts").ClassifyAndRouteProps} ClassifyAndRouteProps */
// @smithers-type-exports-end

import React from "react";
import { Sequence } from "./Sequence.js";
import { Parallel } from "./Parallel.js";
import { Task } from "./Task.js";
/** @typedef {import("@smithers-orchestrator/agents/AgentLike").AgentLike} AgentLike */
/** @typedef {import("./CategoryConfig.ts").CategoryConfig} CategoryConfig */

/**
 * @param {AgentLike | CategoryConfig} value
 * @returns {value is CategoryConfig}
 */
function isConfig(value) {
    return "agent" in value && typeof value.generate !== "function";
}
/**
 * <ClassifyAndRoute> — Classify items then route to category-specific agents.
 *
 * Composes Sequence, Task, and Parallel. First a classifier Task assigns items
 * to categories, then a Parallel block routes each classified item to the
 * appropriate category agent.
 * @param {ClassifyAndRouteProps} props
 */
export function ClassifyAndRoute(props) {
    if (props.skipIf)
        return null;
    const { id, items, categories, classifierAgent, classifierOutput, routeOutput, classificationResult, maxConcurrency, children, } = props;
    const prefix = id ?? "classify-and-route";
    const itemList = Array.isArray(items) ? items : [items];
    const categoryNames = Object.keys(categories);
    // Step 1: Classification task
    const classifyTask = React.createElement(Task, {
        key: `${prefix}-classify`,
        id: `${prefix}-classify`,
        output: classifierOutput,
        agent: classifierAgent,
        label: "Classify items",
        children: children ??
            `Classify the following items into categories: ${categoryNames.join(", ")}.\n\nItems:\n${JSON.stringify(itemList, null, 2)}`,
    });
    // Step 2: Route each classified item to its category agent
    const classifications = classificationResult?.classifications ?? [];
    const routeElements = classifications.map((c, idx) => {
        const catKey = c.category;
        const catEntry = categories[catKey];
        if (!catEntry)
            return null;
        const agent = isConfig(catEntry) ? catEntry.agent : catEntry;
        const output = isConfig(catEntry) ? (catEntry.output ?? routeOutput) : routeOutput;
        const prompt = isConfig(catEntry) && catEntry.prompt
            ? catEntry.prompt(c)
            : `Handle item classified as "${catKey}":\n${JSON.stringify(c, null, 2)}`;
        return React.createElement(Task, {
            key: `${prefix}-route-${c.itemId ?? idx}`,
            id: `${prefix}-route-${c.itemId ?? idx}`,
            output,
            agent,
            continueOnFail: true,
            label: `Route: ${catKey}${c.itemId ? ` (${c.itemId})` : ""}`,
            children: prompt,
        });
    }).filter(Boolean);
    const sequenceChildren = [classifyTask];
    if (routeElements.length > 0) {
        sequenceChildren.push(React.createElement(Parallel, {
            key: `${prefix}-routes`,
            id: `${prefix}-routes`,
            maxConcurrency,
        }, ...routeElements));
    }
    return React.createElement(Sequence, null, ...sequenceChildren);
}