Skip to content

Commit

Permalink
docs: adding concept doc for concepts (#168)
Browse files Browse the repository at this point in the history
Adds a conceptual doc on concepts and a simple example. Also makes minor
tweaks to the basic concepts section on contexts.
  • Loading branch information
dereklegenzoff authored Jan 29, 2025
1 parent a27722a commit bb46bbe
Show file tree
Hide file tree
Showing 14 changed files with 357 additions and 117 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ This repo contains a number of [examples](./examples) to help you get up and run
| 🔄 [Reflection](./examples/reflection) | Shows how to use a self-reflection pattern with GenSX |
| 🌊 [Streaming](./examples/streaming) | Shows how to handle streaming responses with GenSX |
| 🔌 [Providers](./examples/providers) | Shows how to create a custom provider for GenSX |
| 🗃️ [Contexts](./examples/contexts) | Shows how to use contexts to manage state in GenSX |

### Full Examples

Expand Down
25 changes: 14 additions & 11 deletions docs/src/content/docs/basic-concepts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ Rather than having to run an entire workflow to test a change to a single compon

## Contexts

Contexts provide a way to share values across components without explicitly passing them through props. They're similar to React's Context system but adapted for GenSX workflows. Contexts are particularly useful for:
Contexts provide a way to share data across components without explicitly passing them through props. They're similar to React's Context API but adapted for GenSX workflows. Contexts are particularly useful for:

- Sharing configuration across multiple components
- Providing dependencies to deeply nested components
Expand All @@ -158,27 +158,30 @@ Contexts provide a way to share values across components without explicitly pass
Here's how to create and use a context:

```tsx
interface User {
name: string;
}

// Create a context with a default value
const UserContext = gsx.createContext({ name: "default", age: 0 });
const UserContext = gsx.createContext({ name: "" });

// Use the context in a component
const Greeting = gsx.Component<GreetingProps, GreetingOutput>(
"Greeting",
() => {
const user = gsx.useContext(UserContext);
return `Hello, ${user.name}!`;
},
);
const Greeting = gsx.Component<Record<never, never>, string>("Greeting", () => {
const user = gsx.useContext(UserContext);
return `Hello, ${user.name}!`;
});

// Provide a value to the context
const result = await gsx.execute(
<UserContext.Provider value={{ name: "John", age: 30 }}>
<UserContext.Provider value={{ name: "John" }}>
<Greeting />
</UserContext.Provider>,
);
```

Contexts are a useful way to pass configuration without prop drilling. However, they do make your components less reusable so it is recommended that you use them sparingly.
Contexts are a useful way to pass configuration without prop drilling. However, they do make your components less reusable so we recommend that you use them sparingly.

For more details on providers, see the [Contexts](/docs/concepts/context) page.

## Providers

Expand Down
199 changes: 199 additions & 0 deletions docs/src/content/docs/concepts/context.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
---
title: Context and providers
description: Learn how to use contexts and providers in GenSX to share data across components.
sidebar:
order: 3
---

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](https://react.dev/reference/react/useContext) 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 `gsx.createContext<T>()` to initialize it along with a default value. For example, here's how to create a `User` context:

```tsx
import { gsx } from "gensx";

// Define the interface
interface User {
name: string;
}

// Create a context with a default value
const UserContext = gsx.createContext<User>({
name: "",
});
```

### Step 2: Use the context in a component

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

```tsx
const Greeting = gsx.Component<Record<never, never>, GreetingOutput>(
"Greeting",
() => {
const user = gsx.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:

```tsx
const result = await gsx.execute(
<UserContext.Provider value={{ name: "John" }}>
<Greeting />
</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:

```tsx
const result = await gsx.execute(
<OpenAIProvider apiKey={process.env.OPENAI_API_KEY}>
<ChatCompletion
model="gpt-4"
messages={[{ role: "user", content: "Hello!" }]}
/>
</OpenAIProvider>,
);
```

### 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](https://www.firecrawl.dev/) API.

#### Step 1: Create a context

Start by importing `gsx` and the package you want to use:

```tsx
import { gsx } from "gensx";
import FirecrawlApp, { FirecrawlAppConfig } from "@mendable/firecrawl-js";
```

Then, create the context:

```tsx
// Create a context
export const FirecrawlContext = gsx.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:

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

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

#### Step 3: Use the provider in a component

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

```tsx
export const ScrapePage = gsx.Component<ScrapePageProps, string>(
"ScrapePage",
async ({ url }) => {
const context = gsx.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`:

```tsx
const markdown = await gsx.execute(
<FirecrawlProvider apiKey={process.env.FIRECRAWL_API_KEY}>
<ScrapePage url="https://gensx.com/overview/" />
</FirecrawlProvider>,
);
```

## 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:

```tsx
const result = await gsx.execute(
<OpenAIProvider apiKey={process.env.OPENAI_API_KEY}>
<FirecrawlProvider apiKey={process.env.FIRECRAWL_API_KEY}>
<WebPageSummarizer url="https://gensx.com/overview/" />
</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.

## Additional Resources

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

- [Context examples](https://github.com/gensx-inc/gensx/tree/main/examples/contexts)
- [Provider examples](https://github.com/gensx-inc/gensx/tree/main/examples/providers)
96 changes: 0 additions & 96 deletions docs/src/content/docs/concepts/providers.mdx

This file was deleted.

1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Make sure to check what environment variables are required for each example.
| 🔄 [Reflection](./reflection) | Shows how to use a self-reflection pattern with GenSX |
| 🌊 [Streaming](./streaming) | Demonstrates how to handle streaming responses with GenSX |
| 🔌 [Providers](./providers) | Shows how to create a custom provider for GenSX |
| 🗃️ [Contexts](./contexts) | Shows how to use contexts to manage state in GenSX |

## Full Examples

Expand Down
13 changes: 13 additions & 0 deletions examples/contexts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Contexts Example

This example shows how to create and use contexts in GenSX. For more details on contexts, see the [Contexts](https://gensx.dev/concepts/context) page.

## Usage

```bash
# Install dependencies
pnpm install

# Run the example
pnpm run start
```
22 changes: 22 additions & 0 deletions examples/contexts/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import rootConfig from "../../eslint.config.mjs";

export default [
...rootConfig,
{
files: ["**/*.{js,mjs,cjs,jsx,ts,tsx}"],
languageOptions: {
parserOptions: {
tsconfigRootDir: import.meta.dirname,
project: "./tsconfig.json",
},
},
rules: {
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"node/no-unsupported-features/es-syntax": "off",
},
},
];
Loading

0 comments on commit bb46bbe

Please sign in to comment.