Basic concepts
GenSX is a simple typescript framework for building complex LLM applications. It’s built around functional, reusable components that are composed to create and orchestrate workflows.
Components
Components are the building blocks of GenSX applications; they’re pure TypeScript functions that:
- Accept an input and produce an output
- Don’t depend on global state
- Are strongly typed using TypeScript
You can also think of components as a unit of tracing: the inputs and outputs of components are recorded and traced making it easy to understand how data flows through your workflow.
Here’s an example of a simple component:
interface GreetUserInput {
name: string;
}
const GreetUser = gensx.Component(
"GreetUser",
async ({ name }: GreetUserInput) => {
return `Hello, ${name}!`;
},
);
At first glance, this syntax may seem a bit complex, but in reality, you’re simply passing a name and a function to gensx.Component()
, a higher-order function that returns a component. Another way to write the code above is:
function greetUser({ name }: GreetUserInput) {
return `Hello, ${name}!`;
}
const GreetUser = gensx.Component("GreetUser", greetUser);
Components can be used like any other functions in typescript. They can consume other components, return other components, and return any type of data. Here’s an example of a component that uses generateText
from the Vercel AI SDK to call an LLM:
import { generateText } from "@gensx/vercel-ai";
import { openai } from "@ai-sdk/openai";
const GreetUser = gensx.Component(
"GreetUser",
async ({ name }: GreetUserInput) => {
const result = await generateText({
model: openai("gpt-4.1-mini"),
messages: [
{
role: "system",
content: "You are a friendly assistant that greets people warmly.",
},
{ role: "user", content: `Write a greeting for ${name}.` },
],
});
return result.text;
},
);
To run a component, you just call it like a function:
const result = await GreetUser({ name: "John" });
Workflows
Workflows are a special type of component in GenSX. While components are the re-usable building blocks of your application, workflows are the top-level components that handle the orchestration and serve as the entry point for your application.
Each workflow creates a top level trace that shows all components that were executed along with their inputs and outputs.
Workflows are created in almost the same way as components:
const WriteBlog = gensx.Workflow(
"WriteBlog",
async ({ title, description }: WriteBlogInput) => {
const queries = await GenerateQueries({
title,
description,
});
const research = await ResearchBlog({ queries });
const draft = await WriteDraft({ title, context: research });
const final = await EditDraft({ title, content: draft });
return final;
},
);
Here you can see that components are executed sequentially, with the output of each component being passed as the input to the next component. Of course, you can also execute components in parallel using Promise.all
:
const CreateContent = gensx.Workflow(
"CreateContent",
async ({ title, description }: CreateContentInput) => {
// Create the different assets in parallel
const context = await GatherContext({ title, description });
const [blog, tweet, email] = await Promise.all([
WriteBlog({ title, description, context }),
WriteTweet({ title, description, context }),
WriteEmail({ title, description, context }),
]);
return { blog, tweet, email };
},
);
Just like components, workflows are invoked just like any other function:
const title = "How AI and agents broke modern infra";
const description = "...";
const result = await CreateContent({ title, description });
Another special property of workflows is that any workflow exported from your workflows.ts
file will be automatically turned into API endpoints that can be called both synchronously and asynchronously. More details on that here.
Component Isolation
Because workflows are just components, you can run and evaluate them in isolation, making it easy to debug and verify individual steps of your workflow. This is particularly valuable when building complex LLM applications that need robust evaluation.
Rather than having to run an entire workflow to test a change to a single component, you can test just that component, dramatically speeding up your dev loop. This isolation also makes unit testing more manageable, as you can create specific test cases without having to worry about the rest of the workflow.