Skip to Content
ConceptsWorking with context and providers

Context and providers

Contexts and providers are powerful tools in GenSX for sharing data and managing configuration across components without explicitly passing props through every level of your component tree. They work similarly to React’s Context API but are adapted to work with GenSX workflows.

What are contexts and providers?

Contexts and providers work together to share data and manage dependencies across components.

  • Contexts give you a way to share data (like state, configuration, or dependencies) across components without manually passing props down the component tree.
  • Providers are components that supply data or services to a context. Any component within a provider’s subtree can access the context.

The two concepts are interdependent so you can’t use one without the other. Combined, they’re great for:

  • Providing data to components without prop drilling
  • Sharing configuration and dependencies, such as clients, for your workflow
  • Managing state that needs to be accessed by multiple components

The remainder of this document will show you how to create and use both contexts and providers in GenSX.

Creating and using contexts

This next section walks through the steps needed to create and use a context in your GenSX workflow.

Step 1: Create a context

To create a context, start by defining its interface and then use gensx.createContext<T>() to initialize it along with a default value. For example, here’s how to create a User context:

import * as gensx from "@gensx/core"; // Define the interface interface User { name: string; } // Create a context with a default value const UserContext = gensx.createContext<User>({ name: "", });

Step 2: Use the context in a component

To use the context, call the gensx.useContext(context) hook inside of a component. Here a Greeting component is created that uses the UserContext to get the user’s name:

const GreetUser = gensx.Component<{}, string>("GreetUser", () => { const user = gensx.useContext(UserContext); return `Hello, ${user.name}!`; });

Step 3: Provide the context value

To make the context value available to your components, you need to wrap your component in a Provider component and pass in a value via the value prop:

const ContextExample = gensx.Component<{}, string>("ContextExample", () => ( <UserContext.Provider value={{ name: "John" }}> <GreetUser /> </UserContext.Provider> ));

Using providers for configuration and dependencies

Providers are a specialized way to use contexts that focus on managing configuration and dependencies for your workflow. They simplify the process of sharing data like API keys, client instances, or feature flags across your components.

Built-in providers

The main provider available today is the OpenAIProvider, which manages your OpenAI API key and client:

const BasicChat = gensx.Component<BasicChatProps, string>( "BasicChat", async ({ prompt }) => { return ( <OpenAIProvider apiKey={process.env.OPENAI_API_KEY}> <ChatCompletion model="gpt-4o-mini" messages={[{ role: "user", content: prompt }]} /> </OpenAIProvider> ); }, ); const result = await gensx.Workflow("BasicChat", BasicChat).run({ prompt: "Hello!", });

Creating a Custom Provider

If you need a provider that isn’t available out of the box, you can easily create your own. The example below shows how to create a provider for the Firecrawl API.

Step 1: Create a context

Start by importing from @gensx/core and the package you want to use:

import * as gensx from "@gensx/core"; import FirecrawlApp, { FirecrawlAppConfig } from "@mendable/firecrawl-js";

Then, create the context:

// Create a context export const FirecrawlContext = gensx.createContext<{ client?: FirecrawlApp; }>({});

The context contains the client that you’ll use to interact with the Firecrawl API.

Step 2: Create the provider

Next, wrap your context in a provider component:

// Create the provider export const FirecrawlProvider = gensx.Component<FirecrawlAppConfig, never>( "FirecrawlProvider", (args: FirecrawlAppConfig) => { const client = new FirecrawlApp({ apiKey: args.apiKey, }); return <FirecrawlContext.Provider value={{ client }} />; }, { secretProps: ["apiKey"], }, );

The provider will take in the apiKey as a prop and use it to initialize the Firecrawl client.

Note that in the provider definition, an option bag is passed in as the third argument containing the secretProps property. This tells GenSX to treat the apiKey prop as a secret add it will be redacted in any traces.

Step 3: Use the provider in a component

Finally, you can build components that consume the context supplied by the provider:

export const ScrapePage = gensx.Component<ScrapePageProps, string>( "ScrapePage", async ({ url }) => { const context = gensx.useContext(FirecrawlContext); if (!context.client) { throw new Error( "Firecrawl client not found. Please wrap your component with FirecrawlProvider.", ); } const result = await context.client.scrapeUrl(url, { formats: ["markdown"], timeout: 30000, }); if (!result.success || !result.markdown) { throw new Error(`Failed to scrape url: ${url}`); } return result.markdown; }, );

Step 4: Use the provider in your workflow

Now when you use the ScrapePage component in your workflow, you’ll wrap it in the FirecrawlProvider and pass in the apiKey:

const FirecrawlExample = gensx.Component<FirecrawlExampleProps, string>( "FirecrawlExample", async ({ url }) => { return ( <FirecrawlProvider apiKey={process.env.FIRECRAWL_API_KEY}> <ScrapePage url={url} /> </FirecrawlProvider> ); }, ); const result = await gensx.Workflow("FirecrawlExample", FirecrawlExample).run({ url: "https://gensx.com/docs/", });

Nesting providers

You can nest multiple providers to combine different services or configurations in your workflow. This is useful when a component needs access to multiple contexts. Here’s an example that combines the OpenAI provider with our custom Firecrawl provider:

const NestedProviderExample = gensx.Component< NestedProviderExampleProps, string >("NestedProviderExample", async ({ url }) => { return ( <OpenAIProvider apiKey={process.env.OPENAI_API_KEY}> <FirecrawlProvider apiKey={process.env.FIRECRAWL_API_KEY}> <WebPageSummarizer url={url} /> </FirecrawlProvider> </OpenAIProvider> ); });

In this example, the WebPageSummarizer component can access both the OpenAI client and Firecrawl client through their respective contexts.

The order of nesting doesn’t matter as long as the component using a context is wrapped by its corresponding provider somewhere up the tree.

Using multiple OpenAI compatible providers

You can also nest multiple providers to use multiple OpenAI compatible APIs in the same workflow. When you nest multiple OpenAI providers, components will always use the closest provider above it in the tree. The example below shows how you could use models from OpenAI and Groq in the same workflow using their OpenAI compatible APIs.

First, create a provider for Groq that wraps the OpenAIProvider and points to the correct base URL:

const GroqProvider = gensx.Component<{}, never>("GroqProvider", () => ( <OpenAIProvider apiKey={process.env.GROQ_API_KEY} baseURL="https://api.groq.com/openai/v1" /> ));

Because components will always use the closest provider above them in the tree, EditTutorial will use the Groq API while WriteTutorial will use the OpenAI API:

export const WriteAndEditTutorial = gensx.Component<WriteTutorialProps, string>( "WriteAndEditTutorial", ({ subject }) => { return ( <OpenAIProvider apiKey={process.env.OPENAI_API_KEY}> <WriteTutorial subject={subject}> {(tutorial) => { console.log("\n📝 Original tutorial from OpenAI:\n", tutorial); return ( <GroqProvider> <EditTutorial tutorial={tutorial} /> </GroqProvider> ); }} </WriteTutorial> </OpenAIProvider> ); }, );

Note that you don’t have to create the GroqProvider component; instead you could just use the OpenAIProvider and pass in the correct props. However, wrapping the provider can make your code cleaner and easier to manage.

To make your components more reusable, you could also pass in the model name as a prop. For more details, see the guide on creating reusable components.

Additional resources

You can find the full example code demonstrating these concepts on GitHub:

Last updated on