Anvia
Pipelines

Composition Patterns

Compose reusable workflows from small pipeline stages.

Once the basic flow works, split reusable stages into small pipeline operations and compose them with .use(...).

Reuse a Pipeline Operation

const normalizeTicket = new PipelineBuilder<string>()
  .step((ticket) => ticket.trim())
  .step((ticket) => ticket.replace(/\s+/g, " "))
  .build();

const supportPipeline = new PipelineBuilder<string>()
  .use(normalizeTicket)
  .prompt(supportAgent)
  .build();

Use .use(...) when a whole stage is useful in more than one workflow.

Normalize, Prompt, Extract

const triagePipeline = new PipelineBuilder<string>()
  .use(normalizeTicket)
  .step((ticket) => `Summarize and classify:\n\n${ticket}`)
  .prompt(triageAgent)
  .extract(triageExtractor)
  .build();

This is the default shape for many production workflows: deterministic preprocessing, model reasoning, then schema validation.

Branch, Then Merge

const pipeline = new PipelineBuilder<string>()
  .use(normalizeTicket)
  .parallel({
    reply: new PipelineBuilder<string>().prompt(replyAgent).build(),
    triage: new PipelineBuilder<string>().prompt(triageAgent).extract(triageExtractor).build(),
  })
  .step(({ reply, triage }) => ({
    reply,
    priority: triage.priority,
  }))
  .build();

Use this when the branches do not depend on each other but the final result should combine them.

Keep the Shape Visible

Prefer a pipeline that reads like the workflow:

const pipeline = new PipelineBuilder<string>()
  .use(loadTicket)
  .use(addRetrievalContext)
  .prompt(agent)
  .extract(outputExtractor)
  .use(saveResult)
  .build();

If a step becomes hard to name, split it into smaller steps.