# Why JSX? GenSX uses JSX for workflow composition because it's a natural fit for the programming model. Most people think of React and frontend when JSX is mentioned, so choosing it for a backend workflow orchestration framework may seem surprising. This page explains why JSX is a perfect fit for anyone building LLM applications, whether it be simple linear workflows or complex cyclical agents. At the end of the day, building agents and workflows is all about constructing a dataflow graph. And agents in particular need to dynamically branch and execute conditionally at runtime. This is exactly what GenSX excels at. Read the full blog post on [why a React-like model perfect for building agents and workflows](/blog/why-react-is-the-best-backend-workflow-engine). ## Why not graphs? Graph APIs are the standard for LLM frameworks. They provide APIs to define nodes, edges between those nodes, and a global state object that is passed around the workflow. A workflow for writing a blog post might look like this: ```tsx const graph = new Graph() .addNode("hnCollector", collectHNStories) .addNode("analyzeHNPosts", analyzePosts) .addNode("trendAnalyzer", analyzeTrends) .addNode("pgEditor", editAsPG) .addNode("pgTweetWriter", writeTweet); graph .addEdge(START, "hnCollector") .addEdge("hnCollector", "analyzeHNPosts") .addEdge("analyzeHNPosts", "trendAnalyzer") .addEdge("trendAnalyzer", "pgEditor") .addEdge("pgEditor", "pgTweetWriter") .addEdge("pgTweetWriter", END); ``` Can you easily read this code and visualize the workflow? On the other hand, the same workflow with GenSX and JSX reads top to bottom like a normal programming language: ```tsx {(stories) => ( {({ analyses }) => ( {(report) => ( {(editedReport) => ( )} )} )} )} ``` As you'll see in the next section, trees are just another kind of graph and you can express all of the same things. ## Graphs, DAGs, and trees Most workflow frameworks use explicit graph construction with nodes and edges. This makes sense - workflows are fundamentally about connecting steps together, and graphs are a natural way to represent these connections. Trees are just a special kind of graph - one where each node has a single parent. At first glance, this might seem more restrictive than a general graph. But JSX gives us something powerful: the ability to express _programmatic_ trees. Consider a cycle in a workflow: ```tsx const AgentWorkflow = gensx.Component<{}, AgentWorkflowOutput>( "AgentWorkflow", {(result) => result.needsMoreWork ? ( // Recursion creates AgentWorkflow -> AgentStep -> AgentWorkflow -> etc. ) : ( result ) } , ); ``` This tree structure visually represents the workflow, and programmatic JSX and typescript allow you to express cycles through normal programming constructs. This gives you the best of both worlds: - Visual clarity of a tree structure - Full expressiveness of a graph API - Natural control flow through standard TypeScript - No explicit edge definitions needed JSX isn't limited to static trees. It gives you a way to express dynamic, programmatic trees that can represent any possible workflow. ## Pure functional components GenSX uses JSX to encourage a functional component model, enabling you to compose your workflows from discrete, reusable steps. Functional and reusable components can be published and shared on `npm`, and it's easy to test and evaluate them in isolation. Writing robust evals is the difference between a prototype and a high quality AI app. Usually you start with end to end evals, but as workflows grow these become expensive, take a long time to run, and it can be difficult to isolate and understand the impact of changes in your workflow. By breaking down your workflow into discrete components, you can write more focused evals that are easier to run, faster to complete, and test the impact of specific changes in your workflow. ## Nesting via child functions Standard JSX allows you to nest components to form a tree: ```tsx
``` GenSX extends this with the ability to nest components via child functions. The output of any component can be accessed as a child function, giving a simple pattern to chain workflow steps together: {/* prettier-ignore-start */} ```tsx {(result) => } ``` {/* prettier-ignore-end */} ## Programming language native JSX with TypeScript gives you everything you expect from a modern programming language: - Conditionals via `if`, `??`, and other standard primitives - Looping via `for` and `while` - Vanilla function calling - Type safety No DSL required, just standard TypeScript. ## Familiar mental model If you've used React, GenSX will feel familiar even though there are some differences. - The tree structure is the same. - Elements can be composed and nested. - It uses a similar `Provider` and `Context` pattern for passing minimal configuration where necessary. - Familiar syntax and type safety. # TypeScript Compatibility GenSX is designed to be compatible with TypeScript. This page has some tips and tricks for using GenSX with TypeScript. ## Minimum TypeScript Version GenSX requires TypeScript version 5.1 or higher. ## TypeScript Configuration - `target` should be set to at least `es6` - `module` should be set to at least `es6` - `jsx` should be set to `react-jsx` - `jsxImportSource` should be set to `@gensx/core` ### JSX Pragmas If you are unable to change the `jsx` or `jsxImportSource` settings in your TypeScript configuration, you can use the following pragmas to enable GenSX: ```tsx /** @jsxRuntime automatic */ /** @jsxImportSource @gensx/core */ ``` ## JavaScript Compatibility GenSX is bundled with builds for both CommonJS and ESM. This means that you can use GenSX in any environment that supports either of these module formats. When using GenSX in a TypeScript project that targets CommonJS, you may need to configure the paths so that the TypeScript compiler can properly resolve the GenSX types. ```json { "compilerOptions": { "paths": { "@gensx/core/jsx-runtime": [ "./node_modules/@gensx/core/dist/cjs/jsx-runtime" ], "@gensx/core/jsx-dev-runtime": [ "./node_modules/@gensx/core/dist/cjs/jsx-dev-runtime", ], }, }, }; ``` # Quickstart In this quickstart, you'll learn how to get up and running with GenSX. GenSX is a simple typescript framework for building complex LLM applications using JSX. If you haven't already, check out the [basic concepts](/docs/basic-concepts) to learn more about how GenSX works. ## Prerequisites Before getting started, make sure you have the following: - [Node.js](https://nodejs.org/) version 20 or higher installed - An [OpenAI API key](https://platform.openai.com/api-keys) with access to the required models - A package manager of your choice ([npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/), or [pnpm](https://pnpm.io/)) ## Install the `gensx` CLI You can install the `gensx` CLI using your package manager of choice: ```bash # For a global installation npm i -g gensx # or prefix every command with npx npx gensx ``` ## Login to GenSX Cloud (optional) If you want to be able to visualize your workflows and view traces, you'll need to login to GenSX Cloud: ![blog writing trace](/quickstart/blog-trace.png) Run the following command to log into GenSX Cloud: ```bash # If you installed the CLI globally gensx login # Using npx npx gensx login ``` Now traces will automatically be saved to the cloud so you can visualize and debug workflow executions, but this step is optional and you can skip it for now if you prefer. ![gensx login](/quickstart/gensx-login.png) Hit enter and your browser will open the login page. ![console login](/quickstart/login.png) Once you've logged in, you'll see the following success message: ![login success](/quickstart/login-success.png) Now you're ready to create a new workflow! Return to your terminal for the next steps. ## Create a new workflow To get started, run the following command with your package manager of choice in an empty directory. This will create a new GenSX workflow to get you started. ```bash # If you installed the CLI globally gensx new . # Using npx npx gensx new . ``` When creating a new project, you'll be prompted to select IDE rules to add to your project. These rules help AI assistants like Claude, Cursor, Cline, and Windsurf understand your GenSX project better, providing more accurate code suggestions and help. You can also specify IDE rules directly using the `--ide-rules` flag: ```bash # Install Cline and Windsurf rules gensx new . --ide-rules cline,windsurf # Skip IDE rule selection gensx new . --skip-ide-rules ``` You can also install or update these rules later using npx: ```bash # Install Claude.md template npx @gensx/claude-md # Install Cursor rules npx @gensx/cursor-rules # Install Cline rules npx @gensx/cline-rules # Install Windsurf rules npx @gensx/windsurf-rules ``` This will create a new started project, ready for you to run your first workflow: ![create project](/quickstart/gensx-new.png) In `index.tsx`, you'll find a simple OpenAI chat completion component: ```tsx import * as gensx from "@gensx/core"; import { OpenAIProvider, ChatCompletion } from "@gensx/openai"; interface RespondProps { userInput: string; } type RespondOutput = string; const Respond = gensx.Component( "Respond", async ({ userInput }) => { return ( ); }, ); const WorkflowComponent = gensx.Component<{ userInput: string }, string>( "Workflow", ({ userInput }) => ( ), ); const workflow = gensx.Workflow("MyGSXWorkflow", WorkflowComponent); const result = await workflow.run({ userInput: "Hi there! Say 'Hello, World!' and nothing else.", }); console.log(result); ``` The component is executed through `gensx.Workflow.run()`, which processes the JSX tree from top to bottom. In this example: 1. First, the `OpenAIProvider` component is initialized with your API key 2. Then, the `Respond` component receives the `userInput` prop 3. Inside `Respond`, a `ChatCompletion` component is created with the specified model and messages 4. The result flows back up through the tree, ultimately returning the response from gpt-4o-mini. Components in GenSX are pure functions that take props and return outputs, making them easy to test and compose. The JSX structure makes the data flow clear and explicit - each component's output can be used by its children through standard TypeScript/JavaScript. ### Running the workflow To run the workflow, you'll need to set the `OPENAI_API_KEY` environment variable. ```bash # Set the environment variable export OPENAI_API_KEY= # Run the project pnpm dev ``` ![run project](/quickstart/gensx-run.png) If you chose to log in, you can now view the trace for this run in GenSX Cloud by clicking the link: ![trace](/quickstart/trace.png) The trace shows a flame graph of your workflow, including every component that executed with inputs and outputs. Some components will be hidden by default, but you can click the carat to expand them. Clicking on a component will show you details about it's input props and outputs. For longer running workflows, this view will update in real time as the workflow executes. ## Combining components The example above is a simple workflow with a single component. In practice, you'll often want to combine multiple components to create more complex workflows. Components can be nested to create multi-step workflows with each component's output being passed through a child function. For example, let's define two components: a `Research` component that gathers information about a topic, and a `Writer` component that uses that information to write a blog post. ```tsx // Research component that gathers information const Research = gensx.Component<{ topic: string }, string>( "Research", async ({ topic }) => { return ( ); }, ); // Writer component that uses research to write content const WriteArticle = gensx.Component< { topic: string; research: string }, string >("WriteArticle", async ({ topic, research }) => { return ( ); }); ``` Now you can combine these components using a child function: ```tsx // Combine components using child functions const ResearchAndWrite = gensx.Component<{ topic: string }, string>( "ResearchAndWrite", ({ topic }) => ( {(research) => } ), ); const workflow = gensx.Workflow("ResearchAndWriteWorkflow", ResearchAndWrite, { printUrl: true, }); ``` In this example, the `Research` component gathers information about the topic which then passes the information to the `WriteArticle` component. The `WriteArticle` component uses that information to write an article about the topic which is then returned as the `result`. ### Streaming One common challenge with LLM workflows is handling streaming responses. Any given LLM call can return a response as a string or as a stream of tokens. Typically you'll want the last component of your workflow to stream the response. To take advantage of streaming, all you need to do is update the `WriteArticle` component to use `StreamComponent` and set the `stream` prop to `true` in the `ChatCompletion` component. ```tsx interface WriterProps { topic: string; research: string; stream?: boolean; } const Writer = gensx.StreamComponent( "Writer", async ({ topic, research, stream }) => { return ( ); }, ); const ResearchAndWrite = gensx.Component<{ topic: string }, string>( "ResearchAndWrite", ({ topic }) => ( {(research) => ( )} ), ); const workflow = gensx.Workflow("ResearchAndWriteWorkflow", ResearchAndWrite); const stream = await workflow.run( { topic: "latest quantum computing chips" }, { printUrl: true }, ); // Print the streaming response for await (const chunk of stream) { process.stdout.write(chunk); } ``` Now we can see our research step running before the write step: ![streaming](/quickstart/writer-trace.png) While this is nice, the real power of streaming components comes when you expand or refactor your workflow. Now you could easily add an `` component to the workflow that streams the response to the user with minimal changes. There's no extra plumbing needed besides removing the `stream={true}` prop on the `WriteArticle` component. ```tsx const ResearchAndWrite = gensx.Component<{ topic: string }, string>( "ResearchAndWrite", ({ topic }) => ( {(research) => ( {(content) => } )} , ); const workflow = gensx.Workflow("ResearchAndWriteWorkflow", ResearchAndWrite, { printUrl: true }); const stream = await workflow.run({ topic: "latest quantum computing chips" }); ``` ![blog writing trace](/quickstart/blog-trace.png) ## Running the dev server Now that you've built your workflows, you can easily turn them into REST APIs. GenSX provides a local development server with local REST APIs that match the shape of workflows deployed to GenSX Cloud. You can run the dev server from the CLI: ```bash # Start the development server gensx start src/workflows.tsx ``` The development server provides several key features: - **Hot reloading**: Changes to your code are automatically detected and recompiled - **API endpoints**: Each workflow is exposed as a REST endpoint - **Swagger UI**: Interactive documentation for your workflows at `http://localhost:1337/swagger-ui` - **Local storage**: Built-in support for blob storage and databases You'll see something like this when you start the server: ```bash πŸ” Starting GenSX Dev Server... β„Ή Starting development server... βœ” Compilation completed βœ” Generating schema πŸ“‹ Available workflows: - ResearchAndWriteWorkflow: http://localhost:1337/workflows/ResearchAndWriteWorkflow βœ… Server is running. Press Ctrl+C to stop. ``` You can now test your workflow by sending requests to the provided URL using any HTTP client, or using the built-in Swagger UI at `http://localhost:1337/swagger-ui`. ## Deploying your project to GenSX Cloud Now that you've tested your APIs locally, you can deploy them to the cloud. GenSX Cloud provides serverless deployment with zero configuration: ```bash # Deploy your project to GenSX Cloud gensx deploy src/workflows.tsx -ev OPENAI_API_KEY ``` This command: 1. Builds your TypeScript code for production 2. Bundles all dependencies 3. Uploads the package to GenSX Cloud 4. Creates REST API endpoints for each workflow 5. Configures serverless infrastructure For production deployments, you can target a specific environment: ```bash # Deploy to production environment gensx deploy src/index.ts --env production ``` ### Running a workflow from the CLI Once deployed, you can execute your workflows directly from the command line: ```bash # Run a workflow synchronously gensx run ResearchAndWriteWorkflow --input '{"topic":"quantum computing"}' # Save the output to a file gensx run ResearchAndWriteWorkflow --input '{"topic":"quantum computing"}' --output result.json ``` The CLI makes it easy to test your workflows and integrate them into scripts or automation. ### Running a workflow from the GenSX console The GenSX Cloud console provides a visual interface for managing and executing your workflows: 1. Log in to [app.gensx.com](https://app.gensx.com) 2. Navigate to your project and environment 3. Select the workflow you want to run 4. Click the "Run" button and enter your input 5. View the results directly in the console ![Running a workflow in the console](/cloud/console-playground.png) The console also provides API documentation and code snippets for your workflows as well as execution history and tracing for all previous workflow runs. ## Adding persistence with storage Now that you've deployed your first workflow, you can add in storage so you can build more sophisticated workflows. GenSX offers three types of builtin storage: blob storage, sql database storage and, full text and vector search storage. ### Blob storage Use the `useBlob` hook to store and retrieve unstructured data: ```tsx import { BlobProvider, useBlob } from "@gensx/storage"; const ChatWithMemory = gensx.Component< { userInput: string; threadId: string }, string >("ChatWithMemory", async ({ userInput, threadId }) => { // Load chat history from cloud storage const blob = useBlob(`chat-history/${threadId}.json`); const history = (await blob.getJSON()) ?? []; // Add user message and get AI response // ... // Save updated history await blob.putJSON(updatedHistory); return response; }); // Wrap your workflow with BlobProvider const WorkflowComponent = gensx.Component< { userInput: string; threadId: string }, string >("Workflow", ({ userInput, threadId }) => ( )); ``` ### SQL database Use the `useDatabase` hook for structured data: ```tsx import { DatabaseProvider, useDatabase } from "@gensx/storage"; const SqlCopilot = gensx.Component<{ question: string }, string>( "SqlCopilot", async ({ question }) => { const db = await useDatabase("baseball"); // Set up schema if needed await db.executeMultiple(` CREATE TABLE IF NOT EXISTS baseball_stats ( player TEXT PRIMARY KEY, team TEXT, batting_avg REAL ) `); // Execute query const result = await db.execute("SELECT * FROM baseball_stats LIMIT 5"); // Use the result to inform your LLM response // ... return response; }, ); // Wrap your workflow with DatabaseProvider const WorkflowComponent = gensx.Component<{ question: string }, string>( "DatabaseWorkflowComponent", ({ question }) => ( ), ); ``` ### Full-text and vector search Use the `useSearch` hook for semantic search and RAG applications: ```tsx import { SearchProvider, useSearch } from "@gensx/storage"; import { OpenAIEmbedding } from "@gensx/openai"; const SemanticSearch = gensx.Component<{ query: string }, any[]>( "SemanticSearch", async ({ query }) => { // Access a vector search namespace const namespace = await useSearch("documents"); // Generate an vector embedding for the query const embedding = await OpenAIEmbedding.run({ model: "text-embedding-3-small", input: query, }); // Search for similar documents const results = await namespace.query({ vector: embedding.data[0].embedding, topK: 5, includeAttributes: true, }); return results; }, ); // Wrap your workflow with SearchProvider const WorkflowComponent = gensx.Component<{ query: string }, any[]>( "SearchWorkflow", ({ query }) => ( ), ); ``` ## Learning more Explore these resources to dive deeper into GenSX: - [Serverless Deployments](/docs/cloud/serverless-deployments): Deploy and manage your workflows in the cloud - [Local Development](/docs/cloud/local-development): Set up a productive local environment - [Storage Components](/docs/component-reference/storage-components): Persistent storage for your workflows - [Observability & Tracing](/docs/cloud/observability): Debug and monitor your workflows - [Projects & Environments](/docs/cloud/projects-environments): Organize your deployments Check out these example projects to see GenSX in action: - [Blog Writer](https://github.com/gensx-inc/gensx/tree/main/examples/blogWriter) - [Chat with Memory](https://github.com/gensx-inc/gensx/tree/main/examples/chat-memory) - [Text to SQL](https://github.com/gensx-inc/gensx/tree/main/examples/text-to-sql) - [RAG](https://github.com/gensx-inc/gensx/tree/main/examples/rag) # GenSX Overview GenSX is a simple TypeScript framework for building complex LLM applications. It’s a workflow engine designed for building agents, chatbot APIs, and more using common patterns like RAG and reflection. In addition, GenSX Cloud offers serverless hosting, cloud storage, and tracing and observability to build production-ready agents and workflows. It uses `jsx` to define and orchestrate workflows with functional, reusable components: {/* prettier-ignore-start */} ```tsx const WriteBlog = gensx.Component( "WriteBlog", async ({ prompt }) => { return ( {(blog) => ( {(draft) => console.log(draft)} )} , ); const workflow = gensx.Workflow("WriteBlogWorkflow", WriteBlog); const result = await workflow.run({ prompt: "Write a blog post about AI developer tools" }); ``` {/* prettier-ignore-end */} Most LLM frameworks are graph oriented, inspired by popular python tools like Airflow. You express nodes, edges, and a global state object for your workflow. While graph APIs are highly expressive, they are also cumbersome: - Building a mental model and visualizing the execution of a workflow from a node/edge builder is difficult. - Global state makes refactoring difficult. - All of this leads to low velocity when experimenting with and evolving your LLM workflows. LLM workflows are fundamentally about function composition and data flow - exactly what JSX was designed to express. There is no need for a graph DSL. GenSX expresses control flow, loops, and recursion using plain old typescript language primitives. To learn more about why GenSX uses JSX, read [Why JSX?](docs/why-jsx). While GenSX uses JSX, it does not share the same execution model as React, and has zero dependencies. ## GenSX Cloud [GenSX Cloud](/docs/cloud) provides everything you need to ship production grade agents and workflow including a serverless runtime designed for long-running workloads, cloud storage to build stateful workflows and agents, and tracing and observability. ### Serverless deployments Deploy any workflow to a hosted API endpoint with a single command. The GenSX cloud platform handles scaling, infrastructure management, and API generation automatically: ```bash # Deploy your workflow to GenSX Cloud $ gensx deploy ./src/workflows.tsx ``` ```bash βœ” Building workflow using Docker βœ” Generating schema βœ” Successfully built project β„Ή Using project name from gensx.yaml: support-tools βœ” Deploying project to GenSX Cloud (Project: support-tools) βœ” Successfully deployed project to GenSX Cloud Dashboard: https://app.gensx.com/gensx/support-tools/default/workflows Available workflows: - ChatAgent - TextToSQLWorkflow - RAGWorkflow - AnalyzeDiscordWorkflow Project: support-tools Environment: default ``` The platform is optimized for AI workloads with millisecond-level cold starts and support for long-running executions up to an hour. ### Tracing and observability GenSX Cloud provides comprehensive tracing and observability for all your workflows and agents. ![Workflow component tree](/cloud/trace.png) Inputs and outputs are recorded for every component that executes in your workflows, including prompts, tool calls, and token usage. This makes it easy to debug hallucinations, prompt upgrades, and monitor costs. ![Component trace](/cloud/component-trace.png) ### Cloud Storage Build stateful AI applications with zero configuration using built-in storage primitives that provide managed blob storage, SQL databases, and full-text + vector search: ```tsx const ChatWithMemory = gensx.Component( "ChatWithMemory", async (props) => { const { userInput, threadId } = props; // Load chat history from blob storage const blob = useBlob(`chat-history/${threadId}.json`); const history = await blob.getJSON(); // Add new message and run LLM // ... // Save updated history await blob.putJSON(updatedHistory); return response; }, ); ``` For more details about GenSX Cloud see the [complete cloud reference](/docs/cloud). ## Reusable by default GenSX components are pure functions, depend on zero global state, and are _reusable_ by default. Components accept `props` and return an output. ```tsx interface ResearchTopicProps { topic: string; } type ResearchTopicOutput = string; const ResearchTopic = gensx.Component( "ResearchTopic", async ({ topic }) => { console.log("πŸ“š Researching topic:", topic); const systemPrompt = `You are a helpful assistant that researches topics...`; return ( ); }, ); ``` Because components are pure functions, they are easy to test and eval in isolation. This enables you to move quickly and experiment with the structure of your workflow. A second order benefit of reusability is that components can be _shared_ and published to package managers like `npm`. If you build a functional component, you can make it available to the community - something that frameworks that depend on global state preclude by default. ## Composition All GenSX components support nesting, a pattern to access component outputs via a child function. This creates a natural way to pass data between steps: ```tsx export const WriteBlog = gensx.StreamComponent( "WriteBlog", async ({ prompt }) => { return ( {(research) => ( {(draft) => } )} ); }, ); ``` There is no need for a DSL or graph API to define the structure of your workflow. More complex patterns like cycles and agents can be encapsulated in components that use standard loops and conditionals. Typescript and JSX unifies workflow definition and execution in plain old typescript, with the ability to express all of the same patterns. ## Visual clarity Workflow composition with JSX reads naturally from top to bottom like a standard programming language. ```tsx {(stories) => ( {({ analyses }) => ( {(report) => ( {(editedReport) => ( )} )} )} )} ``` Contrast this with graph APIs, where you need to build a mental model of the workflow from a node/edge builder: ```tsx const graph = new Graph() .addNode("fetchHNPosts", fetchHNPosts) .addNode("analyzeHNPosts", analyzePosts) .addNode("generateReport", generateReport) .addNode("editReport", editReport) .addNode("writeTweet", writeTweet); graph .addEdge(START, "fetchHNPosts") .addEdge("fetchHNPosts", "analyzeHNPosts") .addEdge("analyzeHNPosts", "generateReport") .addEdge("generateReport", "editReport") .addEdge("editReport", "writeTweet") .addEdge("writeTweet", END); ``` Nesting makes dependencies explicit, and it is easy to see the data flow between steps. No graph DSL required. ## Streaming baked in LLMs are unique in that any given call can return a stream or a prompt response, but streaming is typically applied to just the last step. You shouldn't have to touch the innards of your components to experiment with the shape of your workflow. GenSX makes this easy to handle with `StreamComponent`. A single component implementation can be used in both streaming and non-streaming contexts by setting the `stream` prop: ```tsx const EditDraft = gensx.StreamComponent( "EditDraft", async ({ draft }) => { console.log("πŸ” Editing draft"); const systemPrompt = `You are a helpful assistant that edits blog posts...`; return ( ); }, ); ``` From there you can use the component in a streaming context: ```tsx const stream = await gensx .Workflow("EditDraftWorkflow", EditDraft) .run({ draft, stream: true }); for await (const chunk of stream) { process.stdout.write(chunk); } ``` And you can use the component in a non-streaming context: ```tsx const result = await gensx .Workflow("EditDraftWorkflow", EditDraft) .run({ draft, stream: false }); console.log(result); ``` ## Designed for velocity The GenSX programming model is optimized for speed of iteration in the long run. The typical journey building an LLM application looks like: 1. Ship a prototype that uses a single LLM prompt. 2. Add in evals to measure progress. 3. Add in external context via RAG. 4. Break tasks down into smaller discrete LLM calls chained together to improve quality. 5. Add advanced patterns like reflection, and agents. Experimentation speed depends on the ability to refactor, rearrange, and inject new steps. In our experience, this is something that frameworks that revolve around global state and a graph model slow down. The functional component model in GenSX support an iterative loop that is fast on day one through day 1000. # Basic concepts GenSX is a simple typescript framework for building complex LLM applications. It's built around functional, reusable components that are composed using JSX to create and orchestrate workflows. ## Components Components are the building blocks of GenSX applications; they're pure TypeScript functions that: - Accept props as input - Produce an output that can be used by other components or returned as the result of the workflow - Don't depend on global state - Are strongly typed using TypeScript When you define a component, you need to include four things: 1. The input type, or props, for the component 2. The output type for the component 3. The name of the component that's used for telemetry, tracing, visualization, and caching 4. The function that produces the component's output Here's an example of a simple component: ```tsx interface GreetUserProps { name: string; } type GreetUserOutput = string; const GreetUser = gensx.Component( "GreetUser", async ({ name }) => { return `Hello, ${name}!`; }, ); ``` Components can return data as well as other components. Here's an example of a component that returns another component: ```tsx const GreetUser = gensx.Component( "GreetUser", async ({ name }) => { return ( ); }, ); ``` Components can also return arrays and structured objects containing other components. The pattern also works with array operations like `map` for processing items in parallel. ```tsx const AnalyzePosts = gensx.Component( "AnalyzePosts", ({ posts }) => ({ analyses: posts.map((post) => ({ summary: , commentAnalysis: ( ), })), }), ); ``` In this example, `AnalyzePosts` will produce an array of objects that contains the `summaries` and `commentAnalysis` for each post. GenSX handles resolving all of the nested components and collects them into a final value for you. ## JSX and data flow GenSX uses JSX to compose components into workflows. The output of a parent component can be passed to a child component through a child function. This pattern enables you to create chains of components where each step's output feeds into the next component's input. When components are combined, they will return the value of the innermost component. ```tsx // Parent receives output from ChildComponent through the child function {(parentResult) => } ``` Unlike React's concurrent rendering model, GenSX evaluates your workflow as a dependency graph. Components execute in parallel whenever possible, while automatically ensuring that all required inputs are available before a component starts executing. While GenSX uses a [tree-based structure with JSX](https://gensx.com/docs/why-jsx/#graphs-dags-and-trees), it still supports all the capabilities you'd expect from graph-based workflows including representing cyclic and agentic patterns. ## Component types GenSX provides two main types of components: 1. **Components** (`gensx.Component`) - Components that produce an output value and can handle both synchronous and asynchronous operations 2. **Streaming Components** (`gensx.StreamComponent`) - Components designed to handle responses from LLMs, working in both streaming and non-streaming contexts The power of a `StreamComponent` is that a single implementation can be used interchangeably in both streaming and non-streaming contexts without any code changes - you just toggle the `stream` prop: ```tsx // Returns a string when executed // Returns a stream when executed ``` ## Running workflows Workflows and components are synonymous in GenSX. You'll often design workflows that are composed of multiple components, but they can also just be a single component. Regardless, you'll need to define a top level component that will be used to run the workflow. You can then use `gensx.Workflow().run(props)` to run the workflow: ```tsx const result = await gensx .Workflow("MyWorkflow", MyComponent) .run({ input: "some data" }); ``` When components are run via a workflow, GenSX processes the JSX tree from top to bottom while executing components in parallel wherever possible. 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. ## Executing sub-workflows In some cases, you may need to run a sub-workflow within a larger workflow. You can do this using the `run()` method on a component: ```tsx const result = await MyComponent.run({ input: "some data" }); ``` This allows you to work with a component in TypeScript without having to drop into the JSX layer. When used inside a component, `Component.run()` preserves the current context and maintains the component hierarchy so the calls will integrate naturally with the rest of the workflow. Sub-workflows are especially useful for more complex operations like map/reduce: ```tsx const AnalyzeReviews = gensx.Component< AnalyzeReviewsProps, AnalyzeReviewsOutput >("AnalyzeReviews", async ({ reviews }) => { // Map: Extract topics from each review in parallel using the component const topics = reviews.map((r) => GetTopic.run({ review: r })); // Reduce: Count frequency of each topic return topics.reduce( (counts, topic) => ({ ...counts, [topic]: (counts[topic] || 0) + 1 }), {}, ); }); ``` You can also use `gensx.execute()` to execute a sub-workflow. One benefit of this is that you can execute a tree without assigning it to a component: ```tsx const result = await gensx.execute( {(parentResult) => } , ); ``` ## Contexts 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 - Managing state that needs to be accessed by multiple components Here's how to create and use a context: ```tsx interface GreetUserProps { name: string; } // Create a context with a default value const UserContext = gensx.createContext({ name: "" }); // Use the context in a component const GreetUser = gensx.Component<{}, string>("GreetUser", () => { const user = gensx.useContext(UserContext); return `Hello, ${user.name}!`; }); // Provide a value to the context const ContextExample = gensx.Component<{}, string>("ContextExample", () => ( )); ``` 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 [Context and Providers](/docs/concepts/context) page. ## Providers Providers are a specialized use of contexts that focus on managing configuration and dependencies for your workflow. They're built using the context system described above, but provide a more focused API for configuration management. The main provider available today is the `OpenAIProvider`, which manages your OpenAI API key: ```tsx const BasicChat = gensx.Component( "BasicChat", async ({ prompt }) => { return ( ); }, ); ``` For more details on providers, see the [Context and Providers](/docs/concepts/context) page. # Using tools with GenSX Using tools provides a powerful and flexible way to extend the capabilities of an LLM. GenSX offers several different ways for you to work with tools: - Using the `GSXChatCompletion` component from [@gensx/openai](../component-reference/openai.mdx) or [@gensx/anthropic](../component-reference/anthropic.mdx) for fully automated tool calling - Using the `OpenAIChatCompletion` or `AnthropicChatCompletion` components from [@gensx/openai](../component-reference/openai.mdx) or [@gensx/anthropic](../component-reference/anthropic.mdx) and following the same pattern as described in the [OpenAI](https://platform.openai.com/docs/guides/function-calling?api-mode=responses) and [Anthropic](https://docs.anthropic.com/en/docs/build-with-claude/tool-use/overview) docs - Using tools with the [Vercel AI SDK](https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling) via the [@gensx/vercel-ai-sdk](../component-reference/vercel-ai.mdx) package This guide will focus on the first option: using `GSXTool` and the `GSXChatCompletion` component for automated tool calling. The examples below show how to work with the [@gensx/openai](../component-reference/openai.mdx) package but you can follow a similar pattern with the [@gensx/anthropic](../component-reference/anthropic.mdx) package. ## Defining a tool Start by defining the tool. A `GSXTool` contains four main pieces: - `name`: The name of the tool - `description`: A description of the tool - `schema`: The schema of the tool, defined as a Zod object - `run`: The function that will be called when the tool is invoked by an LLM Here's an example of a basic weather tool with mocked functionality: ```tsx import { GSXTool } from "@gensx/openai"; import { z } from "zod"; // Define the schema as a Zod object const weatherSchema = z.object({ location: z.string(), }); // Use z.infer to get the type for our parameters type WeatherParams = z.infer; // Create the tool with the correct type - using the schema type, not the inferred type const weatherTool = new GSXTool({ name: "get_weather", description: "get the weather for a given location", schema: weatherSchema, run: async ({ location }: WeatherParams) => { console.log("getting weather for", location); const weather = ["sunny", "cloudy", "rainy", "snowy"]; return Promise.resolve({ weather: weather[Math.floor(Math.random() * weather.length)], }); }, }); ``` You can also define tools using `gensx.GSXToolProps` which allows you to create a tool independent of the @gensx/openai and @gensx/anthropic packages: ```tsx import * as gensx from "@gensx/core"; const weatherToolProps: gensx.GSXToolProps = { name: "get_weather", // rest of the tool definition }; ``` ## Using a tool Now that the tool is defined, you can pass it to the `GSXChatCompletion` component in the `tools` prop. If the model chooses to call the tool, GenSX will execute the tool on your behalf and return the response to the LLM to continue the conversation. ```tsx const WeatherAssistant = gensx.Component<{}, ChatCompletionOutput>( "WeatherAssistant", () => ( ), ); const workflow = gensx.Workflow("WeatherAssistantWorkflow", WeatherAssistant); const result = await workflow.run({}); console.log(result.choices[0].message.content); ``` `GSXChatCompletion` also returns the full message history, including the tool calls, so you can see the sequence of events leading up to the response. ```tsx console.log(JSON.stringify(result.messages, null, 2)); ``` ## Resources For more examples of using tools with GenSX, see the following examples: - [Anthropic tools example](https://github.com/gensx-inc/gensx/blob/main/examples/anthropic) - [OpenAI tools example](https://github.com/gensx-inc/gensx/blob/main/examples/gsxChatCompletion) # Structured outputs Workflows regularly require getting structured outputs (JSON) from LLMs. GenSX supports an easy way to get structured outputs from an LLM by using the `GSXChatCompletion` component and Zod. However, you can also use the `response_format` prop to get a structured output from OpenAI models without using GenSX helpers, just like you would when using the OpenAI SDK or API. This guide shows how to use structured outputs both with and without the helpers that GenSX provides. You can find the complete example code in the [structured outputs example](https://github.com/gensx-inc/gensx/tree/main/examples/structuredOutputs). ## Structured outputs with GSXChatCompletion When using the `GSXChatCompletion` component, you can define the Zod schema for the output format you want and GenSX will handle the rest. Start by defining the Zod schema for the output format you want: ```ts import { z } from "zod"; const ExtractEntitiesSchema = z.object({ people: z.array(z.string()), places: z.array(z.string()), organizations: z.array(z.string()), }); ``` Then define the input and output types for the component that will be used to get the structured output: ```tsx interface ExtractEntitiesProps { text: string; } type ExtractEntitiesOutput = z.infer; ``` Finally, define the component and set the `outputSchema` prop on the `GSXChatCompletion` component to get the output matching that schema: ```tsx const ExtractEntities = gensx.Component< ExtractEntitiesProps, ExtractEntitiesOutput >("ExtractEntities", ({ text }) => { const prompt = `Please review the following text and extract all the people, places, and organizations mentioned. ${text} Please return json with the following format: { "people": ["person1", "person2", "person3"], "places": ["place1", "place2", "place3"], "organizations": ["org1", "org2", "org3"] }`; return ( ); }); ``` The `GSXChatCompletion` component will return the structured output directly matching the type of the `ExtractEntitiesSchema` with no extra parsing required. ### Structured output strategies When using structured outputs with OpenAI models, GenSX uses the `response_format` parameter to return a structured output. However, not all models support structured outputs out of the box. For non-OpenAI models, structured outputs are handled by converting the schema into a tool and forcing the model to call that tool. GenSX looks at the `baseURL` of the OpenAIProvider to determine which approach to use but you can manually control this behavior by setting the `structuredOutputStrategy` prop. There are three available options: - `default`: Picks the best strategy based on the `baseURL` of the OpenAIProvider. - `tools`: Converts the schema into a tool to return a structured output. - `response_format`: Uses the `response_format` parameter to return a structured output. ### Retries In the case that the model doesn't return valid JSON matching the schema, the task will be retried up to 3 times by default. You can change this behavior by setting the `retry` prop. ```tsx ``` ## Structured outputs without GenSX helpers You can also use the `response_format` prop to get a structured output from OpenAI models without using GenSX helpers, just like you would when using the OpenAI SDK or API. The main difference is that you will need to parse the response yourself. Using the same `ExtractEntitiesSchema` from the previous example, you can define the component and set the `response_format` prop to the `ExtractEntitiesSchema` using the `zodResponseFormat` helper. ```tsx import { zodResponseFormat } from "openai/helpers/zod"; const ExtractEntitiesWithoutHelpers = gensx.Component< ExtractEntitiesProps, ExtractEntitiesOutput >("ExtractEntitiesWithoutHelpers", ({ text }) => { const prompt = `Please review the following text and extract all the people, places, and organizations mentioned. ${text} Please return json with the following format: { "people": ["person1", "person2", "person3"], "places": ["place1", "place2", "place3"], "organizations": ["org1", "org2", "org3"] }`; return ( {(response: string) => { return ExtractEntitiesSchema.parse(JSON.parse(response)); }} ); }); ``` This example uses the `ChatCompletion` component which always returns a string but you could also use the `GSXChatCompletion` component or `OpenAIChatCompletion` component which returns the full response object from OpenAI. ## Running the example You can run the examples above using the following code: ```tsx const workflow = gensx.Workflow("ExtractEntitiesWorkflow", ExtractEntities); const result = await workflow.run({ text: "John Doe is a software engineer at Google.", }); console.log(result); ``` This will output the following: ```json { "people": ["John Doe"], "places": [], "organizations": ["Google"] } ``` # Self-reflection Self-reflection is a common prompting technique used to improve the outputs of LLMs. With self-reflection, an LLM is used to evaluate it's own output and then improve it, similar to how a humans would review and edit their own work. Self-reflection works well because it's easy for LLMs to make mistakes. LLMs are simply predicting tokens one after the next so a single bad token choice can create a cascading effect. Self-reflection allows the model to evaluate the output in its entirety giving the model a chance to catch and correct any mistakes. ## Self-reflection in GenSX The nested approach to creating GenSX workflows might make it seem difficult to implement looping patterns like self-reflection. However, GenSX allows you to express dynamic, programmatic trees giving you all the flexibility you need. The [reflection example](https://github.com/gensx-inc/gensx/tree/main/examples/reflection) defines a helper function `createReflectionLoop` that you can use to implement self-reflection in your GenSX workflows. To implement self-reflection, you'll need: 1. **An evaluation component** that assesses the output and provides feedback 2. **An improvement component** that processes the input using the feedback to create a better output The output you want to improve becomes the `input` to the reflection loop itself. You can choose to run a single round of self-reflection or multiple rounds to iteratively refine the output, based on your scenario. When you call `createReflectionLoop`, the function will: 1. Use the evaluation component (`EvaluateFn`) to analyze the output and decide if further improvements are necessary. 2. If improvements are warranted, execute the improvement component (`ImproveFn`) to refine the output. 3. Recursively call itself with the improved output, continuing until the specified `maxIterations` is reached or no more improvements are needed. Here's the implementation of the `createReflectionLoop` function: ```tsx export function createReflectionLoop(name: string) { return gensx.Component, TInput>( name, async ({ input, ImproveFn, EvaluateFn, iterations = 0, maxIterations = 3, }) => { // Check if we should continue processing const { feedback, continueProcessing } = await EvaluateFn.run({ input }); if (continueProcessing && iterations < maxIterations) { // Process the input const newInput: TInput = await ImproveFn.run({ input, feedback }); // Recursive call with updated input and iteration count const Reflection = createReflectionLoop( `${name}-${iterations + 1}`, ); return ( ); } // Return the final input when we're done processing return input; }, ); } ``` ## Implementing self-reflection Now that you've seen the pattern and the helper function for doing self-reflection, let's implement it. The example below shows how to use the `createReflectionLoop` function to evaluate and improve text. ### Step 1: Define the evaluation component First, you need to define the component that will be used to evaluate the text. As its designed above, the evaluation component needs to return a string, `feedback`, and a boolean, `continueProcessing`. To get good results, you'll need to provide useful instructions on what feedback to provide. In this example, we focus on trying to make the text sound more authentic and less AI-generated. ```tsx const EvaluateText = gensx.Component<{ input: string }, ReflectionOutput>( "EvaluateText", ({ input }) => { const systemPrompt = `You're a helpful assistant that evaluates text and suggests improvements if needed. ## Evaluation Criteria - Check for genuine language: flag any buzzwords, corporate jargon, or empty phrases like "cutting-edge solutions" - Look for clear, natural expression: mark instances of flowery language or clichΓ©d openers like "In today's landscape..." - Review word choice: highlight where simpler alternatives could replace complex or technical terms - Assess authenticity: note when writing tries to "sell" rather than inform clearly and factually - Evaluate tone: identify where the writing becomes overly formal instead of warm and conversational - Consider flow and engagement - flag where transitions feel choppy or content becomes dry and predictable ## Output Format Return your response as JSON with the following two properties: - feedback: A string describing the improvements that can be made to the text. Return feedback as short bullet points. If no improvements are needed, return an empty string. - continueProcessing: A boolean indicating whether the text should be improved further. If no improvements are needed, return false. You will be given a piece of text. Your job is to evaluate the text and return a JSON object with the following format: { "feedback": "string", "continueProcessing": "boolean" } `; return ( {(response: string) => { return JSON.parse(response) as ReflectionOutput; }} ); }, ); ``` ### Step 2: Define the improvement component Next, you need to define the component that will be used to improve the text. This component will take the `input` text and the `feedback` as input and return the improved text. ```tsx const ImproveText = gensx.Component< { input: string; feedback: string }, string >("ImproveText", ({ input, feedback }) => { console.log("\nπŸ“ Current draft:\n", input); console.log("\nπŸ” Feedback:\n", feedback); console.log("=".repeat(50)); const systemPrompt = `You're a helpful assistant that improves text by fixing typos, removing buzzwords, jargon, and making the writing sound more authentic. You will be given a piece of text and feedback on the text. Your job is to improve the text based on the feedback. You should return the improved text and nothing else.`; const prompt = ` ${feedback} ${input} `; return ( ); }); ``` ### Step 3: Create the reflection loop Now that you have the evaluation and improvement components, you can create the reflection loop. ```tsx export const ImproveTextWithReflection = gensx.Component< { text: string; maxIterations?: number; }, string >("ImproveTextWithReflection", ({ text, maxIterations = 3 }) => { const Reflection = createReflectionLoop("ImproveTextWithReflection"); return ( ); }); ``` ### Step 4: Run the example You can run the text improvement example using the following code: ```tsx const text = `We are a cutting-edge technology company leveraging bleeding-edge AI solutions to deliver best-in-class products to our customers. Our agile development methodology ensures we stay ahead of the curve with paradigm-shifting innovations.`; const workflow = gensx.Workflow( "ReflectionWorkflow", ImproveTextWithReflection, ); const improvedText = await workflow.run({ text }); console.log("🎯 Final text:\n", improvedText); ``` You can find the complete example code in the [reflection example](https://github.com/gensx-inc/gensx/tree/main/examples/reflection). # OpenAI Computer Use The [OpenAI Computer Use](https://github.com/gensx-inc/gensx/tree/main/examples/openai-computer-use) example show how to use OpenAI's computer-use model with GenSX to control a web browser with natural language. ## Workflow The OpenAI Computer Use workflow consists of the following steps: 1. Launch a browser session with Playwright (``) 2. Send an initial user prompt to the OpenAI computer-use model (``) 3. Process any computer actions requested by the model - Execute browser actions like clicking, scrolling, typing, etc. (``) - Take a screenshot after each action and send it back to the model 4. Optionally collect human feedback and continue the conversation (``) 5. Process subsequent model responses and browser actions until completion Here's an example trace of the workflow showing the actions taken at each step: ![OpenAI Computer Use Workflow](/docs/computer-use-trace.png) ## Running the example From the root of the [GensX Github Repo](https://github.com/gensx-inc/gensx), run the following commands: ```bash # Install dependencies pnpm install # Install playwright npx playwright install # Run the example OPENAI_API_KEY= pnpm start:example openai-computer-use ``` The default prompt is `how long does it take to drive from seattle to portland? use google maps` but you can change this by editing the `index.tsx` file. You can also control whether or not the example is multi-turn by toggling the `allowHumanFeedback` prop. This is set to `false` by default but you might what to change this to `true` so you can continue the conversation with the model in the terminal. When you run the example, you'll see an output like the following: ```bash πŸš€ Starting the computer use example 🎯 PROMPT: how long does it take to drive from seattle to portland? use google maps πŸ’» Action: screenshot πŸ’» Action: click at (188, 180) with button 'left' πŸ’» Action: type text 'Google Maps' πŸ’» Action: keypress 'ENTER' πŸ’» Action: wait πŸ’» Action: click at (233, 230) with button 'left' πŸ’» New tab opened πŸ’» Action: wait πŸ’» Action: click at (389, 38) with button 'left' πŸ’» Action: type text 'Seattle to Portland' πŸ’» Action: keypress 'ENTER' πŸ’» Action: wait βœ… Computer use complete ✨ Final response: The estimated driving time from Seattle to Portland on Google Maps is approximately 2 hours and 58 minutes via I-5 S, covering a distance of 174 miles. Would you like any more assistance with your route? ``` ## Key patterns ### Browser automation The example uses Playwright to control a web browser, creating a context that's shared throughout the workflow. The `BrowserProvider` component initializes a browser session and makes it available to child components: ```tsx const BrowserProvider = gensx.Component( "BrowserProvider", async ({ initialUrl }) => { const browser = await chromium.launch({ headless: false, chromiumSandbox: true, env: {}, args: ["--disable-extensions", "--disable-file-system"], }); const page = await browser.newPage(); await page.setViewportSize({ width: 1024, height: 768 }); await page.goto(initialUrl); return ; }, ); ``` ### Processing model actions The `ProcessComputerCalls` component handles the computer actions returned by the model. For each action, it: 1. Extracts the action from the model response 2. Executes the action on the browser using the `UseBrowser` component 3. Takes a screenshot of the result 4. Sends the screenshot back to the model 5. Processes the next model response ```tsx const ProcessComputerCalls = gensx.Component< ProcessComputerCallsProps, ProcessComputerCallsResult >("ProcessComputerCalls", async ({ response }) => { let currentResponse = response; let computerCalls = currentResponse.output.filter( (item) => item.type === "computer_call", ); while (computerCalls.length > 0) { // Execute browser action and take screenshot // ... // Send screenshot back to model // ... // Get updated response // ... } return { updatedResponse: currentResponse }; }); ``` ### Interactive feedback loop The example supports an interactive conversation with the model, allowing you to provide feedback or additional instructions once the model finishes an initial turn: ```tsx // Start conversation loop with human feedback let currentResponse = updatedResponse; let continueConversation = true; while (continueConversation) { // Get human feedback const { userMessage, shouldExit } = await HumanFeedback.run({ assistantMessage: currentResponse.output_text, }); // Exit if requested if (shouldExit) { continueConversation = false; continue; } // Send user message to model // ... // Process any computer calls in the response // ... } ``` ## Additional resources Check out the other examples in the [GenSX Github Repo](https://github.com/gensx-inc/gensx/tree/main/examples). # Model Context Protocol This example demonstrates how to integrate an MCP server into your workflow, and enable those tools to be used in a GenSX workflow. See it on [Github](https://github.com/gensx-inc/gensx/tree/main/examples/mcp). ### What it demonstrates - Instantiate an MCP server - Pass the tools to OpenAI for execution ## Getting Started 1. Install dependencies: ```bash pnpm install ``` 2. Run the example: ```bash # From the root of the repo OPENAI_API_KEY= pnpm start:example mcp ``` ## What is the Model Context Protocol (MCP)? The Model Context Protocol is an open standard that enables developers to build secure, two-way connections between their data sources and AI-powered tools. MCP allows AI assistants to access relevant context from various systems where data lives, including content repositories, business tools, and development environments. This example demonstrates how to create components that connect to and interact with MCP servers. ## Learn More For more information about the Model Context Protocol, visit [Anthropic's MCP page](https://www.anthropic.com/news/model-context-protocol). ### Sample Output for this example ``` Sequential Thinking MCP Server running on stdio β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ πŸ’­ Thought 1/5 β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ I need to find the area of the 25x25 room first. β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 🌿 Branch 2/5 (from thought 1, ID: area_calculation) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ The area of the full room is calculated by multiplying the length and the width: 25 ft * 25 ft = 625 square feet. β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 🌿 Branch 3/5 (from thought 2, ID: area_calculation) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Next, calculate the area to be subtracted from the total, which is the 3.5 ft x 3 ft region. β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 🌿 Branch 4/5 (from thought 3, ID: area_calculation) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ The subtracted area is 3.5 ft * 3 ft = 10.5 square feet. β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 🌿 Branch 5/5 (from thought 4, ID: area_calculation) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Subtract the unused area from the total room area to get the flooring area required: 625 square feet - 10.5 square feet = 614.5 square feet. β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ πŸ’­ Thought 6/8 β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Now, I need to calculate the amount of flooring based on the size of each piece, which is 5 inches x 4 feet. β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 🌿 Branch 7/8 (from thought 6, ID: flooring_calculation) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Convert the dimensions of the flooring from inches to feet: 5 inches is 5/12 feet. β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 🌿 Branch 8/8 (from thought 7, ID: flooring_calculation) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Calculate the area of one piece of flooring: (5/12 ft) * 4 ft. β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 🌿 Branch 9/10 (from thought 8, ID: flooring_calculation) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ The area of one piece of flooring is (5/12 ft) * 4 ft = 5/3 square feet. β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 🌿 Branch 10/10 (from thought 9, ID: flooring_calculation) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Divide the total flooring area by the area of one piece of flooring to determine the number of pieces needed: 614.5 square feet / (5/3 square feet per piece). β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 🌿 Branch 11/11 (from thought 10, ID: flooring_calculation) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ The number of pieces needed is 614.5 / (5/3) = 368.7, so approximately 369 pieces are required after rounding up. β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ To cover the 25x25 room while excluding the 3.5 x 3 ft area, you'll need approximately **369 pieces** of flooring, given each piece measures 5 inches by 4 feet. ``` # Hacker News analyzer The [Hacker News Analyzer](https://github.com/gensx-inc/gensx/tree/main/examples/hackerNewsAnalyzer) example uses GenSX to fetch, analyze, and extract trends from the top Hacker News posts. It shows how to combine data fetching, parallel analysis, and content generation in a single workflow to create two outputs: a detailed report and a tweet of the trends. ## Workflow The Hacker News Analyzer workflow is composed of the following steps: 1. Fetch the top 500 Hacker News posts and filters down to `text` posts (``) 2. Process each post in parallel (``) - Summarize the content (``) - Analyze the comments (``) 3. Writes a detailed report identifying the key trends across all posts (``) 4. Edits the report into the style of Paul Graham (``) 5. Generates a tweet in the voice of Paul Graham (``) ## Running the example ```bash # From the root of the repo # Install dependencies pnpm install # Set your OpenAI API key export OPENAI_API_KEY= # Run the example pnpm start:example hackerNewsAnalyzer ``` The workflow will create two files: - `hn_analysis_report.md`: A detailed analysis report - `hn_analysis_tweet.txt`: A tweet-sized summary of the analysis ## Key patterns ### Parallel processing The workflow uses the `` component to process each post in parallel. GenSX extends JSX to support returning objects so that components can return arrays and structured objects containing other components, which will all be resolved at runtime. This component takes advantage of this and uses `array.map` to return a list of `analyses`, each containing a summary of the post and an analysis of its comments. ```tsx const AnalyzeHNPosts = gensx.Component< AnalyzeHNPostsProps, AnalyzeHNPostsOutput >("AnalyzeHNPosts", ({ stories }) => { return { analyses: stories.map((story) => ({ summary: , commentAnalysis: ( ), })), }; }); ``` ### Multiple outputs This example also shows a simple pattern returning multiple outputs from a workflow. In this case, both the outputs of `` and `` are returned as a single object. This is done by using the the child function of `` to combine the outputs of multiple components into a single object. ```tsx {(report) => ( {(editedReport) => ( {(tweet) => ({ report: editedReport, tweet })} )} )} ``` ## Additional resources Check out the other examples in the [GenSX Github Repo](https://github.com/gensx-inc/gensx/tree/main/examples). # Blog writer Breaking down complex tasks into smaller, discrete steps is one of the best ways to improve the quality of LLM outputs. The [blog writer workflow example](https://github.com/gensx-inc/gensx/tree/main/examples/blogWriter) does this by following the same approach a human would take to write a blog post: first conducting research, then generating a first draft, and finally editing that draft. ## Workflow The Blog Writer workflow consists of the following steps: 1. Parallel research phase: - Brainstorm topics using LLM (``) - Research each topic in detail (``) - Gather web research (``) 2. Write initial draft based on research (``) 3. Edit and polish the content (``) ## Running the example ```bash # From the root of the repo # Install dependencies pnpm install # Set your OpenAI API key export OPENAI_API_KEY= # Run the example pnpm run start ``` ## Key patterns ### Running components in parallel The `` component runs both the `BrainstormTopics` and `SearchWeb` components in parallel. Both sub-workflows return an array of strings which are automatically combined into a single array. ```tsx const Research = gensx.Component( "Research", ({ prompt }) => { return ( <> {({ topics }) => { return topics.map((topic) => ); }} ); }, ); ``` ### Streaming Output The workflow streams back the final output to reduce the time that it takes for the user to receive the first token. The `` component is a [`StreamComponent`](/docs/basic-concepts#component-types) and the `` component has `stream={true}`. ```tsx const EditDraft = gensx.StreamComponent( "EditDraft", ({ draft }) => { return ( ); }, ); ``` Then when the component is invoked, `stream={true}` is passed to the component so that the output is streamed back and can be surfaced to the user: ```tsx ``` ## Additional resources Check out the other examples in the [GenSX Github Repo](https://github.com/gensx-inc/gensx/tree/main/examples). # Building reusable components GenSX allows you to create reusable components that can be used with different models and providers. This is particularly useful if you want to be able to compare the behavior of different models in a component or want to share your components with others. ## Creating a reusable component The pattern for creating reusable components is fairly simple--you just need to parameterize the model name and provider details. Here's an example of a `SummarizeDocument` component that can be re-used with any OpenAI compatible API. First, define the `ProviderConfig` interface that specifies the client options, such as the `baseURL` and `apiKey`, and the model name: ```tsx interface ProviderConfig { clientOptions: ClientOptions; model: string; } ``` Then include the `ProviderConfig` in the component's props interface: ```tsx interface SummarizeDocumentProps { document: string; provider: ProviderConfig; } const SummarizeDocument = gensx.Component( "SummarizeDocument", ({ document, provider }) => ( ${document}`, }, ]} /> ), ); ``` Now, whenever your invoke the `SummarizeDocument` component, you can pass in any provider configuration that you'd like: ```tsx const gpt4oProviderConfig = { clientOptions: { apiKey: process.env.OPENAI_API_KEY, }, model: "gpt-4o", }; const llama8bProviderConfig = { clientOptions: { apiKey: process.env.GROQ_API_KEY, baseURL: "https://api.groq.com/openai/v1", }, model: "llama-3.1-8b-instant", }; const gptSummary = await gensx.execute( , ); const llamaSummary = await gensx.execute( , ); ``` ## Creating parameterized workflows You can take this one step further and create parameterized workflows with multiple provider configurations. One common use case for this is using both a small and large model in your workflow and having separate configurations for each so you can easily swap out the individual models. To demonstrate this, let's create a `ProcessDocument` component that builds on the `SummarizeDocument` component but also extracts keywords and categorizes the document. To get started, define an interface that receives a document and two provider configurations: ```tsx interface ProcessDocumentProps { document: string; defaultProvider: ProviderConfig; smallModelProvider?: ProviderConfig; } ``` In this example, the `defaultProvider` is required, but the `smallModelProvider` is optional. If it's not provided, the `defaultProvider` will be used in all components. Now, we can define the `ProcessDocument` component and pass the provider configuration to the individual components: ```tsx export const ProcessDocument = gensx.Component< ProcessDocumentProps, ProcessDocumentOutput >("ProcessDocument", (props) => { // Use the small model provider if it's provided, otherwise use the default provider const smallModelProvider = props.smallModelProvider ?? props.defaultProvider; return { summary: ( ), keywords: [ , ], category: ( ), }; }); ``` You can find the full code for the `ProcessDocument` component in the [reusable components example](https://github.com/gensx-inc/gensx/tree/main/examples/reusableComponents). Finally, to run the workflow, you can invoke the `ProcessDocument` component with the appropriate provider configurations: ```tsx const gpt4oProviderConfig = { clientOptions: { apiKey: process.env.OPENAI_API_KEY, }, model: "gpt-4o", }; const llama8bProviderConfig = { clientOptions: { apiKey: process.env.GROQ_API_KEY, baseURL: "https://api.groq.com/openai/v1", }, model: "llama-3.1-8b-instant", }; const documentMetadata = await gensx.execute( , ); ``` # How GenSX works GenSX is a simple framework for building complex LLM workflows. While most LLM frameworks use graph-based APIs that require explicit node and edge definitions, GenSX takes a different approach. It uses JSX to dynamically construct an execution graph by programmatically constructing a tree from your components. Trees are just a kind of graph, and JSX enables building trees programmatically which lets you express common agentic patterns like reflection and recursion -- all with a more intuitive, composable syntax that is easier to understand. ## JSX and component model Unlike React's UI components, GenSX uses JSX to compose data processing workflows. While the syntax is familiar to React developers, there are key differences: - Components are pure functions that transform data, not UI elements - There's no virtual DOM or reconciliation - Components execute once and produce a value, rather than rendering and re-rendering - The component tree represents data flow, not visual hierarchy Here's a basic example of a component that takes in a list of tweets and analyzes them using a LLM: ```tsx const TweetAnalyzer = gensx.Component( "TweetAnalyzer", async ({ tweets }) => { const prompt = tweets .map( (tweet) => `${tweet.author}${tweet.content}`, ) .join("\n"); return ( ); }, ); ``` You can also create components that are composed of other components: ```tsx const TweetWorkflow = gensx.Component( "TweetWorkflow", async ({ query }) => ( {(tweets) => ( {(trends) => } )} ), ); ``` ## Component resolution and execution You can turn any component into a runnable workflow using `gensx.workflow()`: ```tsx const myWorkflow = gensx.workflow("TweetWorkflow", TweetWorkflow); ``` Then to run the workflow, call the `run()` method on the workflow and pass in the props: ```tsx const result = await myWorkflow.run({ query: "DeepSeek R1 vs o3-mini" }); ``` When you run a workflow, GenSX will: 1. Creates a dependency graph from your JSX tree 2. Tracks dependencies between components through prop passing and child functions 3. Executes components in parallel where possible while respecting dependencies 4. Resolves all values - including promises, arrays, objects, and nested components This automatic dependency tracking ensures components only execute when their dependencies are resolved, while taking advantage of parallelization wherever possible. For example, when executing the TweetWorkflow, GenSX will: - Start executing `TweetCollector` immediately - Wait for its result, `tweets`, to be available through the child function - Pass the tweets to `TweetAnalyzer` and execute it - Finally execute `ReportGenerator` with the trends from `TweetAnalyzer` and the tweets ### Executing sub-workflows GenSX also allows you to execute sub-workflows from within a component using the `run()` method on a component: ```tsx const result = await MyComponent.run({ input: "some data" }); ``` When used inside a component, `Component.run()` preserves the current context and maintains the component hierarchy so the calls will integrate naturally with the rest of the workflow. The result is automatically resolved to a plain JavaScript value, allowing you to work with it directly without additional JSX syntax. ## Resolving nested values and complex structures GenSX automatically handles resolution of: - Promises and async functions - Arrays of components or values - Objects containing components or values - Nested JSX elements - Child function results For example, consider this nested structure: ```tsx const DocumentProcessor = gensx.Component< DocumentProcessorProps, DocumentProcessorOutput >("DocumentProcessor", async ({ text1, text2 }) => ({ summaries: [, ], metadata: { sentiment: , topics: Promise.resolve(["ai", "tech"]), }, })); const myWorkflow = gensx.workflow("DocumentProcessor", DocumentProcessor); const result = await myWorkflow.run({ text1: "...", text2: "..." }); ``` When you execute the DocumentProcessor workflow, GenSX will: 1. Execute both `Summarize` components in parallel 2. Execute `AnalyzeSentiment` in parallel with the summaries 3. Resolve the topics promise 4. Maintain the object structure in the final output: ```tsx { summaries: [ "First summary...", "Second summary..." ], metadata: { sentiment: "positive", topics: ['ai', 'tech'] } } ``` ## Visualizing and debugging workflows When used with [GenSX Cloud](/docs/cloud), GenSX automatically tracks the execution of your workflow to give you full visibility into your workflow's execution. You can see the execution graph, the inputs and outputs of each component, and other information like the execution time and status of each component. This makes it easy to debug and understand your workflow so you can see what's happening and make improvements where needed. ![Workflow Visualization](/docs/gensx-visualize-workflow.png) # 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](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 `gensx.createContext()` to initialize it along with a default value. For example, here's how to create a `User` context: ```tsx import * as gensx from "@gensx/core"; // Define the interface interface User { name: string; } // Create a context with a default value const UserContext = gensx.createContext({ 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: ```tsx 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: ```tsx const ContextExample = gensx.Component<{}, string>("ContextExample", () => ( )); ``` ## 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 BasicChat = gensx.Component( "BasicChat", async ({ prompt }) => { return ( ); }, ); 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](https://www.firecrawl.dev/) API. #### Step 1: Create a context Start by importing from `@gensx/core` and the package you want to use: ```tsx import * as gensx from "@gensx/core"; import FirecrawlApp, { FirecrawlAppConfig } from "@mendable/firecrawl-js"; ``` Then, create the context: ```tsx // 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: ```tsx // Create the provider export const FirecrawlProvider = gensx.Component( "FirecrawlProvider", (args: FirecrawlAppConfig) => { const client = new FirecrawlApp({ apiKey: args.apiKey, }); return ; }, { 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: ```tsx export const ScrapePage = gensx.Component( "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`: ```tsx const FirecrawlExample = gensx.Component( "FirecrawlExample", async ({ url }) => { return ( ); }, ); 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: ```tsx const NestedProviderExample = gensx.Component< NestedProviderExampleProps, string >("NestedProviderExample", async ({ url }) => { return ( ); }); ``` 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](https://www.groq.com/) that wraps the `OpenAIProvider` and points to the correct base URL: ```tsx const GroqProvider = gensx.Component<{}, never>("GroqProvider", () => ( )); ``` 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: ```tsx export const WriteAndEditTutorial = gensx.Component( "WriteAndEditTutorial", ({ subject }) => { return ( {(tutorial) => { console.log("\nπŸ“ Original tutorial from OpenAI:\n", tutorial); return ( ); }} ); }, ); ``` 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](/docs/concepts/reusable-components). ## 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) - [Multiple OpenAI compatible providers example](https://github.com/gensx-inc/gensx/tree/main/examples/nestedProviders) # Arrays GenSX includes an array primitive that lets you operate over arrays of components with the same standard JavaScript APIs you're used to including `map`, `filter`, `flatMap`, and `reduce`. This allows you to chain together array operations over lists of components for scenarios like: - Evaluating the usefulness of each chunk returned by your vector database - Translating a single document into multiple languages - Analyzing key themes in a set of customer reviews When you use `gensx.array`, GenSX takes care of executing components and producing outputs as needed so that you can focus on the logic of your workflow. ## Example Here's a practical example of using `gensx.array`. The `Research` component below receives a prompt and a list of queries, and then returns a list of summaries from relevant research papers. ```tsx const Research = gensx.Component( "Research", async ({ queries: string[], prompt: string }) => { return await gensx .array(queries) .flatMap((query) => ) .filter((document) => ( )) .map((document) => ( )) .toArray(); }, ); ``` Walking through this code step by step, it: 1. Instantiates a `GsxArray` from the `queries` 2. Uses `flatMap` to call the `ArxivSearch` component for each query and flatten the output into a single array of documents 3. Calls `filter` to filter out any documents that an LLM judge deems irrelevant. `GradeDocument` returns a boolean 4. Chains another `map` operation to call the `FetchAndSummarize` component for each document 5. Calls `toArray()` to convert the `GsxArray` into a `ArxivSummary[]` You can find the full code for this in the [Deep Research example](https://github.com/gensx-inc/gensx/tree/main/examples/deepResearch). ## Working with arrays Now that you've seen an example showing a lot of the functionality of `gensx.array`, let's explore each of these operations in more detail. ### Creating an array To access the array operations, you first need to create a `GsxArray`. You can create an array from raw values: ```tsx import * as gensx from "@gensx/core"; // Create from raw values const numbers = gensx.array([1, 2, 3]); ``` You can also create arrays from components: ```tsx const NumberWrapper = gensx.Component<{ n: number }, number>( "NumberWrapper", ({ n }) => n, ); // Create from components const wrappedNumbers = gensx.array([ , , , ]); ``` To convert a `GsxArray` back to a plain array, you simply call the `toArray()` method which produces an array of resolved component outputs. ### Map and FlatMap `gensx.array` supports both `map` and `flatMap` operations. Both of these operations behave exactly as you'd expect if you're familiar with `array.map` and `array.flatMap` in JavaScript. #### Map Map transforms each element in an array using a component: ```tsx const numbers = gensx.array([1, 2, 3]); const doubled = await numbers .map((n) => ) .toArray(); // Result: [2, 4, 6] ``` #### FlatMap Similarly, flatMap invokes a component for each element in the array and flattens the result. For example, the code below retrieves an array of search results for each query and flattens the results into a single array: ```tsx const queries = gensx.array(["chain of thought", "reasoning models"]); const documents = await queries .flatMap((query) => ( // returns a list of documents )) .toArray(); ``` ### Filter Filters allow you to filter out elements that don't match a given condition. Inside the filter predicate, you can pass either: - A component that returns a boolean - A plain boolean expression #### Filtering with a component When you filter with a component, the component needs to result in a boolean value. ```tsx // Define a component that returns a boolean const EvenNumberFilter = gensx.Component<{ value: number }, boolean>( "EvenNumberFilter", ({ value }) => { return value % 2 === 0; }, ); // Use a component that returns a boolean to filter const evenNumbers = await gensx .array([10, 11, 12, 13, 14]) .filter((n) => ) .toArray(); ``` You can also use the component's child function to convert the output of a component into a boolean: ```tsx const evenNumbers = await gensx .array([10, 11, 12, 13, 14]) .filter((n) => ( {({ result: string }) => result.toLowerCase() === "even"} )) .toArray(); ``` #### Filter using a plain boolean expression When you filter with a boolean expression, you use the filter method just like you would with JavaScript arrays: ```tsx const evenNumbers = await gensx .array([10, 11, 12, 13, 14]) .filter((n) => n % 2 === 0) .toArray(); ``` #### Filter using index and array parameters Filter expressions can also access the current index and the entire array. Here's a basic example of using this to remove duplicates from an array: ```tsx const uniqueNumbers = await gensx .array([1, 2, 2, 3, 3, 3, 4]) .filter((num, index, array) => array.indexOf(num) === index) .toArray(); // Result: [1, 2, 3, 4] ``` ### Reduce The reduce operation allows you to process all of the items in an array to produce a single value. For example, if you wanted to translate a markdown document section by section and combine the results into a single document, it would look something like this: ```tsx const markdownContent = ""; const translatedContent = await gensx .array(markdownContent.split(/(?=^#{1,2} )/m)) .map((value) => ) .reduce( (acc, value) => , "", ); ``` To break down this example a bit more: - `acc` is the accumulator, which starts as the initial value, in this case an empty string - `value` is the current section of the markdown content - `CombineSections` is a component that does the combining given both the accumulator and the current value ## How array operations work `gensx.array` is a convenience wrapper around `gensx.execute` function and the corresponding JavaScript array methods. To illustrate this, take the following example: ```tsx const numbers = [1, 2, 3]; const result = await gensx .array(numbers) .map((n) => ) .filter((n) => ) .toArray(); ``` That same code can be written using `gensx.execute` like this: ```tsx const numbers = [1, 2, 3]; const doubledNumbers = await gensx.execute( numbers.map((n) => ), ); const result = await gensx.execute( doubledNumbers.filter((n) => ), ); ``` When using `gensx.array`, each operation in the chain will execute in sequence so if you have `gensx.array().map().map()`, all of the components in the first `map` will execute in parallel and will complete before the second `map` starts. # Vercel AI SDK The [@gensx/vercel-ai-sdk](https://www.npmjs.com/package/@gensx/vercel-ai-sdk) package provides [Vercel AI SDK](https://sdk.vercel.ai/docs/introduction) compatible components for GenSX, allowing you to use Vercel's AI SDK with GenSX's component model. ## Installation To install the package, run the following command: ```bash npm install @gensx/vercel-ai-sdk ``` You'll also need to install the Vercel AI SDK: ```bash npm install ai ``` Then import the components you need from the package: ```tsx import { GenerateText, GenerateObject } from "@gensx/vercel-ai-sdk"; ``` ## Supported components |
Component
| Description | | :-------------------------------------------- | :------------------------------------------------------------- | | [`StreamText`](#streamtext) | Stream text responses from language models | | [`StreamObject`](#streamobject) | Stream structured JSON objects from language models | | [`GenerateText`](#generatetext) | Generate complete text responses from language models | | [`GenerateObject`](#generateobject) | Generate complete structured JSON objects from language models | | [`Embed`](#embed) | Generate embeddings for a single text input | | [`EmbedMany`](#embedmany) | Generate embeddings for multiple text inputs | | [`GenerateImage`](#generateimage) | Generate images from text prompts | ## Component Reference #### `` The [StreamText](https://sdk.vercel.ai/docs/ai-sdk-core/generating-text#streamtext) component streams text responses from language models, making it ideal for chat interfaces and other applications where you want to show responses as they're generated. ```tsx import { StreamText } from "@gensx/vercel-ai-sdk"; import { openai } from "@ai-sdk/openai"; const languageModel = openai("gpt-4o"); ``` ```tsx ``` ##### Props The `StreamText` component accepts all parameters from the Vercel AI SDK's `streamText` function: - `prompt` (required): The text prompt to send to the model - `model` (required): The language model to use (from Vercel AI SDK) - Plus all other parameters supported by the Vercel AI SDK ##### Return Type Returns a streaming response that can be consumed token by token. #### `` The `StreamObject` component streams structured JSON objects from language models, allowing you to get structured data with type safety. ```tsx import * as gensx from "@gensx/core"; import { StreamObject } from "@gensx/vercel-ai-sdk"; import { openai } from "@ai-sdk/openai"; import { z } from "zod"; const languageModel = openai("gpt-4o"); // Define a schema for the response const recipeSchema = z.object({ recipe: z.object({ name: z.string(), ingredients: z.array(z.string()), steps: z.array(z.string()), }), }); ``` ```tsx // Streams a structured object when executed ``` ##### Props The [StreamObject](https://sdk.vercel.ai/docs/ai-sdk-core/generating-structured-data#stream-object) component accepts all parameters from the Vercel AI SDK's `streamObject` function: - `prompt` (required): The text prompt to send to the model - `model` (required): The language model to use (from Vercel AI SDK) - `schema`: A Zod schema defining the structure of the response - `output`: The output format ("object", "array", or "no-schema") - Plus all other parameters supported by the Vercel AI SDK ##### Return Type Returns a structured object matching the provided schema. #### `` The [GenerateText](https://sdk.vercel.ai/docs/ai-sdk-core/generating-text#generatetext) component generates complete text responses from language models, waiting for the entire response before returning. ```tsx import * as gensx from "@gensx/core"; import { GenerateText } from "@gensx/vercel-ai-sdk"; import { openai } from "@ai-sdk/openai"; const languageModel = openai("gpt-4o"); ``` ```tsx // Generates a complete text response when executed ``` ##### Props The `GenerateText` component accepts all parameters from the Vercel AI SDK's `generateText` function: - `prompt` (required): The text prompt to send to the model - `model` (required): The language model to use (from Vercel AI SDK) - Plus any other parameters supported by the Vercel AI SDK ##### Return Type Returns a complete text string containing the model's response. #### `` The [GenerateObject](https://sdk.vercel.ai/docs/ai-sdk-core/generating-structured-data#generate-object) component generates complete structured JSON objects from language models, with type safety through Zod schemas. ```tsx import * as gensx from "@gensx/core"; import { GenerateObject } from "@gensx/vercel-ai-sdk"; import { openai } from "@ai-sdk/openai"; import { z } from "zod"; const languageModel = openai("gpt-4o"); // Define a schema for the response const userSchema = z.object({ user: z.object({ name: z.string(), age: z.number(), interests: z.array(z.string()), contact: z.object({ email: z.string().email(), phone: z.string().optional(), }), }), }); ``` ```tsx // Generates a structured object when executed ``` ##### Props The `GenerateObject` component accepts all parameters from the Vercel AI SDK's `generateObject` function: - `prompt` (required): The text prompt to send to the model - `model` (required): The language model to use (from Vercel AI SDK) - `schema`: A Zod schema defining the structure of the response - `output`: The output format ("object", "array", or "no-schema") - Plus any other optional parameters supported by the Vercel AI SDK ##### Return Type Returns a structured object matching the provided schema. #### `` The [Embed](https://sdk.vercel.ai/docs/ai-sdk-core/embeddings) component generates embeddings for a single text input, which can be used for semantic search, clustering, and other NLP tasks. ```tsx import * as gensx from "@gensx/core"; import { Embed } from "@gensx/vercel-ai-sdk"; import { openai } from "@ai-sdk/openai"; const embeddingModel = openai.embedding("text-embedding-3-small"); ``` ```tsx // Generates an embedding when executed ``` ##### Props The `Embed` component accepts all parameters from the Vercel AI SDK's `embed` function: - `value` (required): The text to generate an embedding for - `model` (required): The embedding model to use (from Vercel AI SDK) - Plus any other optional parameters supported by the Vercel AI SDK ##### Return Type Returns a vector representation (embedding) of the input text. #### `` The [EmbedMany](https://sdk.vercel.ai/docs/ai-sdk-core/embeddings#embedding-many-values) component generates embeddings for multiple text inputs in a single call, which is more efficient than making separate calls for each text. ```tsx import * as gensx from "@gensx/core"; import { EmbedMany } from "@gensx/vercel-ai-sdk"; import { openai } from "@ai-sdk/openai"; const embeddingModel = openai.embedding("text-embedding-3-small"); ``` ```tsx // Generates embeddings for multiple texts when executed ``` ##### Props The `EmbedMany` component accepts all parameters from the Vercel AI SDK's `embedMany` function: - `values` (required): Array of texts to generate embeddings for - `model` (required): The embedding model to use (from Vercel AI SDK) - Plus any other optional parameters supported by the Vercel AI SDK ##### Return Type Returns an array of vector representations (embeddings) for the input texts. #### `` The [GenerateImage](https://sdk.vercel.ai/docs/ai-sdk-core/image-generation) component generates images from text prompts using image generation models. ```tsx import * as gensx from "@gensx/core"; import { GenerateImage } from "@gensx/vercel-ai-sdk"; import { openai } from "@ai-sdk/openai"; const imageModel = openai.image("dall-e-3"); ``` ```tsx // Generates an image when executed ``` ##### Props The `GenerateImage` component accepts all parameters from the Vercel AI SDK's `experimental_generateImage` function: - `prompt` (required): The text description of the image to generate - `model` (required): The image generation model to use (from Vercel AI SDK) - Plus any other optional parameters supported by the Vercel AI SDK ##### Return Type Returns an object containing information about the generated image, including its URL. ## Usage with Different Models The Vercel AI SDK supports multiple model providers. Here's how to use different providers with GenSX components: ```tsx // OpenAI import { openai } from "@ai-sdk/openai"; const openaiModel = openai("gpt-4o"); // Anthropic import { anthropic } from "@ai-sdk/anthropic"; const anthropicModel = anthropic("claude-3-opus-20240229"); // Cohere import { cohere } from "@ai-sdk/cohere"; const cohereModel = cohere("command-r-plus"); // Use with GenSX components import { GenerateText } from "@gensx/vercel-ai-sdk"; const openaiResponse = await gensx.execute( , ); const anthropicResponse = await gensx.execute( , ); ``` For more information on the Vercel AI SDK, visit the [official documentation](https://sdk.vercel.ai/docs). # OpenRouter [OpenRouter](https://openrouter.ai) provides a unified API to access various AI models from different providers. You can use GenSX with OpenRouter by configuring the OpenAIProvider component with OpenRouter's API endpoint. ## Installation To use OpenRouter with GenSX, you need to install the OpenAI package: ```bash npm install @gensx/openai ``` ## Configuration Configure the `OpenAIProvider` with your OpenRouter API key and the OpenRouter base URL: ```tsx import { OpenAIProvider } from "@gensx/openai"; {/* Your components here */} ; ``` ## Example Usage Here's a complete example of using OpenRouter with GenSX: ```tsx import * as gensx from "@gensx/core"; import { ChatCompletion, OpenAIProvider } from "@gensx/openai"; interface RespondProps { userInput: string; } type RespondOutput = string; const GenerateText = gensx.Component( "GenerateText", ({ userInput }) => ( ), ); const OpenRouterExampleComponent = gensx.Component< { userInput: string }, string >("OpenRouter", ({ userInput }) => ( )); const workflow = gensx.Workflow( "OpenRouterWorkflow", OpenRouterExampleComponent, ); const result = await workflow.run({ userInput: "Hi there! Write me a short story about a cat that can fly.", }); ``` ## Specifying Models When using OpenRouter, you can specify models using their full identifiers: - `anthropic/claude-3.7-sonnet` - `openai/gpt-4o` - `google/gemini-1.5-pro` - `mistral/mistral-large-latest` Check the [OpenRouter documentation](https://openrouter.ai/docs) for a complete list of available models. ## Provider Options You can use the `provider` property in the `ChatCompletion` component to specify OpenRouter-specific options: ```tsx ``` ## Learn More - [OpenRouter Documentation](https://openrouter.ai/docs) - [GenSX OpenAI Components](/docs/component-reference/openai) # OpenAI The [@gensx/openai](https://www.npmjs.com/package/@gensx/openai) package provides OpenAI API compatible components for GenSX. ## Installation To install the package, run the following command: ```bash npm install @gensx/openai ``` Then import the components you need from the package: ```tsx import { OpenAIProvider, GSXChatCompletion } from "@gensx/openai"; ``` ## Supported components |
Component
| Description | | :---------------------------------------------- | :---------------------------------------------------------------------------------------------- | | [`OpenAIProvider`](#openaiprovider) | OpenAI Provider that handles configuration and authentication for child components | | [`GSXChatCompletion`](#gsxchatcompletion) | Enhanced component with enhanced features for OpenAI chat completions | | [`ChatCompletion`](#chatcompletion) | Simplified component for chat completions with streamlined output interface | | [`OpenAIChatCompletion`](#openaichatcompletion) | Low-level component that directly matches the OpenAI SDK interface for the Chat Completions API | | [`OpenAIResponses`](#openairesponses) | Low-level component that directly matches the OpenAI SDK interface for the Responses API | | [`OpenAIEmbedding`](#openaiembedding) | Low-level component that directly matches the OpenAI SDK interface for the Embeddings API | ## Component Comparison The package provides three different chat completion components to suit different use cases: - **OpenAIChatCompletion**: Direct mapping to the OpenAI API with identical inputs and outputs - **GSXChatCompletion**: Enhanced component with additional features like structured output and automated tool calling - **ChatCompletion**: Simplified interface that returns string responses or simple streams while maintaining identical inputs to the OpenAI API ## Reference #### `` The `OpenAIProvider` component initializes and provides an OpenAI client instance to all child components. Any components that use OpenAI's API need to be wrapped in an `OpenAIProvider`. ```tsx ``` By configuring the baseURL, you can also use the `OpenAIProvider` with other OpenAI compatible APIs like [x.AI](https://docs.x.ai/docs/overview#featured-models) and [Groq](https://console.groq.com/docs/openai). ```tsx ``` ##### Props The `OpenAIProvider` accepts all configuration options from the [OpenAI Node.js client library](https://github.com/openai/openai-node) including: - `apiKey` (required): Your OpenAI API key - `organization`: Optional organization ID - `baseURL`: Optional API base URL #### `` The `GSXChatCompletion` component is an advanced chat completion component that provides enhanced features beyond the standard OpenAI API. It supports structured output, tool calling, and streaming, with automatic handling of tool execution. To get a structured output, pass a [Zod schema](https://www.npmjs.com/package/zod) to the `outputSchema` prop. ```tsx // Returns an object matching the outputSchema when executed ``` To use tools, create a `GSXTool` object: ```tsx const calculator = GSXTool.create({ name: "calculator", description: "Perform mathematical calculations", schema: z.object({ expression: z.string(), }), run: async ({ expression }) => { return { result: eval(expression) }; }, }); ``` Then pass the tool to the `tools` prop. ```tsx ``` ##### Props The `GSXChatCompletion` component accepts all parameters from OpenAI's chat completion API plus additional options: - `model` (required): ID of the model to use (e.g., `"gpt-4o"`, `"gpt-4o-mini"`) - `messages` (required): Array of messages in the conversation - `stream`: Whether to stream the response (when `true`, returns a `Stream`) - `tools`: Array of `GSXTool` instances for function calling - `outputSchema`: Zod schema for structured output (when provided, returns data matching the schema) - `structuredOutputStrategy`: Strategy to use for structured output. Supported values are `default`, `tools`, and `response_format`. - Plus all standard OpenAI chat completion parameters (temperature, maxTokens, etc.) ##### Return Types The return type of `GSXChatCompletion` depends on the props: - With `stream: true`: Returns `Stream` from OpenAI SDK - With `outputSchema`: Returns data matching the provided Zod schema - Default: Returns `GSXChatCompletionResult` (OpenAI response with message history) #### `` The `ChatCompletion` component provides a simplified interface for chat completions. It returns either a string or a simple stream of string tokens while having identical inputs to the OpenAI API. ```tsx // Returns a string when executed // Returns an AsyncIterableIterator when executed ``` ##### Props The `ChatCompletion` component accepts all parameters from OpenAI's chat completion API: - `model` (required): ID of the model to use (e.g., `"gpt-4o"`, `"gpt-4o-mini"`) - `messages` (required): Array of messages in the conversation - `temperature`: Sampling temperature (0-2) - `stream`: Whether to stream the response - `maxTokens`: Maximum number of tokens to generate - `responseFormat`: Format of the response (example: `{ "type": "json_object" }`) - `tools`: Array of `GSXTool` instances for function calling ##### Return Types - With `stream: false` (default): Returns a string containing the model's response - With `stream: true`: Returns an `AsyncIterableIterator` that yields tokens as they're generated #### `` The `OpenAIChatCompletion` component is a low-level component that directly maps to the OpenAI SDK. It has identical inputs and outputs to the OpenAI API, making it suitable for advanced use cases where you need full control. ```tsx ``` ##### Props The `OpenAIChatCompletion` component accepts all parameters from the OpenAI SDK's `chat.completions.create` method: - `model` (required): ID of the model to use - `messages` (required): Array of messages in the conversation - `temperature`: Sampling temperature - `stream`: Whether to stream the response - `maxTokens`: Maximum number of tokens to generate - `tools`: Array of OpenAI tool definitions for function calling - Plus all other OpenAI chat completion parameters ##### Return Types - With `stream: false` (default): Returns the full `ChatCompletionOutput` object from OpenAI SDK - With `stream: true`: Returns a `Stream` from OpenAI SDK #### `` The `OpenAIResponses` component is a low-level component that directly maps to the [OpenAI Responses API](https://platform.openai.com/docs/api-reference/responses). It has identical inputs and outputs to the OpenAI Responses API, making it suitable for advanced use cases where you need full control. ```tsx ``` ##### Props The `OpenAIResponses` component accepts all parameters from the OpenAI SDK's `responses.create` method: - `model` (required): ID of the model to use - `input` (required): The input to the response - Plus all other optional parameters from the OpenAI Responses API ##### Return Types - With `stream: false` (default): Returns the full `Response` object from OpenAI SDK - With `stream: true`: Returns a `Stream` from OpenAI SDK #### `` The `OpenAIEmbedding` component is a low-level component that directly maps to the OpenAI SDK's `embeddings.create` method. It has identical inputs and outputs to the OpenAI Embeddings API, making it suitable for advanced use cases where you need full control. ```tsx ``` ##### Props The `OpenAIEmbedding` component accepts all parameters from the OpenAI SDK's `embeddings.create` method: - `model` (required): ID of the model to use - `input` (required): The input to the embedding (`string` or `string[]`) - Plus all other optional parameters from the OpenAI Embeddings API ##### Return Types - Returns the full `CreateEmbeddingResponse` object from OpenAI SDK # MCP Components The `@gensx/mcp` package provides Model Control Protocol (MCP) components for GenSX, bringing in a provider and context helper that enables you to easily call tools, resources, and prompts that are provided by the MCP server. This supports either a server command and arguments, and will manage the lifecycle of that MCP process, or it accepts a pre-connected MCP client as the source for MCP resources. See the example [here](../examples/mcp). ## Installation To install the package, run the following command: ```bash npm install @gensx/mcp ``` ## Supported components and utilities |
Component/Utility
| Description | | :---------------------------------------------------- | :--------------------------------------------------------------------- | | [`createMCPServerContext`](#createmcpservercontext) | Creates a context provider and hook for accessing MCP server resources | | [`MCPTool`](#mcptool) | Wrapper used to call a tool resource provided by an MCP server | | [`MCPResource`](#mcpresource) | Wrapper used to call a resource provided by an MCP server | | [`MCPResourceTemplate`](#mcpresourcetemplate) | Wrapper used to call a resource template provided by an MCP server | | [`MCPPrompt`](#mcpprompt) | Wrapper used to call a prompt resource provided by an MCP server | ## Reference ### `createMCPServerContext()` The `createMCPServerContext` function creates a context provider and hook for accessing MCP server resources. It returns an object containing a Provider component and a useContext hook. If a server command is provided, it will be used to start the MCP server, and close the connection when the component is unmounted. Otherwise, the MCP client will be used to connect to an existing server. ```tsx import { createMCPServerContext } from "@gensx/mcp"; const { Provider, useContext } = createMCPServerContext({ serverCommand: "your-server-command", serverArgs: ["--arg1", "--arg2"], // Or provide a client directly client: yourMCPClient, }); // Use the Provider to wrap your application ; // Use the context hook in your components const MyComponent = () => { const { tools, resources, resourceTemplates, prompts } = useContext(); // Use the MCP server context... }; ``` #### Parameters The `createMCPServerContext` function accepts a server definition object with the following properties: - Either: - `serverCommand`: The command to start the MCP server - `serverArgs`: Array of arguments for the server command - Or: - `client`: A pre-configured MCP client instance #### Return Value Returns an object containing: - `Provider`: A React component that provides the MCP server context - `useContext`: A hook that returns the current MCP server context ### Types #### MCPServerContext The context object returned by `useContext` contains: ```tsx interface MCPServerContext { tools: MCPTool[]; // Available tools in the server resources: MCPResource[]; // Available resources resourceTemplates: MCPResourceTemplate[]; // Available resource templates prompts: MCPPrompt[]; // Available prompts } ``` #### MCPTool Wrapper used to call a tool resource provided by an MCP server. This makes it easy to call any of the tools provided by an MCP server, with the correct arguments and parameters. #### MCPResource Wrapper used to call a resource provided by an MCP server. This makes it easy to access any of the resources provided by an MCP server. #### MCPResourceTemplate Wrapper used to call a resource template provided by an MCP server. This makes it easy to access any of the resource templates provided by an MCP server, with the correct arguments and parameters. #### MCPPrompt Wrapper used to call a prompt resource provided by an MCP server. This makes it easy to access any of the prompts provided by an MCP server, with the correct arguments and parameters. ## Example Usage ```tsx import { createMCPServerContext } from "@gensx/mcp"; import { OpenAIProvider, ChatCompletion } from "@gensx/openai"; // Create the MCP server context const { Provider, useContext: useMCPContext } = createMCPServerContext({ serverCommand: "npx", serverArgs: ["-y", "@/"], }); // Wrap your application with the Provider const App = () => ( ); // Use the context in your components const MCPComponent = () => { const { tools, resources } = useMCPContext(); return ( tool.asGSXTool())} /> ); }; ``` # Anthropic The [@gensx/anthropic](https://www.npmjs.com/package/@gensx/anthropic) package provides [Anthropic API](https://docs.anthropic.com/en/api/getting-started) compatible components for GenSX. ## Installation To install the package, run the following command: ```bash npm install @gensx/anthropic ``` Then import the components you need from the package: ```tsx import { AnthropicProvider, GSXChatCompletion } from "@gensx/anthropic"; ``` ## Supported components |
Component
| Description | | :---------------------------------------------------- | :------------------------------------------------------------------------------------ | | [`AnthropicProvider`](#anthropicprovider) | Anthropic Provider that handles configuration and authentication for child components | | [`GSXChatCompletion`](#gsxchatcompletion) | Enhanced component with advanced features for Anthropic chat completions | | [`ChatCompletion`](#chatcompletion) | Simplified component for chat completions with streamlined output interface | | [`AnthropicChatCompletion`](#anthropicchatcompletion) | Low-level component that directly matches the Anthropic SDK interface | ## Component Comparison The package provides three different chat completion components to suit different use cases: - **AnthropicChatCompletion**: Direct mapping to the Anthropic API with identical inputs and outputs - **GSXChatCompletion**: Enhanced component with additional features like structured output and automated tool calling - **ChatCompletion**: Simplified interface that returns string responses or simple streams while maintaining identical inputs to the Anthropic API ## Reference #### `` The `AnthropicProvider` component initializes and provides an Anthropic client instance to all child components. Any components that use Anthropic's API need to be wrapped in an `AnthropicProvider`. ```tsx ``` ##### Props The `AnthropicProvider` accepts all configuration options from the [Anthropic Node.js client library](https://github.com/anthropics/anthropic-sdk-typescript) including: - `apiKey` (required): Your Anthropic API key - Plus all other Anthropic client configuration options #### `` The `GSXChatCompletion` component is an advanced chat completion component that provides enhanced features beyond the standard Anthropic API. It supports structured output, tool calling, and streaming, with automatic handling of tool execution. To get a structured output, pass a [Zod schema](https://www.npmjs.com/package/zod) to the `outputSchema` prop. ```tsx // Returns an object matching the outputSchema when executed ``` To use tools, create a `GSXTool` object: ```tsx const weatherTool = GSXTool.create({ name: "get_weather", description: "Get the weather for a given location", schema: z.object({ location: z.string(), }), run: async ({ location }) => { return { weather: "sunny" }; }, }); ``` Then pass the tool to the `tools` prop. ```tsx ``` ##### Props The `GSXChatCompletion` component accepts all parameters from Anthropic's messages API plus additional options: - `model` (required): ID of the model to use (e.g., `"claude-3-7-sonnet-latest"`, `"claude-3-5-haiku-latest"`) - `messages` (required): Array of messages in the conversation - `max_tokens` (required): Maximum number of tokens to generate - `system`: System prompt to set the behavior of the assistant - `stream`: Whether to stream the response (when `true`, returns a `Stream`) - `tools`: Array of `GSXTool` instances for function calling - `outputSchema`: Zod schema for structured output (when provided, returns data matching the schema) - `temperature`: Sampling temperature - Plus all standard Anthropic message parameters ##### Return Types The return type of `GSXChatCompletion` depends on the props: - With `stream: true`: Returns `Stream` from Anthropic SDK - With `outputSchema`: Returns data matching the provided Zod schema - Default: Returns `GSXChatCompletionResult` (Anthropic response with message history) #### `` The `ChatCompletion` component provides a simplified interface for chat completions. It returns either a string or a simple stream of string tokens while having identical inputs to the Anthropic API. ```tsx // Returns a string when executed // Returns an AsyncIterableIterator when executed ``` ##### Props The `ChatCompletion` component accepts all parameters from Anthropic's messages API: - `model` (required): ID of the model to use (e.g., `"claude-3-5-sonnet-latest"`, `"claude-3-haiku-latest"`) - `messages` (required): Array of messages in the conversation - `max_tokens` (required): Maximum number of tokens to generate - `system`: System prompt to set the behavior of the assistant - `temperature`: Sampling temperature - `stream`: Whether to stream the response - `tools`: Array of `GSXTool` instances for function calling (not compatible with streaming) ##### Return Types - With `stream: false` (default): Returns a string containing the model's response - With `stream: true`: Returns an `AsyncIterableIterator` that yields tokens as they're generated #### `` The `AnthropicChatCompletion` component is a low-level component that directly maps to the Anthropic SDK. It has identical inputs and outputs to the Anthropic API, making it suitable for advanced use cases where you need full control. ```tsx ``` ##### Props The `AnthropicChatCompletion` component accepts all parameters from the Anthropic SDK's `messages.create` method: - `model` (required): ID of the model to use - `messages` (required): Array of messages in the conversation - `max_tokens` (required): Maximum number of tokens to generate - `system`: System prompt to set the behavior of the assistant - `temperature`: Sampling temperature - `stream`: Whether to stream the response - `tools`: Array of Anthropic tool definitions for function calling - Plus all other Anthropic message parameters ##### Return Types - With `stream: false` (default): Returns the full `Message` object from Anthropic SDK - With `stream: true`: Returns a `Stream` from Anthropic SDK # Serverless deployments Deploy your GenSX workflows as serverless APIs with support for both synchronous and asynchronous execution, as well as long-running operations. ## Deploy with the CLI Projects are a collection of workflows and environment variables that deploy together into an `environment` that you configure. Each project has a `gensx.yaml` file at the root and a `workflows.tsx` file that exports all of your deployable workflows. Run `gensx deploy` from the root of your project to deploy it: ```bash # Deploy the workflow file with default settings npx gensx deploy src/workflows.tsx # Deploy with environment variables npx gensx deploy src/workflows.tsx -ev OPENAI_API_KEY ``` Environment variables are encrypted with per-project encryption keys. ### Deploying to different environments GenSX supports multiple environments within a project (such as development, staging, and production) to help manage your deployment lifecycle. ```bash # Deploy to a specific environment npx gensx deploy src/workflows.tsx --env production # Deploy to staging with environment-specific variables npx gensx deploy src/workflows.tsx --env staging -ev OPENAI_API_KEY -ev LOG_LEVEL=debug ``` Each environment can have its own configuration and environment variables, allowing you to test in isolation before promoting changes to production. When you deploy a workflow, GenSX: 1. Builds your TypeScript code for production 2. Bundles your dependencies 3. Uploads the package to GenSX Cloud 4. Configures serverless infrastructure 5. Creates API endpoints for each exported workflow 6. Encrypts and sets up environment variables 7. Activates the deployment The entire process typically takes 15 seconds. ## Running workflows from the CLI Once deployed, you can execute workflows directly from the CLI: ```bash # Run a workflow synchronously with input data npx gensx run MyWorkflow --input '{"prompt":"Generate a business name"}' --project my-app # Run and save the output to a file npx gensx run MyWorkflow --input '{"prompt":"Generate a business name"}' --output results.json # Run asynchronously (start the workflow but don't wait for completion) npx gensx run MyWorkflow --input '{"prompt":"Generate a business name"}' --project my-app ``` ### CLI run options | Option | Description | | ----------- | ----------------------------------------------- | | `--input` | JSON string with input data | | `--no-wait` | Do not wait for workflow to finish | | `--output` | Save results to a file | | `--project` | Specify the project name | | `--env` | Specify the environment name | ## API endpoints Each workflow is exposed as an API endpoint: ``` https://api.gensx.com/org/{org}/projects/{project}/environments/{environment}/workflows/{workflow} ``` - `{org}` - Your organization ID - `{project}` - Your project name - `{environment}` - The environment (defaults to "default") - `{workflow}` - The name of your workflow For example, if you have a workflow named `BlogWriter` in project `content-tools`, the endpoint would be: ``` https://api.gensx.com/org/your-org/projects/content-tools/environments/default/workflows/BlogWriter ``` ## Authentication All GenSX Cloud API endpoints require authentication using your GenSX API key as a bearer token: ```bash curl -X POST https://api.gensx.com/org/your-org/projects/your-project/environments/default/workflows/YourWorkflow \ -H "Authorization: Bearer your-api-key" \ -H "Content-Type: application/json" \ -d '{"prompt": "Tell me about GenSX"}' ``` ### Obtaining an API Key To generate or manage API keys: 1. Log in to the [GenSX Cloud console](https://app.gensx.com) 2. Navigate to Settings > API Keys 3. Create a new key ## Execution modes ### Synchronous Execution By default, API calls execute synchronously, returning the result when the workflow completes: ```bash curl -X POST https://api.gensx.com/org/your-org/projects/your-project/environments/default/workflows/YourWorkflow \ -H "Authorization: Bearer your-api-key" \ -H "Content-Type: application/json" \ -d '{"prompt": "Tell me about GenSX"}' ``` ### Asynchronous execution For longer-running workflows, use asynchronous execution by calling the `/start` endpoint: ```bash # Request asynchronous execution curl -X POST https://api.gensx.com/org/your-org/projects/your-project/environments/default/workflows/YourWorkflow/start \ -H "Authorization: Bearer your-api-key" \ -H "Content-Type: application/json" \ -d '{"prompt": "Tell me about GenSX"}' # Response includes an execution ID # { # "status": "ok", # "data": { # "executionId": "exec_123abc" # } # } # Check status later curl -X GET https://api.gensx.com/executions/exec_123abc \ -H "Authorization: Bearer your-api-key" ``` ### Streaming responses For workflows that support streaming, you can receive tokens as they're generated: ```bash curl -X POST https://api.gensx.com/org/your-org/projects/your-project/environments/default/workflows/YourWorkflow \ -H "Authorization: Bearer your-api-key" \ -H "Content-Type: application/json" \ -d '{"prompt": "Tell me about GenSX", "stream": true }' ``` The response is delivered as a stream of server-sent events (SSE). ## Execution time limits GenSX Cloud is optimized for long-running workflows and agents, with generous execution time limits: | Plan | Maximum Execution Time | | ---------- | ----------------------- | | Free Tier | Up to 5 minutes | | Pro Tier | Up to 60 minutes | | Enterprise | Custom limits available | These extended timeouts make GenSX ideal for complex AI workflows that might involve: - Multiple LLM calls in sequence - Real-time agent tool use - Complex data processing - Extensive RAG operations ## Cold starts and performance The GenSX Cloud serverless architecture is designed to minimize cold starts: - **Millisecond-level cold starts**: Initial cold starts typically range from 10-30ms - **Warm execution**: Subsequent executions of recently used workflows start in 1-5ms - **Auto-scaling**: Infrastructure automatically scales with workloads ## Managing deployments in the console GenSX Cloud provides a console to run, debug, and view all of your workflows. ### Viewing workflows ![View projects in the console](/cloud/console-workflows.png) 1. Log in to [app.gensx.com](https://app.gensx.com) 2. Navigate to your project and environment 3. The workflows tab shows all deployed workflows with status information 4. Click on a workflow to view its details, including schema, recent executions, and performance metrics The workflow page includes API documentation and code snippets that you can copy/paste to run your workflow from within another app: ![View projects in the console](/cloud/console-workflow-docs.png) ### Running workflows manually You can test workflows directly from the console: 1. Navigate to the workflow detail page 2. Click the "Run" button 3. Enter JSON input in the provided editor 4. Choose execution mode (sync, async, or streaming) 5. View results directly in the console ![Run workflows in the console](/cloud/console-playground.png) ### Viewing execution history Each workflow execution generates a trace you can review: 1. Navigate to the "Executions" tab in your project 2. Browse the list of recent executions 3. Click on any execution to see detailed traces 4. Explore the component tree, inputs/outputs, and execution timeline ## Next steps - [Learn about cloud storage options](/docs/cloud/storage) - [Explore observability and tracing](/docs/cloud/observability) # Projects and environments GenSX organizes your workflows and deployments using a flexible structure of projects and environments, making it easy to match the rest of your application architecture and CI/CD topology. Projects are a top level resource and environments are instances of a project that you deploy to. ## Project structure A project in GenSX is a collection of related workflows that are deployed, managed, and monitored together: - **Projects as logical units**: Group related workflows that serve a common purpose - **Shared configuration**: Apply settings across all workflows in a project - **Collective deployment**: Deploy all workflows within a project in one operation - **Unified monitoring**: View traces and metrics for an entire project Projects typically correspond to a codebase or application that contains multiple workflows. ## Environment separation Within each project, you can have multiple environments. For example, you could create three environments for each project: - **Development**: For building and testing new features - **Staging**: For pre-production validation - **Production**: For live, user-facing workflows You have full control over your environments so you can organize them however you see fit. Each environment maintains separate: - Workflow deployments - Configuration and environment variables - Execution traces and monitoring data ## Configuring projects ### Project configuration file Projects are defined using a `gensx.yaml` file at the root of your codebase: ```yaml # gensx.yaml projectName: customer-support-bot description: AI assistant for customer support ``` This configuration applies to both local development and cloud deployments. ## Working with environments ### Deploying to different environments Deploy your workflows to specific environments using the CLI: ```bash # Deploy to the default environment gensx deploy src/workflows.tsx # Deploy to a staging environment gensx deploy src/workflows.tsx --env staging # Deploy to production gensx deploy src/workflows.tsx --env production ``` ### Environment-specific configuration Set environment-specific variables during deployment: ```bash # Development-specific settings gensx deploy src/workflows.tsx --env development \ -ev LOG_LEVEL=debug \ -ev OPENAI_API_KEY # Production-specific settings gensx deploy src/workflows.tsx --env production \ -ev LOG_LEVEL=error \ -ev OPENAI_API_KEY ``` ## Projects in the GenSX Console The GenSX Console organizes everything by project and environment: ![Project structure in console](/cloud/project-structure.png) Selecting an environment brings you to the workflows view: ![Environment page](/cloud/console-workflows.png) When you click into a workflow, you can trigger it within the console if you've deployed it to GenSX Cloud: ![Triggering a workflow in the console](/cloud/console-playground.png) You can also see API documentation and sample code for calling that workflow: ![Workflow API documentation](/cloud/console-workflow-docs.png) ## Next steps - [Configure serverless deployments](/docs/cloud/serverless-deployments) for your projects - [Set up local development](/docs/cloud/local-development) for testing - [Learn about observability](/docs/cloud/observability) across environments # Pricing & limits GenSX Cloud offers flexible pricing tiers designed to scale with your needs, including a free tier for individuals. ## Pricing tiers GenSX Cloud offers three pricing tiers: - **Free Tier**: Perfect for learning, experimentation, and small projects - **Pro Tier** ($20/month per developer): For professional development and production workloads - **Enterprise**: Custom pricing for large-scale deployments with additional features and support Each plan includes monthly allowances for compute, tracing, and storage: | Resource | Free Tier | Pro Tier ($20/month/dev) | Overage/Action | | ------------------ | ---------------------- | ------------------------ | ---------------- | | Serverless Compute | 50K sec | 500K sec | $0.00003/sec | | Traces (events) | 100K events | 1M events | $0.20/10K | | Blob Storage | 500MB | 5GB | $0.25/GB | | SQLite Storage | 500MB | 1GB | $1.50/GB | | Vector Storage | 250MB | 1GB | $1.00/GB | | Execution time | Up to 5 minutes | Up to 60 minutes | Custom | | Observability | 7 days trace retention | 30 days trace retention | Custom retention | The free tier is only for individuals, and you will need to upgrade to the Pro tier before adding additional members to your org. ## Limits When limits are reached, additional operations may be throttled or declined. Exceeding any limits on the free tier requires upgrading to the Pro tier. ### Serverless - Free tier: Maximum workflow execution time of 5 minutes - Pro tier: Maximum workflow execution time of 60 minutes - Maximum payload size: 10MB per request - Maximum response size: 10MB ### Blob storage - Maximum blob size: 100MB - Maximum number of blobs: Unlimited (subject to total storage limits) - Rate limits: 100 operations/second on free tier, 1000 operations/second on pro tier ### Databases - Maximum database size: Limited by your storage quota - Maximum databases: unlimited - SQLite Writes: 1M rows/month on free tier, 10M rows/month on pro tier - SQLite Reads: 100M rows/month on free tier, 1B rows/month on pro tier ### Full-text & vector search - Maximum documents per search namespace: 100M - Maximum writes: 10k writes/second per namespace - Vector Writes: 1GB written/month on free tier, 10GB written/month on pro tier - Vector Reads: 10GB queried/month on free tier, 100GB queried/month on pro tier - Maximum vector dimensions: 10,752 - Maximum attributes per document: 256 ## Enterprise Plans Enterprise plans include: - **Higher resource limits** with customizable quotas - **Volume discounts** on all resources - **Advanced security features** including SSO, RBAC, and audit logs - **Priority support** with dedicated account management - **SLA guarantees** for uptime and performance - **Custom integrations** with your existing infrastructure - **Training and onboarding** for your team - **SOC2** certification and custom data processing agreements. To learn more about Enterprise plans, [contact our sales team](mailto:contact@gensx.com). ## Try GenSX Cloud Start with our free tier today - no credit card required: [Get Started for Free](https://signin.gensx.com/sign-up) For any questions about pricing or custom plans, please [contact our sales team](mailto:contact@gensx.com). # Observability & tracing GenSX provides observability tools that make it easy to understand, debug, and optimize your workflows. Every component execution is automatically traced, giving you full visibility into what's happening inside your LLM workflows. You can view traces in realtime as workflows execute, and view historical traces to debug production issues like hallucinations. ## Viewing traces When you run a workflow, GenSX automatically generates a trace that captures the entire execution flow, including all component inputs, outputs, and timing information. ### Accessing the trace viewer The GenSX cloud console includes a trace viewer. You can access traces in several ways: 1. **From the Console**: Navigate to your project in the [GenSX Console](https://app.gensx.com) and select the "Executions" tab 2. **Trace URL**: When running a workflow with `printUrl: true`, a direct link to the trace is printed to the console 3. **API Response**: When running a workflow in the cloud, the execution ID from API responses can be used to view traces in the console ```tsx // Executing a workflow with trace URL printing const result = await MyWorkflow.run( { input: "What is GenSX?" }, { printUrl: true }, ); // Console output includes: // [GenSX] View execution at: https://app.gensx.com/your_org/executions/your_execution_id ``` ### Understanding the flame graph The flame graph visualizes the entire execution tree including branches, all nested sub-components, and timing: ![Workflow component tree](/cloud/trace.png) - **Component hierarchy**: See the nested structure of your components and their relationships - **Execution timing**: The width of each bar represents the relative execution time - **Status indicators**: Quickly spot errors or warnings with color coding - **Component filtering**: Focus on specific components or component types Click on any component in the flame graph to inspect its details, including inputs, outputs, and timing information. ### Viewing component inputs and outputs For each component in your workflow, you can inspect: 1. **Input properties**: All props passed to the component 2. **Output values**: The data returned by the component 3. **Execution duration**: How long the component took to execute 4. **Metadata**: Additional information like token counts for LLM calls ![Component trace](/cloud/component-trace.png) This visualization is particularly valuable for debugging production and user-reported issues like hallucinations. ### Viewing historical traces The GenSX Console maintains a history of all your workflow executions, allowing you to: - **Compare executions**: See how behavior changes across different runs - **Identify patterns**: Spot recurring issues or performance bottlenecks - **Filter by status**: Focus on successful, failed, or in-progress executions - **Search**: Find historical executions Historical traces are automatically organized by project and environment, making it easy to find relevant executions. ## Configuring traces GenSX provides flexible options for configuring and organizing traces for the GenSX Cloud serverless platform, local development, and any other deployment platform like vercel, cloudflare and AWS. ### Tracing GenSX Cloud workflows When running workflows deployed to GenSX Cloud, tracing is automatically configured: - **Project context**: Traces are associated with the correct project - **Environment segregation**: Development, staging, and production traces are kept separate - **Authentication**: API keys and organization information are handled automatically - **Retention**: Traces are stored according to your plan limits No additional configuration is needed – everything works out of the box. ### Tracing on other deployment platforms To enable tracing for workflows deployed outside of GenSX Cloud (like AWS Lambda, GCP Cloud Run, etc.), you need to set several environment variables: ```bash # Required variables GENSX_API_KEY=your_api_key_here GENSX_ORG=your_gensx_org_name GENSX_PROJECT=your_project_name # Optional variables GENSX_ENVIRONMENT=your_environment_name # Separate traces into specific environments GENSX_CHECKPOINTS=false # Explicitly disable ``` ### Configuring traces for local development For local development, the tracing configuration is automatically inferred from: 1. The `gensx.yaml` file in your project root 2. Your local configuration managed by the `gensx` CLI in `~/.config/gensx/config` 3. Optionally the `GENSX_ENVIRONMENT` environment variable can be set to separate local traces from other environments The local development server started with `gensx start` uses this same configuration scheme as well. ### Organizing traces by environment GenSX allows you to organize traces by environment (such as development, staging, production, etc.) to keep your debugging data well-structured: ```bash # Deploy to a specific environment with its own traces gensx deploy src/workflows.tsx --env production ``` In the GenSX Console, you can filter traces by environment to focus on relevant executions. This separation also helps when: - Debugging issues specific to an environment - Comparing behavior between environments - Isolating production traces from development noise ## Instrumenting additional code Every GenSX component is automatically traced. If want to trace additional sub-steps of a workflow, wrap that code in a `gensx.Component` and execute it via `myComponent.Run(props)`. ```tsx import * as gensx from "@gensx/core"; const MyWorkflow = gensx.Component("MyWorkflow", async ({ input }) => { // Step 1: Process input const processedData = await ProcessData.run({ data: input }); // Step 2: Generate response const response = await GenerateResponse.run({ data: processedData }); return response; }); // Create a component to trace a specific processing step const ProcessData = gensx.Component("ProcessData", async ({ data }) => { // This entire function execution will be captured in traces const parsedData = JSON.parse(data); const enrichedData = await fetchAdditionalInfo(parsedData); return enrichedData; }); // Create a component to trace response generation const GenerateResponse = gensx.Component( "GenerateResponse", async ({ data }) => { // This will appear as a separate node in the trace return `Processed result: ${JSON.stringify(data)}`; }, ); ``` ## Secrets scrubbing GenSX enables you to configure which input props and outputs are marked as secrets and redacted from traces. Scrubbing happens locally before traces are sent to GenSX Cloud. ### How secrets scrubbing works When a component executes, GenSX automatically: 1. Identifies secrets in component props and outputs 2. Replaces these secrets with `[secret]` in the trace data 3. Propagates secret detection across the entire component hierarchy Even if a secret is passed down through multiple components, it remains scrubbed in all traces. ### Marking secrets in component props To mark specific props as containing secrets: ```tsx import * as gensx from "@gensx/core"; const AuthenticatedClient = gensx.Component( "AuthenticatedClient", ({ apiKey, endpoint, query, credentials }) => { // Use apiKey securely, knowing it won't appear in traces return fetchData(endpoint, query, apiKey, credentials); }, { // Mark these props as containing sensitive data secretProps: ["apiKey", "credentials.privateKey"], }, ); ``` The `secretProps` option can specify both top-level props and nested paths using dot notation. ### Marking component outputs as secrets For components that might return sensitive information, you can mark the entire output as sensitive: ```tsx const GenerateCredentials = gensx.Component( "GenerateCredentials", async ({ userId }) => { // This entire output will be marked as secret return { accessToken: "sk-1234567890abcdef", refreshToken: "rt-0987654321fedcba", expiresAt: Date.now() + 3600000, }; }, { secretOutputs: true, }, ); ``` When `secretOutputs` is set to `true`, the entire output object or value will be treated as sensitive and masked in traces. ## Limits GenSX observability features have certain limits based on your subscription tier: | Feature | Free Tier | Pro Tier ($20/month/dev) | Enterprise | | ------------------------- | -------------- | ------------------------ | ---------- | | Traced components | 100K per month | 1M per month | Custom | | Overage cost | N/A | $0.20 per 10K components | Custom | | Trace retention | 7 days | 30 days | Custom | | Maximum input/output size | 4MB each | 4MB each | 4MB each | A few important notes on these limits: - **Component count**: Each component execution in your workflow counts as one traced component - **Size limits**: Component inputs and outputs are limited to 4MB each; larger data is truncated - **Secret scrubbing**: API keys and sensitive data are automatically redacted from traces - **Retention**: After the retention period, traces are automatically deleted For use cases requiring higher limits or longer retention, contact the GenSX team for enterprise options. ## Next steps - [Set up serverless deployments](/docs/cloud/serverless-deployments) to automatically trace cloud workflows - [Learn about local development](/docs/cloud/local-development) for testing with traces - [Explore project and environment organization](/docs/cloud/projects-environments) to structure your traces # GenSX Cloud MCP server `@gensx/gensx-cloud-mcp` is a Model Context Protocol server for [GenSX Cloud](/docs/cloud) workflows. It enables you to connect your GenSX Cloud workflows to MCP-compatible tools like Claude desktop, Cursor, and more. ![Running a GenSX workflow from Claude desktop](/cloud/claude-desktop.png) ## Usage Once you have run [`gensx deploy`](/docs/cli-reference/deploy) to deploy your project to the [GenSX Cloud serverless runtime](/docs/cloud/serverless-deployments), you can consume those workflows via the `@gensx/gensx-cloud-mcp` server. MCP-compatible tools use a standard JSON file to configure available MCP servers. Update your MCP config file for your tool of choice to include the following: ```json { "mcpServers": { "gensx": { "command": "npx", "args": [ "-y", "@gensx/gensx-cloud-mcp", "you_org_name", "your_project_name", "your_environment_name" ] } } } ``` Your MCP client will run this command automatically at startup and handle acquiring the GenSX Cloud MCP server on your behalf. See the [Claude desktop](https://modelcontextprotocol.io/quickstart/user), and [Cursor docs](https://docs.cursor.com/context/model-context-protocol) on configuring MCP servers for more details. By default, the server reads your API credentials from the config saved by running the `gensx login` command. Alternatively, you can specify your GenSX API key as an environment variable in your MCP config: ```json { "mcpServers": { "gensx": { "command": "npx", "args": [ "@gensx/gensx-cloud-mcp", "you_org_name", "your_project_name", "your_environment_name" ], "env": { "GENSX_API_KEY": "my_api_key" } } } } ``` The GenSX build process automatically extracts input and output schemas from your typescript types, so no additional configuration or manual `zod` schema is required to consume your workflows from an MCP server. # Local development server GenSX provides local development experience that mirrors the cloud environment, making it easy to build and test workflows on your machine before deploying them. ## Starting the dev server The `gensx start` command launches a local development server with hot-reloading: ```bash gensx start ./src/workflows.tsx ``` ```bash πŸ” Starting GenSX Dev Server... β„Ή Starting development server... βœ” Compilation completed βœ” Generating schema Importing compiled JavaScript file: /Users/evan/code/gensx-console/samples/support-tools/dist/src/workflows.js πŸš€ GenSX Dev Server running at http://localhost:1337 πŸ§ͺ Swagger UI available at http://localhost:1337/swagger-ui πŸ“‹ Available workflows: - RAGWorkflow: http://localhost:1337/workflows/RAGWorkflow - AnalyzeDiscordWorkflow: http://localhost:1337/workflows/AnalyzeDiscordWorkflow - TextToSQLWorkflow: http://localhost:1337/workflows/TextToSQLWorkflow - ChatAgent: http://localhost:1337/workflows/ChatAgent βœ… Server is running. Press Ctrl+C to stop. ``` ## Development server features ### Identical API shape The local API endpoints match exactly what you'll get in production, making it easy to test your workflows before deploying them. The only difference is that the `/org/{org}/project/{project}/environments/{env}` path is left out of the url for simplicity. ``` http://localhost:1337/workflows/{workflow} ``` Every workflow you export is automatically available as an API endpoint. ### Hot reloading The development server watches your TypeScript files and automatically: 1. Recompiles when files change 2. Regenerates API schemas 3. Restarts the server with your updated code This enables a fast development cycle without manual restarts. ### API documentation The development server includes a built-in Swagger UI for exploring and testing your workflows: ``` http://localhost:1337/swagger-ui ``` ![Swagger UI for local development](/cloud/local-swagger-ui.png) The Swagger interface provides: - Complete documentation of all your workflow APIs - Interactive testing - Request/response examples - Schema information ## Running workflows locally ### Using the API You can use any HTTP client to interact with your local API: ```bash # Run a workflow synchronously curl -X POST http://localhost:1337/workflows/ChatAgent \ -H "Content-Type: application/json" \ -d '{"input": {"prompt": "Tell me about GenSX"}}' # Run asynchronously curl -X POST http://localhost:1337/workflows/ChatAgent/start \ -H "Content-Type: application/json" \ -d '{"input": {"prompt": "Tell me about GenSX"}}' ``` The inputs and outputs of the APIs match exactly what you'll encounter in production. ### Using the Swagger UI The built-in Swagger UI provides an easy way to inspect and test your workflows: 1. Navigate to `http://localhost:1337/swagger-ui` 2. Select the workflow you want to test 3. Click the "Try it out" button 4. Enter your input data 5. Execute the request and view the response ![Run a workflow from the Swagger UI](/cloud/swagger-execute.png) ## Local storage options GenSX provides local implementations for cloud storage services, enabling you to develop and test stateful workflows without deploying to the cloud. ### Blob storage When using `BlobProvider` in local development, data is stored in your local filesystem: ```tsx import { BlobProvider, useBlob } from "@gensx/storage"; const StoreData = gensx.Component("StoreData", async ({ key, data }) => { // Locally, this will write to .gensx/blobs directory const blob = useBlob(`data/${key}.json`); await blob.putJSON(data); return { success: true }; }); ``` Files are stored in the `.gensx/blobs` directory in your project, making it easy to inspect the stored data. ### SQL databases When using `DatabaseProvider` locally, GenSX uses [libSQL](https://github.com/libsql/libsql) to provide a SQLite-compatible database: ```tsx import { DatabaseProvider, useDatabase } from "@gensx/storage"; const QueryData = gensx.Component("QueryData", async ({ query }) => { // Locally, this creates a SQLite database in .gensx/databases const db = await useDatabase("my-database"); const result = await db.execute(query); return result.rows; }); ``` Database files are stored in the `.gensx/databases` directory as SQLite files that you can inspect with any SQLite client. ### Vector search For vector search operations with `SearchProvider`, your local environment connects to the cloud service: ```tsx import { SearchProvider, useSearch } from "@gensx/storage"; const SearchDocs = gensx.Component("SearchDocs", async ({ query }) => { // Uses cloud vector search even in local development const namespace = await useSearch("documents"); const results = await namespace.query({ text: query, topK: 5, }); return results; }); ``` ## Next steps - [Deploying to production](/docs/cloud/serverless-deployments) - [Working with cloud storage](/docs/cloud/storage) - [Setting up observability and tracing](/docs/cloud/observability) # GenSX Cloud GenSX Cloud provides everything you need to ship production-grade agents and workflows: - **Serverless runtime**: One command to deploy all of your workflows and agents as REST APIs running on serverless infrastructure optimized for long-running agents and workflows. Support for synchronous and background invocation, streaming, and intermediate status included. - **Cloud storage**: build stateful agents and workflows with builtin blob storage, SQL databases, and full-text + vector search indices -- all provisioned at runtime. - **Tracing and observability**: Real-time tracing of all component inputs and outputs, tool calls, and LLM calls within your agents and workflows. Tools to visualize and debug all historic executions. - **Collaboration**: Organize agents, workflows, and traces into projects and environments. Search and view traces and to debug historical executions. Unlike traditional serverless offerings, GenSX Cloud is optimized for long-running workflows. Free tier workflows can run up to 5 minutes and Pro tier workflows can run for up to 60 minutes. All of this is available on a free tier for individuals and with $20/developer pricing for teams. ## Serverless deployments Serverless deployments allow you to turn your GenSX workflows and agents into APIs with a single command: - **Generated REST APIs**: `gensx deploy` generates a REST API complete with schema and validation for every workflow in your project. - **Long-running**: GenSX Cloud is optimized for long running LLM workloads. Workflows can run up to 5 minutes on the free tier and 60 minutes on the Pro tier. - **Millisecond-level cold starts**: initial cold starts are on the order of 10s of milliseconds -- an order of magnitude faster than other serverless providers. Serverless deployments are billed per-second, with 50,000 seconds included per month in the free tier for individuals. Projects are deployed with a single CLI command: ```bash $ npx gensx deploy ./src/workflows.tsx ``` ```bash βœ” Building workflow using Docker βœ” Generating schema βœ” Successfully built project β„Ή Using project name from gensx.yaml: support-tools βœ” Deploying project to GenSX Cloud (Project: support-tools) βœ” Successfully deployed project to GenSX Cloud Dashboard: https://app.gensx.com/gensx/support-tools/default/workflows Available workflows: - ChatAgent - TextToSQLWorkflow - RAGWorkflow - AnalyzeDiscordWorkflow Project: support-tools ``` Each workflow is available via both a synchronous and asynchronous API: ``` // For synchronous and streaming calls: https://api.gensx.com/org/{orgName}/projects/{projectName}/environments/{environmentName}/workflows/{workflowName} // For running workflows async in the background https://api.gensx.com/org/{orgName}/projects/{projectName}/environments/{environmentName}/workflows/{workflowName}/start ``` For more details see the full [serverless deployments reference](/docs/cloud/serverless-deployments). ## Cloud storage GenSX Cloud includes runtime-provisioned storage to build stateful agents and workflows: - **Blob storage**: Store and retrieve JSON and binary data for things like conversation history, agent memory, and audio and image generation. - **SQL databases**: Runtime provisioned databases for scenarios like text-to-SQL. - **Full-text + vector search**: Store and query vector embeddings for semantic search and retrieval augmented generation (RAG). State can be long-lived and shared across workflows and agents, or it can be provisioned ephemerally on a per-request basis. ### Blob storage GenSX Cloud provides blob storage for persisting unstructured data like JSON, text, and binary files. With the `BlobProvider` component and `useBlob` hook, you can easily store and retrieve data across workflow executions. Common scenarios enabled by blob storage include: - Persistent chat thread history. - Simple memory implementations. - Storing generated audio, video, and photo files. ```tsx import { BlobProvider, useBlob } from "@gensx/storage"; // Store and retrieve data with the useBlob hook const ChatWithMemory = gensx.Component( "ChatWithMemory", async ({ userInput, threadId }) => { // Get access to a blob at a specific path const blob = useBlob(`chats/${threadId}.json`); // Load existing data (returns null if it doesn't exist) const history = (await blob.getJSON()) ?? []; // Add new data history.push({ role: "user", content: userInput }); // Save updated data await blob.putJSON(history); return "Data stored successfully"; }, ); // Just wrap your workflow with BlobProvider const Workflow = gensx.Component("MyWorkflow", ({ userInput, threadId }) => ( )); ``` Blob storage automatically adapts between local development (using filesystem) and cloud deployment with zero configuration changes. For more details see the full [storage components reference](docs/component-reference/storage-components/blob-reference). ### SQL databases GenSX Cloud provides SQLite-compatible databases powered by [Turso](https://turso.tech), enabling structured data storage with several properties important to agentic workloads: - **Millisecond provisioning**: Databases are created on-demand in milliseconds, making them perfect for ephemeral workloads like parsing and querying user-uploaded CSVs or creating per-agent structured data stores. - **Strong consistency**: All operations are linearizable, maintaining an ordered history, with writes fully serialized and subsequent writes awaiting transaction completion. - **Zero configuration**: Like all GenSX storage components, databases work identically in both development and production. - **Local development**: Uses libsql locally to enable a fast, isolated development loop without external dependencies. ```tsx import { DatabaseProvider, useDatabase } from "@gensx/storage"; // Access a database with the useDatabase hook const QueryTeamStats = gensx.Component("QueryTeamStats", async ({ team }) => { // Get access to a database (created on first use) const db = await useDatabase("baseball"); // Execute SQL queries directly const result = await db.execute("SELECT * FROM players WHERE team = ?", [ team, ]); return result.rows; // Returns the query results }); // Just wrap your workflow with DatabaseProvider const Workflow = ({ team }) => ( ); ``` For more details see the full [storage components reference](docs/component-reference/storage-components/database-reference). ### Full-text and vector search GenSX Cloud provides vector and full-text search capabilities powered by [turbopuffer](https://turbopuffer.com/), enabling semantic search and retrieval augmented generation (RAG) with minimal setup: - **Vector search**: Store and query high-dimensional vectors for semantic similarity search with millisecond-level latency, perfect for RAG applications and finding content based on meaning rather than exact matches. - **Full-text search**: Built-in BM25 search engine for string and string array fields, enabling traditional keyword search with low latency. - **Hybrid search**: Combine vector similarity with full-text BM25 search to get both semantically relevant results and exact keyword matches in a single query. - **Rich filtering**: Apply metadata filters to narrow down search results based on categories, timestamps, or any custom attributes, enhancing precision and relevance. ```tsx import { SearchProvider, useNamespace } from "@gensx/storage"; import { OpenAIEmbedding } from "@gensx/openai"; // Perform semantic search with the useNamespace hook const SearchDocuments = gensx.Component( "SearchDocuments", async ({ query }) => { // Get access to a vector search namespace const namespace = await useNamespace("documents"); // Generate an embedding for the query const embedding = await OpenAIEmbedding.run({ model: "text-embedding-3-small", input: query, }); // Search for similar documents const results = await namespace.query({ vector: embedding.data[0].embedding, topK: 5, }); return results.map((r) => r.attributes?.title); }, ); // Just wrap your workflow with SearchProvider const Workflow = ({ query }) => ( ); ``` > **Note**: Unlike blob storage and SQL databases, vector search doesn't have a local development implementation. When using `SearchProvider` locally, you'll connect to the cloud service. For more details see the full [storage components reference](docs/component-reference/storage-components/search-reference). ## Observability GenSX Cloud provides comprehensive tracing and observability for all your workflows and agents. ![Workflow component tree](/cloud/trace.png) - **Complete execution traces**: Every workflow execution generates a detailed trace that captures the entire flow from start to finish, allowing you to understand exactly what happened during execution. - **Comprehensive component visibility**: Each component in your workflow automatically records its inputs and outputs, including: - All LLM calls with full prompts, parameters, and responses - Every tool invocation with input arguments and return values - All intermediate steps and state changes in your agents and workflows - **Real-time monitoring**: Watch your workflows execute step by step in real time, which is especially valuable for debugging long-running agents or complex multi-step workflows. - **Historical execution data**: Access and search through all past executions to diagnose issues, analyze performance patterns, and understand user interactions over time. - **Project and environment organization**: Traces are automatically organized by project (a collection of related workflows in a codebase) and environment (such as development, staging, or production), making it easy to find relevant executions. ```tsx // Traces are automatically captured when workflows are executed // No additional instrumentation required const result = await MyWorkflow.run( { input: "User query" }, { printUrl: true }, // log the tracing URL ); ``` The trace viewer provides multiple ways to analyze workflow execution: - **Timeline view**: See how long each component took and their sequence of execution - **Component tree**: Navigate the hierarchical structure of your workflow - **Input/output inspector**: Examine the exact data flowing between components - **Error highlighting**: Quickly identify where failures occurred ![Component trace](/cloud/component-trace.png) For more details see the full [observability reference](/docs/cloud/observability). ## Local development GenSX provides a seamless development experience that mirrors the cloud environment, allowing you to build and test your workflows locally before deployment: ### Development server The `gensx start` command launches a local development server that: - Compiles your TypeScript workflows on the fly - Automatically generates schemas for your workflows - Creates local REST API endpoints identical to the cloud endpoints - Hot-reloads your code when files change - Provides the same API shape locally as in production ```bash # Start the development server with a TypeScript file npx gensx start ./src/workflows.tsx ``` When you start the development server, you'll see something like this: ```bash πŸ” Starting GenSX Dev Server... β„Ή Starting development server... βœ” Compilation completed βœ” Generating schema πŸš€ GenSX Dev Server running at http://localhost:1337 πŸ§ͺ Swagger UI available at http://localhost:1337/swagger-ui πŸ“‹ Available workflows: - MyWorkflow: http://localhost:1337/workflows/MyWorkflow βœ… Server is running. Press Ctrl+C to stop. ``` ### Local storage providers GenSX provides local implementations for most storage providers, enabling development without cloud dependencies: - **BlobProvider**: Uses local filesystem storage (`.gensx/blobs`) for development - **DatabaseProvider**: Uses local SQLite databases (`.gensx/databases`) for development - **SearchProvider**: Connects to the cloud vector search service even in development mode The local APIs mirror the cloud APIs exactly, so code that works locally will work identically when deployed: ```tsx // This component works the same locally and in the cloud const SaveData = gensx.Component<{ key: string; data: any }, null>( "SaveData", async ({ key, data }) => { // Blob storage works the same locally (filesystem) and in cloud const blob = useBlob(`data/${key}.json`); await blob.putJSON(data); return null; }, ); ``` For more details see the full [local development reference](/docs/cloud/local-development). ## Projects & environments GenSX Cloud organizes your workflows and deployments using a flexible structure of projects and environments: **Projects** are a collection of related workflows that are deployed together, typically corresponding to a codebase or application. Projects help you organize and manage your AI components as cohesive units. Projects are defined by the `projectName` field in your `gensx.yaml` configuration file at the root of your codebase: ```yaml # gensx.yaml projectName: my-chatbot-app ``` **Environments** are sub-groupings within a project that allow you to deploy multiple instances of the same workflows with different configuration. This supports the common development pattern of separating dev, staging, and production environments. ```bash # Deploy to the default environment npx gensx deploy ./src/workflows.tsx # Deploy to a specific environment npx gensx deploy ./src/workflows.tsx --env production ``` Each environment can have its own configuration and environment variables to match the rest of your deployed infrastructure. Traces and observability data are also separated by project and environment, making it easier to: - Distinguish between development testing and production traffic - Isolate and debug issues specific to a particular environment - Compare performance or behavior between environments This organizational structure is designed to be flexible and adaptable, allowing you to customize it to fit with the rest of your development, testing, and deployment lifecycle. For more details see the full [projects and environments reference](/docs/cloud/projects-environments). ## Pricing GenSX Cloud offers simple, predictable pricing designed to scale with your needs, including a free tier for individuals: | Resource | Free Tier | Pro Tier ($20/month/dev) | Overage/Action | | ------------------ | ---------------------- | ------------------------ | ---------------- | | Serverless Compute | 50K sec | 500K sec | $0.00003/sec | | Traces (events) | 100K events | 1M events | $0.20/10K | | Blob Storage | 500MB | 5GB | $0.25/GB | | SQLite Storage | 500MB | 1GB | $1.50/GB | | Vector Storage | 250MB | 1GB | $1.00/GB | | Execution time | Up to 5 minutes | Up to 60 minutes | Custom | | Observability | 7 days trace retention | 30 days trace retention | Custom retention | For more details, visit our [pricing page](/docs/cloud/pricing) or [contact us](mailto:contact@gensx.com) for enterprise needs. ## Get started Ready to build AI agents and workflows with GenSX Cloud? Follow our step-by-step [quickstart guide](/docs/quickstart) to create and deploy your first project in minutes: 1. Install the GenSX CLI: `npm install -g gensx` 2. Create a new project: `gensx new my-project` 3. Run it locally: `gensx start src/workflows.tsx` 4. Deploy to the cloud: `gensx deploy src/workflows.tsx` # gensx start The `gensx start` command starts a local development server that enables you to test and debug your GenSX workflows. ## Usage ```bash gensx start [options] ``` ## Arguments | Argument | Description | | -------- | ------------------------------------------------------- | | `` | The workflow file to serve (e.g., `src/workflows.tsx`). | ## Options | Option | Description | | ---------------------- | ---------------------------------- | | `--port ` | Port to run the server on. | | `-q, --quiet` | Suppress output. | | `-h, --help` | Display help for the command. | ## Description This command starts a local development server that: - Watches your workflow file for changes and automatically reloads - Provides a web interface to test and debug your workflows - Simulates the cloud environment locally - Runs your workflow components in a development mode The development server includes: - A web UI for testing your workflows - Real-time logs and execution visibility - Access to the GenSX development dashboard ## Examples ```bash # Start server with a specific workflow file gensx start src/workflows.tsx <<<<<<< HEAD # Start server with minimal output gensx start src/workflows.tsx --quiet ======= # Start server with a custom project name gensx start src/workflows.tsx --project my-custom-name # Start server on port 3000 gensx start src/workflows.tsx --port 3000 >>>>>>> 4c75b171c6cd6b0bd9779d07cc1ca5b9e1679e0b ``` ## Notes - The server runs on port 1337 by default - You can access the development UI at `http://localhost:1337/swagger-ui` - Environment variables from your local environment are available to the workflow - For more complex environment variable setups, consider using a `.env` file in your project root # gensx run The `gensx run` command executes a workflow that has been deployed to GenSX Cloud. By default it infers your project from the `gensx.yaml` file in the current working directory. ## Usage ```bash gensx run [options] ``` ## Arguments | Argument | Description | | ------------ | ---------------------------- | | `` | Name of the workflow to run. | ## Options | Option | Description | | ---------------------- | ------------------------------------------------------------ | | `-i, --input ` | Input to pass to the workflow (as JSON). | | `--no-wait` | Do not wait for the workflow to finish (run asynchronously). | | `-p, --project ` | Project name where the workflow is deployed. | | `-e, --env ` | Environment name where the workflow is deployed. | | `-o, --output ` | Output file to write the workflow result to. | | `-h, --help` | Display help for the command. | ## Description This command triggers execution of a deployed workflow on GenSX Cloud with the specified input. By default, it waits for the workflow to complete and displays the result. When running a workflow, you can: - Provide input data as JSON - Choose whether to wait for completion or run asynchronously - Save the output to a file ## Examples ```bash # Run a workflow with no input gensx run MyWorkflow # Run a workflow with JSON input gensx run MyWorkflow --input '{"text": "Hello, world!"}' # Run a workflow in a specific project and environment gensx run MyWorkflow --project my-project --env prod # Run a workflow asynchronously (don't wait for completion) gensx run MyWorkflow --no-wait # Run a workflow and save output to a file gensx run MyWorkflow --output result.json ``` ## Notes - You must be logged in to GenSX Cloud to run workflows (`gensx login`) - The workflow must have been previously deployed using `gensx deploy` - When using `--input`, the input must be valid JSON - When using `--no-wait`, the command returns immediately with a workflow ID that can be used to check status later - Error handling: if the workflow fails, the command will return with a non-zero exit code and display the error # gensx new The `gensx new` command creates a new GenSX project with a predefined structure and dependencies. ## Usage ```bash gensx new [options] ``` ## Arguments | Argument | Description | | --------------------- | ---------------------------------------------------------------------------- | | `` | Directory to create the project in. If it doesn't exist, it will be created. | ## Options | Option | Description | | -------------------------- | ----------------------------------------------------------------------------------------------- | | `-t, --template ` | Template to use. Currently supports `ts` (TypeScript). | | `-f, --force` | Overwrite existing files in the target directory. | | `--skip-ide-rules` | Skip IDE rules selection. | | `--ide-rules ` | Comma-separated list of IDE rules to install. Options: `cline`, `windsurf`, `claude`, `cursor`. | | `-d, --description ` | Optional project description. | | `-h, --help` | Display help for the command. | ## Description This command scaffolds a new GenSX project with the necessary files and folder structure. It sets up: - Project configuration files (`package.json`, `tsconfig.json`) - Basic project structure with example workflows - Development dependencies - IDE integrations based on selected rules ## Examples ```bash # Create a basic project gensx new my-gensx-app # Create a project with a specific template and description gensx new my-gensx-app --template ts --description "My AI workflow app" # Create a project with specific IDE rules gensx new my-gensx-app --ide-rules cursor,claude # Force create even if directory has existing files gensx new my-gensx-app --force ``` ## Notes - If no template is specified, `ts` (TypeScript) is used by default. - The command will install all required dependencies, so make sure you have npm installed. - After creation, you can navigate to the project directory and start the development server with `gensx start`. # gensx login The `gensx login` command authenticates you with GenSX Cloud, allowing you to deploy and run workflows remotely. ## Usage ```bash gensx login ``` ## Description When you run this command, it will: 1. Open your default web browser to the GenSX authentication page 2. Prompt you to log in with your GenSX account or create a new one 3. Store your authentication credentials locally for future CLI commands After successful login, you can use other commands that require authentication, such as `deploy` and `run`. ## Examples ```bash # Log in to GenSX Cloud gensx login ``` ## Notes - Your authentication token is stored in your user directory (typically `~/.gensx/config.json`) - The token is valid until you log out or revoke it from the GenSX dashboard - If you're behind a corporate firewall or using strict network policies, ensure that outbound connections to `api.gensx.com` are allowed # GenSX CLI reference The GenSX command-line interface (CLI) provides a set of commands to help you build, deploy, and manage your GenSX applications. ## Installation The GenSX CLI is included when you install the main GenSX package: ```bash npm install -g gensx ``` ## Available commands ### Auth | Command | Description | | -------------------------- | -------------------------------- | | [`gensx login`](./login) | Log in to GenSX Cloud | ### Development | Command | Description | | -------------------------- | -------------------------------- | | [`gensx new`](./new) | Create a new GenSX project | | [`gensx start`](./start) | Start a local development server | | [`gensx build`](./build) | Build a workflow for deployment | ### Deployment & Execution | Command | Description | | -------------------------- | -------------------------------- | | [`gensx deploy`](./deploy) | Deploy a workflow to GenSX Cloud | | [`gensx run`](./run) | Run a workflow on GenSX Cloud | ### Environment Management | Command | Description | | -------------------------------- | -------------------------------------------- | | [`gensx env`](./env/show) | Show the current environment details | | [`gensx env create`](./env/create) | Create a new environment | | [`gensx env ls`](./env/ls) | List all environments for a project | | [`gensx env select`](./env/select) | Select an environment as active | | [`gensx env unselect`](./env/unselect) | Unselect the current environment | ## Common Workflows ### Starting a New Project ```bash # Log in to GenSX Cloud gensx login # Create a new project gensx new my-project cd my-project # Start local development gensx start src/workflows.tsx ``` ### Managing Environments ```bash # Create and switch to a development environment gensx env create dev gensx env select dev # View current environment gensx env ``` ### Deploying and Running Workflows ```bash # Build and deploy your workflow gensx deploy src/workflows.tsx # Run a workflow gensx run my-workflow --input '{"message": "Hello, world!"}' ``` For detailed information about each command, please refer to the corresponding documentation pages. # gensx deploy The `gensx deploy` command uploads and deploys a workflow to GenSX Cloud, making it available for remote execution. ## Usage ```bash gensx deploy [options] ``` ## Arguments | Argument | Description | | -------- | ------------------------------------------------------------------------------------------ | | `` | File to deploy. This should be a TypeScript file that exports a GenSX workflow. | ## Options | Option | Description | | ----------------------- | ---------------------------------------------------------------------------- | | `-ev, --env-var ` | Environment variable to include with deployment. Can be used multiple times. | | `-p, --project ` | Project name to deploy to. | | `-e, --env ` | Environment name to deploy to. | | `-h, --help` | Display help for the command. | ## Description This command: 1. Builds your workflow 2. Uploads it to GenSX Cloud 3. Creates or updates the deployment 4. Sets up any environment variables specified After successful deployment, your workflow will be available for remote execution via the [GenSX Cloud console](https://app.gensx.com) or through the `gensx run` command. ## Examples ```bash # Deploy a workflow gensx deploy src/workflows.tsx # Deploy to a specific project and environment gensx deploy src/workflows.tsx --project my-production-project --env dev # Deploy with environment variables gensx deploy src/workflows.tsx -ev API_KEY=abc123 -ev DEBUG=true # Deploy with an environment variable taken from your local environment gensx deploy src/workflows.tsx -ev OPENAI_API_KEY ``` ## Notes - You must be logged in to GenSX Cloud to deploy (`gensx login`) - `gensx deploy` requires Docker to be running - If your workflow requires API keys or other secrets, provide them using the `-ev` or `--env-var` option - For environment variables without a specified value, the CLI will use the value from your local environment - After deployment, you can manage your workflows from the GenSX Cloud console - The deployment process automatically handles bundling dependencies # gensx build The `gensx build` command compiles and bundles a GenSX workflow for deployment to GenSX Cloud. ## Usage ```bash gensx build [options] ``` ## Arguments | Argument | Description | | -------- | -------------------------------------------------------------------------------------------------------------- | | `` | Workflow file to build (e.g., `src/workflows.tsx`). This should export an object with one or more GenSX workflows | ## Options | Option | Description | | ----------------------- | ----------------------------------------------- | | `-o, --out-dir ` | Output directory for the built files. | | `-t, --tsconfig ` | Path to a custom TypeScript configuration file. | | `-h, --help` | Display help for the command. | ## Description This command builds your GenSX workflow into an optimized bundle that can be deployed to GenSX Cloud. It: - Transpiles TypeScript to JavaScript - Bundles all dependencies - Optimizes the code for production - Prepares the workflow for deployment After building, the command outputs the path to the bundled file, which can be used with the [`gensx deploy`](/docs/cli-reference/deploy) command. ## Examples ```bash # Build a workflow with default options gensx build src/workflows.tsx # Build a workflow with a custom output directory gensx build src/workflows.tsx --out-dir ./dist # Build a workflow with a custom TypeScript configuration gensx build src/workflows.tsx --tsconfig ./custom-tsconfig.json ``` ## Notes - The build process requires that your workflow file exports an object with one or more GenSX workflows. - `gensx build` requires Docker to be running - If no output directory is specified, the build files will be placed in a `.gensx` directory - The build process does not include environment variables - these should be provided during deployment # Search reference API reference for GenSX Cloud search components. Search is powered by turbopuffer, and their documentation for [query](https://turbopuffer.com/docs/query) and [upsert operations](https://turbopuffer.com/docs/write) is a useful reference to augment this document. ## Installation ```bash npm install @gensx/storage ``` ## SearchProvider Provides vector search capabilities to its child components. ### Import ```tsx import { SearchProvider } from "@gensx/storage"; ``` ### Example ```tsx import { SearchProvider } from "@gensx/storage"; const Workflow = gensx.Component("SearchWorkflow", ({ input }) => ( )); ``` ## useSearch Hook that provides access to vector search for a specific namespace. ### Import ```tsx import { useSearch } from "@gensx/storage"; ``` ### Signature ```tsx function useSearch(name: string): Namespace; ``` ### Parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------- | | `name` | `string` | The namespace name to access | ### Returns Returns a namespace object with methods to interact with vector search. ### Example ```tsx const namespace = await useSearch("documents"); const results = await namespace.query({ vector: queryEmbedding, includeAttributes: true, }); ``` ## Namespace methods The namespace object returned by `useSearch` provides these methods: ### write Inserts, updates, or deletes vectors in the namespace. ```tsx async write(options: WriteParams): Promise ``` #### Parameters | Parameter | Type | Default | Description | | ----------------- | ---------------- | ----------- | ------------------------------------------- | | `upsertColumns` | `UpsertColumns` | `undefined` | Column-based format for upserting documents | | `upsertRows` | `UpsertRows` | `undefined` | Row-based format for upserting documents | | `patchColumns` | `PatchColumns` | `undefined` | Column-based format for patching documents | | `patchRows` | `PatchRows` | `undefined` | Row-based format for patching documents | | `deletes` | `Id[]` | `undefined` | Array of document IDs to delete | | `deleteByFilter` | `Filters` | `undefined` | Filter to match documents for deletion | | `distanceMetric` | `DistanceMetric` | `undefined` | Distance metric for similarity calculations | | `schema` | `Schema` | `undefined` | Optional schema definition for attributes | #### Example ```tsx // Upsert documents in column-based format await namespace.write({ upsertColumns: { id: ["doc-1", "doc-2"], vector: [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]], text: ["Document 1", "Document 2"], category: ["article", "blog"] }, distanceMetric: "cosine_distance", schema: { text: { type: "string" }, category: { type: "string" } } }); // Upsert documents in row-based format await namespace.write({ upsertRows: [ { id: "doc-1", vector: [0.1, 0.2, 0.3], text: "Document 1", category: "article" }, { id: "doc-2", vector: [0.4, 0.5, 0.6], text: "Document 2", category: "blog" } ], distanceMetric: "cosine_distance" }); // Delete documents by ID await namespace.write({ deletes: ["doc-1", "doc-2"] }); // Delete documents by filter await namespace.write({ deleteByFilter: [ "And", [ ["category", "Eq", "article"], ["createdAt", "Lt", "2023-01-01"] ] ] }); // Patch documents (update specific fields) await namespace.write({ patchRows: [ { id: "doc-1", category: "updated-category" } ] }); ``` #### Return value Returns the number of rows affected by the operation. ### query Searches for similar vectors based on a query vector. ```tsx async query(options: QueryOptions): Promise ``` #### Parameters | Parameter | Type | Default | Description | | ------------------- | ---------------------------------------- | ----------- | ---------------------------------------- | | `vector` | `number[]` | Required | Query vector for similarity search | | `topK` | `number` | `10` | Number of results to return | | `includeVectors` | `boolean` | `false` | Whether to include vectors in results | | `includeAttributes` | `boolean \| string[]` | `true` | Include all attributes or specified ones | | `filters` | `Filters` | `undefined` | Metadata filters | | `rankBy` | `RankBy` | `undefined` | Attribute-based ranking or text ranking | | `consistency` | `string` | `undefined` | Consistency level for reads | #### Example ```tsx const results = await namespace.query({ vector: [0.1, 0.2, 0.3, ...], // Query vector topK: 10, // Number of results to return includeVectors: false, // Whether to include vectors in results includeAttributes: true, // Include all attributes or specific ones filters: [ // Optional metadata filters "And", [ ["category", "Eq", "article"], ["createdAt", "Gte", "2023-01-01"] ] ], rankBy: ["attributes.importance", "asc"], // Optional attribute-based ranking }); ``` #### Return value Returns an array of matched documents with similarity scores: ```tsx [ { id: "doc-1", // Document ID score: 0.87, // Similarity score (0-1) vector?: number[], // Original vector (if includeVectors=true) attributes?: { // Metadata (if includeAttributes=true) text: "Document content", category: "article", createdAt: "2023-07-15" } }, // ...more results ] ``` ### getSchema Retrieves the current schema for the namespace. ```tsx async getSchema(): Promise ``` #### Example ```tsx const schema = await namespace.getSchema(); console.log(schema); // { // text: "string", // category: "string", // createdAt: "string" // } ``` ### updateSchema Updates the schema for the namespace. ```tsx async updateSchema(options: { schema: Schema }): Promise ``` #### Parameters | Parameter | Type | Description | | --------- | -------- | --------------------- | | `schema` | `Schema` | New schema definition | #### Example ```tsx const updatedSchema = await namespace.updateSchema({ schema: { text: "string", category: "string", createdAt: "string", newField: "number", // Add new field tags: "string[]", // Add array field }, }); ``` #### Return value Returns the updated schema. ### getMetadata Retrieves metadata about the namespace. ```tsx async getMetadata(): Promise ``` #### Example ```tsx const metadata = await namespace.getMetadata(); console.log(metadata); // { // vectorCount: 1250, // dimensions: 1536, // distanceMetric: "cosine_distance", // created: "2023-07-15T12:34:56Z" // } ``` ## Namespace management Higher-level operations for managing namespaces (these are accessed directly from the search object, not via `useSearch`): ```tsx import { SearchClient } from "@gensx/storage"; const search = new SearchClient(); // List all namespaces const namespaces = await search.listNamespaces({ prefix: "docs-", // Optional prefix filter }); // Check if namespace exists const exists = await search.namespaceExists("my-namespace"); // Create namespace if it doesn't exist const { created } = await search.ensureNamespace("my-namespace"); // Delete a namespace const { deleted } = await search.deleteNamespace("old-namespace"); // Get a namespace directly for vector operations const namespace = search.getNamespace("products"); // Write vectors using the namespace await namespace.write({ upsertRows: [ { id: "product-1", vector: [0.1, 0.3, 0.5, ...], // embedding vector name: "Ergonomic Chair", category: "furniture", price: 299.99 }, { id: "product-2", vector: [0.2, 0.4, 0.6, ...], name: "Standing Desk", category: "furniture", price: 499.99 } ], distanceMetric: "cosine_distance", schema: { name: { type: "string" }, category: { type: "string" }, price: { type: "number" } } }); // Query vectors directly with the namespace const searchResults = await namespace.query({ vector: [0.15, 0.35, 0.55, ...], // query vector topK: 5, includeAttributes: true, filters: [ "And", [ ["category", "Eq", "furniture"], ["price", "Lt", 400] ] ] }); ``` The `SearchClient` is a standard typescript library and can be used outside of GenSX workflows in your normal application code as well. ## useSearchStorage Hook that provides direct access to the search storage instance, which includes higher-level namespace management functions. ### Import ```tsx import { useSearchStorage } from "@gensx/storage"; ``` ### Signature ```tsx function useSearchStorage(): SearchStorage; ``` ### Example ```tsx const searchStorage = useSearchStorage(); ``` The search storage object provides these management methods: ### getNamespace Get a namespace object for direct interaction. ```tsx // Get a namespace directly (without calling useSearch) const searchStorage = useSearchStorage(); const namespace = searchStorage.getNamespace("documents"); // Usage example await namespace.write({ upsertRows: [...], distanceMetric: "cosine_distance" }); ``` ### listNamespaces List all namespaces in your project. ```tsx const searchStorage = useSearchStorage(); const namespaces = await searchStorage.listNamespaces({ prefix: "docs-" // Optional prefix filter }); console.log(namespaces); // ["docs-articles", "docs-products"] ``` ### ensureNamespace Create a namespace if it doesn't exist. ```tsx const searchStorage = useSearchStorage(); const { created } = await searchStorage.ensureNamespace("documents"); if (created) { console.log("Namespace was created"); } else { console.log("Namespace already existed"); } ``` ### deleteNamespace Delete a namespace and all its data. ```tsx const searchStorage = useSearchStorage(); const { deleted } = await searchStorage.deleteNamespace("old-namespace"); if (deleted) { console.log("Namespace was deleted"); } else { console.log("Namespace could not be deleted"); } ``` ### namespaceExists Check if a namespace exists. ```tsx const searchStorage = useSearchStorage(); const exists = await searchStorage.namespaceExists("documents"); if (exists) { console.log("Namespace exists"); } else { console.log("Namespace does not exist"); } ``` ### hasEnsuredNamespace Check if a namespace has been ensured in the current session. ```tsx const searchStorage = useSearchStorage(); const isEnsured = searchStorage.hasEnsuredNamespace("documents"); if (isEnsured) { console.log("Namespace has been ensured in this session"); } else { console.log("Namespace has not been ensured yet"); } ``` ## SearchClient The `SearchClient` class provides a way to interact with GenSX vector search capabilities outside of the GenSX workflow context, such as from regular Node.js applications or server endpoints. ### Import ```tsx import { SearchClient } from "@gensx/storage"; ``` ### Constructor ```tsx constructor() ``` #### Example ```tsx const searchClient = new SearchClient(); ``` ### Methods #### getNamespace Get a namespace instance and ensure it exists first. ```tsx async getNamespace(name: string): Promise ``` ##### Example ```tsx const namespace = await searchClient.getNamespace("products"); // Then use the namespace to upsert or query vectors await namespace.write({ upsertRows: [ { id: "product-1", vector: [0.1, 0.2, 0.3, ...], name: "Product 1", category: "electronics" } ], distanceMetric: "cosine_distance" }); ``` #### ensureNamespace Create a namespace if it doesn't exist. ```tsx async ensureNamespace(name: string): Promise ``` ##### Example ```tsx const { created } = await searchClient.ensureNamespace("products"); if (created) { console.log("Namespace was created"); } ``` #### listNamespaces List all namespaces. ```tsx async listNamespaces(options?: { prefix?: string }): Promise ``` ##### Example ```tsx const namespaces = await searchClient.listNamespaces({ prefix: "customer-" // Optional prefix filter }); console.log("Available namespaces:", namespaces); ``` #### deleteNamespace Delete a namespace. ```tsx async deleteNamespace(name: string): Promise ``` ##### Example ```tsx const { deleted } = await searchClient.deleteNamespace("temp-namespace"); if (deleted) { console.log("Namespace was removed"); } ``` #### namespaceExists Check if a namespace exists. ```tsx async namespaceExists(name: string): Promise ``` ##### Example ```tsx if (await searchClient.namespaceExists("products")) { console.log("Products namespace exists"); } else { console.log("Products namespace doesn't exist yet"); } ``` ### Usage in applications The SearchClient is particularly useful when you need to access vector search functionality from: - Regular Express.js or Next.js API routes - Background jobs or workers - Custom scripts or tools - Any Node.js application outside the GenSX workflow context ```tsx // Example: Using SearchClient in an Express handler import express from 'express'; import { SearchClient } from '@gensx/storage'; import { OpenAI } from 'openai'; const app = express(); const searchClient = new SearchClient(); const openai = new OpenAI(); app.post('/api/search', async (req, res) => { try { const { query } = req.body; // Generate embedding for the query const embedding = await openai.embeddings.create({ model: "text-embedding-3-small", input: query }); // Search for similar documents const namespace = await searchClient.getNamespace('documents'); const results = await namespace.query({ vector: embedding.data[0].embedding, topK: 5, includeAttributes: true }); res.json(results); } catch (error) { console.error('Search error:', error); res.status(500).json({ error: 'Search error' }); } }); app.listen(3000, () => { console.log('Server running on port 3000'); }); ``` ## Filter operators Filters use a structured array format with the following pattern: ```tsx // Basic filter structure [ "Operation", // And, Or, Not [ // Array of conditions ["field", "Operator", value] ] ] ``` Available operators: | Operator | Description | Example | | ------------- | ---------------------- | -------------------------------------------- | | `Eq` | Equals | `["field", "Eq", "value"]` | | `Ne` | Not equals | `["field", "Ne", "value"]` | | `Gt` | Greater than | `["field", "Gt", 10]` | | `Gte` | Greater than or equal | `["field", "Gte", 10]` | | `Lt` | Less than | `["field", "Lt", 10]` | | `Lte` | Less than or equal | `["field", "Lte", 10]` | | `In` | In array | `["field", "In", ["a", "b"]]` | | `Nin` | Not in array | `["field", "Nin", ["a", "b"]]` | | `Contains` | String contains | `["field", "Contains", "text"]` | | `ContainsAny` | Contains any of values | `["tags", "ContainsAny", ["news", "tech"]]` | | `ContainsAll` | Contains all values | `["tags", "ContainsAll", ["imp", "urgent"]]` | ## RankBy options The `rankBy` parameter can be used in two primary ways: ### Attribute-based ranking Sorts by a field in ascending or descending order: ```tsx // Sort by the createdAt attribute in ascending order rankBy: ["createdAt", "asc"] // Sort by price in descending order (highest first) rankBy: ["price", "desc"] ``` ### Text-based ranking For full-text search relevance scoring: ```tsx // Basic BM25 text ranking rankBy: ["text", "BM25", "search query"] // BM25 with multiple search terms rankBy: ["text", "BM25", ["term1", "term2"]] // Combined text ranking strategies rankBy: ["Sum", [ ["text", "BM25", "search query"], ["text", "BM25", "another term"] ]] // Weighted text ranking (multiply BM25 score by 0.5) rankBy: ["Product", [["text", "BM25", "search query"], 0.5]] // Alternative syntax for weighted ranking rankBy: ["Product", [0.5, ["text", "BM25", "search query"]]] ``` Use these options to fine-tune the relevance and ordering of your search results. # Database reference API reference for GenSX Cloud SQL database components. ## Installation ```bash npm install @gensx/storage ``` ## DatabaseProvider Provides SQL database capabilities to its child components. ### Import ```tsx import { DatabaseProvider } from "@gensx/storage"; ``` ### Props | Prop | Type | Default | Description | | --------------- | --------------------- | ----------- | ------------------------------------------------ | | `kind` | `"filesystem" \| "cloud"` | Auto-detected | The storage backend to use. Defaults filesystem when running locally and cloud when deployed to the serverless runtime. | | `rootDir` | `string` | `".gensx/databases"` | Root directory for storing database files (filesystem only) | ### Example ```tsx import { DatabaseProvider } from "@gensx/storage"; // Cloud storage (production) const Workflow = gensx.Component("DatabaseWorkflow", ({ input }) => ( )); // Local filesystem storage (development) const DevWorkflow = gensx.Component("DevDatabaseWorkflow", ({ input }) => ( )); ``` ## useDatabase Hook that provides access to a specific SQL database. ### Import ```tsx import { useDatabase } from "@gensx/storage"; ``` ### Signature ```tsx function useDatabase(name: string): Database; ``` ### Parameters | Parameter | Type | Description | | --------- | -------- | --------------------------- | | `name` | `string` | The database name to access | ### Returns Returns a database object with methods to interact with SQL database. ### Example ```tsx const db = await useDatabase("users"); const result = await db.execute("SELECT * FROM users WHERE id = ?", [ "user-123", ]); ``` ## Database methods The database object returned by `useDatabase` provides these methods: ### execute Executes a single SQL statement with optional parameters. ```tsx async execute(sql: string, params?: InArgs): Promise ``` #### Parameters | Parameter | Type | Description | | --------- | -------- | ------------------------------------------ | | `sql` | `string` | SQL statement to execute | | `params` | `InArgs` | Optional parameters for prepared statement | > `InArgs` can be provided as an array of values or as a record with named parameters. Values can be primitives, booleans, Uint8Array, or Date objects. #### Example ```tsx // Query with parameters const result = await db.execute("SELECT * FROM users WHERE email = ?", [ "user@example.com", ]); // Insert data await db.execute("INSERT INTO users (id, name, email) VALUES (?, ?, ?)", [ "user-123", "John Doe", "john@example.com", ]); // Update data await db.execute("UPDATE users SET last_login = ? WHERE id = ?", [ new Date().toISOString(), "user-123", ]); ``` #### Return value Returns a result object with the following properties: ```tsx { columns: string[]; // Column names from result set rows: unknown[][]; // Array of result rows as arrays rowsAffected: number; // Number of rows affected by statement lastInsertId?: number; // ID of last inserted row (for INSERT statements) } ``` ### batch Executes multiple SQL statements in a single transaction. ```tsx async batch(statements: DatabaseStatement[]): Promise ``` #### Parameters | Parameter | Type | Description | | ------------ | --------------------- | ------------------------------------------------ | | `statements` | `DatabaseStatement[]` | Array of SQL statements with optional parameters | #### DatabaseStatement format ```tsx { sql: string; // SQL statement params?: InArgs; // Optional parameters } ``` #### Example ```tsx const results = await db.batch([ { sql: "INSERT INTO users (id, name) VALUES (?, ?)", params: ["user-123", "John Doe"], }, { sql: "INSERT INTO user_preferences (user_id, theme) VALUES (?, ?)", params: ["user-123", "dark"], }, ]); ``` #### Return value Returns a result object containing an array of individual results: ```tsx { results: [ { columns: [], rows: [], rowsAffected: 1, lastInsertId: 42 }, { columns: [], rows: [], rowsAffected: 1, lastInsertId: 43 } ] } ``` ### executeMultiple Executes multiple SQL statements as a script (without transaction semantics). ```tsx async executeMultiple(sql: string): Promise ``` #### Parameters | Parameter | Type | Description | | --------- | -------- | ----------------------------------------------- | | `sql` | `string` | Multiple SQL statements separated by semicolons | #### Example ```tsx const results = await db.executeMultiple(` CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, name TEXT NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_users_name ON users(name); `); ``` #### Return value Returns a result object containing an array of individual results, one for each statement executed. ### migrate Executes SQL migration statements with foreign keys disabled. ```tsx async migrate(sql: string): Promise ``` #### Parameters | Parameter | Type | Description | | --------- | -------- | ------------------------ | | `sql` | `string` | SQL migration statements | #### Example ```tsx const results = await db.migrate(` -- Migration v1: Initial schema CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE, created_at TEXT DEFAULT CURRENT_TIMESTAMP ); -- Migration v2: Add last_login field ALTER TABLE users ADD COLUMN last_login TEXT; `); ``` #### Return value Returns a result object containing an array of individual results, one for each statement executed. ### getInfo Retrieves metadata about the database. ```tsx async getInfo(): Promise ``` #### Example ```tsx const info = await db.getInfo(); console.log(info); // { // name: "users", // size: 12800, // lastModified: Date("2023-07-15T12:34:56Z"), // tables: [ // { // name: "users", // columns: [ // { // name: "id", // type: "TEXT", // notNull: true, // primaryKey: true // }, // { // name: "name", // type: "TEXT", // notNull: true, // primaryKey: false // } // ] // } // ] // } ``` ## Database management Higher-level operations for managing databases are available through the `useDatabaseStorage` hook: ### useDatabaseStorage Hook that provides access to the database storage instance, which includes higher-level database management functions. ```tsx import { useDatabaseStorage } from "@gensx/storage"; // Get access to database management functions const dbStorage = useDatabaseStorage(); ``` The database storage object provides these management methods: ### listDatabases Lists all databases in your project. ```tsx import { useDatabaseStorage } from "@gensx/storage"; const dbStorage = useDatabaseStorage(); const databases = await dbStorage.listDatabases(); console.log(databases); // ["users", "products", "analytics"] ``` ### ensureDatabase Creates a database if it doesn't exist. ```tsx const dbStorage = useDatabaseStorage(); const { created } = await dbStorage.ensureDatabase("new-database"); if (created) { console.log("Database was created"); } else { console.log("Database already existed"); } ``` ### deleteDatabase Deletes a database and all its data. ```tsx const dbStorage = useDatabaseStorage(); const { deleted } = await dbStorage.deleteDatabase("old-database"); if (deleted) { console.log("Database was deleted"); } else { console.log("Database could not be deleted"); } ``` ### hasEnsuredDatabase Checks if a database has been ensured in the current session. ```tsx const dbStorage = useDatabaseStorage(); const isEnsured = dbStorage.hasEnsuredDatabase("my-database"); if (isEnsured) { console.log("Database has been ensured in this session"); } else { console.log("Database has not been ensured yet"); } ``` ### getDatabase Get a database instance directly (without calling useDatabase). ```tsx const dbStorage = useDatabaseStorage(); // Get a database directly // Note: This doesn't ensure the database exists, unlike useDatabase const db = dbStorage.getDatabase("users"); // You may want to ensure it exists first await dbStorage.ensureDatabase("users"); const db = dbStorage.getDatabase("users"); ``` ## DatabaseClient The `DatabaseClient` class provides a way to interact with GenSX databases outside of the GenSX workflow context, such as from regular Node.js applications or server endpoints. ### Import ```tsx import { DatabaseClient } from "@gensx/storage"; ``` ### Constructor ```tsx constructor(props?: DatabaseProviderProps) ``` #### Parameters | Parameter | Type | Default | Description | | --------- | --------------------- | ----------- | ------------------------------------------------ | | `props` | `DatabaseProviderProps` | `{}` | Optional configuration properties | #### Example ```tsx // Default client (uses filesystem locally, cloud in production) const dbClient = new DatabaseClient(); // Explicitly use filesystem storage const localClient = new DatabaseClient({ kind: "filesystem", rootDir: "./my-data" }); // Explicitly use cloud storage const cloudClient = new DatabaseClient({ kind: "cloud" }); ``` ### Methods #### getDatabase Get a database instance and ensure it exists first. ```tsx async getDatabase(name: string): Promise ``` ##### Example ```tsx const db = await dbClient.getDatabase("users"); const results = await db.execute("SELECT * FROM users LIMIT 10"); ``` #### ensureDatabase Create a database if it doesn't exist. ```tsx async ensureDatabase(name: string): Promise ``` ##### Example ```tsx const { created } = await dbClient.ensureDatabase("analytics"); if (created) { console.log("Database was created"); } ``` #### listDatabases List all databases. ```tsx async listDatabases(): Promise ``` ##### Example ```tsx const databases = await dbClient.listDatabases(); console.log("Available databases:", databases); ``` #### deleteDatabase Delete a database. ```tsx async deleteDatabase(name: string): Promise ``` ##### Example ```tsx const { deleted } = await dbClient.deleteDatabase("temp-db"); if (deleted) { console.log("Database was removed"); } ``` #### databaseExists Check if a database exists. ```tsx async databaseExists(name: string): Promise ``` ##### Example ```tsx if (await dbClient.databaseExists("users")) { console.log("Users database exists"); } else { console.log("Users database doesn't exist yet"); } ``` ### Usage in applications The DatabaseClient is particularly useful when you need to access GenSX databases from: - Regular Express.js or Next.js API routes - Background jobs or workers - Custom scripts or tools - Any Node.js application outside the GenSX workflow context ```tsx // Example: Using DatabaseClient in an Express handler import express from 'express'; import { DatabaseClient } from '@gensx/storage'; const app = express(); const dbClient = new DatabaseClient(); app.get('/api/users', async (req, res) => { try { const db = await dbClient.getDatabase('users'); const result = await db.execute('SELECT * FROM users'); res.json(result.rows); } catch (error) { console.error('Database error:', error); res.status(500).json({ error: 'Database error' }); } }); app.listen(3000, () => { console.log('Server running on port 3000'); }); ``` # Blob storage reference API reference for GenSX Cloud blob storage components. ## Installation ```bash npm install @gensx/storage ``` ## BlobProvider Provides blob storage capabilities to its child components. ### Import ```tsx import { BlobProvider } from "@gensx/storage"; ``` ### Props | Prop | Type | Default | Description | | --------------- | ------------------------- | -------------- | ------------------------------------- | | `kind` | `"filesystem" \| "cloud"` | Auto-detected | Storage backend to use. Defaults filesystem when running locally and cloud when deployed to the serverless runtime. | | `rootDir` | `string` | `.gensx/blobs` | Root directory for filesystem storage | | `defaultPrefix` | `string` | `undefined` | Optional prefix for all blob keys | ### Example ```tsx import { BlobProvider } from "@gensx/storage"; const Workflow = gensx.Component("Workflow", ({ input }) => ( )); ``` ## useBlob Hook that provides access to blob storage for a specific key. ### Import ```tsx import { useBlob } from "@gensx/storage"; ``` ### Signature ```tsx function useBlob(key: string): Blob; ``` ### Parameters | Parameter | Type | Description | | --------- | ------------ | -------------------------------- | | `key` | `string` | The unique key for the blob | | `T` | Generic type | Type of the JSON data (optional) | ### Returns Returns a blob object with methods to interact with blob storage. ### Example ```tsx const blob = useBlob("users/123.json"); const profile = await blob.getJSON(); ``` ## Blob methods The blob object returned by `useBlob` provides these methods: ### JSON operations ```tsx // Get JSON data const data = await blob.getJSON(); // Returns null if not found // Save JSON data await blob.putJSON(data, options); // Returns { etag: string } ``` ### String operations ```tsx // Get string content const text = await blob.getString(); // Returns null if not found // Save string content await blob.putString("Hello world", options); // Returns { etag: string } ``` ### Binary operations ```tsx // Get binary data with metadata const result = await blob.getRaw(); // Returns null if not found // Returns { content, contentType, etag, lastModified, size, metadata } // Save binary data await blob.putRaw(buffer, options); // Returns { etag: string } ``` ### Stream operations ```tsx // Get data as a stream const stream = await blob.getStream(); // Save data from a stream await blob.putStream(readableStream, options); // Returns { etag: string } ``` ### Metadata operations ```tsx // Check if blob exists const exists = await blob.exists(); // Returns boolean // Delete blob await blob.delete(); // Get metadata const metadata = await blob.getMetadata(); // Returns null if not found // Update metadata await blob.updateMetadata({ key1: "value1", key2: "value2", }); ``` ## Options object Many methods accept an options object with these properties: ```tsx { contentType?: string, // MIME type of the content etag?: string, // For optimistic concurrency control metadata?: { // Custom metadata key-value pairs [key: string]: string } } ``` ## useBlobStorage Hook that provides direct access to the blob storage instance, allowing you to perform blob operations across multiple keys. ### Import ```tsx import { useBlobStorage } from "@gensx/storage"; ``` ### Signature ```tsx function useBlobStorage(): BlobStorage; ``` ### Example ```tsx const blobStorage = useBlobStorage(); ``` The blob storage object provides these methods: ### getBlob Get a blob object for a specific key. ```tsx const blobStorage = useBlobStorage(); const userBlob = blobStorage.getBlob("users/123.json"); ``` ### listBlobs List all blob keys with an optional prefix filter. ```tsx const blobStorage = useBlobStorage(); const userBlobKeys = await blobStorage.listBlobs("users/"); console.log(userBlobKeys); // ["users/123.json", "users/456.json"] ``` ### blobExists Check if a blob exists. ```tsx const blobStorage = useBlobStorage(); const exists = await blobStorage.blobExists("users/123.json"); if (exists) { console.log("User profile exists"); } ``` ### deleteBlob Delete a blob. ```tsx const blobStorage = useBlobStorage(); const { deleted } = await blobStorage.deleteBlob("temp/file.json"); if (deleted) { console.log("Temporary file deleted"); } ``` ## BlobClient The `BlobClient` class provides a way to interact with GenSX blob storage outside of the GenSX workflow context, such as from regular Node.js applications or server endpoints. ### Import ```tsx import { BlobClient } from "@gensx/storage"; ``` ### Constructor ```tsx constructor(props?: BlobProviderProps) ``` #### Parameters | Parameter | Type | Default | Description | | --------- | ------------------ | ------- | ------------------------------------- | | `props` | `BlobProviderProps` | `{}` | Optional configuration properties | #### Example ```tsx // Default client (uses filesystem locally, cloud in production) const blobClient = new BlobClient(); // Explicitly use filesystem storage const localClient = new BlobClient({ kind: "filesystem", rootDir: "./my-data" }); // Explicitly use cloud storage with a prefix const cloudClient = new BlobClient({ kind: "cloud", defaultPrefix: "app-data/" }); ``` ### Methods #### getBlob Get a blob instance for a specific key. ```tsx getBlob(key: string): Blob ``` ##### Example ```tsx const userBlob = blobClient.getBlob("users/123.json"); const profile = await userBlob.getJSON(); // Update the profile profile.lastLogin = new Date().toISOString(); await userBlob.putJSON(profile); ``` #### listBlobs List all blob keys with an optional prefix. ```tsx async listBlobs(prefix?: string): Promise ``` ##### Example ```tsx const chatHistory = await blobClient.listBlobs("chats/"); console.log("Chat histories:", chatHistory); ``` #### blobExists Check if a blob exists. ```tsx async blobExists(key: string): Promise ``` ##### Example ```tsx if (await blobClient.blobExists("settings.json")) { console.log("Settings file exists"); } else { console.log("Need to create settings file"); } ``` #### deleteBlob Delete a blob. ```tsx async deleteBlob(key: string): Promise ``` ##### Example ```tsx const { deleted } = await blobClient.deleteBlob("temp/cache.json"); if (deleted) { console.log("Cache file was deleted"); } ``` ### Usage in applications The BlobClient is particularly useful when you need to access blob storage from: - Express.js or Next.js API routes - Background jobs or workers - Custom scripts or tools - Any Node.js application outside the GenSX workflow context ```tsx // Example: Using BlobClient in an Express handler import express from 'express'; import { BlobClient } from '@gensx/storage'; const app = express(); const blobClient = new BlobClient(); // Save user data endpoint app.post('/api/users/:userId', async (req, res) => { try { const { userId } = req.params; const userBlob = blobClient.getBlob(`users/${userId}.json`); // Get existing profile or create new one const existingProfile = await userBlob.getJSON() || {}; // Merge with updated data const updatedProfile = { ...existingProfile, ...req.body, updatedAt: new Date().toISOString() }; // Save the updated profile await userBlob.putJSON(updatedProfile); res.json({ success: true }); } catch (error) { console.error('Error saving user data:', error); res.status(500).json({ error: 'Failed to save user data' }); } }); app.listen(3000, () => { console.log('Server running on port 3000'); }); ``` # SQL database GenSX's SQL database service provides zero-configuration SQLite databases. It enables you to create, query, and manage relational data without worrying about infrastructure or database administration. Because new databases can be provisioned in milliseconds, they are perfect for per-agent or per workflow state. Cloud databases are powered by [Turso](https://turso.tech), with several properties that make them ideal for AI agents and workflows: - **Millisecond provisioning**: Databases are created on-demand in milliseconds, making them perfect for ephemeral workloads like parsing and querying user-uploaded CSVs or creating per-agent structured data stores. - **Strong consistency**: All operations are linearizable, maintaining an ordered history, with writes fully serialized and subsequent writes awaiting transaction completion. - **Zero configuration**: Like all GenSX storage components, databases work identically in both development and production environments with no setup required. - **Local development**: Uses libsql locally to enable a fast, isolated development loop without external dependencies. ## Basic usage To use SQL databases in your GenSX application: 1. Install the storage package: ```bash npm install @gensx/storage ``` 2. Add the `DatabaseProvider` to your workflow: ```tsx import { DatabaseProvider } from "@gensx/storage"; const Workflow = ({ input }) => ( ); ``` 3. Access databases within your components using the `useDatabase` hook: ```tsx import { useDatabase } from "@gensx/storage"; const db = await useDatabase("my-database"); ``` ### Executing queries The simplest way to interact with a database is by executing SQL queries: ```tsx import * as gensx from "@gensx/core"; import { useDatabase } from "@gensx/storage"; const QueryTeamStats = gensx.Component("QueryTeamStats", async ({ team }) => { // Get access to a database (creates it if it doesn't exist) const db = await useDatabase("baseball"); // Execute SQL queries with parameters const result = await db.execute("SELECT * FROM players WHERE team = ?", [ team, ]); // Access query results console.log(result.columns); // Column names console.log(result.rows); // Data rows console.log(result.rowsAffected); // Number of rows affected return result.rows; }); ``` ### Creating tables and initializing data You can create database schema and populate it with data: ```tsx const InitializeDatabase = gensx.Component("InitializeDatabase", async () => { const db = await useDatabase("baseball"); // Create table if it doesn't exist await db.execute(` CREATE TABLE IF NOT EXISTS baseball_stats ( player TEXT, team TEXT, position TEXT, at_bats INTEGER, hits INTEGER, runs INTEGER, home_runs INTEGER, rbi INTEGER, batting_avg REAL ) `); // Check if data already exists const result = await db.execute("SELECT COUNT(*) FROM baseball_stats"); const count = result.rows[0][0] as number; if (count === 0) { // Insert sample data await db.execute(` INSERT INTO baseball_stats (player, team, position, at_bats, hits, runs, home_runs, rbi, batting_avg) VALUES ('Marcus Bennett', 'Portland Pioneers', '1B', 550, 85, 25, 32, 98, 0.312), ('Ethan Carter', 'San Antonio Stallions', 'SS', 520, 92, 18, 24, 76, 0.298) `); } return "Database initialized"; }); ``` ## Practical examples ### Text-to-SQL agent One of the most powerful applications is building a natural language to SQL interface: ```tsx import * as gensx from "@gensx/core"; import { GSXChatCompletion, GSXTool } from "@gensx/openai"; import { useDatabase } from "@gensx/storage"; import { z } from "zod"; // Create a tool that executes SQL queries const queryTool = new GSXTool({ name: "execute_query", description: "Execute a SQL query against the baseball database", schema: z.object({ query: z.string().describe("The SQL query to execute"), }), run: async ({ query }) => { const db = await useDatabase("baseball"); const result = await db.execute(query); return JSON.stringify(result, null, 2); }, }); // SQL Copilot component that answers questions using SQL const SqlCopilot = gensx.Component("SqlCopilot", ({ question }) => ( {(result) => result.choices[0].message.content} )); ``` ### Transactions with batch operations For operations that need to be performed atomically, you can use batch operations: ```tsx const TransferFunds = gensx.Component( "TransferFunds", async ({ fromAccount, toAccount, amount }) => { const db = await useDatabase("banking"); try { // Execute multiple statements as a transaction const result = await db.batch([ { sql: "UPDATE accounts SET balance = balance - ? WHERE account_id = ?", params: [amount, fromAccount], }, { sql: "UPDATE accounts SET balance = balance + ? WHERE account_id = ?", params: [amount, toAccount], }, ]); return { success: true, rowsAffected: result.rowsAffected }; } catch (error) { return { success: false, error: error.message }; } }, ); ``` ### Multi-statement scripts For complex database changes, you can execute multiple statements at once: ```tsx const SetupUserSystem = gensx.Component("SetupUserSystem", async () => { const db = await useDatabase("users"); // Execute a SQL script with multiple statements await db.executeMultiple(` CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE ); CREATE TABLE IF NOT EXISTS user_preferences ( user_id TEXT PRIMARY KEY, theme TEXT DEFAULT 'light', notifications BOOLEAN DEFAULT 1, FOREIGN KEY (user_id) REFERENCES users(id) ); CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); `); return "User system set up successfully"; }); ``` ### Database schema migrations When you need to update your database schema, use migrations: ```tsx const MigrateDatabase = gensx.Component( "MigrateDatabase", async ({ version }) => { const db = await useDatabase("app_data"); if (version === "v2") { // Run migrations with foreign key checks disabled await db.migrate(` ALTER TABLE products ADD COLUMN category TEXT; CREATE TABLE product_categories ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, description TEXT ); `); return "Database migrated to v2"; } return "No migration needed"; }, ); ``` ## Development vs. production GenSX SQL databases work identically in both local development and cloud environments: - **Local development**: Databases are stored as SQLite files in the `.gensx/databases` directory by default - **Cloud deployment**: Databases are automatically provisioned in the cloud If you don't specify a "kind" that the framework auto-infers this value for you based on the runtime environment. No code changes are needed when moving from development to production. ## Use cases ### Data-backed agents Create agents that can query and update structured data, using the components defined above: ```tsx const DataAnalyst = gensx.Component("DataAnalyst", async ({ query }) => { // Initialize the database with the baseball stats await InitializeDatabase(); // Use the SQL Copilot to answer the question return ; }); ``` ### User data storage Store user data and preferences in a structured format: ```tsx const UserPreferences = gensx.Component( "UserPreferences", async ({ userId, action, data }) => { const db = await useDatabase("user_data"); if (action === "get") { const result = await db.execute( "SELECT * FROM preferences WHERE user_id = ?", [userId], ); return result.rows.length > 0 ? result.rows[0] : null; } else if (action === "set") { await db.execute( "INSERT OR REPLACE INTO preferences (user_id, settings) VALUES (?, ?)", [userId, JSON.stringify(data)], ); return { success: true }; } }, ); ``` ### Collaborative workflows Build workflows that share structured data between steps: ```tsx const DataCollector = gensx.Component("DataCollector", async ({ source }) => { const db = await useDatabase("workflow_data"); // Collect data from source and store in database // ... return { success: true }; }); const DataAnalyzer = gensx.Component("DataAnalyzer", async () => { const db = await useDatabase("workflow_data"); // Analyze data from database // ... return { results: "..." }; }); ``` ## Reference See the [database component reference](docs/component-reference/storage-components/database-reference) for full details. # Search GenSX's Cloud search service provides full-text and vector search for AI applications. It enables you to store, query, and manage vector embeddings for semantic search, retrieval-augmented generation (RAG), and other AI use cases. Search is powered by [turbopuffer](https://turbopuffer.com/), fully featured and ready for AI workloads: - **Combined vector and keyword search**: Perform hybrid searches using both semantic similarity (vectors) and keyword matching (BM25). - **Millisecond query latency**: Get results quickly, even with large vector collections. - **Flexible filtering**: Apply metadata filters to narrow down search results based on categories, timestamps, or any custom attributes. ## Basic usage To use search in your GenSX application: 1. Install the storage package: ```bash npm install @gensx/storage ``` 2. Add the `SearchProvider` to your workflow: ```tsx import { SearchProvider } from "@gensx/storage"; const Workflow = gensx.Component("SearchWorkflow", { input }) => ( ); ``` 3. Access search namespaces within your components using the `useSearch` hook: ```tsx import { useSearch } from "@gensx/storage"; const search = await useSearch("documents"); ``` ### Storing vector embeddings The first step in using search is to convert your data into vector embeddings and store them: ```tsx import * as gensx from "@gensx/core"; import { OpenAIEmbedding } from "@gensx/openai"; import { useSearch } from "@gensx/storage"; const IndexDocuments = gensx.Component( "IndexDocuments", async ({ documents }) => { // Get access to a search namespace const search = await useSearch("documents"); // Generate embeddings for the documents const embeddings = await OpenAIEmbedding.run({ model: "text-embedding-3-small", input: documents.map((doc) => doc.text), }); // Store the embeddings with original text as metadata await search.write({ upsertRows: documents.map((doc, index) => ({ id: doc.id, vector: embeddings.data[index].embedding, text: doc.text, category: doc.category, createdAt: new Date().toISOString(), })), distanceMetric: "cosine_distance", }); return { success: true, count: documents.length }; }, ); ``` ### Searching for similar documents Once you've stored embeddings, you can search for semantically similar content: ```tsx const SearchDocuments = gensx.Component( "SearchDocuments", async ({ query, category }) => { // Get access to the search namespace const search = await useSearch("documents"); // Generate an embedding for the query const embedding = await OpenAIEmbedding.run({ model: "text-embedding-3-small", input: query, }); // Build query options const queryOptions = { vector: embedding.data[0].embedding, includeAttributes: true, topK: 5, // Return top 5 results }; // Add filters if category is specified if (category) { queryOptions.filters = { where: { category: { $eq: category } }, }; } // Perform the search const results = await search.query(queryOptions); // Process and return results return results.map((result) => ({ id: result.id, text: result.attributes?.text, score: result.score, })); }, ); ``` ## Building a RAG application Retrieval-Augmented Generation (RAG) is one of the most common use cases for vector search. Here's how to build a complete RAG workflow: ### Step 1: Index your documents First, create a component to prepare and index your documents: ```tsx const PrepareDocuments = gensx.Component("PrepareDocuments", async () => { // Sample baseball player data const documents = [ { id: "1", text: "Marcus Bennett is a first baseman for the Portland Pioneers. He has 32 home runs this season.", category: "player", }, { id: "2", text: "Ethan Carter plays shortstop for the San Antonio Stallions with 24 home runs.", category: "player", }, { id: "3", text: "The Portland Pioneers are leading the Western Division with a 92-70 record.", category: "team", }, ]; // Index the documents return ; }); ``` ### Step 2: Create a query tool Next, create a tool that can access your search index: ```tsx import { GSXTool } from "@gensx/openai"; import { z } from "zod"; // Define a tool to query the search index const queryTool = new GSXTool({ name: "query", description: "Query the baseball knowledge base", schema: z.object({ query: z.string().describe("The text query to search for"), }), run: async ({ query }) => { // Access search index const search = await useSearch("baseball"); // Generate query embedding const embedding = await OpenAIEmbedding.run({ model: "text-embedding-3-small", input: query, }); // Search for relevant documents const results = await search.query({ vector: embedding.data[0].embedding, includeAttributes: true, }); // Return formatted results return JSON.stringify( results.map((r) => r.attributes?.text), null, 2, ); }, }); ``` ### Step 3: Create the RAG agent Now, create an agent that uses the query tool to access relevant information: ```tsx const RagAgent = gensx.Component("RagAgent", ({ question }) => ( {(result) => result.choices[0].message.content} )); ``` ### Step 4: Combine Everything in a Workflow Finally, put it all together in a workflow: ```tsx const RagWorkflow = gensx.Component( "RagWorkflow", async ({ question, shouldReindex }) => { // Optionally reindex documents if (shouldReindex) { await PrepareDocuments(); } // Use the RAG agent to answer the question return ; }, ); ``` ## Practical examples ### Agent memory system One powerful application of vector search is creating a long-term memory system for AI agents: ```tsx import * as gensx from "@gensx/core"; import { OpenAIEmbedding } from "@gensx/openai"; import { useSearch } from "@gensx/storage"; // Component to store a memory const StoreMemory = gensx.Component( "StoreMemory", async ({ userId, memory, importance = "medium" }) => { const search = await useSearch(`memories-${userId}`); // Generate embedding for this memory const embedding = await OpenAIEmbedding.run({ model: "text-embedding-3-small", input: memory, }); // Store the memory with metadata await search.write({ upsertRows: [ { id: `memory-${Date.now()}`, vector: embedding.data[0].embedding, content: memory, timestamp: new Date().toISOString(), importance: importance, // "high", "medium", "low" source: "user-interaction", }, ], distanceMetric: "cosine_distance", }); return { success: true }; }, ); // Component to recall relevant memories const RecallMemories = gensx.Component( "RecallMemories", async ({ userId, context, maxResults = 5 }) => { const search = await useSearch(`memories-${userId}`); // Generate embedding for the context const embedding = await OpenAIEmbedding.run({ model: "text-embedding-3-small", input: context, }); // Query for relevant memories, prioritizing important ones const results = await search.query({ vector: embedding.data[0].embedding, topK: maxResults, includeAttributes: true, // Optional: rank by both relevance and importance rankBy: ["attributes.importance", "asc"], }); // Format memories for the agent return results.map((result) => ({ content: result.attributes?.content, timestamp: result.attributes?.timestamp, relevance: result.score.toFixed(3), })); }, ); // Component that uses memories in a conversation const MemoryAwareAgent = gensx.Component( "MemoryAwareAgent", async ({ userId, userMessage }) => { // Recall relevant memories based on the current conversation const memories = await RecallMemories({ userId, context: userMessage, maxResults: 3, }); // Use memories to inform the response const response = await ChatCompletion.run({ model: "gpt-4o-mini", messages: [ { role: "system", content: `You are an assistant with memory. Consider these relevant memories about this user: ${memories.map((m) => `[${m.timestamp}] ${m.content} (relevance: ${m.relevance})`).join("\n")}`, }, { role: "user", content: userMessage }, ], }); // Store this interaction as a new memory await StoreMemory({ userId, memory: `User asked: "${userMessage}". I replied: "${response}"`, importance: "medium", }); return response; }, ); ``` ### Knowledge base search Another powerful application is a knowledge base with faceted search capabilities: ```tsx const SearchKnowledgeBase = gensx.Component( "SearchKnowledgeBase", async ({ query, filters = {} }) => { const search = await useSearch("knowledge-base"); // Generate embedding for the query const embedding = await OpenAIEmbedding.run({ model: "text-embedding-3-small", input: query, }); // Build filter conditions from user-provided filters let filterConditions = ["And", []]; if (filters.category) { filterConditions[1].push(["category", "Eq", filters.category]); } if (filters.dateRange) { filterConditions[1].push(["publishedDate", "Gte", filters.dateRange.start]); filterConditions[1].push(["publishedDate", "Lte", filters.dateRange.end]); } if (filters.tags && filters.tags.length > 0) { filterConditions[1].push(["tags", "ContainsAny", filters.tags]); } // Perform hybrid search (vector + keyword) with filters const results = await search.query({ vector: embedding.data[0].embedding, rankBy: ["text", "BM25", query], includeAttributes: true, topK: 10, filters: filterConditions[1].length > 0 ? filterConditions : undefined, }); // Return formatted results return results.map((result) => ({ title: result.attributes?.title, snippet: result.attributes?.snippet, url: result.attributes?.url, category: result.attributes?.category, tags: result.attributes?.tags, score: result.score, })); }, ); ``` ## Advanced usage ### Filtering by metadata Use filters to narrow down search results: ```tsx const search = await useSearch("articles"); // Search with filters const results = await search.query({ vector: queryEmbedding, filters: [ "And", [ ["category", "Eq", "sports"], ["publishDate", "Gte", "2023-01-01"], ["publishDate", "Lt", "2024-01-01"], ["author", "In", ["Alice", "Bob", "Carol"]], ], ], }); ``` ### Updating schema Manage your vector collection's schema: ```tsx const search = await useSearch("products"); // Get current schema const currentSchema = await search.getSchema(); // Update schema to add new fields await search.updateSchema({ ...currentSchema, newField: { type: "number" }, anotherField: { type: "string[]" }, }); ``` ## Reference See the [search component reference](docs/component-reference/storage-components/search-reference) for full details. # Blob storage Blob storage provides zero-configuration persistent storage for your GenSX applications. It enables you to store JSON, text, or binary data for your agents and workflows without worrying about managing infrastructure. ## Basic usage To use blob storage in your GenSX application: 1. Install the storage package: ```bash npm install @gensx/storage ``` 2. Add the `BlobProvider` to your workflow: ```tsx import { BlobProvider } from "@gensx/storage"; const Workflow = ({ input }) => ( ); ``` 3. Access blobs within your components using the `useBlob` hook: ```tsx import { useBlob } from "@gensx/storage"; const blob = useBlob("your-key.json"); ``` ### Reading blobs The `useBlob` hook provides simple methods to read different types of data: ```tsx import { useBlob } from "@gensx/storage"; // Read JSON data const profileBlob = useBlob("users/profile.json"); const profile = await profileBlob.getJSON(); console.log(profile?.name); // Read text data const notesBlob = useBlob("notes/meeting.txt"); const notes = await notesBlob.getString(); // Read binary data const imageBlob = useBlob("images/photo.jpg"); const image = await imageBlob.getRaw(); console.log(image?.contentType); // "image/jpeg" ``` ### Writing blobs You can write data in various formats: ```tsx import { useBlob } from "@gensx/storage"; // Write JSON data const profileBlob = useBlob("users/profile.json"); await profileBlob.putJSON({ name: "Alice", preferences: { theme: "dark" } }); // Write text data const notesBlob = useBlob("notes/meeting.txt"); await notesBlob.putString("Meeting agenda:\n1. Project updates\n2. Action items"); // Write binary data const imageBlob = useBlob("images/photo.jpg"); await imageBlob.putRaw(imageBuffer, { contentType: "image/jpeg", metadata: { originalName: "vacation.jpg" } }); ``` ## Practical examples ### Persistent chat threads One of the most common use cases for blob storage is maintaining conversation history across multiple interactions: ```tsx import * as gensx from "@gensx/core"; import { ChatCompletion } from "@gensx/openai"; import { useBlob } from "@gensx/storage"; interface ChatMessage { role: "system" | "user" | "assistant"; content: string; } const ChatWithMemory = gensx.Component( "ChatWithMemory", async ({ userInput, threadId }) => { // Get a reference to the thread's storage const blob = useBlob(`chats/${threadId}.json`); // Load existing messages or start with a system prompt const messages = (await blob.getJSON()) ?? [ { role: "system", content: "You are a helpful assistant.", }, ]; // Add the new user message messages.push({ role: "user", content: userInput }); // Generate a response using the full conversation history const response = await ChatCompletion.run({ model: "gpt-4o-mini", messages, }); // Save the assistant's response to the history messages.push({ role: "assistant", content: response }); await blob.putJSON(messages); return response; }, ); ``` ### Memory for agents For more complex agents, you can store structured memory: ```tsx interface AgentMemory { facts: string[]; tasks: { description: string; completed: boolean }[]; lastUpdated: string; } const AgentWithMemory = gensx.Component( "AgentWithMemory", async ({ input, agentId }) => { // Load agent memory const memoryBlob = useBlob(`agents/${agentId}/memory.json`); const memory = (await memoryBlob.getJSON()) ?? { facts: [], tasks: [], lastUpdated: new Date().toISOString(), }; // Process input using memory // ... // Update and save memory memory.facts.push("New fact learned from input"); memory.tasks.push({ description: "Follow up on X", completed: false }); memory.lastUpdated = new Date().toISOString(); await memoryBlob.putJSON(memory); return "Response that uses memory context"; }, ); ``` ### Saving files You can use blob storage to save and retrieve binary files like images: ```tsx const StoreImage = gensx.Component( "StoreImage", async ({ imageBuffer, filename }) => { const imageBlob = useBlob(`images/${filename}`); // Save image with metadata await imageBlob.putRaw(imageBuffer, { contentType: "image/png", metadata: { uploadedAt: new Date().toISOString(), pixelSize: "800x600", }, }); return { success: true, path: `images/${filename}` }; }, ); const GetImage = gensx.Component("GetImage", async ({ filename }) => { const imageBlob = useBlob(`images/${filename}`); // Check if image exists const exists = await imageBlob.exists(); if (!exists) { return { found: false }; } // Get the image with metadata const image = await imageBlob.getRaw(); return { found: true, data: image?.content, contentType: image?.contentType, metadata: image?.metadata, }; }); ``` ### Optimistic concurrency control For scenarios where multiple processes might update the same data, you can use ETags to prevent conflicts: ```tsx const UpdateCounter = gensx.Component( "UpdateCounter", async ({ counterName }) => { const blob = useBlob(`counters/${counterName}.json`); // Get current value and metadata const counter = (await blob.getJSON<{ value: number }>()) ?? { value: 0 }; const metadata = await blob.getMetadata(); // Update counter counter.value += 1; try { // Save with ETag to prevent conflicts await blob.putJSON(counter, { etag: metadata?.etag, }); return { success: true, value: counter.value }; } catch (error) { if (error.name === "BlobConflictError") { return { success: false, message: "Counter was updated by another process", }; } throw error; } }, ); ``` ## Development vs. production GenSX blob storage works identically in both local development and cloud environments: - **Local development**: Blobs are stored in the `.gensx/blobs` directory by default - **Cloud deployment**: Blobs are automatically stored in cloud storage If you don't specify a "kind" that the framework auto-infers this value for you based on the runtime environment. No code changes are needed when moving from development to production. ## Reference See the [blob storage component reference](docs/component-reference/storage-components/blob-reference) for full details. # gensx env unselect The `gensx env unselect` command de-selects the currently selected environment for your project. This means subsequent commands will require explicit environment specification. ## Usage ```bash gensx env unselect [options] ``` ## Options | Option | Description | | ----------------------- | ---------------------------------------------------------------------------- | | `-p, --project ` | Project name to unselect the environment in. | | `-h, --help` | Display help for the command. | ## Examples ```bash # Unselect the current environment gensx env unselect # Unselect environment in a specific project gensx env unselect --project my-app ``` ## Notes - You must be logged in to GenSX Cloud to unselect environments (`gensx login`) - Unselecting an environment does not delete it, it just removes the selection. - You can check if an environment is selected using `gensx env` - To select a new environment, use `gensx env select` - After unselecting, you'll need to specify the environment for each command that requires one # gensx env The `gensx env` command displays the name of the currently selected environment. ## Usage ```bash gensx env [options] ``` ## Options | Option | Description | | ----------------------- | ---------------------------------------------------------------------------- | | `-p, --project ` | Project name to show environment details for. | | `-h, --help` | Display help for the command. | ## Examples ```bash # Show the current environment gensx env # Show the current environment for a specific project gensx env --project my-app ``` ## Notes - You must be logged in to GenSX Cloud to show environment details (`gensx login`) - You can use this command to verify your current environment before running important operations - If no environment is selected, the command will indicate this # gensx env select The `gensx env select` command sets a specific environment as the active environment for your current project. This environment will be used by default for subsequent commands like `deploy` and `run`. ## Usage ```bash gensx env select [options] ``` ## Arguments | Argument | Description | | -------- | ------------------------------------------------------------------------------------------ | | `` | Name of the environment to select. | ## Options | Option | Description | | ----------------------- | ---------------------------------------------------------------------------- | | `-p, --project ` | Project name to select the environment in. | | `-h, --help` | Display help for the command. | ## Description This command: 1. Sets the specified environment as active for your current project 2. Updates your local configuration to remember this selection 3. Makes this environment the default target for subsequent commands After selecting an environment: - `gensx deploy` will deploy to this environment by default - `gensx run` will run workflows in this environment by default - You can still override the environment for specific commands using the `--env` option ## Examples ```bash # Select the development environment gensx env select dev # Select a production environment in a specific project gensx env select production --project my-app ``` ## Notes - You must be logged in to GenSX Cloud to select environments (`gensx login`) - The selected environment persists across CLI sessions - You can check the currently selected environment using `gensx env show` - To unselect an environment, use `gensx env unselect` # gensx env ls The `gensx env ls` command lists all environments in your GenSX project. ## Usage ```bash gensx env ls [options] ``` ## Options | Option | Description | | ----------------------- | ---------------------------------------------------------------------------- | | `-p, --project ` | Project name to list environments for. | | `-h, --help` | Display help for the command. | ## Examples ```bash # List all environments in the current project gensx env ls # List environments in a specific project gensx env ls --project my-app ``` ## Notes - You must be logged in to GenSX Cloud to list environments (`gensx login`) # gensx env create The `gensx env create` command creates a new environment in your GenSX project. Environments allow you to manage different deployment configurations (like development, staging, and production) for your workflows. ## Usage ```bash gensx env create [options] ``` ## Arguments | Argument | Description | | -------- | ------------------------------------------------------------------------------------------ | | `` | Name of the environment to create (e.g., "dev", "staging", "production"). | ## Options | Option | Description | | ----------------------- | ---------------------------------------------------------------------------- | | `-p, --project ` | Project name to create the environment in. | | `-h, --help` | Display help for the command. | ## Examples ```bash # Create a development environment gensx env create dev # Create a production environment in a specific project gensx env create production --project my-app ``` ## Notes - You must be logged in to GenSX Cloud to create environments (`gensx login`) - Each project can have multiple environments - Environment names should be descriptive and follow a consistent naming convention