mirror of
https://github.com/run-llama/workflows-ts.git
synced 2026-06-30 21:57:58 -04:00
chore: clean up demo (#185)
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
This commit is contained in:
@@ -31,7 +31,7 @@ jobs:
|
||||
run: pnpm install
|
||||
|
||||
- name: Run build
|
||||
run: pnpm run build
|
||||
run: pnpm run build --force
|
||||
|
||||
- name: Pre Release
|
||||
run: pnpx pkg-pr-new publish --pnpm ./packages/*
|
||||
|
||||
@@ -25,6 +25,10 @@ bun add @llamaindex/workflow-core
|
||||
deno add npm:@llamaindex/workflow-core
|
||||
```
|
||||
|
||||
### Demos
|
||||
|
||||
For examples, check out the [demo folder](./demo).
|
||||
|
||||
### First, define events
|
||||
|
||||
```ts
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# LlamaIndex Workflows TS Demos
|
||||
|
||||
This directory contains various demo applications showcasing different aspects of the LlamaIndex Workflows TS engine across different runtimes and frameworks. Each demo is **standalone** and can be run independently with `npm install` and the appropriate start command.
|
||||
|
||||
## Demos
|
||||
|
||||
### 🌐 **Browser** - Frontend-only workflow execution
|
||||
- **Location**: `./browser/`
|
||||
- **Tech**: React + Vite + TypeScript
|
||||
- **Features**: Client-side workflow execution, React integration
|
||||
- **Run**: `npm install && npm run dev`
|
||||
|
||||
### ☁️ **Cloudflare Workers** - Edge runtime workflows
|
||||
- **Location**: `./cloudflare/`
|
||||
- **Tech**: Cloudflare Workers + Hono + Wrangler
|
||||
- **Features**: Edge computing, serverless workflows
|
||||
- **Run**: `npm install && npm run dev`
|
||||
|
||||
### 🦕 **Deno** - Deno runtime integration
|
||||
- **Location**: `./deno/`
|
||||
- **Tech**: Deno + JSR + npm compatibility
|
||||
- **Features**: Native Deno support, JSR imports, testing
|
||||
- **Run**: `deno task dev`
|
||||
|
||||
### 🚀 **Express** - Client-server architecture
|
||||
- **Location**: `./express/`
|
||||
- **Tech**: Express.js + TypeScript + OpenAI
|
||||
- **Features**: REST API, human-in-the-loop, state snapshots
|
||||
- **Run**: `npm install && npm run server`
|
||||
|
||||
### ⚡ **Hono** - Lightweight web framework
|
||||
- **Location**: `./hono/`
|
||||
- **Tech**: Hono + Node.js + OpenAI
|
||||
- **Features**: Fast web framework, AI agents, HITL workflows
|
||||
- **Run**: `npm install && npm run dev`
|
||||
|
||||
### 📦 **Node.js** - Comprehensive Node.js examples
|
||||
- **Location**: `./node/`
|
||||
- **Tech**: Node.js + TypeScript + various libraries
|
||||
- **Features**: Multiple patterns, RxJS, MCP, file parsing
|
||||
- **Run**: `npm install && npm run basic`
|
||||
|
||||
### ⚛️ **Next.js** - Full-stack React application
|
||||
- **Location**: `./next/`
|
||||
- **Tech**: Next.js + React + Tailwind CSS
|
||||
- **Features**: PDF processing, AI workflows, modern UI
|
||||
- **Run**: `npm install && npm run dev`
|
||||
|
||||
### 🔍 **Trace Events** - OpenTelemetry integration
|
||||
- **Location**: `./trace-events/`
|
||||
- **Tech**: Node.js + OpenTelemetry + TypeScript
|
||||
- **Features**: Workflow tracing, observability, debugging
|
||||
- **Run**: `npm install && npm run dev`
|
||||
|
||||
### 📊 **Visualization** - Workflow visualization
|
||||
- **Location**: `./visualization/`
|
||||
- **Tech**: React + Vite + D3.js
|
||||
- **Features**: Workflow graph visualization, interactive UI
|
||||
- **Run**: `npm install && npm run dev`
|
||||
|
||||
+1
-4
@@ -2,9 +2,6 @@
|
||||
"root": false,
|
||||
"extends": "//",
|
||||
"files": {
|
||||
"includes": ["src/**", "tests/**"]
|
||||
},
|
||||
"linter": {
|
||||
"enabled": false
|
||||
"includes": ["src/**", "tests/**", "!**/.next"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "browser",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"name": "demo-browser",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llamaindex/workflow-core": "latest",
|
||||
"@llamaindex/workflow-core": "^1.3.3",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
},
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import llamaindexLogo from "/llamaindex.svg";
|
||||
import reactLogo from "./assets/react.svg";
|
||||
import "./App.css";
|
||||
import {
|
||||
createWorkflow,
|
||||
getContext,
|
||||
workflowEvent,
|
||||
} from "@llamaindex/workflow-core";
|
||||
import { runWorkflow } from "@llamaindex/workflow-core/stream/run";
|
||||
import { createWorkflow, workflowEvent } from "@llamaindex/workflow-core";
|
||||
import { Suspense } from "react";
|
||||
|
||||
const startEvent = workflowEvent();
|
||||
@@ -14,14 +9,16 @@ const stopEvent = workflowEvent<string>();
|
||||
|
||||
const workflow = createWorkflow();
|
||||
|
||||
workflow.handle([startEvent], () => {
|
||||
workflow.handle([startEvent], (context) => {
|
||||
setTimeout(() => {
|
||||
const context = getContext();
|
||||
context.sendEvent(stopEvent.with("Hello, World!"));
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
const promise = runWorkflow(workflow, startEvent.with(), stopEvent);
|
||||
const context = workflow.createContext();
|
||||
context.sendEvent(startEvent.with());
|
||||
|
||||
const events = await context.stream.until(stopEvent).toArray();
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -38,7 +35,7 @@ function App() {
|
||||
<div className="card">
|
||||
<p>
|
||||
<Suspense fallback="Loading...">
|
||||
{promise.then(({ data }) => data)}
|
||||
{events.map(({ data }) => data)}
|
||||
</Suspense>
|
||||
</p>
|
||||
<p>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App.tsx";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
# Cloudflare Workers Workflow Demo
|
||||
|
||||
This demo shows how to use the LlamaIndex Workflows TS engine with Cloudflare Workers, demonstrating a simple workflow that processes user input and returns a response.
|
||||
|
||||
> **Note**: This demo is part of the `cloudflare-workers-openapi` package and showcases basic workflow integration with Cloudflare Workers.
|
||||
|
||||
## Overview
|
||||
|
||||
The demo creates a Cloudflare Worker that:
|
||||
- Serves a simple HTML form interface
|
||||
- Handles POST requests to execute workflows
|
||||
- Uses Hono as the web framework
|
||||
- Demonstrates basic workflow event handling
|
||||
|
||||
## Architecture
|
||||
|
||||
The demo consists of:
|
||||
|
||||
- **`src/index.ts`** - Main Cloudflare Worker entry point with Hono app
|
||||
- **`wrangler.jsonc`** - Cloudflare Workers configuration
|
||||
- **`package.json`** - Dependencies and scripts
|
||||
|
||||
## How it works
|
||||
|
||||
1. **User visits the page** - The worker serves an HTML form with an input field
|
||||
2. **User submits form** - JavaScript sends a POST request to `/workflow` with the input data
|
||||
3. **Workflow execution** - The worker processes the input through a simple workflow:
|
||||
- `startEvent` receives the user input
|
||||
- Handler processes the input and emits a `stopEvent` with a greeting
|
||||
4. **Response** - The worker returns the workflow result as JSON
|
||||
|
||||
## Workflow Definition
|
||||
|
||||
```typescript
|
||||
const startEvent = workflowEvent<string>();
|
||||
const stopEvent = workflowEvent<string>();
|
||||
const workflow = createWorkflow();
|
||||
|
||||
workflow.handle([startEvent], (_context, { data }) => {
|
||||
return stopEvent.with(`hello, ${data}!`);
|
||||
});
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18+
|
||||
- npm (or pnpm)
|
||||
- Cloudflare account (for deployment)
|
||||
|
||||
### Local Development
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Start the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This will start the Wrangler development server, typically at `http://localhost:8787`
|
||||
|
||||
3. Open your browser and navigate to the local URL
|
||||
|
||||
4. Enter a name in the form and click "Send event" to test the workflow
|
||||
|
||||
### Deployment
|
||||
|
||||
1. Configure your Cloudflare Worker (if not already done):
|
||||
|
||||
```bash
|
||||
npm run cf-typegen
|
||||
```
|
||||
|
||||
2. Deploy to Cloudflare Workers:
|
||||
|
||||
```bash
|
||||
npm run deploy
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
- **`GET /`** - Serves the HTML form interface
|
||||
- **`POST /workflow`** - Executes the workflow with the provided input data
|
||||
|
||||
## Configuration
|
||||
|
||||
The `wrangler.jsonc` file contains the Cloudflare Workers configuration:
|
||||
|
||||
- **`name`**: Worker name (currently "workspace-worker")
|
||||
- **`main`**: Entry point file (`src/index.ts`)
|
||||
- **`compatibility_flags`**: Enables Node.js compatibility features
|
||||
- **`compatibility_date`**: Sets the compatibility date for Cloudflare Workers features
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **`@llamaindex/workflow-core`** - Core workflow engine
|
||||
- **`hono`** - Lightweight web framework for Cloudflare Workers
|
||||
- **`@cloudflare/workers-types`** - TypeScript types for Cloudflare Workers
|
||||
- **`wrangler`** - Cloudflare Workers CLI tool
|
||||
|
||||
## Key Features Demonstrated
|
||||
|
||||
- **Event-driven workflows** - Simple event handling with type safety
|
||||
- **Cloudflare Workers integration** - Running workflows in the edge runtime
|
||||
- **Hono framework** - Clean, minimal web framework for Workers
|
||||
- **TypeScript support** - Full type safety throughout the application
|
||||
- **Client-side interaction** - JavaScript form handling with fetch API
|
||||
@@ -5,11 +5,10 @@
|
||||
"scripts": {
|
||||
"deploy": "wrangler deploy",
|
||||
"dev": "wrangler dev",
|
||||
"start": "wrangler dev",
|
||||
"cf-typegen": "wrangler types"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llamaindex/workflow-core": "latest",
|
||||
"@llamaindex/workflow-core": "^1.3.3",
|
||||
"hono": "^4.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* For more details on how to configure Wrangler, refer to:
|
||||
* https://developers.cloudflare.com/workers/wrangler/configuration/
|
||||
* For testing locally, add "nodejs_compat" to the compatibility_flags.
|
||||
*/
|
||||
{
|
||||
"$schema": "node_modules/wrangler/config-schema.json",
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Deno Workflow Demo
|
||||
|
||||
Simple workflow demo using LlamaIndex Workflows TS with Deno runtime.
|
||||
|
||||
## Files
|
||||
|
||||
- `main.ts` - Workflow definition and execution
|
||||
- `main_test.ts` - Deno test suite
|
||||
- `deno.json` - Deno configuration
|
||||
|
||||
## How it works
|
||||
|
||||
1. Creates workflow with start/end events
|
||||
2. Uses setTimeout for async processing
|
||||
3. Sends "Hello World!" message
|
||||
4. Tests workflow with Deno streams
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Run workflow
|
||||
deno run main.ts
|
||||
|
||||
# Run with watch mode
|
||||
deno task dev
|
||||
|
||||
# Run tests
|
||||
deno test
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `@llamaindex/workflow-core` - Workflow engine (npm)
|
||||
- `@std/assert` - Deno assertions (JSR)
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Deno Integration** - Native runtime support
|
||||
- **JSR Imports** - JavaScript Registry
|
||||
- **NPM Compatibility** - npm packages in Deno
|
||||
- **Stream Testing** - Native TransformStream/WritableStream
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
ChatCompletionMessageFunctionToolCall as ToolCall,
|
||||
ChatCompletionToolMessageParam as ToolResponseMessage,
|
||||
} from "openai/resources/chat/completions";
|
||||
import * as readline from "readline/promises";
|
||||
import * as readline from "node:readline/promises";
|
||||
|
||||
type AgentWorkflowState = {
|
||||
expectedToolCount: number;
|
||||
@@ -195,11 +195,14 @@ workflow.handle([toolCallEvent], async (context, event) => {
|
||||
|
||||
workflow.handle([humanResponseEvent], async (context, event) => {
|
||||
const { sendEvent, state } = context;
|
||||
if (!state.humanToolId) {
|
||||
throw new Error("No human tool id");
|
||||
}
|
||||
sendEvent(
|
||||
toolResponseEvent.with({
|
||||
role: "tool",
|
||||
content: "My name is " + event.data,
|
||||
tool_call_id: state.humanToolId!,
|
||||
content: `My name is ${event.data}`,
|
||||
tool_call_id: state.humanToolId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as readline from "readline/promises";
|
||||
import * as readline from "node:readline/promises";
|
||||
|
||||
const SERVER_URL = "http://localhost:3000";
|
||||
|
||||
async function makeRequest(endpoint: string, data: any) {
|
||||
async function makeRequest(endpoint: string, data: object) {
|
||||
const response = await fetch(`${SERVER_URL}${endpoint}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
||||
@@ -185,11 +185,14 @@ workflow.handle([toolCallEvent], async (context, event) => {
|
||||
|
||||
workflow.handle([humanResponseEvent], async (context, event) => {
|
||||
const { sendEvent, state } = context;
|
||||
if (!state.humanToolId) {
|
||||
throw new Error("No human tool id");
|
||||
}
|
||||
sendEvent(
|
||||
toolResponseEvent.with({
|
||||
role: "tool",
|
||||
content: "My name is " + event.data,
|
||||
tool_call_id: state.humanToolId!,
|
||||
content: `My name is ${event.data}`,
|
||||
tool_call_id: state.humanToolId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
{
|
||||
"name": "@llamaindex/express-demo",
|
||||
"private": true,
|
||||
"version": "1.0.4",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"name": "demo-express",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"server": "tsx 5-server.ts",
|
||||
"client": "tsx 5-client.ts",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"dev": "tsx 5-server.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.15.0",
|
||||
"dependencies": {
|
||||
"@llamaindex/workflow-core": "workspace:*",
|
||||
"@llamaindex/workflow-core": "^1.3.3",
|
||||
"express": "^5.1.0",
|
||||
"openai": "^5.7.0",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^24.0.4",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"tsx": "^4.20.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
# Hono Workflow Demo
|
||||
|
||||
This demo shows how to use the LlamaIndex Workflows TS engine with Hono web framework, demonstrating AI agent workflows with tool calling and human-in-the-loop interactions.
|
||||
|
||||
## Overview
|
||||
|
||||
The demo creates a Hono server that:
|
||||
- Handles AI agent workflows with tool calling
|
||||
- Implements human-in-the-loop (HITL) workflows with state snapshots
|
||||
- Uses OpenAI for AI completions and tool execution
|
||||
- Demonstrates workflow state management and resumption
|
||||
|
||||
## Architecture
|
||||
|
||||
The demo consists of:
|
||||
|
||||
- **`app.ts`** - Main Hono server with workflow endpoints
|
||||
- **`package.json`** - Dependencies and scripts
|
||||
- **`../workflows/tool-call-agent.ts`** - AI agent workflow with tool calling
|
||||
- **`../workflows/human-in-the-loop.ts`** - HITL workflow with state management
|
||||
|
||||
## How it works
|
||||
|
||||
1. **Tool Call Workflow** - AI agent processes user input and can call tools (like weather API)
|
||||
2. **Human-in-the-Loop Workflow** - AI can request human input and resume from snapshots
|
||||
3. **State Management** - Workflows can be paused, snapshotted, and resumed
|
||||
4. **Response** - Server returns workflow results as JSON
|
||||
|
||||
## Workflow Definitions
|
||||
|
||||
### Tool Call Agent
|
||||
```typescript
|
||||
const startEvent = workflowEvent<string>();
|
||||
const toolCallEvent = workflowEvent<ChatCompletionMessageToolCall>();
|
||||
const stopEvent = workflowEvent<string>();
|
||||
|
||||
workflow.handle([startEvent], async (context, { data }) => {
|
||||
// Process with OpenAI and handle tool calls
|
||||
});
|
||||
```
|
||||
|
||||
### Human-in-the-Loop
|
||||
```typescript
|
||||
const { withState } = createStatefulMiddleware();
|
||||
const workflow = withState(createWorkflow());
|
||||
|
||||
workflow.handle([startEvent], async (context, { data }) => {
|
||||
// AI can request human input via humanRequestEvent
|
||||
});
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18+
|
||||
- npm (or pnpm)
|
||||
- OpenAI API key
|
||||
|
||||
### Local Development
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Set your OpenAI API key:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=your-api-key-here
|
||||
```
|
||||
|
||||
3. Start the development server:
|
||||
|
||||
```bash
|
||||
npx tsx app.ts
|
||||
```
|
||||
|
||||
This will start the server at `http://localhost:3000`
|
||||
|
||||
4. Test the endpoints with curl or a REST client
|
||||
|
||||
### API Endpoints
|
||||
|
||||
- **`POST /workflow`** - Execute tool call agent workflow
|
||||
- **`POST /human-in-the-loop`** - Execute HITL workflow with state snapshots
|
||||
|
||||
### Example Usage
|
||||
|
||||
```bash
|
||||
# Tool call workflow
|
||||
curl -X POST http://localhost:3000/workflow \
|
||||
-H "Content-Type: text/plain" \
|
||||
-d "What's the weather like in Tokyo?"
|
||||
|
||||
# Human-in-the-loop workflow
|
||||
curl -X POST http://localhost:3000/human-in-the-loop \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"data": "Hello"}'
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The server uses:
|
||||
- **Hono** - Lightweight web framework
|
||||
- **@hono/node-server** - Node.js server adapter
|
||||
- **OpenAI** - AI completions and tool calling
|
||||
- **tsx** - TypeScript execution
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **`@llamaindex/workflow-core`** - Core workflow engine
|
||||
- **`hono`** - Web framework
|
||||
- **`@hono/node-server`** - Node.js server
|
||||
- **`openai`** - OpenAI API client
|
||||
- **`tsx`** - TypeScript runner
|
||||
|
||||
## Key Features Demonstrated
|
||||
|
||||
- **AI Agent Workflows** - Tool calling with OpenAI
|
||||
- **Human-in-the-Loop** - Interactive workflows with human input
|
||||
- **State Management** - Workflow snapshots and resumption
|
||||
- **Hono Integration** - Clean web framework integration
|
||||
- **TypeScript Support** - Full type safety throughout
|
||||
+14
-8
@@ -1,11 +1,8 @@
|
||||
import { serve } from "@hono/node-server";
|
||||
import { createHonoHandler } from "@llamaindex/workflow-core/hono";
|
||||
import { Hono } from "hono";
|
||||
import {
|
||||
startEvent,
|
||||
stopEvent,
|
||||
toolCallWorkflow,
|
||||
} from "../workflows/tool-call-agent.js";
|
||||
import { startEvent, stopEvent, toolCallWorkflow } from "./tool-call-agent.js";
|
||||
import type { SnapshotData } from "@llamaindex/workflow-core/middleware/state";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
@@ -18,7 +15,10 @@ app.post(
|
||||
),
|
||||
);
|
||||
|
||||
const serializableMemoryMap = new Map<string, any>();
|
||||
const serializableMemoryMap = new Map<
|
||||
string,
|
||||
Omit<SnapshotData, "unrecoverableQueue">
|
||||
>();
|
||||
|
||||
app.post("/human-in-the-loop", async (ctx) => {
|
||||
const {
|
||||
@@ -27,12 +27,15 @@ app.post("/human-in-the-loop", async (ctx) => {
|
||||
startEvent,
|
||||
humanRequestEvent,
|
||||
humanInteractionEvent,
|
||||
} = await import("../workflows/human-in-the-loop");
|
||||
} = await import("./human-in-the-loop.js");
|
||||
const json = await ctx.req.json();
|
||||
let context: ReturnType<typeof workflow.createContext>;
|
||||
if (json.requestId) {
|
||||
const data = json.data;
|
||||
const serializable = serializableMemoryMap.get(json.requestId);
|
||||
if (!serializable) {
|
||||
throw new Error("Snapshot data not found");
|
||||
}
|
||||
context = workflow.resume(serializable);
|
||||
context.sendEvent(humanInteractionEvent.with(data));
|
||||
} else {
|
||||
@@ -62,7 +65,10 @@ app.post("/human-in-the-loop", async (ctx) => {
|
||||
.until(stopEvent)
|
||||
.toArray()
|
||||
.then((events) => {
|
||||
const stopEvent = events.at(-1)!;
|
||||
const stopEvent = events.at(-1);
|
||||
if (!stopEvent) {
|
||||
throw new Error("No stop event");
|
||||
}
|
||||
resolve(Response.json(stopEvent.data));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,7 +67,7 @@ For example, alex is from "Alexander the Great", who was a king of the ancient G
|
||||
);
|
||||
}
|
||||
}
|
||||
return stopEvent.with(response.choices[0].message.content!);
|
||||
return stopEvent.with(response.choices[0].message.content ?? "");
|
||||
});
|
||||
|
||||
workflow.handle([humanInteractionEvent], async (context, { data }) => {
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "demo-hono",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "tsx app.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.14.4",
|
||||
"@llamaindex/workflow-core": "^1.3.3",
|
||||
"hono": "^4.8.3",
|
||||
"openai": "^4.0.0",
|
||||
"zod": "^3.22.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.4",
|
||||
"tsx": "^4.20.3"
|
||||
}
|
||||
}
|
||||
@@ -70,13 +70,14 @@ toolCallWorkflow.handle([chatEvent], async (context, { data }) => {
|
||||
}),
|
||||
)
|
||||
)
|
||||
.map((list) => list.at(-1)!)
|
||||
.map((list) => list.at(-1))
|
||||
.filter((event) => event !== undefined)
|
||||
.map(({ data }) => data)
|
||||
.join("\n");
|
||||
console.log("toolcall result", result);
|
||||
sendEvent(chatEvent.with(result));
|
||||
} else {
|
||||
console.log("no choices");
|
||||
return stopEvent.with(choices[0]!.message.content!);
|
||||
return stopEvent.with(choices[0]?.message.content ?? "");
|
||||
}
|
||||
});
|
||||
@@ -26,7 +26,8 @@
|
||||
"puppeteer": "^24.19.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"tailwind-merge": "^3.3.1"
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* biome-ignore-all lint/suspicious/noUnknownAtRules: needed for Tailwind v4 custom rules */
|
||||
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ export default function HomePage() {
|
||||
<div className="space-y-4">
|
||||
<Search className="w-12 h-12 mx-auto text-muted-foreground" />
|
||||
<div className="space-y-2">
|
||||
{/* biome-ignore lint/correctness/useUniqueElementIds: used for search */}
|
||||
<Input
|
||||
id="search-docs"
|
||||
value={query}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as React from "react";
|
||||
import type * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as React from "react";
|
||||
import type * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as React from "react";
|
||||
import type * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as React from "react";
|
||||
import type * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import type * as React from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import type * as React from "react";
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import type * as React from "react";
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -52,6 +52,7 @@ function Slider({
|
||||
{Array.from({ length: _values.length }, (_, index) => (
|
||||
<SliderPrimitive.Thumb
|
||||
data-slot="slider-thumb"
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: used for slider
|
||||
key={index}
|
||||
className="border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import fs from "fs/promises";
|
||||
import fs from "node:fs/promises";
|
||||
|
||||
export async function readFileBlob(path: string): Promise<Blob> {
|
||||
const content = await fs.readFile(path);
|
||||
|
||||
@@ -7,10 +7,6 @@ type ReportContent = {
|
||||
reportTitle: string | null;
|
||||
};
|
||||
|
||||
const client = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY!,
|
||||
});
|
||||
|
||||
const QueryApprove = z.object({
|
||||
isNewsRelatedQuery: z.boolean(),
|
||||
enhancedQuery: z.string(),
|
||||
@@ -21,7 +17,17 @@ const Report = z.object({
|
||||
reportContent: z.string(),
|
||||
});
|
||||
|
||||
function getLLM() {
|
||||
if (!process.env.OPENAI_API_KEY) {
|
||||
throw new Error("OPENAI_API_KEY is not set in the environment variables");
|
||||
}
|
||||
return new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
});
|
||||
}
|
||||
|
||||
export async function webSearch(textInput: string): Promise<string> {
|
||||
const client = getLLM();
|
||||
const response = await client.responses.create({
|
||||
model: "gpt-4.1",
|
||||
tools: [{ type: "web_search" }],
|
||||
@@ -32,6 +38,7 @@ export async function webSearch(textInput: string): Promise<string> {
|
||||
}
|
||||
|
||||
export async function evaluateQueryAndEnhance(text: string): Promise<string> {
|
||||
const client = getLLM();
|
||||
const response = await client.responses.parse({
|
||||
model: "gpt-4.1",
|
||||
input: [
|
||||
@@ -40,7 +47,7 @@ export async function evaluateQueryAndEnhance(text: string): Promise<string> {
|
||||
content:
|
||||
"Please evaluate the query by the user, identifying whether or not it is related to searching the news, and, if so, produce an enhanced query. If the user's query is not related to news, leave the enhanced query simply as an empty string.",
|
||||
},
|
||||
{ role: "user", content: "Evaluate the following query: '" + text + "'" },
|
||||
{ role: "user", content: `Evaluate the following query: '${text}'` },
|
||||
],
|
||||
text: {
|
||||
format: zodTextFormat(QueryApprove, "query_approve"),
|
||||
@@ -62,6 +69,7 @@ export async function evaluateQueryAndEnhance(text: string): Promise<string> {
|
||||
export async function createReport(
|
||||
webSearchText: string,
|
||||
): Promise<ReportContent> {
|
||||
const client = getLLM();
|
||||
const response = await client.responses.parse({
|
||||
model: "gpt-4.1",
|
||||
input: [
|
||||
@@ -72,7 +80,7 @@ export async function createReport(
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: "Evaluate the following query: '" + webSearchText + "'",
|
||||
content: `Evaluate the following query: '${webSearchText}'`,
|
||||
},
|
||||
],
|
||||
text: {
|
||||
@@ -83,11 +91,7 @@ export async function createReport(
|
||||
const generatedReport = response.output_parsed;
|
||||
if (generatedReport) {
|
||||
return {
|
||||
reportContent:
|
||||
"# " +
|
||||
generatedReport.reportTitle +
|
||||
"\n\n" +
|
||||
generatedReport.reportContent,
|
||||
reportContent: `# ${generatedReport.reportTitle}\n\n${generatedReport.reportContent}`,
|
||||
reportTitle: generatedReport.reportTitle,
|
||||
} as ReportContent;
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
# Node.js Workflow Demos
|
||||
|
||||
This directory contains various Node.js examples demonstrating different aspects of the LlamaIndex Workflows TS engine.
|
||||
|
||||
## Overview
|
||||
|
||||
The demos showcase:
|
||||
- Basic workflow patterns with parallel processing
|
||||
- AI agent workflows with tool calling
|
||||
- File parsing workflows with state management
|
||||
- Human-in-the-loop interactions
|
||||
- RxJS integration for reactive programming
|
||||
- MCP (Model Context Protocol) server integration
|
||||
|
||||
## Files
|
||||
|
||||
- **`basic.ts`** - Basic workflow with parallel branch processing
|
||||
- **`tool-call-agent.ts`** - AI agent with tool calling capabilities
|
||||
- **`llama-parse-workflow.ts`** - File parsing using LlamaIndex
|
||||
- **`name-ask-readline.ts`** - Human-in-the-loop with readline interface
|
||||
- **`file-parse-promise.ts`** - File parsing with promise-based approach
|
||||
- **`file-parse-rxjs.ts`** - File parsing with RxJS reactive streams
|
||||
- **`mcp-file-parse-tool.ts`** - MCP server with file parsing workflow
|
||||
|
||||
## How it works
|
||||
|
||||
### Basic Workflow (`basic.ts`)
|
||||
1. **Parallel Processing** - Emits multiple events simultaneously
|
||||
2. **Branch Handling** - Each branch processes independently
|
||||
3. **Result Aggregation** - Collects results from all branches
|
||||
4. **Stream Processing** - Uses Node.js streams for event handling
|
||||
|
||||
### AI Agent Workflow (`tool-call-agent.ts`)
|
||||
1. **Tool Calling** - AI can call external tools (weather API)
|
||||
2. **Workflow Execution** - Uses `runWorkflow` helper for simple execution
|
||||
3. **Response Handling** - Returns AI-generated responses
|
||||
|
||||
### File Parsing (`file-parse-*.ts`)
|
||||
1. **Directory Scanning** - Recursively processes files
|
||||
2. **State Management** - Tracks parsing progress and results
|
||||
3. **Multiple Approaches** - Promise-based and RxJS implementations
|
||||
|
||||
### Human-in-the-Loop (`name-ask-readline.ts`)
|
||||
1. **Interactive Input** - Uses readline for user interaction
|
||||
2. **Event Handling** - Listens for human request events
|
||||
3. **Workflow Resumption** - Continues after human input
|
||||
|
||||
## Usage
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18+
|
||||
- npm (or pnpm)
|
||||
- OpenAI API key (for AI workflows)
|
||||
- LlamaIndex API key (for file parsing)
|
||||
|
||||
### Basic Examples
|
||||
|
||||
```bash
|
||||
# Basic parallel workflow
|
||||
npx tsx basic.ts
|
||||
|
||||
# AI agent with tool calling
|
||||
export OPENAI_API_KEY=your-key
|
||||
npx tsx tool-call-agent.ts
|
||||
|
||||
# File parsing workflow
|
||||
export LLAMA_CLOUD_API=your-key
|
||||
npx tsx llama-parse-workflow.ts path/to/file.pdf
|
||||
|
||||
# Human-in-the-loop interaction
|
||||
npx tsx name-ask-readline.ts
|
||||
```
|
||||
|
||||
### Advanced Examples
|
||||
|
||||
```bash
|
||||
# File parsing with promises
|
||||
npx tsx file-parse-promise.ts
|
||||
|
||||
# File parsing with RxJS
|
||||
npx tsx file-parse-rxjs.ts
|
||||
|
||||
# MCP server
|
||||
npx tsx mcp-file-parse-tool.ts
|
||||
```
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Basic Parallel Processing
|
||||
```typescript
|
||||
const workflow = createWorkflow();
|
||||
workflow.handle([startEvent], async () => {
|
||||
const { sendEvent, stream } = getContext();
|
||||
sendEvent(branchAEvent.with("Branch A"));
|
||||
sendEvent(branchBEvent.with("Branch B"));
|
||||
sendEvent(branchCEvent.with("Branch C"));
|
||||
|
||||
const results = await stream.filter(branchCompleteEvent).take(3).toArray();
|
||||
return allCompleteEvent.with(results.map((e) => e.data).join(", "));
|
||||
});
|
||||
```
|
||||
|
||||
### AI Agent with Tools
|
||||
```typescript
|
||||
import { runWorkflow } from "@llamaindex/workflow-core/stream/run";
|
||||
|
||||
runWorkflow(
|
||||
toolCallWorkflow,
|
||||
startEvent.with("what is weather today"),
|
||||
stopEvent,
|
||||
).then(({ data }) => {
|
||||
console.log("AI response", data);
|
||||
});
|
||||
```
|
||||
|
||||
### Human-in-the-Loop
|
||||
```typescript
|
||||
stream.on(humanRequestEvent, async (event) => {
|
||||
const name = await input({
|
||||
message: JSON.parse(event.data).message,
|
||||
});
|
||||
sendEvent(humanInteractionEvent.with(name));
|
||||
});
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **`@llamaindex/workflow-core`** - Core workflow engine
|
||||
- **`rxjs`** - Reactive programming (file-parse-rxjs.ts)
|
||||
- **`@inquirer/prompts`** - Interactive prompts (name-ask-readline.ts)
|
||||
- **`@modelcontextprotocol/sdk`** - MCP server (mcp-file-parse-tool.ts)
|
||||
- **`zod`** - Schema validation (mcp-file-parse-tool.ts)
|
||||
|
||||
## Key Features Demonstrated
|
||||
|
||||
- **Parallel Processing** - Multiple workflow branches
|
||||
- **AI Integration** - OpenAI tool calling
|
||||
- **State Management** - Workflow state tracking
|
||||
- **Human Interaction** - Interactive workflows
|
||||
- **Reactive Programming** - RxJS integration
|
||||
- **Stream Processing** - Node.js stream handling
|
||||
- **MCP Integration** - Model Context Protocol server
|
||||
- **File Processing** - Document parsing workflows
|
||||
+4
-5
@@ -4,7 +4,6 @@ import {
|
||||
getContext,
|
||||
workflowEvent,
|
||||
} from "@llamaindex/workflow-core";
|
||||
import { collect } from "@llamaindex/workflow-core/stream/consumer";
|
||||
|
||||
//#region define workflow events
|
||||
const startEvent = workflowEvent<string>();
|
||||
@@ -30,19 +29,19 @@ workflow.handle([startEvent], async () => {
|
||||
return allCompleteEvent.with(results.map((e) => e.data).join(", "));
|
||||
});
|
||||
|
||||
workflow.handle([branchAEvent], (context, branchA) => {
|
||||
workflow.handle([branchAEvent], (_context, branchA) => {
|
||||
return branchCompleteEvent.with(branchA.data);
|
||||
});
|
||||
|
||||
workflow.handle([branchBEvent], (context, branchB) => {
|
||||
workflow.handle([branchBEvent], (_context, branchB) => {
|
||||
return branchCompleteEvent.with(branchB.data);
|
||||
});
|
||||
|
||||
workflow.handle([branchCEvent], (context, branchC) => {
|
||||
workflow.handle([branchCEvent], (_context, branchC) => {
|
||||
return branchCompleteEvent.with(branchC.data);
|
||||
});
|
||||
|
||||
workflow.handle([allCompleteEvent], (context, allComplete) => {
|
||||
workflow.handle([allCompleteEvent], (_context, allComplete) => {
|
||||
return stopEvent.with(allComplete.data);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
fileParseWorkflow,
|
||||
startEvent,
|
||||
stopEvent,
|
||||
} from "../workflows/file-parse-agent.js";
|
||||
} from "./workflows/file-parse-agent.js";
|
||||
|
||||
const directory = "..";
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
fileParseWorkflow,
|
||||
messageEvent,
|
||||
startEvent,
|
||||
} from "../workflows/file-parse-agent.js";
|
||||
} from "./workflows/file-parse-agent.js";
|
||||
|
||||
const directory = "..";
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ import {
|
||||
llamaParseWorkflow,
|
||||
startEvent,
|
||||
stopEvent,
|
||||
} from "../workflows/llama-parse-workflow.js";
|
||||
} from "./workflows/llama-parse-workflow.js";
|
||||
|
||||
runWorkflow(
|
||||
llamaParseWorkflow,
|
||||
startEvent.with({
|
||||
inputFile: process.argv[2],
|
||||
apiKey: process.env.LLAMA_CLOUD_API!,
|
||||
apiKey: process.env.LLAMA_CLOUD_API,
|
||||
}),
|
||||
stopEvent,
|
||||
).then(({ data }) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mcpTool } from "@llamaindex/workflow-core/mcp";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { z } from "zod";
|
||||
import { fileParseWorkflow } from "../workflows/file-parse-agent.js";
|
||||
import { fileParseWorkflow } from "./workflows/file-parse-agent.js";
|
||||
|
||||
const server = new McpServer({
|
||||
name: "Demo",
|
||||
@@ -21,7 +21,7 @@ const wrappedWorkflow = createWorkflow();
|
||||
|
||||
wrappedWorkflow.handle(
|
||||
[startEvent],
|
||||
async (context, { data: { filePath } }) => {
|
||||
async (_context, { data: { filePath } }) => {
|
||||
const { stream, sendEvent, state } = fileParseWorkflow.createContext();
|
||||
sendEvent(startEvent.with({ filePath }));
|
||||
await stream.until(stopEvent).toArray();
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
startEvent,
|
||||
stopEvent,
|
||||
workflow,
|
||||
} from "../workflows/human-in-the-loop";
|
||||
} from "./workflows/human-in-the-loop";
|
||||
|
||||
const name = await input({
|
||||
message: "What is your name?",
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "demo-node",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"basic": "tsx basic.ts",
|
||||
"tool-call": "tsx tool-call-agent.ts",
|
||||
"llama-parse": "tsx llama-parse-workflow.ts",
|
||||
"name-ask": "tsx name-ask-readline.ts",
|
||||
"file-parse-promise": "tsx file-parse-promise.ts",
|
||||
"file-parse-rxjs": "tsx file-parse-rxjs.ts",
|
||||
"mcp-server": "tsx mcp-file-parse-tool.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llamaindex/workflow-core": "^1.3.3",
|
||||
"@inquirer/prompts": "^7.8.6",
|
||||
"@modelcontextprotocol/sdk": "^1.18.0",
|
||||
"openai": "^4.0.0",
|
||||
"rxjs": "^7.8.0",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.4",
|
||||
"tsx": "^4.20.3"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
startEvent,
|
||||
stopEvent,
|
||||
toolCallWorkflow,
|
||||
} from "../workflows/tool-call-agent.js";
|
||||
} from "./workflows/tool-call-agent.js";
|
||||
|
||||
runWorkflow(
|
||||
toolCallWorkflow,
|
||||
|
||||
@@ -24,7 +24,7 @@ export const stopEvent = workflowEvent({
|
||||
debugLabel: "stop",
|
||||
});
|
||||
|
||||
const { withState, getContext } = createStatefulMiddleware(() => ({
|
||||
const { withState } = createStatefulMiddleware(() => ({
|
||||
output: "",
|
||||
apiKey: "",
|
||||
}));
|
||||
@@ -53,7 +53,7 @@ fileParseWorkflow.handle(
|
||||
context.sendEvent(messageEvent.with(dir));
|
||||
const { sendEvent } = context;
|
||||
const items = await readdir(dir);
|
||||
context.state.output += " ".repeat(tab) + dir + "\n";
|
||||
context.state.output += `${" ".repeat(tab)}${dir}\n`;
|
||||
await Promise.all(
|
||||
items.map(async (item) => {
|
||||
const filePath = resolve(dir, item);
|
||||
@@ -93,7 +93,7 @@ fileParseWorkflow.handle(
|
||||
lock.finish = true;
|
||||
}
|
||||
context.sendEvent(messageEvent.with(filePath));
|
||||
context.state.output += " ".repeat(tab) + filePath + "\n";
|
||||
context.state.output += `${" ".repeat(tab)}${filePath}\n`;
|
||||
return readResultEvent.with();
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,85 @@
|
||||
import { createWorkflow, workflowEvent } from "@llamaindex/workflow-core";
|
||||
import { createStatefulMiddleware } from "@llamaindex/workflow-core/middleware/state";
|
||||
import { OpenAI } from "openai";
|
||||
|
||||
const openai = new OpenAI();
|
||||
|
||||
const { withState } = createStatefulMiddleware();
|
||||
const workflow = withState(createWorkflow());
|
||||
|
||||
const startEvent = workflowEvent<string>({
|
||||
debugLabel: "start",
|
||||
});
|
||||
const humanInteractionEvent = workflowEvent<string>({
|
||||
debugLabel: "humanInteraction",
|
||||
});
|
||||
const humanRequestEvent = workflowEvent<string>({
|
||||
debugLabel: "humanRequest",
|
||||
});
|
||||
const stopEvent = workflowEvent<string>({
|
||||
debugLabel: "stop",
|
||||
});
|
||||
|
||||
workflow.handle([startEvent], async (context, { data }) => {
|
||||
const response = await openai.chat.completions.create({
|
||||
stream: false,
|
||||
model: "gpt-4.1",
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: `You are a helpful assistant.
|
||||
If user doesn't provide his/her name, call ask_name tool to ask for user's name.
|
||||
Otherwise, analyze user's name with a good meaning and return the analysis.
|
||||
|
||||
For example, alex is from "Alexander the Great", who was a king of the ancient Greek kingdom of Macedon and one of history's greatest military minds.`,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: data,
|
||||
},
|
||||
],
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "ask_name",
|
||||
description: "Ask for user's name",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
description: "The message to ask for user's name",
|
||||
},
|
||||
},
|
||||
required: ["message"],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const tools = response.choices[0].message.tool_calls;
|
||||
if (tools && tools.length > 0) {
|
||||
const askName = tools.find((tool) => tool.function.name === "ask_name");
|
||||
if (askName) {
|
||||
return context.sendEvent(
|
||||
humanRequestEvent.with(askName.function.arguments),
|
||||
);
|
||||
}
|
||||
}
|
||||
return stopEvent.with(response.choices[0].message.content ?? "");
|
||||
});
|
||||
|
||||
workflow.handle([humanInteractionEvent], async (context, { data }) => {
|
||||
const { sendEvent } = context;
|
||||
// going back to the start event
|
||||
sendEvent(startEvent.with(data));
|
||||
});
|
||||
|
||||
export {
|
||||
workflow,
|
||||
startEvent,
|
||||
humanInteractionEvent,
|
||||
humanRequestEvent,
|
||||
stopEvent,
|
||||
};
|
||||
+1
-1
@@ -95,7 +95,7 @@ const context = llamaParseWorkflow.createContext();
|
||||
context.sendEvent(
|
||||
startEvent.with({
|
||||
inputFile: "sample.pdf",
|
||||
apiKey: process.env.LLAMA_CLOUD_API_KEY!,
|
||||
apiKey: process.env.LLAMA_CLOUD_API_KEY,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { createWorkflow, workflowEvent } from "@llamaindex/workflow-core";
|
||||
import { OpenAI } from "openai";
|
||||
import type {
|
||||
ChatCompletionMessageToolCall,
|
||||
ChatCompletionTool,
|
||||
} from "openai/resources/chat/completions/completions";
|
||||
|
||||
const llm = new OpenAI();
|
||||
const tools = [
|
||||
{
|
||||
function: {
|
||||
name: "get_weather",
|
||||
description: "Get Weather Weather",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
location: {
|
||||
type: "string",
|
||||
description: "City and country e.g. Bogotá, Colombia",
|
||||
},
|
||||
},
|
||||
required: ["location"],
|
||||
},
|
||||
},
|
||||
type: "function",
|
||||
},
|
||||
] satisfies ChatCompletionTool[];
|
||||
|
||||
export const startEvent = workflowEvent<string>();
|
||||
const chatEvent = workflowEvent<string>();
|
||||
const toolCallEvent = workflowEvent<ChatCompletionMessageToolCall>();
|
||||
const toolCallResultEvent = workflowEvent<string>();
|
||||
export const stopEvent = workflowEvent<string>();
|
||||
export const toolCallWorkflow = createWorkflow();
|
||||
toolCallWorkflow.handle([startEvent], async (context, { data }) => {
|
||||
console.log("start event");
|
||||
context.sendEvent(chatEvent.with(data));
|
||||
});
|
||||
toolCallWorkflow.handle([toolCallEvent], async () => {
|
||||
console.log("tool call event");
|
||||
return toolCallResultEvent.with("Today is sunny.");
|
||||
});
|
||||
toolCallWorkflow.handle([chatEvent], async (context, { data }) => {
|
||||
console.log("chat event");
|
||||
const { choices } = await llm.chat.completions.create({
|
||||
model: "gpt-4-turbo",
|
||||
tools,
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: "You are a helpful assistant.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: data,
|
||||
},
|
||||
],
|
||||
});
|
||||
const { sendEvent, stream } = context;
|
||||
if (
|
||||
choices[0]?.message?.tool_calls?.length &&
|
||||
choices[0].message.tool_calls.length > 0
|
||||
) {
|
||||
console.log("sending choices", choices[0].message.tool_calls);
|
||||
const result = (
|
||||
await Promise.all(
|
||||
choices[0].message.tool_calls.map(async (tool_call) => {
|
||||
sendEvent(toolCallEvent.with(tool_call));
|
||||
return stream.until(toolCallResultEvent).toArray();
|
||||
}),
|
||||
)
|
||||
)
|
||||
.map((list) => list.at(-1))
|
||||
.filter((event) => event !== undefined)
|
||||
.map(({ data }) => data)
|
||||
.join("\n");
|
||||
console.log("toolcall result", result);
|
||||
sendEvent(chatEvent.with(result));
|
||||
} else {
|
||||
console.log("no choices");
|
||||
return stopEvent.with(choices[0]?.message.content ?? "");
|
||||
}
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "demo",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.14.4",
|
||||
"@inquirer/prompts": "^7.5.3",
|
||||
"@llamaindex/workflow-core": "latest",
|
||||
"@modelcontextprotocol/sdk": "^1.13.1",
|
||||
"hono": "^4.8.3",
|
||||
"openai": "^5.7.0",
|
||||
"p-retry": "^6.2.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.67"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.4",
|
||||
"tsx": "^4.20.3"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
{
|
||||
"name": "trace-events-demo",
|
||||
"name": "demo-trace-events",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "tsx open-telemetry.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llamaindex/workflow-core": "^1.3.1",
|
||||
"@llamaindex/workflow-otel": "^1.0.1",
|
||||
"@llamaindex/workflow-core": "^1.3.3",
|
||||
"@llamaindex/workflow-otel": "^1.0.4",
|
||||
"openai": "^5.7.0",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/sdk-node": "^0.203.0",
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "bundler",
|
||||
"outDir": "./lib",
|
||||
"tsBuildInfoFile": "./lib/.tsbuildinfo",
|
||||
"lib": ["DOM", "DOM.AsyncIterable", "DOM.Iterable", "esnext"],
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["hono", "node", "workflows", "express/4-adding-hitl.ts"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "./cloudflare/tsconfig.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "demo-viz",
|
||||
"private": true,
|
||||
"name": "demo-visualization",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llamaindex/workflow-core": "^1.3.1",
|
||||
"@llamaindex/workflow-viz": "^1.0.1",
|
||||
"@llamaindex/workflow-core": "^1.3.3",
|
||||
"@llamaindex/workflow-viz": "^1.0.4",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
},
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
node_modules
|
||||
dist
|
||||
.env*
|
||||
*.tsbuildinfo
|
||||
.cache
|
||||
.DS_Store
|
||||
*.pem
|
||||
src/pages.gen.ts
|
||||
@@ -1,18 +0,0 @@
|
||||
import { defaultPlugins, defineConfig } from "@hey-api/openapi-ts";
|
||||
|
||||
export default defineConfig({
|
||||
input: "https://api.cloud.llamaindex.ai/api/openapi.json",
|
||||
output: "src/lib/api",
|
||||
plugins: [
|
||||
...defaultPlugins,
|
||||
"@hey-api/client-fetch",
|
||||
"zod",
|
||||
"@hey-api/schemas",
|
||||
"@hey-api/sdk",
|
||||
{
|
||||
enums: "javascript",
|
||||
identifierCase: "PascalCase",
|
||||
name: "@hey-api/typescript",
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"name": "waku-project",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"postinstall": "openapi-ts",
|
||||
"dev": "waku dev",
|
||||
"build": "waku build",
|
||||
"start": "waku start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llamaindex/workflow-core": "latest",
|
||||
"@llamaindex/workflow-http": "latest",
|
||||
"@neondatabase/serverless": "^1.0.1",
|
||||
"lucide-react": "^0.523.0",
|
||||
"openai": "^5.7.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-server-dom-webpack": "19.1.0",
|
||||
"stable-hash": "^0.0.6",
|
||||
"waku": "0.23.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hey-api/client-fetch": "^0.13.1",
|
||||
"@hey-api/openapi-ts": "^0.77.0",
|
||||
"@tailwindcss/postcss": "4.1.10",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"postcss": "8.5.6",
|
||||
"tailwindcss": "4.1.10",
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.6 KiB |
@@ -1,77 +0,0 @@
|
||||
"use client";
|
||||
import { createClient } from "@llamaindex/workflow-http/client";
|
||||
import { useCallback, useState } from "react";
|
||||
import * as events from "../workflow/events";
|
||||
|
||||
const { fetch } = createClient("/api/store", events);
|
||||
|
||||
export const RAG = () => {
|
||||
const [list, setList] = useState<any[]>([]);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
|
||||
const handleFileInput = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) {
|
||||
const selectedFiles = Array.from(e.target.files);
|
||||
setFile(selectedFiles[0]!);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="border-blue-400 -mx-4 mt-4 rounded-sm border p-4">
|
||||
<div>
|
||||
<input type="file" onChange={handleFileInput} id="file-upload" />
|
||||
<button
|
||||
onClick={async () => {
|
||||
fetch({
|
||||
file,
|
||||
}).then((stream) => {
|
||||
stream.forEach((event) => {
|
||||
console.log(event);
|
||||
if (event.data) {
|
||||
setList((prev) => [...prev, `${event.data}`]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
Run
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form
|
||||
action={(form) => {
|
||||
const search = form.get("search") as string;
|
||||
fetch({
|
||||
search,
|
||||
}).then((stream) => {
|
||||
stream.forEach((event) => {
|
||||
console.log(event);
|
||||
if (event.data) {
|
||||
setList((prev) => [...prev, `${event.data}`]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
name="search"
|
||||
className="border-gray-400 mt-4 w-full rounded-sm border p-2"
|
||||
placeholder="Search something..."
|
||||
/>
|
||||
<button type="submit" />
|
||||
</form>
|
||||
|
||||
{list.map((item, index) => (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
<div className="text-sm max-h-12 max-w-64 overflow-scroll">
|
||||
{item}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
export const Footer = () => {
|
||||
return (
|
||||
<footer className="p-6 lg:fixed lg:bottom-0 lg:left-0">
|
||||
<div>
|
||||
visit{" "}
|
||||
<a
|
||||
href="https://waku.gg/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="mt-4 inline-block underline"
|
||||
>
|
||||
waku.gg
|
||||
</a>{" "}
|
||||
to learn more
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Link } from "waku";
|
||||
|
||||
export const Header = () => {
|
||||
return (
|
||||
<header className="flex items-center gap-4 p-6 lg:fixed lg:left-0 lg:top-0">
|
||||
<h2 className="text-lg font-bold tracking-tight">
|
||||
<Link to="/">Waku starter</Link>
|
||||
</h2>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
import "../styles.css";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { Footer } from "../components/footer";
|
||||
import { Header } from "../components/header";
|
||||
|
||||
type RootLayoutProps = { children: ReactNode };
|
||||
|
||||
export default async function RootLayout({ children }: RootLayoutProps) {
|
||||
const data = await getData();
|
||||
|
||||
return (
|
||||
<div className="font-['Nunito']">
|
||||
<meta name="description" content={data.description} />
|
||||
<link rel="icon" type="image/png" href={data.icon} />
|
||||
<Header />
|
||||
<main className="m-6 flex items-center *:min-h-64 *:min-w-64 lg:m-0 lg:min-h-svh lg:justify-center">
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getData = async () => {
|
||||
const data = {
|
||||
description: "An internet website!",
|
||||
icon: "/images/favicon.png",
|
||||
};
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getConfig = async () => {
|
||||
return {
|
||||
render: "static",
|
||||
} as const;
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Link } from "waku";
|
||||
|
||||
export default async function AboutPage() {
|
||||
const data = await getData();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<title>{data.title}</title>
|
||||
<h1 className="text-4xl font-bold tracking-tight">{data.headline}</h1>
|
||||
<p>{data.body}</p>
|
||||
<Link to="/" className="mt-4 inline-block underline">
|
||||
Return home
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getData = async () => {
|
||||
const data = {
|
||||
title: "About",
|
||||
headline: "About Waku",
|
||||
body: "The minimal React framework",
|
||||
};
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getConfig = async () => {
|
||||
return {
|
||||
render: "static",
|
||||
} as const;
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import { createServer } from "@llamaindex/workflow-http/server";
|
||||
import { workflow } from "../../workflow/basic";
|
||||
import { searchEvent, stopEvent, storeEvent } from "../../workflow/events";
|
||||
import { upload } from "../../workflow/llama-parse";
|
||||
|
||||
process.on("unhandledRejection", (reason) => {
|
||||
console.error("Unhandled Rejection at:", reason);
|
||||
});
|
||||
|
||||
export const POST = createServer(
|
||||
workflow,
|
||||
async (data, sendEvent) => {
|
||||
if (data.file) {
|
||||
const file = data.file;
|
||||
const job = await upload({
|
||||
file,
|
||||
});
|
||||
const text = await job.markdown();
|
||||
sendEvent(storeEvent.with(text));
|
||||
} else if (data.search) {
|
||||
const search = data.search;
|
||||
sendEvent(searchEvent.with(search));
|
||||
}
|
||||
},
|
||||
(stream) => stream.until(stopEvent),
|
||||
);
|
||||
@@ -1,30 +0,0 @@
|
||||
import { RAG } from "../components/RAG";
|
||||
|
||||
export default async function HomePage() {
|
||||
const data = await getData();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<title>{data.title}</title>
|
||||
<h1 className="text-4xl font-bold tracking-tight">{data.headline}</h1>
|
||||
<p>{data.body}</p>
|
||||
<RAG />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getData = async () => {
|
||||
const data = {
|
||||
title: "Waku",
|
||||
headline: "Waku",
|
||||
body: "Hello world!",
|
||||
};
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getConfig = async () => {
|
||||
return {
|
||||
render: "static",
|
||||
} as const;
|
||||
};
|
||||
@@ -1,130 +0,0 @@
|
||||
import { z } from "zod";
|
||||
import { FailPageMode, ParserLanguages, ParsingMode } from "../lib/api";
|
||||
|
||||
type Language = (typeof ParserLanguages)[keyof typeof ParserLanguages];
|
||||
const VALUES: [Language, ...Language[]] = [
|
||||
ParserLanguages.EN,
|
||||
...Object.values(ParserLanguages),
|
||||
];
|
||||
const languageSchema = z.enum(VALUES);
|
||||
|
||||
const PARSE_PRESETS = [
|
||||
"fast",
|
||||
"balanced",
|
||||
"premium",
|
||||
"structured",
|
||||
"auto",
|
||||
"scientific",
|
||||
"invoice",
|
||||
"slides",
|
||||
"_carlyle",
|
||||
] as const;
|
||||
|
||||
export const parsePresetSchema = z.enum(PARSE_PRESETS);
|
||||
|
||||
export const parseFormSchema = z.object({
|
||||
adaptive_long_table: z.boolean().optional(),
|
||||
annotate_links: z.boolean().optional(),
|
||||
auto_mode: z.boolean().optional(),
|
||||
auto_mode_trigger_on_image_in_page: z.boolean().optional(),
|
||||
auto_mode_trigger_on_table_in_page: z.boolean().optional(),
|
||||
auto_mode_trigger_on_text_in_page: z.string().optional(),
|
||||
auto_mode_trigger_on_regexp_in_page: z.string().optional(),
|
||||
auto_mode_configuration_json: z.string().optional(),
|
||||
azure_openai_api_version: z.string().optional(),
|
||||
azure_openai_deployment_name: z.string().optional(),
|
||||
azure_openai_endpoint: z.string().optional(),
|
||||
azure_openai_key: z.string().optional(),
|
||||
bbox_bottom: z.number().min(0).max(1).optional(),
|
||||
bbox_left: z.number().min(0).max(1).optional(),
|
||||
bbox_right: z.number().min(0).max(1).optional(),
|
||||
bbox_top: z.number().min(0).max(1).optional(),
|
||||
disable_ocr: z.boolean().optional(),
|
||||
disable_reconstruction: z.boolean().optional(),
|
||||
disable_image_extraction: z.boolean().optional(),
|
||||
do_not_cache: z.coerce.boolean().optional(),
|
||||
do_not_unroll_columns: z.coerce.boolean().optional(),
|
||||
extract_charts: z.boolean().optional(),
|
||||
guess_xlsx_sheet_name: z.boolean().optional(),
|
||||
html_make_all_elements_visible: z.boolean().optional(),
|
||||
html_remove_fixed_elements: z.boolean().optional(),
|
||||
html_remove_navigation_elements: z.boolean().optional(),
|
||||
http_proxy: z
|
||||
.string()
|
||||
.url(
|
||||
'Set a valid URL for the HTTP proxy, e.g., "http://proxy.example.com:8080"',
|
||||
)
|
||||
.refine(
|
||||
(url) => {
|
||||
try {
|
||||
const parsedUrl = new URL(url);
|
||||
return (
|
||||
parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:"
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{
|
||||
message: "Invalid HTTP proxy URL",
|
||||
},
|
||||
)
|
||||
.optional(),
|
||||
input_s3_path: z.string().optional(),
|
||||
input_s3_region: z.string().optional(),
|
||||
input_url: z.string().optional(),
|
||||
invalidate_cache: z.boolean().optional(),
|
||||
language: z.array(languageSchema).optional(),
|
||||
extract_layout: z.boolean().optional(),
|
||||
max_pages: z.number().nullable().optional(),
|
||||
output_pdf_of_document: z.boolean().optional(),
|
||||
output_s3_path_prefix: z.string().optional(),
|
||||
output_s3_region: z.string().optional(),
|
||||
page_prefix: z.string().optional(),
|
||||
page_separator: z.string().optional(),
|
||||
page_suffix: z.string().optional(),
|
||||
preserve_layout_alignment_across_pages: z.boolean().optional(),
|
||||
skip_diagonal_text: z.boolean().optional(),
|
||||
spreadsheet_extract_sub_tables: z.boolean().optional(),
|
||||
structured_output: z.boolean().optional(),
|
||||
structured_output_json_schema: z.string().optional(),
|
||||
structured_output_json_schema_name: z.string().optional(),
|
||||
take_screenshot: z.boolean().optional(),
|
||||
target_pages: z.string().optional(),
|
||||
vendor_multimodal_api_key: z.string().optional(),
|
||||
vendor_multimodal_model_name: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
webhook_url: z.string().url().optional(),
|
||||
parse_mode: z.nativeEnum(ParsingMode).nullable().optional(),
|
||||
system_prompt: z.string().optional(),
|
||||
system_prompt_append: z.string().optional(),
|
||||
user_prompt: z.string().optional(),
|
||||
job_timeout_in_seconds: z.number().optional(),
|
||||
job_timeout_extra_time_per_page_in_seconds: z.number().optional(),
|
||||
strict_mode_image_extraction: z.boolean().optional(),
|
||||
strict_mode_image_ocr: z.boolean().optional(),
|
||||
strict_mode_reconstruction: z.boolean().optional(),
|
||||
strict_mode_buggy_font: z.boolean().optional(),
|
||||
save_images: z.boolean().optional(),
|
||||
ignore_document_elements_for_layout_detection: z.boolean().optional(),
|
||||
output_tables_as_HTML: z.boolean().optional(),
|
||||
use_vendor_multimodal_model: z.boolean().optional(),
|
||||
bounding_box: z.string().optional(),
|
||||
gpt4o_mode: z.boolean().optional(),
|
||||
gpt4o_api_key: z.string().optional(),
|
||||
complemental_formatting_instruction: z.string().optional(),
|
||||
content_guideline_instruction: z.string().optional(),
|
||||
premium_mode: z.boolean().optional(),
|
||||
is_formatting_instruction: z.boolean().optional(),
|
||||
continuous_mode: z.boolean().optional(),
|
||||
parsing_instruction: z.string().optional(),
|
||||
fast_mode: z.boolean().optional(),
|
||||
formatting_instruction: z.string().optional(),
|
||||
preset: parsePresetSchema.optional(),
|
||||
compact_markdown_table: z.boolean().optional(),
|
||||
markdown_table_multiline_header_separator: z.string().optional(),
|
||||
page_error_tolerance: z.number().min(0).max(1).optional(),
|
||||
replace_failed_page_mode: z.nativeEnum(FailPageMode).nullable().optional(),
|
||||
replace_failed_page_with_error_message_prefix: z.string().optional(),
|
||||
replace_failed_page_with_error_message_suffix: z.string().optional(),
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,400;0,700;1,400;1,700&display=swap")
|
||||
layer(base);
|
||||
@import "tailwindcss";
|
||||
@@ -1,44 +0,0 @@
|
||||
import { createWorkflow, getContext } from "@llamaindex/workflow-core";
|
||||
import { neon } from "@neondatabase/serverless";
|
||||
import { OpenAI } from "openai";
|
||||
import { getEnv } from "waku";
|
||||
import { searchEvent, stopEvent, storeEvent } from "./events";
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: getEnv("OPENAI_API_KEY")!,
|
||||
});
|
||||
const sql = neon(getEnv("DATABASE_URL")!);
|
||||
|
||||
export const workflow = createWorkflow();
|
||||
|
||||
workflow.handle([storeEvent], async (context, { data }) => {
|
||||
const embeddingResponse = await openai.embeddings.create({
|
||||
model: "text-embedding-ada-002",
|
||||
input: data,
|
||||
});
|
||||
const embedding = embeddingResponse.data[0]!.embedding;
|
||||
await sql`
|
||||
INSERT INTO text_vectors (text, embedding)
|
||||
VALUES (${data}, ${JSON.stringify(embedding)})
|
||||
`;
|
||||
return stopEvent.with("success");
|
||||
});
|
||||
|
||||
workflow.handle([searchEvent], async (context, { data }) => {
|
||||
const { signal } = context;
|
||||
signal.addEventListener("abort", () => {
|
||||
console.error("error", signal.reason);
|
||||
});
|
||||
const embeddingResponse = await openai.embeddings.create({
|
||||
model: "text-embedding-ada-002",
|
||||
input: data,
|
||||
});
|
||||
const embedding = embeddingResponse.data[0]!.embedding;
|
||||
const result = await sql`
|
||||
SELECT text
|
||||
FROM text_vectors
|
||||
ORDER BY embedding <=> ${JSON.stringify(embedding)}
|
||||
LIMIT 5
|
||||
`;
|
||||
return stopEvent.with(result.map((row) => row.text).join("\n"));
|
||||
});
|
||||
@@ -1,68 +0,0 @@
|
||||
import { workflowEvent } from "@llamaindex/workflow-core";
|
||||
import { zodEvent } from "@llamaindex/workflow-core/util/zod";
|
||||
import { z } from "zod";
|
||||
import { parseFormSchema } from "../schema";
|
||||
|
||||
export const searchEvent = workflowEvent<string>({
|
||||
debugLabel: "search",
|
||||
uniqueId: "search",
|
||||
});
|
||||
export const storeEvent = workflowEvent<string>({
|
||||
debugLabel: "store",
|
||||
uniqueId: "store",
|
||||
});
|
||||
export const stopEvent = workflowEvent<string>({
|
||||
debugLabel: "stop",
|
||||
uniqueId: "stop",
|
||||
});
|
||||
|
||||
export const startEvent = zodEvent(
|
||||
parseFormSchema.merge(
|
||||
z.object({
|
||||
file: z
|
||||
.string()
|
||||
.or(z.instanceof(File))
|
||||
.or(z.instanceof(Blob))
|
||||
.or(z.instanceof(Uint8Array))
|
||||
.optional()
|
||||
.describe("input"),
|
||||
}),
|
||||
),
|
||||
{
|
||||
debugLabel: "llama-parse",
|
||||
uniqueId: "llama-parse",
|
||||
},
|
||||
);
|
||||
export const checkStatusEvent = workflowEvent<string>({
|
||||
debugLabel: "check-status",
|
||||
uniqueId: "check-status",
|
||||
});
|
||||
export const checkStatusSuccessEvent = workflowEvent<string>({
|
||||
debugLabel: "check-status-success",
|
||||
uniqueId: "check-status-success",
|
||||
});
|
||||
export const requestMarkdownEvent = workflowEvent<string>({
|
||||
debugLabel: "markdown-request",
|
||||
uniqueId: "markdown-request",
|
||||
});
|
||||
export const requestTextEvent = workflowEvent<string>({
|
||||
debugLabel: "text-request",
|
||||
uniqueId: "text-request",
|
||||
});
|
||||
export const requestJsonEvent = workflowEvent<string>({
|
||||
debugLabel: "json-request",
|
||||
uniqueId: "json-request",
|
||||
});
|
||||
|
||||
export const markdownResultEvent = workflowEvent<string>({
|
||||
debugLabel: "markdown-result",
|
||||
uniqueId: "markdown-result",
|
||||
});
|
||||
export const textResultEvent = workflowEvent<string>({
|
||||
debugLabel: "text-result",
|
||||
uniqueId: "text-result",
|
||||
});
|
||||
export const jsonResultEvent = workflowEvent<unknown>({
|
||||
debugLabel: "json-result",
|
||||
uniqueId: "json-result",
|
||||
});
|
||||
@@ -1,258 +0,0 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { createClient, createConfig } from "@hey-api/client-fetch";
|
||||
import {
|
||||
createWorkflow,
|
||||
type InferWorkflowEventData,
|
||||
workflowEvent,
|
||||
} from "@llamaindex/workflow-core";
|
||||
import { createStatefulMiddleware } from "@llamaindex/workflow-core/middleware/state";
|
||||
import { withTraceEvents } from "@llamaindex/workflow-core/middleware/trace-events";
|
||||
import { pRetryHandler } from "@llamaindex/workflow-core/util/p-retry";
|
||||
import { zodEvent } from "@llamaindex/workflow-core/util/zod";
|
||||
import hash from "stable-hash";
|
||||
import { getEnv } from "waku";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
type BodyUploadFileApiV1ParsingUploadPost,
|
||||
getJobApiV1ParsingJobJobIdGet,
|
||||
getJobJsonResultApiV1ParsingJobJobIdResultJsonGet,
|
||||
getJobResultApiV1ParsingJobJobIdResultMarkdownGet,
|
||||
getJobTextResultApiV1ParsingJobJobIdResultTextGet,
|
||||
type StatusEnum,
|
||||
uploadFileApiV1ParsingUploadPost,
|
||||
} from "../lib/api";
|
||||
import { parseFormSchema } from "../schema";
|
||||
import {
|
||||
checkStatusEvent,
|
||||
checkStatusSuccessEvent,
|
||||
jsonResultEvent,
|
||||
markdownResultEvent,
|
||||
requestJsonEvent,
|
||||
requestMarkdownEvent,
|
||||
requestTextEvent,
|
||||
startEvent,
|
||||
textResultEvent,
|
||||
} from "./events";
|
||||
|
||||
export type LlamaParseWorkflowParams = {
|
||||
region?: "us" | "eu" | "us-staging";
|
||||
apiKey?: string;
|
||||
};
|
||||
|
||||
const URLS = {
|
||||
us: "https://api.cloud.llamaindex.ai",
|
||||
eu: "https://api.cloud.eu.llamaindex.ai",
|
||||
"us-staging": "https://api.staging.llamaindex.ai",
|
||||
} as const;
|
||||
|
||||
const { withState } = createStatefulMiddleware(
|
||||
(params: LlamaParseWorkflowParams) => {
|
||||
const apiKey = params.apiKey ?? getEnv("LLAMA_CLOUD_API_KEY");
|
||||
const region = params.region ?? "us";
|
||||
if (!apiKey) {
|
||||
throw new Error("LLAMA_CLOUD_API_KEY is not set");
|
||||
}
|
||||
return {
|
||||
cache: {} as Record<string, StatusEnum>,
|
||||
client: createClient(
|
||||
createConfig({
|
||||
baseUrl: URLS[region],
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
}),
|
||||
),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const llamaParseWorkflow = withState(withTraceEvents(createWorkflow()));
|
||||
|
||||
llamaParseWorkflow.handle([startEvent], async (context, { data: form }) => {
|
||||
const { state } = context;
|
||||
const finalForm = { ...form };
|
||||
if ("file" in form) {
|
||||
// support loads from the file system
|
||||
const file = form?.file;
|
||||
const isFilePath = typeof file === "string";
|
||||
const data = isFilePath ? await fs.readFile(file) : file;
|
||||
const filename: string | undefined = isFilePath
|
||||
? path.basename(file)
|
||||
: undefined;
|
||||
finalForm.file = data
|
||||
? globalThis.File && filename
|
||||
? new File([data], filename)
|
||||
: new Blob([data])
|
||||
: undefined;
|
||||
}
|
||||
const {
|
||||
data: { id, status },
|
||||
} = await uploadFileApiV1ParsingUploadPost({
|
||||
throwOnError: true,
|
||||
body: {
|
||||
...finalForm,
|
||||
} as BodyUploadFileApiV1ParsingUploadPost,
|
||||
client: state.client,
|
||||
});
|
||||
state.cache[id] = status;
|
||||
return checkStatusEvent.with(id);
|
||||
});
|
||||
|
||||
llamaParseWorkflow.handle(
|
||||
[checkStatusEvent],
|
||||
pRetryHandler(
|
||||
async (context, { data: uuid }) => {
|
||||
const { state } = context;
|
||||
if (state.cache[uuid] === "SUCCESS") {
|
||||
return checkStatusSuccessEvent.with(uuid);
|
||||
}
|
||||
const {
|
||||
data: { status },
|
||||
} = await getJobApiV1ParsingJobJobIdGet({
|
||||
throwOnError: true,
|
||||
path: {
|
||||
job_id: uuid,
|
||||
},
|
||||
client: state.client,
|
||||
});
|
||||
state.cache[uuid] = status;
|
||||
if (status === "SUCCESS") {
|
||||
return checkStatusSuccessEvent.with(uuid);
|
||||
}
|
||||
throw new Error(`LLamaParse status: ${status}`);
|
||||
},
|
||||
{
|
||||
retries: 100,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
//#region sub workflow
|
||||
llamaParseWorkflow.handle(
|
||||
[requestMarkdownEvent],
|
||||
async (context, { data: job_id }) => {
|
||||
const { state } = context;
|
||||
const { data } = await getJobResultApiV1ParsingJobJobIdResultMarkdownGet({
|
||||
throwOnError: true,
|
||||
path: {
|
||||
job_id,
|
||||
},
|
||||
client: state.client,
|
||||
});
|
||||
return markdownResultEvent.with(data.markdown);
|
||||
},
|
||||
);
|
||||
|
||||
llamaParseWorkflow.handle(
|
||||
[requestTextEvent],
|
||||
async (context, { data: job_id }) => {
|
||||
const { state } = context;
|
||||
const { data } = await getJobTextResultApiV1ParsingJobJobIdResultTextGet({
|
||||
throwOnError: true,
|
||||
path: {
|
||||
job_id,
|
||||
},
|
||||
client: state.client,
|
||||
});
|
||||
return textResultEvent.with(data.text);
|
||||
},
|
||||
);
|
||||
|
||||
llamaParseWorkflow.handle(
|
||||
[requestJsonEvent],
|
||||
async (context, { data: job_id }) => {
|
||||
const { state } = context;
|
||||
const { data } = await getJobJsonResultApiV1ParsingJobJobIdResultJsonGet({
|
||||
throwOnError: true,
|
||||
path: {
|
||||
job_id,
|
||||
},
|
||||
client: state.client,
|
||||
});
|
||||
return jsonResultEvent.with(data.pages);
|
||||
},
|
||||
);
|
||||
//#endregion
|
||||
|
||||
const cacheMap = new Map<
|
||||
string,
|
||||
ReturnType<typeof llamaParseWorkflow.createContext>
|
||||
>();
|
||||
|
||||
export type ParseJob = {
|
||||
get jobId(): string;
|
||||
get signal(): AbortSignal;
|
||||
get context(): ReturnType<typeof llamaParseWorkflow.createContext>;
|
||||
get form(): InferWorkflowEventData<typeof startEvent>;
|
||||
|
||||
markdown(): Promise<string>;
|
||||
text(): Promise<string>;
|
||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
json(): Promise<any[]>;
|
||||
};
|
||||
|
||||
export const upload = async (
|
||||
params: InferWorkflowEventData<typeof startEvent> & LlamaParseWorkflowParams,
|
||||
): Promise<ParseJob> => {
|
||||
//#region cache
|
||||
const key = hash({ apiKey: params.apiKey, region: params.region });
|
||||
if (!cacheMap.has(key)) {
|
||||
const context = llamaParseWorkflow.createContext(params);
|
||||
cacheMap.set(key, context);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region upload event
|
||||
const context = cacheMap.get(key)!;
|
||||
const { stream, sendEvent } = context;
|
||||
const ev = startEvent.with(params);
|
||||
sendEvent(ev);
|
||||
|
||||
const uploadThread = await llamaParseWorkflow
|
||||
.substream(ev, stream)
|
||||
.until((ev) => checkStatusSuccessEvent.include(ev))
|
||||
.toArray();
|
||||
//#region
|
||||
const jobId: string = uploadThread.at(-1)!.data;
|
||||
return {
|
||||
get signal() {
|
||||
// lazy load
|
||||
return context.signal;
|
||||
},
|
||||
get jobId() {
|
||||
return jobId;
|
||||
},
|
||||
get form() {
|
||||
return ev.data;
|
||||
},
|
||||
get context() {
|
||||
return context;
|
||||
},
|
||||
async markdown(): Promise<string> {
|
||||
const requestEv = requestMarkdownEvent.with(jobId);
|
||||
const { sendEvent, stream } = llamaParseWorkflow.createContext(params);
|
||||
sendEvent(requestEv);
|
||||
const markdownThread = await stream.until(markdownResultEvent).toArray();
|
||||
return markdownThread.at(-1)!.data;
|
||||
},
|
||||
async text(): Promise<string> {
|
||||
const requestEv = requestTextEvent.with(jobId);
|
||||
const { sendEvent, stream } = llamaParseWorkflow.createContext(params);
|
||||
sendEvent(requestEv);
|
||||
const textThread = await stream.until(textResultEvent).toArray();
|
||||
console.log("textThread", textThread);
|
||||
return textThread.at(-1)!.data;
|
||||
},
|
||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async json(): Promise<any[]> {
|
||||
const requestEv = requestJsonEvent.with(jobId);
|
||||
const { sendEvent, stream } = llamaParseWorkflow.createContext(params);
|
||||
sendEvent(requestEv);
|
||||
const jsonThread = await stream
|
||||
.until((ev) => jsonResultEvent.include(ev))
|
||||
.toArray();
|
||||
return jsonThread.at(-1)!.data;
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"target": "esnext",
|
||||
"downlevelIteration": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"skipLibCheck": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
||||
@@ -6,7 +6,7 @@ const processStringEvent = workflowEvent<string>();
|
||||
const processNumberEvent = workflowEvent<number>();
|
||||
export const successEvent = workflowEvent<string>();
|
||||
|
||||
workflow.handle([inputEvent], async (context, event) => {
|
||||
workflow.handle([inputEvent], async (_context, event) => {
|
||||
if (typeof event.data === "string") {
|
||||
return processStringEvent.with(event.data);
|
||||
} else {
|
||||
@@ -14,22 +14,22 @@ workflow.handle([inputEvent], async (context, event) => {
|
||||
}
|
||||
});
|
||||
|
||||
workflow.handle([processStringEvent], async (context, event) => {
|
||||
workflow.handle([processStringEvent], async (_context, event) => {
|
||||
return successEvent.with(`Processed string ${event.data}`);
|
||||
});
|
||||
|
||||
workflow.handle([processNumberEvent], async (context, event) => {
|
||||
workflow.handle([processNumberEvent], async (_context, event) => {
|
||||
return successEvent.with(`Processed number ${event.data}`);
|
||||
});
|
||||
|
||||
let context1 = workflow.createContext();
|
||||
const context1 = workflow.createContext();
|
||||
context1.sendEvent(inputEvent.with("I am some data"));
|
||||
|
||||
const result = await context1.stream.until(successEvent).toArray();
|
||||
console.log(result.at(-1)!.data);
|
||||
console.log(result.at(-1)?.data);
|
||||
|
||||
let context2 = workflow.createContext();
|
||||
const context2 = workflow.createContext();
|
||||
context2.sendEvent(inputEvent.with(1));
|
||||
|
||||
const result2 = await context2.stream.until(successEvent).toArray();
|
||||
console.log(result2.at(-1)!.data);
|
||||
console.log(result2.at(-1)?.data);
|
||||
|
||||
@@ -12,7 +12,7 @@ const { withState } = createStatefulMiddleware(() => ({
|
||||
processResults: [] as string[],
|
||||
}));
|
||||
export const workflow = withState(createWorkflow());
|
||||
workflow.handle([startEvent], async (context, start) => {
|
||||
workflow.handle([startEvent], async (context) => {
|
||||
const { sendEvent, state } = context;
|
||||
state.itemsProcessed = 0; // Reset counter for this execution
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ workflow.handle([startEvent], () => {
|
||||
return humanRequestEvent.with();
|
||||
});
|
||||
|
||||
workflow.handle([humanResponseEvent], (context, event) => {
|
||||
workflow.handle([humanResponseEvent], (_context, event) => {
|
||||
return stopEvent.with(`Human said: ${event.data}`);
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ export const startEvent = workflowEvent<void>();
|
||||
const increaseCounterEvent = workflowEvent<void>();
|
||||
export const stopEvent = workflowEvent<number>();
|
||||
|
||||
workflow.handle([startEvent], async (context, { data }) => {
|
||||
workflow.handle([startEvent], async (context) => {
|
||||
const { sendEvent, state } = context;
|
||||
if (state.counter < state.max_counter) {
|
||||
sendEvent(increaseCounterEvent.with());
|
||||
@@ -24,7 +24,7 @@ workflow.handle([startEvent], async (context, { data }) => {
|
||||
}
|
||||
});
|
||||
|
||||
workflow.handle([increaseCounterEvent], async (context, { data }) => {
|
||||
workflow.handle([increaseCounterEvent], async (context) => {
|
||||
const { sendEvent, state } = context;
|
||||
state.counter += 1;
|
||||
sendEvent(startEvent.with());
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ export const startEvent = workflowEvent<{ userInput: string }>();
|
||||
export const stopEvent = workflowEvent<{ result: string }>();
|
||||
|
||||
workflow.handle([startEvent], async (context, { data }) => {
|
||||
const { sendEvent, state } = context;
|
||||
const { state } = context;
|
||||
const { userInput } = data;
|
||||
|
||||
const previous_message = state.previous_message;
|
||||
|
||||
@@ -37,5 +37,5 @@ workflow.handle([stepEvent], (_context, event) => {
|
||||
});
|
||||
|
||||
// Run
|
||||
const { sendEvent, stream } = workflow.createContext();
|
||||
const { sendEvent } = workflow.createContext();
|
||||
sendEvent(startEvent.with());
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
createWorkflow,
|
||||
workflowEvent,
|
||||
type Workflow,
|
||||
} from "@llamaindex/workflow-core";
|
||||
import { createWorkflow, workflowEvent } from "@llamaindex/workflow-core";
|
||||
import { withDrawing } from "@llamaindex/workflow-viz";
|
||||
|
||||
// Define events (debug labels are used for node names in the graph)
|
||||
|
||||
@@ -3,14 +3,14 @@ import { workflow, inputEvent, successEvent } from "../src/branching";
|
||||
|
||||
describe("Branching workflow should return expected results", () => {
|
||||
test("Sending event with context1", async () => {
|
||||
let context1 = workflow.createContext();
|
||||
const context1 = workflow.createContext();
|
||||
context1.sendEvent(inputEvent.with("I am some data"));
|
||||
|
||||
const result = await context1.stream.until(successEvent).toArray();
|
||||
expect(result.at(-1)!.data).toBe("Processed string I am some data");
|
||||
});
|
||||
test("Sending event with context2", async () => {
|
||||
let context2 = workflow.createContext();
|
||||
const context2 = workflow.createContext();
|
||||
context2.sendEvent(inputEvent.with(1));
|
||||
|
||||
const result2 = await context2.stream.until(successEvent).toArray();
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.15.0",
|
||||
"scripts": {
|
||||
"build": "turbo build --filter=\"./packages/*\" --filter=\"./docs\"",
|
||||
"test": "turbo test --filter=\"./packages/*\" --filter=\"./docs\"",
|
||||
"build": "turbo build",
|
||||
"test": "turbo test",
|
||||
"typecheck": "tsc -b --diagnostics",
|
||||
"format": "biome format .",
|
||||
"format:write": "biome format --write .",
|
||||
|
||||
Generated
+4757
-1652
File diff suppressed because it is too large
Load Diff
+1
-7
@@ -1,11 +1,5 @@
|
||||
packages:
|
||||
- ./demo
|
||||
- ./demo/cloudflare
|
||||
- ./demo/browser
|
||||
- ./demo/waku
|
||||
- ./demo/express
|
||||
- ./demo/visualization
|
||||
- ./demo/trace-events
|
||||
- ./demo/*
|
||||
- ./docs
|
||||
- ./packages/*
|
||||
- ./tests/*
|
||||
|
||||
@@ -56,7 +56,7 @@ describe("Llama Flow Pure CJS Tests", () => {
|
||||
jokeFlow = withState(createWorkflow());
|
||||
|
||||
// Define handlers for each step
|
||||
jokeFlow.handle([startEvent], async (event: any) => {
|
||||
jokeFlow.handle([startEvent], async (_context, event) => {
|
||||
// Increment our manual state counter
|
||||
numIterations++;
|
||||
|
||||
|
||||
+10
-1
@@ -20,11 +20,20 @@
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./demo/tsconfig.json"
|
||||
"path": "./demo/browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "./demo/cloudflare/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "./demo/express/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "./demo/next/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "./demo/trace-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "./demo/visualization/tsconfig.json"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user