From 7bd5d9340c4e4f1ebd5b196dceda8689e6e65f3d Mon Sep 17 00:00:00 2001
From: Thuc Pham <51660321+thucpn@users.noreply.github.com>
Date: Tue, 11 Feb 2025 13:41:11 +0700
Subject: [PATCH] docs: update workflow doc (#1637)

Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
---
 .changeset/quiet-teachers-shave.md            |   5 +
 .../docs/llamaindex/modules/workflows.mdx     | 118 +++++++++---------
 2 files changed, 61 insertions(+), 62 deletions(-)
 create mode 100644 .changeset/quiet-teachers-shave.md

diff --git a/.changeset/quiet-teachers-shave.md b/.changeset/quiet-teachers-shave.md
new file mode 100644
index 000000000..4e1f920dd
--- /dev/null
+++ b/.changeset/quiet-teachers-shave.md
@@ -0,0 +1,5 @@
+---
+"@llamaindex/doc": patch
+---
+
+docs: update workflow doc
diff --git a/apps/next/src/content/docs/llamaindex/modules/workflows.mdx b/apps/next/src/content/docs/llamaindex/modules/workflows.mdx
index c1938b2e4..f717bd5f5 100644
--- a/apps/next/src/content/docs/llamaindex/modules/workflows.mdx
+++ b/apps/next/src/content/docs/llamaindex/modules/workflows.mdx
@@ -13,6 +13,22 @@ When a step function is added to a workflow, you need to specify the input and o
 
 You can create a `Workflow` to do anything! Build an agent, a RAG flow, an extraction flow, or anything else you want.
 
+import { Tab, Tabs } from "fumadocs-ui/components/tabs";
+
+<Tabs groupId="install" items={["npm", "yarn", "pnpm"]} persist>
+	```shell tab="npm"
+	npm install @llamaindex/workflow
+	```
+
+	```shell tab="yarn"
+	yarn add @llamaindex/workflow
+	```
+
+	```shell tab="pnpm"
+	pnpm add @llamaindex/workflow
+	```
+</Tabs>
+
 ## Getting Started
 
 As an illustrative example, let's consider a naive workflow where a joke is generated and then critiqued.
@@ -34,51 +50,59 @@ Events are user-defined classes that extend `WorkflowEvent` and contain arbitrar
 ```typescript
 const llm = new OpenAI();
 ...
-const jokeFlow = new Workflow({ verbose: true });
+const jokeFlow = new Workflow<unknown, string, string>();
 ```
 
-Our workflow is implemented by initiating the `Workflow` class. For simplicity, we created a `OpenAI` llm instance.
+Our workflow is implemented by initiating the `Workflow` class with three generic types: the context type (unknown), input type (string), and output type (string). The context type is `unknown`, as we're not using a shared context in this example.
+
+For simplicity, we created an `OpenAI` llm instance that we're using for inference in our workflow.
 
 ### Workflow Entry Points
 
 ```typescript
-const generateJoke = async (_context: Context, ev: StartEvent) => {
-  const prompt = `Write your best joke about ${ev.data.input}.`;
+const generateJoke = async (_: unknown, ev: StartEvent<string>) => {
+  const prompt = `Write your best joke about ${ev.data}.`;
   const response = await llm.complete({ prompt });
   return new JokeEvent({ joke: response.text });
 };
 ```
 
-Here, we come to the entry-point of our workflow. While events are user-defined, there are two special-case events, the `StartEvent` and the `StopEvent`. Here, the `StartEvent` signifies where to send the initial workflow input.
-
-The `StartEvent` is a bit of a special object since it can hold arbitrary attributes. Here, we accessed the topic with `ev.data.input`.
+Here, we come to the entry-point of our workflow. While events are user-defined, there are two special-case events, the `StartEvent` and the `StopEvent`. These events are predefined, but we can specify the payload type using generic types. We're using `StartEvent<string>` to indicate that we're going to send an input of type string.
 
-At this point, you may have noticed that we haven't explicitly told the workflow what events are handled by which steps.
-
-To do so, we use the `addStep` method which adds a step to the workflow. The first argument is the event type that the step will handle, and the second argument is the previously defined step function:
+To add this step to the workflow, we use the `addStep` method with an object specifying the input and output event types:
 
 ```typescript
-jokeFlow.addStep(StartEvent, generateJoke);
+jokeFlow.addStep(
+  {
+    inputs: [StartEvent<string>],
+    outputs: [JokeEvent],
+  },
+  generateJoke
+);
 ```
 
 ### Workflow Exit Points
 
 ```typescript
-const critiqueJoke = async (_context: Context, ev: JokeEvent) => {
+const critiqueJoke = async (_: unknown, ev: JokeEvent) => {
   const prompt = `Give a thorough critique of the following joke: ${ev.data.joke}`;
   const response = await llm.complete({ prompt });
-  return new StopEvent({ result: response.text });
+  return new StopEvent(response.text);
 };
 ```
 
-Here, we have our second, and last step, in the workflow. We know its the last step because the special `StopEvent` is returned. When the workflow encounters a returned `StopEvent`, it immediately stops the workflow and returns whatever the result was.
-
-In this case, the result is a string, but it could be a map, array, or any other object.
+Here, we have our second and last step in the workflow. We know it's the last step because the special `StopEvent` is returned. When the workflow encounters a returned `StopEvent`, it immediately stops the workflow and returns the result. Note that we're using the generic type `StopEvent<string>` to indicate that we're returning a string.
 
-Don't forget to add the step to the workflow:
+Add this step to the workflow:
 
 ```typescript
-jokeFlow.addStep(JokeEvent, critiqueJoke);
+jokeFlow.addStep(
+  {
+    inputs: [JokeEvent],
+    outputs: [StopEvent<string>],
+  },
+  critiqueJoke
+);
 ```
 
 ### Running the Workflow
@@ -90,42 +114,25 @@ console.log(result.data.result);
 
 Lastly, we run the workflow. The `.run()` method is async, so we use await here to wait for the result.
 
-### Validating Workflows
-
-To tell the workflow what events are produced by each step, you can optionally provide a third argument to `addStep` to specify the output event type:
-
-```typescript
-jokeFlow.addStep(StartEvent, generateJoke, { outputs: JokeEvent });
-jokeFlow.addStep(JokeEvent, critiqueJoke, { outputs: StopEvent });
-```
+## Working with Shared Context/State
 
-To validate a workflow, you need to call the `validate` method:
+Optionally, you can choose to use a shared context between steps by specifying a context type when creating the workflow. Here's an example where multiple steps access a shared state:
 
 ```typescript
-jokeFlow.validate();
-```
+import { HandlerContext } from "@llamaindex/workflow";
 
-To automatically validate a workflow when you run it, you can set the `validate` flag to `true` at initialization:
-
-```typescript
-const jokeFlow = new Workflow({ verbose: true, validate: true });
-```
+type MyContextData = {
+  query: string;
+  intermediateResults: any[];
+}
 
-## Working with Global Context/State
-
-Optionally, you can choose to use global context between steps. For example, maybe multiple steps access the original `query` input from the user. You can store this in global context so that every step has access.
-
-```typescript
-import { Context } from "llamaindex";
-
-const query = async (context: Context, ev: MyEvent) => {
+const query = async (context: HandlerContext<MyContextData>, ev: MyEvent) => {
   // get the query from the context
-  const query = context.get("query");
+  const query = context.data.query;
   // do something with context and event
   const val = ...
-  const result = ...
   // store in context
-  context.set("key", val);
+  context.data.intermediateResults.push(val);
 
   return new StopEvent({ result });
 };
@@ -138,28 +145,15 @@ The context does more than just hold data, it also provides utilities to buffer
 For example, you might have a step that waits for a query and retrieved nodes before synthesizing a response:
 
 ```typescript
-const synthesize = async (context: Context, ev: QueryEvent | RetrieveEvent) => {
-  const events = context.collectEvents(ev, [QueryEvent | RetrieveEvent]);
-  if (!events) {
-    return;
-  }
-  const prompt = events
-    .map((event) => {
-      if (event instanceof QueryEvent) {
-        return `Answer this query using the context provided: ${event.data.query}`;
-      } else if (event instanceof RetrieveEvent) {
-        return `Context: ${event.data.context}`;
-      }
-      return "";
-    })
-    .join("\n");
-
+const synthesize = async (context: Context, ev1: QueryEvent, ev2: RetrieveEvent) => {
+  const subPrompts = [`Answer this query using the context provided: ${ev1.data.query}`, `Context: ${ev2.data.context}`];
+  const prompt = subPrompts.join("\n");
   const response = await llm.complete({ prompt });
   return new StopEvent({ result: response.text });
 };
 ```
 
-Using `ctx.collectEvents()` we can buffer and wait for ALL expected events to arrive. This function will only return events (in the requested order) once all events have arrived.
+Passing multiple events, we can buffer and wait for ALL expected events to arrive. The receiving step function will only be called once all events have arrived.
 
 ## Manually Triggering Events
 
-- 
GitLab