Skip to main content
Steps are reusable units of work that can perform I/O operations like API calls, database queries, and LLM requests. While workflows orchestrate, steps execute.

Basic Step

steps.ts
import { step, z } from '@output.ai/core';

export const sumValues = step({
  name: 'sumValues',
  description: 'Sum all values in an array',
  inputSchema: z.array(z.number()),
  outputSchema: z.number(),
  fn: async (numbers) => {
    return numbers.reduce((sum, n) => sum + n, 0);
  }
});

Step Options

OptionTypeDescription
namestringUnique identifier for the step
descriptionstringHuman-readable description
inputSchemaZodSchemaZod schema for input validation
outputSchemaZodSchemaZod schema for output validation
fnfunctionThe step implementation
optionsobjectTemporal activity options override

Steps vs Workflows

Workflows

  • Orchestrate steps
  • Must be deterministic
  • No direct I/O operations

Steps

  • Execute I/O operations
  • Can call APIs, databases, LLMs
  • Automatically retried on failure

Step with I/O

steps.ts
import { step, z } from '@output.ai/core';
import { generateText } from '@output.ai/llm';

export const generateResponse = step({
  name: 'generateResponse',
  inputSchema: z.object({
    prompt: z.string(),
    context: z.string()
  }),
  outputSchema: z.string(),
  fn: async (input) => {
    const result = await generateText({
      prompt: 'respond@v1',
      variables: {
        prompt: input.prompt,
        context: input.context
      }
    });
    return result;
  }
});

Retry Options

Override retry behavior per step:
export const unreliableApiCall = step({
  name: 'unreliableApiCall',
  inputSchema: z.string(),
  outputSchema: z.object({ data: z.any() }),
  fn: async (url) => {
    const response = await fetch(url);
    return response.json();
  },
  options: {
    retry: {
      maximumAttempts: 5,
      initialInterval: '1s',
      backoffCoefficient: 2
    }
  }
});

Shared Steps

By default, steps are scoped to their workflow. To share steps across workflows, create a shared_steps.ts file:
shared_steps.ts
import { step, z } from '@output.ai/core';

export const logEvent = step({
  name: 'logEvent',
  inputSchema: z.object({
    event: z.string(),
    data: z.any()
  }),
  outputSchema: z.void(),
  fn: async (input) => {
    console.log(`[${input.event}]`, input.data);
  }
});
Import shared steps in any workflow:
workflow.ts
import { workflow, z } from '@output.ai/core';
import { logEvent } from '../shared/shared_steps.js';

export default workflow({
  name: 'myWorkflow',
  inputSchema: z.object({ data: z.any() }),
  outputSchema: z.void(),
  fn: async (input) => {
    await logEvent({ event: 'workflow_started', data: input.data });
    // ... workflow logic
  }
});

Evaluators

Evaluators are a special type of step for analyzing LLM responses. They must return an EvaluationResult:
evaluators.ts
import { evaluator, EvaluationStringResult, z } from '@output.ai/core';

export const judgeQuality = evaluator({
  name: 'judgeQuality',
  inputSchema: z.string(),
  fn: async (response) => {
    // Analyze the response
    const score = analyzeQuality(response);

    return new EvaluationStringResult({
      value: score > 0.8 ? 'good' : 'poor',
      confidence: score
    });
  }
});
Use evaluators like any other step:
workflow.ts
import { workflow, z } from '@output.ai/core';
import { judgeQuality } from './evaluators.js';

export default workflow({
  name: 'evaluateResponse',
  inputSchema: z.object({ response: z.string() }),
  outputSchema: z.object({ quality: z.string() }),
  fn: async (input) => {
    const result = await judgeQuality(input.response);
    return { quality: result.value };
  }
});