Compare commits

...

11 Commits

Author SHA1 Message Date
github-actions[bot] 74fc725f37 Release 0.6.0 (#1199)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: marcusschiesser <marcusschiesser@users.noreply.github.com>
2024-09-13 16:07:16 +07:00
Marcus Schiesser a0a74aed60 fix: release openai package (#1200) 2024-09-13 16:01:00 +07:00
Marcus Schiesser 11feef8c82 Add workflows (#1188)
Co-authored-by: Thuc Pham <51660321+thucpn@users.noreply.github.com>
2024-09-13 15:46:02 +07:00
github-actions[bot] 9c5ff164ac Release 0.5.27 (#1195)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-12 13:47:11 -07:00
Alex Yang 7edeb1c2d7 feat: decouple openai from llamaindex module (#1194) 2024-09-12 13:36:08 -07:00
github-actions[bot] 8b95abdc85 Release 0.5.26 (#1193)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-12 11:38:07 -07:00
Alex Yang ffe0cd1ef1 chore: update changelog 2024-09-12 11:33:32 -07:00
Alex Yang 5d2111a19f feat: init support openai o1 model (#1192) 2024-09-12 11:31:05 -07:00
Alex Yang 68ac7fd57f ci: fix syntax (#1186) 2024-09-11 16:54:39 -07:00
Alex Yang 7320d96a36 fix: waku build (#1185) 2024-09-11 15:36:39 -07:00
Goran ee17fb475b feat: add PostgreSQL storage (#1180) 2024-09-11 12:31:04 -07:00
86 changed files with 2649 additions and 864 deletions
+18 -19
View File
@@ -12,6 +12,10 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
POSTGRES_USER: runneradmin
POSTGRES_HOST_AUTH_METHOD: trust
jobs:
e2e:
strategy:
@@ -19,17 +23,20 @@ jobs:
matrix:
node-version: [18.x, 20.x, 22.x]
name: E2E on Node.js ${{ matrix.node-version }}
env: POSTGRES_DB=vectordb
POSTGRES_USER=testuser
POSTGRES_PASSWORD=testpwd
POSTGRES_HOST_AUTH_METHOD=trust
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ankane/setup-postgres@v1
with:
database: llamaindex_node_test
dev-files: true
- run: |
cd /tmp
git clone --branch v0.7.0 https://github.com/pgvector/pgvector.git
cd pgvector
make
sudo make install
- uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
@@ -47,19 +54,8 @@ jobs:
node-version: [18.x, 20.x, 22.x]
name: Test on Node.js ${{ matrix.node-version }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ankane/setup-postgres@v1
with:
database: llamaindex_node_test
dev-files: true
- run: |
cd /tmp
git clone --branch v0.7.0 https://github.com/pgvector/pgvector.git
cd pgvector
make
sudo make install
- uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -107,7 +103,7 @@ jobs:
- nextjs-agent
- nextjs-edge-runtime
- nextjs-node-runtime
# - waku-query-engine
- waku-query-engine
runs-on: ubuntu-latest
name: Build LlamaIndex Example (${{ matrix.packages }})
steps:
@@ -146,6 +142,9 @@ jobs:
- name: Pack @llamaindex/cloud
run: pnpm pack --pack-destination ${{ runner.temp }}
working-directory: packages/cloud
- name: Pack @llamaindex/openai
run: pnpm pack --pack-destination ${{ runner.temp }}
working-directory: packages/llm/openai
- name: Pack @llamaindex/core
run: pnpm pack --pack-destination ${{ runner.temp }}
working-directory: packages/core
+15
View File
@@ -189,6 +189,21 @@ export async function chatWithAgent(
}
```
### Vite
We have some wasm dependencies for better performance. You can use `vite-plugin-wasm` to load them.
```ts
import wasm from "vite-plugin-wasm";
export default {
plugins: [wasm()],
ssr: {
external: ["tiktoken"],
},
};
```
## Playground
Check out our NextJS playground at https://llama-playground.vercel.app/. The source is available at https://github.com/run-llama/ts-playground
+23
View File
@@ -1,5 +1,28 @@
# docs
## 0.0.69
### Patch Changes
- Updated dependencies [11feef8]
- llamaindex@0.6.0
- @llamaindex/examples@0.0.8
## 0.0.68
### Patch Changes
- Updated dependencies [7edeb1c]
- llamaindex@0.5.27
## 0.0.67
### Patch Changes
- Updated dependencies [ffe0cd1]
- Updated dependencies [ffe0cd1]
- llamaindex@0.5.26
## 0.0.66
### Patch Changes
+37 -3
View File
@@ -4,12 +4,19 @@ sidebar_position: 7
# Storage
Storage in LlamaIndex.TS works automatically once you've configured a `StorageContext` object. Just configure the `persistDir` and attach it to an index.
Storage in LlamaIndex.TS works automatically once you've configured a
`StorageContext` object.
Right now, only saving and loading from disk is supported, with future integrations planned!
## Local Storage
You can configure the `persistDir` and attach it to an index.
```typescript
import { Document, VectorStoreIndex, storageContextFromDefaults } from "./src";
import {
Document,
VectorStoreIndex,
storageContextFromDefaults,
} from "llamaindex";
const storageContext = await storageContextFromDefaults({
persistDir: "./storage",
@@ -21,6 +28,33 @@ const index = await VectorStoreIndex.fromDocuments([document], {
});
```
## PostgreSQL Storage
You can configure the `schemaName`, `tableName`, `namespace`, and
`connectionString`. If a `connectionString` is not
provided, it will use the environment variables `PGHOST`, `PGUSER`,
`PGPASSWORD`, `PGDATABASE` and `PGPORT`.
```typescript
import {
Document,
VectorStoreIndex,
PostgresDocumentStore,
PostgresIndexStore,
storageContextFromDefaults,
} from "llamaindex";
const storageContext = await storageContextFromDefaults({
docStore: new PostgresDocumentStore(),
indexStore: new PostgresIndexStore(),
});
const document = new Document({ text: "Test Text" });
const index = await VectorStoreIndex.fromDocuments([document], {
storageContext,
});
```
## API Reference
- [StorageContext](../api/interfaces/StorageContext.md)
+168
View File
@@ -0,0 +1,168 @@
import CodeBlock from "@theme/CodeBlock";
import CodeSource from "!raw-loader!../../../../examples/workflow/joke.ts";
# Workflows
A `Workflow` in LlamaIndexTS is an event-driven abstraction used to chain together several events. Workflows are made up of `steps`, with each step responsible for handling certain event types and emitting new events.
Workflows in LlamaIndexTS work by defining step functions that handle specific event types and emit new events.
When a step function is added to a workflow, you need to specify the input and optionally the output event types (used for validation). The specification of the input events ensures each step only runs when an accepted event is ready.
You can create a `Workflow` to do anything! Build an agent, a RAG flow, an extraction flow, or anything else you want.
## Getting Started
As an illustrative example, let's consider a naive workflow where a joke is generated and then critiqued.
<CodeBlock language="ts">{CodeSource}</CodeBlock>
There's a few moving pieces here, so let's go through this piece by piece.
### Defining Workflow Events
```typescript
export class JokeEvent extends WorkflowEvent<{ joke: string }> {}
```
Events are user-defined classes that extend `WorkflowEvent` and contain arbitrary data provided as template argument. In this case, our workflow relies on a single user-defined event, the `JokeEvent` with a `joke` attribute of type `string`.
### Setting up the Workflow Class
```typescript
const llm = new OpenAI();
...
const jokeFlow = new Workflow({ verbose: true });
```
Our workflow is implemented by initiating the `Workflow` class. For simplicity, we created a `OpenAI` llm instance.
### Workflow Entry Points
```typescript
const generateJoke = async (_context: Context, ev: StartEvent) => {
const prompt = `Write your best joke about ${ev.data.input}.`;
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`.
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:
```typescript
jokeFlow.addStep(StartEvent, generateJoke);
```
### Workflow Exit Points
```typescript
const critiqueJoke = async (_context: Context, 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 });
};
```
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.
Don't forget to add the step to the workflow:
```typescript
jokeFlow.addStep(JokeEvent, critiqueJoke);
```
### Running the Workflow
```typescript
const result = await jokeFlow.run("pirates");
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 });
```
To validate a workflow, you need to call the `validate` method:
```typescript
jokeFlow.validate();
```
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 });
```
## 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/core/workflow";
const query = async (context: Context, ev: MyEvent) => {
// get the query from the context
const query = context.get("query");
// do something with context and event
const val = ...
const result = ...
// store in context
context.set("key", val);
return new StopEvent({ result });
};
```
## Waiting for Multiple Events
The context does more than just hold data, it also provides utilities to buffer and wait for multiple events.
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 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.
## Manually Triggering Events
Normally, events are triggered by returning another event during a step. However, events can also be manually dispatched using the `ctx.sendEvent(event)` method within a workflow.
## Examples
You can find many useful examples of using workflows in the [examples folder](https://github.com/run-llama/LlamaIndexTS/blob/main/examples/workflow).
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "docs",
"version": "0.0.66",
"version": "0.0.69",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
+9
View File
@@ -1,5 +1,14 @@
# examples
## 0.0.8
### Patch Changes
- 11feef8: Add workflows
- Updated dependencies [11feef8]
- @llamaindex/core@0.2.0
- llamaindex@0.6.0
## 0.0.7
### Patch Changes
+13
View File
@@ -0,0 +1,13 @@
import { OpenAI } from "llamaindex";
(async () => {
const llm = new OpenAI({ model: "o1-preview", temperature: 1 });
const prompt = `What are three compounds we should consider investigating to advance research
into new antibiotics? Why should we consider them?
`;
// complete api
const response = await llm.complete({ prompt });
console.log(response.text);
})();
+3 -3
View File
@@ -1,12 +1,12 @@
{
"name": "@llamaindex/examples",
"private": true,
"version": "0.0.7",
"version": "0.0.8",
"dependencies": {
"@aws-crypto/sha256-js": "^5.2.0",
"@azure/identity": "^4.4.1",
"@datastax/astra-db-ts": "^1.4.1",
"@llamaindex/core": "^0.1.0",
"@llamaindex/core": "^0.2.0",
"@notionhq/client": "^2.2.15",
"@pinecone-database/pinecone": "^3.0.2",
"@zilliz/milvus2-sdk-node": "^2.4.6",
@@ -14,7 +14,7 @@
"commander": "^12.1.0",
"dotenv": "^16.4.5",
"js-tiktoken": "^1.0.14",
"llamaindex": "^0.5.0",
"llamaindex": "^0.6.0",
"mongodb": "^6.7.0",
"pathe": "^1.1.2"
},
+7
View File
@@ -0,0 +1,7 @@
# Workflow Examples
These examples demonstrate LlamaIndexTS's workflow system. Check out [its documentation](https://ts.llamaindex.ai/modules/workflows) for more information.
## Running the Examples
To run the examples, make sure to run them from the parent folder called `examples`). For example, to run the joke workflow, run `npx tsx workflow/joke.ts`.
+122
View File
@@ -0,0 +1,122 @@
import {
Context,
StartEvent,
StopEvent,
Workflow,
WorkflowEvent,
} from "@llamaindex/core/workflow";
import { OpenAI } from "llamaindex";
const MAX_REVIEWS = 3;
// Using the o1-preview model (see https://platform.openai.com/docs/guides/reasoning?reasoning-prompt-examples=coding-planning)
const llm = new OpenAI({ model: "o1-preview", temperature: 1 });
// example specification from https://platform.openai.com/docs/guides/reasoning?reasoning-prompt-examples=coding-planning
const specification = `Python app that takes user questions and looks them up in a
database where they are mapped to answers. If there is a close match, it retrieves
the matched answer. If there isn't, it asks the user to provide an answer and
stores the question/answer pair in the database.`;
// Create custom event types
export class MessageEvent extends WorkflowEvent<{ msg: string }> {}
export class CodeEvent extends WorkflowEvent<{ code: string }> {}
export class ReviewEvent extends WorkflowEvent<{
review: string;
code: string;
}> {}
// Helper function to truncate long strings
const truncate = (str: string) => {
const MAX_LENGTH = 60;
if (str.length <= MAX_LENGTH) return str;
return str.slice(0, MAX_LENGTH) + "...";
};
// the architect is responsible for writing the structure and the initial code based on the specification
const architect = async (context: Context, ev: StartEvent) => {
// get the specification from the start event and save it to context
context.set("specification", ev.data.input);
const spec = context.get("specification");
// write a message to send an update to the user
context.writeEventToStream(
new MessageEvent({
msg: `Writing app using this specification: ${truncate(spec)}`,
}),
);
const prompt = `Build an app for this specification: <spec>${spec}</spec>. Make a plan for the directory structure you'll need, then return each file in full. Don't supply any reasoning, just code.`;
const code = await llm.complete({ prompt });
return new CodeEvent({ code: code.text });
};
// the coder is responsible for updating the code based on the review
const coder = async (context: Context, ev: ReviewEvent) => {
// get the specification from the context
const spec = context.get("specification");
// get the latest review and code
const { review, code } = ev.data;
// write a message to send an update to the user
context.writeEventToStream(
new MessageEvent({
msg: `Update code based on review: ${truncate(review)}`,
}),
);
const prompt = `We need to improve code that should implement this specification: <spec>${spec}</spec>. Here is the current code: <code>${code}</code>. And here is a review of the code: <review>${review}</review>. Improve the code based on the review, keep the specification in mind, and return the full updated code. Don't supply any reasoning, just code.`;
const updatedCode = await llm.complete({ prompt });
return new CodeEvent({ code: updatedCode.text });
};
// the reviewer is responsible for reviewing the code and providing feedback
const reviewer = async (context: Context, ev: CodeEvent) => {
// get the specification from the context
const spec = context.get("specification");
// get latest code from the event
const { code } = ev.data;
// update and check the number of reviews
const numberReviews = context.get("numberReviews", 0) + 1;
context.set("numberReviews", numberReviews);
if (numberReviews > MAX_REVIEWS) {
// the we've done this too many times - return the code
context.writeEventToStream(
new MessageEvent({
msg: `Already reviewed ${numberReviews - 1} times, stopping!`,
}),
);
return new StopEvent({ result: code });
}
// write a message to send an update to the user
context.writeEventToStream(
new MessageEvent({ msg: `Review #${numberReviews}: ${truncate(code)}` }),
);
const prompt = `Review this code: <code>${code}</code>. Check if the code quality and whether it correctly implements this specification: <spec>${spec}</spec>. If you're satisfied, just return 'Looks great', nothing else. If not, return a review with a list of changes you'd like to see.`;
const review = (await llm.complete({ prompt })).text;
if (review.includes("Looks great")) {
// the reviewer is satisfied with the code, let's return the review
context.writeEventToStream(
new MessageEvent({
msg: `Reviewer says: ${review}`,
}),
);
return new StopEvent({ result: code });
}
return new ReviewEvent({ review, code });
};
const codeAgent = new Workflow({ validate: true });
codeAgent.addStep(StartEvent, architect, { outputs: CodeEvent });
codeAgent.addStep(ReviewEvent, coder, { outputs: CodeEvent });
codeAgent.addStep(CodeEvent, reviewer, { outputs: ReviewEvent });
// Usage
async function main() {
const run = codeAgent.run(specification);
for await (const event of codeAgent.streamEvents()) {
const msg = (event as MessageEvent).data.msg;
console.log(`${msg}\n`);
}
const result = await run;
console.log("Final code:\n", result.data.result);
}
main().catch(console.error);
+70
View File
@@ -0,0 +1,70 @@
import {
Context,
StartEvent,
StopEvent,
Workflow,
WorkflowEvent,
} from "@llamaindex/core/workflow";
import { OpenAI } from "llamaindex";
// Create LLM instance
const llm = new OpenAI();
// Create custom event types
export class JokeEvent extends WorkflowEvent<{ joke: string }> {}
export class CritiqueEvent extends WorkflowEvent<{ critique: string }> {}
export class AnalysisEvent extends WorkflowEvent<{ analysis: string }> {}
const generateJoke = async (_context: Context, ev: StartEvent) => {
const prompt = `Write your best joke about ${ev.data.input}.`;
const response = await llm.complete({ prompt });
return new JokeEvent({ joke: response.text });
};
const critiqueJoke = async (_context: Context, ev: JokeEvent) => {
const prompt = `Give a thorough critique of the following joke: ${ev.data.joke}`;
const response = await llm.complete({ prompt });
return new CritiqueEvent({ critique: response.text });
};
const analyzeJoke = async (_context: Context, ev: JokeEvent) => {
const prompt = `Give a thorough analysis of the following joke: ${ev.data.joke}`;
const response = await llm.complete({ prompt });
return new AnalysisEvent({ analysis: response.text });
};
const reportJoke = async (
context: Context,
ev: AnalysisEvent | CritiqueEvent,
) => {
const events = context.collectEvents(ev, [AnalysisEvent, CritiqueEvent]);
if (!events) {
return;
}
const subPrompts = events.map((event) => {
if (event instanceof AnalysisEvent) {
return `Analysis: ${event.data.analysis}`;
} else if (event instanceof CritiqueEvent) {
return `Critique: ${event.data.critique}`;
}
return "";
});
const prompt = `Based on the following information about a joke:\n${subPrompts.join("\n")}\nProvide a comprehensive report on the joke's quality and impact.`;
const response = await llm.complete({ prompt });
return new StopEvent({ result: response.text });
};
const jokeFlow = new Workflow();
jokeFlow.addStep(StartEvent, generateJoke);
jokeFlow.addStep(JokeEvent, critiqueJoke);
jokeFlow.addStep(JokeEvent, analyzeJoke);
jokeFlow.addStep([AnalysisEvent, CritiqueEvent], reportJoke);
// Usage
async function main() {
const result = await jokeFlow.run("pirates");
console.log(result.data.result);
}
main().catch(console.error);
+38
View File
@@ -0,0 +1,38 @@
import {
Context,
StartEvent,
StopEvent,
Workflow,
WorkflowEvent,
} from "@llamaindex/core/workflow";
import { OpenAI } from "llamaindex";
// Create LLM instance
const llm = new OpenAI();
// Create a custom event type
export class JokeEvent extends WorkflowEvent<{ joke: string }> {}
const generateJoke = async (_context: Context, ev: StartEvent) => {
const prompt = `Write your best joke about ${ev.data.input}.`;
const response = await llm.complete({ prompt });
return new JokeEvent({ joke: response.text });
};
const critiqueJoke = async (_context: Context, 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 });
};
const jokeFlow = new Workflow({ verbose: true });
jokeFlow.addStep(StartEvent, generateJoke);
jokeFlow.addStep(JokeEvent, critiqueJoke);
// Usage
async function main() {
const result = await jokeFlow.run("pirates");
console.log(result.data.result);
}
main().catch(console.error);
+49
View File
@@ -0,0 +1,49 @@
import {
Context,
StartEvent,
StopEvent,
Workflow,
WorkflowEvent,
} from "@llamaindex/core/workflow";
import { OpenAI } from "llamaindex";
// Create LLM instance
const llm = new OpenAI();
// Create custom event types
export class JokeEvent extends WorkflowEvent<{ joke: string }> {}
export class MessageEvent extends WorkflowEvent<{ msg: string }> {}
const generateJoke = async (context: Context, ev: StartEvent) => {
context.writeEventToStream(
new MessageEvent({ msg: `Generating a joke about: ${ev.data.input}` }),
);
const prompt = `Write your best joke about ${ev.data.input}.`;
const response = await llm.complete({ prompt });
return new JokeEvent({ joke: response.text });
};
const critiqueJoke = async (context: Context, ev: JokeEvent) => {
context.writeEventToStream(
new MessageEvent({ msg: `Write a critique of this joke: ${ev.data.joke}` }),
);
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 });
};
const jokeFlow = new Workflow();
jokeFlow.addStep(StartEvent, generateJoke);
jokeFlow.addStep(JokeEvent, critiqueJoke);
// Usage
async function main() {
const run = jokeFlow.run("pirates");
for await (const event of jokeFlow.streamEvents()) {
console.log((event as MessageEvent).data.msg);
}
const result = await run;
console.log(result.data.result);
}
main().catch(console.error);
+37
View File
@@ -0,0 +1,37 @@
import {
Context,
StartEvent,
StopEvent,
Workflow,
} from "@llamaindex/core/workflow";
const longRunning = async (_context: Context, ev: StartEvent) => {
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for 2 seconds
return new StopEvent({ result: "We waited 2 seconds" });
};
async function timeout() {
const workflow = new Workflow({ verbose: true, timeout: 1 });
workflow.addStep(StartEvent, longRunning);
// This will timeout
try {
await workflow.run("Let's start");
} catch (error) {
console.error(error);
}
}
async function notimeout() {
// Increase timeout to 3 seconds - no timeout
const workflow = new Workflow({ verbose: true, timeout: 3 });
workflow.addStep(StartEvent, longRunning);
const result = await workflow.run("Let's start");
console.log(result.data.result);
}
async function main() {
await timeout();
await notimeout();
}
main().catch(console.error);
+53
View File
@@ -0,0 +1,53 @@
import {
Context,
StartEvent,
StopEvent,
Workflow,
WorkflowEvent,
} from "@llamaindex/core/workflow";
import { OpenAI } from "llamaindex";
// Create LLM instance
const llm = new OpenAI();
// Create a custom event type
export class JokeEvent extends WorkflowEvent<{ joke: string }> {}
const generateJoke = async (_context: Context, ev: StartEvent) => {
const prompt = `Write your best joke about ${ev.data.input}.`;
const response = await llm.complete({ prompt });
return new JokeEvent({ joke: response.text });
};
const critiqueJoke = async (_context: Context, 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 });
};
async function validateFails() {
try {
const jokeFlow = new Workflow({ verbose: true, validate: true });
jokeFlow.addStep(StartEvent, generateJoke, { outputs: StopEvent });
jokeFlow.addStep(JokeEvent, critiqueJoke, { outputs: StopEvent });
await jokeFlow.run("pirates");
} catch (e) {
console.error("Validation failed:", e);
}
}
async function validate() {
const jokeFlow = new Workflow({ verbose: true, validate: true });
jokeFlow.addStep(StartEvent, generateJoke, { outputs: JokeEvent });
jokeFlow.addStep(JokeEvent, critiqueJoke, { outputs: StopEvent });
const result = await jokeFlow.run("pirates");
console.log(result.data.result);
}
// Usage
async function main() {
await validateFails();
await validate();
}
main().catch(console.error);
+7
View File
@@ -1,5 +1,12 @@
# @llamaindex/autotool
## 3.0.0
### Patch Changes
- Updated dependencies [11feef8]
- llamaindex@0.6.0
## 2.0.1
### Patch Changes
@@ -1,5 +1,30 @@
# @llamaindex/autotool-01-node-example
## 0.0.9
### Patch Changes
- Updated dependencies [11feef8]
- llamaindex@0.6.0
- @llamaindex/autotool@3.0.0
## 0.0.8
### Patch Changes
- Updated dependencies [7edeb1c]
- llamaindex@0.5.27
- @llamaindex/autotool@2.0.1
## 0.0.7
### Patch Changes
- Updated dependencies [ffe0cd1]
- Updated dependencies [ffe0cd1]
- llamaindex@0.5.26
- @llamaindex/autotool@2.0.1
## 0.0.6
### Patch Changes
@@ -13,5 +13,5 @@
"scripts": {
"start": "node --import tsx --import @llamaindex/autotool/node ./src/index.ts"
},
"version": "0.0.6"
"version": "0.0.9"
}
@@ -1,5 +1,30 @@
# @llamaindex/autotool-02-next-example
## 0.1.53
### Patch Changes
- Updated dependencies [11feef8]
- llamaindex@0.6.0
- @llamaindex/autotool@3.0.0
## 0.1.52
### Patch Changes
- Updated dependencies [7edeb1c]
- llamaindex@0.5.27
- @llamaindex/autotool@2.0.1
## 0.1.51
### Patch Changes
- Updated dependencies [ffe0cd1]
- Updated dependencies [ffe0cd1]
- llamaindex@0.5.26
- @llamaindex/autotool@2.0.1
## 0.1.50
### Patch Changes
@@ -1,7 +1,7 @@
{
"name": "@llamaindex/autotool-02-next-example",
"private": true,
"version": "0.1.50",
"version": "0.1.53",
"scripts": {
"dev": "next dev",
"build": "next build",
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@llamaindex/autotool",
"type": "module",
"version": "2.0.1",
"version": "3.0.0",
"description": "auto transpile your JS function to LLM Agent compatible",
"files": [
"dist",
@@ -51,7 +51,7 @@
"unplugin": "^1.12.2"
},
"peerDependencies": {
"llamaindex": "^0.5.25",
"llamaindex": "^0.6.0",
"openai": "^4",
"typescript": "^4"
},
+7
View File
@@ -1,5 +1,12 @@
# @llamaindex/community
## 0.0.34
### Patch Changes
- Updated dependencies [11feef8]
- @llamaindex/core@0.2.0
## 0.0.33
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@llamaindex/community",
"description": "Community package for LlamaIndexTS",
"version": "0.0.33",
"version": "0.0.34",
"type": "module",
"types": "dist/type/index.d.ts",
"main": "dist/cjs/index.js",
+6
View File
@@ -1,5 +1,11 @@
# @llamaindex/core
## 0.2.0
### Minor Changes
- 11feef8: Add workflows
## 0.1.12
### Patch Changes
+15 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@llamaindex/core",
"type": "module",
"version": "0.1.12",
"version": "0.2.0",
"description": "LlamaIndex Core Module",
"exports": {
"./node-parser": {
@@ -143,6 +143,20 @@
"types": "./dist/indices/index.d.ts",
"default": "./dist/indices/index.js"
}
},
"./workflow": {
"require": {
"types": "./dist/workflow/index.d.cts",
"default": "./dist/workflow/index.cjs"
},
"import": {
"types": "./dist/workflow/index.d.ts",
"default": "./dist/workflow/index.js"
},
"default": {
"types": "./dist/workflow/index.d.ts",
"default": "./dist/workflow/index.js"
}
}
},
"files": [
+111
View File
@@ -0,0 +1,111 @@
import { type EventTypes, type WorkflowEvent } from "./events";
import { type StepFunction, type Workflow } from "./workflow";
export class Context {
#workflow: Workflow;
#queues: Map<StepFunction, WorkflowEvent[]> = new Map();
#eventBuffer: Map<EventTypes, WorkflowEvent[]> = new Map();
#globals: Map<string, any> = new Map();
#streamingQueue: WorkflowEvent[] = [];
running: boolean = true;
#verbose: boolean = false;
constructor(params: { workflow: Workflow; verbose?: boolean }) {
this.#workflow = params.workflow;
this.#verbose = params.verbose ?? false;
}
set(key: string, value: any): void {
this.#globals.set(key, value);
}
get(key: string, defaultValue?: any): any {
if (this.#globals.has(key)) {
return this.#globals.get(key);
} else if (defaultValue !== undefined) {
return defaultValue;
}
throw new Error(`Key '${key}' not found in Context`);
}
collectEvents(
event: WorkflowEvent,
expected: EventTypes[],
): WorkflowEvent[] | null {
const eventType = event.constructor as EventTypes;
if (!this.#eventBuffer.has(eventType)) {
this.#eventBuffer.set(eventType, []);
}
this.#eventBuffer.get(eventType)!.push(event);
const retval: WorkflowEvent[] = [];
for (const expectedType of expected) {
const events = this.#eventBuffer.get(expectedType);
if (events && events.length > 0) {
retval.push(events.shift()!);
}
}
if (retval.length === expected.length) {
return retval;
}
// Put back the events if unable to collect all
for (const ev of retval) {
const eventType = ev.constructor as EventTypes;
if (!this.#eventBuffer.has(eventType)) {
this.#eventBuffer.set(eventType, []);
}
this.#eventBuffer.get(eventType)!.unshift(ev);
}
return null;
}
sendEvent(message: WorkflowEvent, step?: StepFunction): void {
const stepName = step?.name ? `step ${step.name}` : "all steps";
if (this.#verbose) {
console.log(`Sending event ${message} to ${stepName}`);
}
if (step === undefined) {
for (const queue of this.#queues.values()) {
queue.push(message);
}
} else {
if (!this.#workflow.hasStep(step)) {
throw new Error(`Step ${step} does not exist`);
}
if (!this.#queues.has(step)) {
this.#queues.set(step, []);
}
this.#queues.get(step)!.push(message);
}
}
getNextEvent(step: StepFunction): WorkflowEvent | undefined {
const queue = this.#queues.get(step);
if (queue && queue.length > 0) {
return queue.shift();
}
return undefined;
}
writeEventToStream(event: WorkflowEvent): void {
this.#streamingQueue.push(event);
}
async *streamEvents(): AsyncGenerator<WorkflowEvent, void, undefined> {
while (true) {
const event = this.#streamingQueue.shift();
if (event) {
yield event;
} else {
if (!this.running) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
}
}
+18
View File
@@ -0,0 +1,18 @@
export class WorkflowEvent<T extends Record<string, any> = any> {
data: T;
constructor(data: T) {
this.data = data;
}
toString() {
return `${this.constructor.name}(${JSON.stringify(this.data)})`;
}
}
export type EventTypes<T extends Record<string, any> = any> = new (
data: T,
) => WorkflowEvent<T>;
export class StartEvent extends WorkflowEvent<{ input: string }> {}
export class StopEvent extends WorkflowEvent<{ result: string }> {}
+3
View File
@@ -0,0 +1,3 @@
export * from "./context";
export * from "./events";
export * from "./workflow";
+221
View File
@@ -0,0 +1,221 @@
import { Context } from "./context";
import {
type EventTypes,
StartEvent,
StopEvent,
WorkflowEvent,
} from "./events";
export type StepFunction<T extends WorkflowEvent = WorkflowEvent> = (
context: Context,
ev: T,
) => Promise<WorkflowEvent | void>;
type EventTypeParam = EventTypes | EventTypes[];
export class Workflow {
#steps: Map<
StepFunction<any>,
{ inputs: EventTypes[]; outputs: EventTypes[] | undefined }
> = new Map();
#contexts: Set<Context> = new Set();
#verbose: boolean = false;
#timeout: number | null = null;
#validate: boolean = false;
constructor(
params: {
verbose?: boolean;
timeout?: number;
validate?: boolean;
} = {},
) {
this.#verbose = params.verbose ?? false;
this.#timeout = params.timeout ?? null;
this.#validate = params.validate ?? false;
}
addStep<T extends WorkflowEvent>(
eventType: EventTypeParam,
method: StepFunction<T>,
params: { outputs?: EventTypeParam } = {},
) {
const inputs = Array.isArray(eventType) ? eventType : [eventType];
const outputs = params.outputs
? Array.isArray(params.outputs)
? params.outputs
: [params.outputs]
: undefined;
this.#steps.set(method, { inputs, outputs });
}
hasStep(step: StepFunction<any>): boolean {
return this.#steps.has(step);
}
#acceptsEvent(step: StepFunction<any>, event: WorkflowEvent): boolean {
const eventType = event.constructor as EventTypes;
const stepInfo = this.#steps.get(step);
if (!stepInfo) {
throw new Error(`No method found for step: ${step.name}`);
}
return stepInfo.inputs.includes(eventType);
}
async *streamEvents(): AsyncGenerator<WorkflowEvent, void, unknown> {
if (this.#contexts.size > 1) {
throw new Error(
"This workflow has multiple concurrent runs in progress and cannot stream events. " +
"To be able to stream events, make sure you call `run()` on this workflow only once.",
);
}
const context = this.#contexts.values().next().value;
if (!context) {
throw new Error("No active context found for streaming events.");
}
yield* context.streamEvents();
}
validate(): void {
if (this.#verbose) {
console.log("Validating workflow...");
}
// Check if all steps have outputs defined
// precondition for the validation to work
const allStepsHaveOutputs = Array.from(this.#steps.values()).every(
(stepInfo) => stepInfo.outputs !== undefined,
);
if (!allStepsHaveOutputs) {
throw new Error(
"Not all steps have outputs defined. Can't validate. Add the 'outputs' parameter to each 'addStep' method call to do validation",
);
}
// input events that are consumed by any step of the workflow
const consumedEvents: Set<EventTypes> = new Set();
// output events that are produced by any step of the workflow
const producedEvents: Set<EventTypes> = new Set([StartEvent]);
for (const [, stepInfo] of this.#steps) {
stepInfo.inputs.forEach((eventType) => consumedEvents.add(eventType));
stepInfo.outputs?.forEach((eventType) => producedEvents.add(eventType));
}
// Check if all consumed events are produced
const unconsumedEvents = Array.from(consumedEvents).filter(
(event) => !producedEvents.has(event),
);
if (unconsumedEvents.length > 0) {
const names = unconsumedEvents.map((event) => event.name).join(", ");
throw new Error(
`The following events are consumed but never produced: ${names}`,
);
}
// Check if there are any unused produced events (except StopEvent)
const unusedEvents = Array.from(producedEvents).filter(
(event) => !consumedEvents.has(event) && event !== StopEvent,
);
if (unusedEvents.length > 0) {
const names = unusedEvents.map((event) => event.name).join(", ");
throw new Error(
`The following events are produced but never consumed: ${names}`,
);
}
if (this.#verbose) {
console.log("Workflow validation passed");
}
}
async run(event: StartEvent | string): Promise<StopEvent> {
// Validate the workflow before running if #validate is true
if (this.#validate) {
this.validate();
}
const context = new Context({ workflow: this, verbose: this.#verbose });
this.#contexts.add(context);
const stopWorkflow = () => {
if (context.running) {
context.running = false;
this.#contexts.delete(context);
}
};
const startEvent: WorkflowEvent =
typeof event === "string" ? new StartEvent({ input: event }) : event;
if (this.#verbose) {
console.log(`Starting workflow with event ${startEvent}`);
}
const workflowPromise = new Promise<StopEvent>((resolve, reject) => {
for (const [step] of this.#steps) {
// send initial event to step
context.sendEvent(startEvent, step);
if (this.#verbose) {
console.log(`Starting tasks for step ${step.name}`);
}
queueMicrotask(async () => {
try {
while (context.running) {
const currentEvent = context.getNextEvent(step);
if (!currentEvent) {
// if there's no event, wait and try again
await new Promise((resolve) => setTimeout(resolve, 0));
continue;
}
if (!this.#acceptsEvent(step, currentEvent)) {
// step does not accept current event, skip it
continue;
}
if (this.#verbose) {
console.log(`Step ${step.name} received event ${currentEvent}`);
}
const result = await step.call(this, context, currentEvent);
if (!context.running) {
// workflow was stopped during the execution (e.g. there was a timeout)
return;
}
if (result instanceof StopEvent) {
if (this.#verbose) {
console.log(`Stopping workflow with event ${result}`);
}
resolve(result);
return;
}
if (result instanceof WorkflowEvent) {
context.sendEvent(result);
}
}
} catch (error) {
if (this.#verbose) {
console.error(`Error in calling step ${step.name}:`, error);
}
reject(error as Error);
} finally {
stopWorkflow();
}
});
}
});
if (this.#timeout !== null) {
const timeout = this.#timeout;
const timeoutPromise = new Promise<never>((_, reject) =>
setTimeout(() => {
stopWorkflow();
reject(new Error(`Operation timed out after ${timeout} seconds`));
}, timeout * 1000),
);
return Promise.race([workflowPromise, timeoutPromise]);
}
return workflowPromise;
}
}
+143
View File
@@ -0,0 +1,143 @@
import { beforeEach, describe, expect, test, vi, type Mocked } from "vitest";
import type { Context } from "../src/workflow/context.js";
import {
StartEvent,
StopEvent,
WorkflowEvent,
} from "../src/workflow/events.js";
import { Workflow } from "../src/workflow/workflow.js";
// mock OpenAI class for testing
class OpenAI {
complete = vi.fn();
}
class JokeEvent extends WorkflowEvent<{ joke: string }> {}
class AnalysisEvent extends WorkflowEvent<{ analysis: string }> {}
describe("Workflow", () => {
let mockLLM: Mocked<OpenAI>;
let generateJoke: Mocked<any>;
let critiqueJoke: Mocked<any>;
let analyzeJoke: Mocked<any>;
beforeEach(() => {
mockLLM = new OpenAI() as Mocked<OpenAI>;
mockLLM.complete
.mockResolvedValueOnce({
text: "Why do pirates make great singers? They can hit the high Cs!",
})
.mockResolvedValueOnce({
text: "This joke is clever but could use improvement...",
})
.mockResolvedValueOnce({
text: "The analysis is insightful and helpful.",
});
generateJoke = vi.fn(async (_context, ev: StartEvent) => {
const response = await mockLLM.complete({
prompt: `Write your best joke about ${ev.data.input}.`,
});
return new JokeEvent({ joke: response.text });
});
critiqueJoke = vi.fn(async (_context, ev: JokeEvent) => {
const response = await mockLLM.complete({
prompt: `Give a thorough critique of the following joke: ${ev.data.joke}`,
});
return new StopEvent({ result: response.text });
});
analyzeJoke = vi.fn(async (_context: Context, ev: JokeEvent) => {
const prompt = `Give a thorough analysis of the following joke: ${ev.data.joke}`;
const response = await mockLLM.complete({ prompt });
return new AnalysisEvent({ analysis: response.text });
});
});
test("addStep", () => {
const jokeFlow = new Workflow({ verbose: true });
jokeFlow.addStep(StartEvent, generateJoke);
jokeFlow.addStep(JokeEvent, critiqueJoke);
expect(jokeFlow.hasStep(generateJoke)).toBe(true);
expect(jokeFlow.hasStep(critiqueJoke)).toBe(true);
});
test("run workflow", async () => {
const jokeFlow = new Workflow({ verbose: true });
jokeFlow.addStep(StartEvent, generateJoke);
jokeFlow.addStep(JokeEvent, critiqueJoke);
const result = await jokeFlow.run("pirates");
expect(generateJoke).toHaveBeenCalledTimes(1);
expect(critiqueJoke).toHaveBeenCalledTimes(1);
expect(result.data.result).toBe(
"This joke is clever but could use improvement...",
);
});
test("stream events", async () => {
const jokeFlow = new Workflow({ verbose: true });
jokeFlow.addStep(StartEvent, generateJoke);
jokeFlow.addStep(JokeEvent, critiqueJoke);
const run = jokeFlow.run("pirates");
const event = await jokeFlow.streamEvents().next(); // get one event to avoid testing timeout
const result = await run;
expect(generateJoke).toHaveBeenCalledTimes(1);
expect(critiqueJoke).toHaveBeenCalledTimes(1);
expect(result.data.result).toBe(
"This joke is clever but could use improvement...",
);
expect(event).not.toBeNull();
});
test("workflow timeout", async () => {
const TIMEOUT = 1;
const jokeFlow = new Workflow({ verbose: true, timeout: TIMEOUT });
const longRunning = async (_context: Context, ev: StartEvent) => {
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for 2 seconds
return new StopEvent({ result: "We waited 2 seconds" });
};
jokeFlow.addStep(StartEvent, longRunning);
const run = jokeFlow.run("Let's start");
await expect(run).rejects.toThrow(
`Operation timed out after ${TIMEOUT} seconds`,
);
});
test("workflow validation", async () => {
const jokeFlow = new Workflow({ verbose: true, validate: true });
jokeFlow.addStep(StartEvent, generateJoke, { outputs: StopEvent });
jokeFlow.addStep(JokeEvent, critiqueJoke, { outputs: StopEvent });
const run = jokeFlow.run("pirates");
await expect(run).rejects.toThrow(
"The following events are consumed but never produced: JokeEvent",
);
});
test("collectEvents", async () => {
let collectedEvents: WorkflowEvent[] | null = null;
const jokeFlow = new Workflow({ verbose: true });
jokeFlow.addStep(StartEvent, generateJoke);
jokeFlow.addStep(JokeEvent, analyzeJoke);
jokeFlow.addStep([AnalysisEvent], async (context, ev) => {
collectedEvents = context.collectEvents(ev, [AnalysisEvent]);
return new StopEvent({ result: "Report generated" });
});
const result = await jokeFlow.run("pirates");
expect(generateJoke).toHaveBeenCalledTimes(1);
expect(analyzeJoke).toHaveBeenCalledTimes(1);
expect(result.data.result).toBe("Report generated");
expect(collectedEvents).toHaveLength(1);
});
});
+22
View File
@@ -1,5 +1,27 @@
# @llamaindex/experimental
## 0.0.78
### Patch Changes
- Updated dependencies [11feef8]
- llamaindex@0.6.0
## 0.0.77
### Patch Changes
- Updated dependencies [7edeb1c]
- llamaindex@0.5.27
## 0.0.76
### Patch Changes
- Updated dependencies [ffe0cd1]
- Updated dependencies [ffe0cd1]
- llamaindex@0.5.26
## 0.0.75
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@llamaindex/experimental",
"description": "Experimental package for LlamaIndexTS",
"version": "0.0.75",
"version": "0.0.78",
"type": "module",
"types": "dist/type/index.d.ts",
"main": "dist/cjs/index.js",
+30
View File
@@ -1,5 +1,35 @@
# llamaindex
## 0.6.0
### Minor Changes
- 11feef8: Add workflows
### Patch Changes
- Updated dependencies [11feef8]
- @llamaindex/core@0.2.0
- @llamaindex/openai@0.1.2
## 0.5.27
### Patch Changes
- 7edeb1c: feat: decouple openai from `llamaindex` module
This should be a non-breaking change, but just you can now only install `@llamaindex/openai` to reduce the bundle size in the future
- Updated dependencies [7edeb1c]
- @llamaindex/openai@0.1.1
## 0.5.26
### Patch Changes
- ffe0cd1: faet: add openai o1 support
- ffe0cd1: feat: add PostgreSQL storage
## 0.5.25
### Patch Changes
@@ -1,5 +1,27 @@
# @llamaindex/cloudflare-worker-agent-test
## 0.0.62
### Patch Changes
- Updated dependencies [11feef8]
- llamaindex@0.6.0
## 0.0.61
### Patch Changes
- Updated dependencies [7edeb1c]
- llamaindex@0.5.27
## 0.0.60
### Patch Changes
- Updated dependencies [ffe0cd1]
- Updated dependencies [ffe0cd1]
- llamaindex@0.5.26
## 0.0.59
### Patch Changes
@@ -1,6 +1,6 @@
{
"name": "@llamaindex/cloudflare-worker-agent-test",
"version": "0.0.59",
"version": "0.0.62",
"type": "module",
"private": true,
"scripts": {
@@ -1,5 +1,27 @@
# @llamaindex/next-agent-test
## 0.1.62
### Patch Changes
- Updated dependencies [11feef8]
- llamaindex@0.6.0
## 0.1.61
### Patch Changes
- Updated dependencies [7edeb1c]
- llamaindex@0.5.27
## 0.1.60
### Patch Changes
- Updated dependencies [ffe0cd1]
- Updated dependencies [ffe0cd1]
- llamaindex@0.5.26
## 0.1.59
### Patch Changes
@@ -1,6 +1,6 @@
{
"name": "@llamaindex/next-agent-test",
"version": "0.1.59",
"version": "0.1.62",
"private": true,
"scripts": {
"dev": "next dev",
@@ -1,5 +1,27 @@
# test-edge-runtime
## 0.1.61
### Patch Changes
- Updated dependencies [11feef8]
- llamaindex@0.6.0
## 0.1.60
### Patch Changes
- Updated dependencies [7edeb1c]
- llamaindex@0.5.27
## 0.1.59
### Patch Changes
- Updated dependencies [ffe0cd1]
- Updated dependencies [ffe0cd1]
- llamaindex@0.5.26
## 0.1.58
### Patch Changes
@@ -1,6 +1,6 @@
{
"name": "@llamaindex/nextjs-edge-runtime-test",
"version": "0.1.58",
"version": "0.1.61",
"private": true,
"scripts": {
"dev": "next dev",
@@ -1,5 +1,27 @@
# @llamaindex/next-node-runtime
## 0.0.43
### Patch Changes
- Updated dependencies [11feef8]
- llamaindex@0.6.0
## 0.0.42
### Patch Changes
- Updated dependencies [7edeb1c]
- llamaindex@0.5.27
## 0.0.41
### Patch Changes
- Updated dependencies [ffe0cd1]
- Updated dependencies [ffe0cd1]
- llamaindex@0.5.26
## 0.0.40
### Patch Changes
@@ -1,6 +1,6 @@
{
"name": "@llamaindex/next-node-runtime-test",
"version": "0.0.40",
"version": "0.0.43",
"private": true,
"scripts": {
"dev": "next dev",
@@ -1,5 +1,27 @@
# @llamaindex/waku-query-engine-test
## 0.0.62
### Patch Changes
- Updated dependencies [11feef8]
- llamaindex@0.6.0
## 0.0.61
### Patch Changes
- Updated dependencies [7edeb1c]
- llamaindex@0.5.27
## 0.0.60
### Patch Changes
- Updated dependencies [ffe0cd1]
- Updated dependencies [ffe0cd1]
- llamaindex@0.5.26
## 0.0.59
### Patch Changes
@@ -1,6 +1,6 @@
{
"name": "@llamaindex/waku-query-engine-test",
"version": "0.0.59",
"version": "0.0.62",
"type": "module",
"private": true,
"scripts": {
@@ -20,6 +20,7 @@
"@types/react-dom": "18.3.0",
"autoprefixer": "10.4.20",
"tailwindcss": "3.4.10",
"typescript": "5.5.4"
"typescript": "5.5.4",
"vite-plugin-wasm": "^3.3.0"
}
}
@@ -11,15 +11,22 @@ export default async function RootLayout({ children }: RootLayoutProps) {
const data = await getData();
return (
<div className="font-['Nunito']">
<meta property="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>
<html>
<head>
<meta property="description" content={data.description} />
<link rel="icon" type="image/png" href={data.icon} />
<title>LlamaIndex Waku Example</title>
</head>
<body>
<div className="font-['Nunito']">
<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>
</body>
</html>
);
}
@@ -31,7 +38,6 @@ const getData = async () => {
return data;
};
export const getConfig = async () => {
return {
render: "static",
@@ -0,0 +1,8 @@
import wasm from "vite-plugin-wasm";
export default {
plugins: [wasm()],
ssr: {
external: ["tiktoken"],
},
};
@@ -1,51 +0,0 @@
import { TransformComponent } from "@llamaindex/core/schema";
import {
BaseEmbedding,
BaseNode,
SimilarityType,
type EmbeddingInfo,
type MessageContentDetail,
} from "llamaindex";
export class OpenAIEmbedding
extends TransformComponent
implements BaseEmbedding
{
embedInfo?: EmbeddingInfo;
embedBatchSize = 512;
constructor() {
super(async (nodes: BaseNode[], _options?: any): Promise<BaseNode[]> => {
nodes.forEach((node) => (node.embedding = [0]));
return nodes;
});
}
async getQueryEmbedding(query: MessageContentDetail) {
return [0];
}
async getTextEmbedding(text: string) {
return [0];
}
async getTextEmbeddings(texts: string[]) {
return [[0]];
}
async getTextEmbeddingsBatch(texts: string[]) {
return [[0]];
}
similarity(
embedding1: number[],
embedding2: number[],
mode?: SimilarityType,
) {
return 1;
}
truncateMaxTokens(input: string[]): string[] {
return input;
}
}
@@ -12,6 +12,15 @@ import type {
import { deepStrictEqual, strictEqual } from "node:assert";
import { llmCompleteMockStorage } from "../../node/utils.js";
import { TransformComponent } from "@llamaindex/core/schema";
import {
BaseEmbedding,
BaseNode,
SimilarityType,
type EmbeddingInfo,
type MessageContentDetail,
} from "llamaindex";
export function getOpenAISession() {
return {};
}
@@ -22,6 +31,7 @@ export function isFunctionCallingModel() {
export class OpenAI implements LLM {
supportToolCall = true;
get metadata() {
return {
model: "mock-model",
@@ -32,6 +42,7 @@ export class OpenAI implements LLM {
isFunctionCallingModel: true,
};
}
chat(
params: LLMChatParamsStreaming<Record<string, unknown>>,
): Promise<AsyncIterable<ChatResponseChunk>>;
@@ -77,6 +88,7 @@ export class OpenAI implements LLM {
}
throw new Error("Method not implemented.");
}
complete(
params: LLMCompletionParamsStreaming,
): Promise<AsyncIterable<CompletionResponse>>;
@@ -103,3 +115,46 @@ export class OpenAI implements LLM {
throw new Error("Method not implemented.");
}
}
export class OpenAIEmbedding
extends TransformComponent
implements BaseEmbedding
{
embedInfo?: EmbeddingInfo;
embedBatchSize = 512;
constructor() {
super(async (nodes: BaseNode[], _options?: any): Promise<BaseNode[]> => {
nodes.forEach((node) => (node.embedding = [0]));
return nodes;
});
}
async getQueryEmbedding(query: MessageContentDetail) {
return [0];
}
async getTextEmbedding(text: string) {
return [0];
}
async getTextEmbeddings(texts: string[]) {
return [[0]];
}
async getTextEmbeddingsBatch(texts: string[]) {
return [[0]];
}
similarity(
embedding1: number[],
embedding2: number[],
mode?: SimilarityType,
) {
return 1;
}
truncateMaxTokens(input: string[]): string[] {
return input;
}
}
+8 -2
View File
@@ -13,8 +13,14 @@ export async function resolve(specifier, context, nextResolve) {
return result;
}
const targetUrl = fileURLToPath(result.url).replace(/\.js$/, ".ts");
const relativePath = relative(packageDistDir, targetUrl);
if (relativePath.startsWith(".") || relativePath.startsWith("/")) {
let relativePath = relative(packageDistDir, targetUrl);
// todo: make it more generic if we have more sub modules fixtures in the future
if (relativePath.startsWith("../../llm/openai")) {
relativePath = relativePath.replace(
"../../llm/openai/dist/index.ts",
"llm/openai.ts",
);
} else if (relativePath.startsWith(".") || relativePath.startsWith("/")) {
return result;
}
const url = pathToFileURL(join(fixturesDir, relativePath)).toString();
@@ -0,0 +1,90 @@
import { Document, VectorStoreQueryMode } from "llamaindex";
import { PGVectorStore } from "llamaindex/vector-store/PGVectorStore";
import assert from "node:assert";
import { test } from "node:test";
import pg from "pg";
import { registerTypes } from "pgvector/pg";
let pgClient: pg.Client | pg.Pool;
test.afterEach(async () => {
await pgClient.end();
});
await test("init with client", async () => {
pgClient = new pg.Client({
database: "llamaindex_node_test",
});
await pgClient.connect();
await pgClient.query("CREATE EXTENSION IF NOT EXISTS vector");
await registerTypes(pgClient);
const vectorStore = new PGVectorStore(pgClient);
assert.deepStrictEqual(await vectorStore.client(), pgClient);
});
await test("init with pool", async () => {
pgClient = new pg.Pool({
database: "llamaindex_node_test",
});
await pgClient.query("CREATE EXTENSION IF NOT EXISTS vector");
const client = await pgClient.connect();
await registerTypes(client);
const vectorStore = new PGVectorStore(client);
assert.deepStrictEqual(await vectorStore.client(), client);
client.release();
});
await test("init without client", async () => {
const vectorStore = new PGVectorStore({
database: "llamaindex_node_test",
});
pgClient = (await vectorStore.client()) as pg.Client;
assert.notDeepStrictEqual(pgClient, undefined);
});
await test("simple node", async () => {
const dimensions = 3;
const schemaName =
"llamaindex_vector_store_test_" + Math.random().toString(36).substring(7);
const nodeId = "5bb16627-f6c0-459c-bb18-71642813ef21";
const node = new Document({
text: "hello world",
id_: nodeId,
embedding: [0.1, 0.2, 0.3],
});
const vectorStore = new PGVectorStore({
database: "llamaindex_node_test",
dimensions,
schemaName,
});
await vectorStore.add([node]);
{
const result = await vectorStore.query({
mode: VectorStoreQueryMode.DEFAULT,
similarityTopK: 1,
queryEmbedding: [1, 2, 3],
});
const actualJSON = result.nodes![0]!.toJSON();
assert.deepStrictEqual(actualJSON, {
...node.toJSON(),
hash: actualJSON.hash,
metadata: actualJSON.metadata,
});
assert.deepStrictEqual(result.ids, [nodeId]);
assert.deepStrictEqual(result.similarities, [1]);
}
await vectorStore.delete(nodeId);
{
const result = await vectorStore.query({
mode: VectorStoreQueryMode.DEFAULT,
similarityTopK: 1,
queryEmbedding: [1, 2, 3],
});
assert.deepStrictEqual(result.nodes, []);
}
pgClient = (await vectorStore.client()) as pg.Client;
});
-1
View File
@@ -10,7 +10,6 @@
},
"devDependencies": {
"@faker-js/faker": "^8.4.1",
"@llamaindex/core": "workspace:*",
"@types/node": "^22.5.1",
"consola": "^3.2.3",
"llamaindex": "workspace:*",
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "llamaindex",
"version": "0.5.25",
"version": "0.6.0",
"license": "MIT",
"type": "module",
"keywords": [
@@ -33,6 +33,7 @@
"@llamaindex/cloud": "workspace:*",
"@llamaindex/core": "workspace:*",
"@llamaindex/env": "workspace:*",
"@llamaindex/openai": "workspace:*",
"@mistralai/mistralai": "^1.0.4",
"@mixedbread-ai/sdk": "^2.2.11",
"@pinecone-database/pinecone": "^3.0.2",
@@ -56,7 +57,7 @@
"md-utils-ts": "^2.0.0",
"mongodb": "^6.7.0",
"notion-md-crawler": "^1.0.0",
"openai": "^4.57.0",
"openai": "^4.60.0",
"papaparse": "^5.4.1",
"pathe": "^1.1.2",
"portkey-ai": "0.1.16",
+1 -1
View File
@@ -5,7 +5,7 @@ import {
} from "@llamaindex/core/prompts";
import { extractText, messagesToHistory } from "@llamaindex/core/utils";
import { tokenizers, type Tokenizer } from "@llamaindex/env";
import { OpenAI } from "./llm/openai.js";
import { OpenAI } from "@llamaindex/openai";
/**
* A ChatHistory is used to keep the state of back and forth chat messages
+1 -1
View File
@@ -8,12 +8,12 @@ import {
import type { QueryType } from "@llamaindex/core/query-engine";
import type { BaseOutputParser } from "@llamaindex/core/schema";
import { extractText, toToolDescriptions } from "@llamaindex/core/utils";
import { OpenAI } from "@llamaindex/openai";
import { SubQuestionOutputParser } from "./OutputParser.js";
import type {
BaseQuestionGenerator,
SubQuestion,
} from "./engines/query/types.js";
import { OpenAI } from "./llm/openai.js";
import type { StructuredOutput } from "./types.js";
/**
+1 -2
View File
@@ -5,8 +5,7 @@ import {
type NodeParser,
SentenceSplitter,
} from "@llamaindex/core/node-parser";
import { OpenAIEmbedding } from "./embeddings/OpenAIEmbedding.js";
import { OpenAI } from "./llm/openai.js";
import { OpenAI, OpenAIEmbedding } from "@llamaindex/openai";
/**
* The ServiceContext is a collection of components that are used in different parts of the application.
+1 -1
View File
@@ -2,7 +2,7 @@ import {
type CallbackManager,
Settings as CoreSettings,
} from "@llamaindex/core/global";
import { OpenAI } from "./llm/openai.js";
import { OpenAI } from "@llamaindex/openai";
import { PromptHelper } from "@llamaindex/core/indices";
+1 -1
View File
@@ -1,5 +1,5 @@
import { OpenAI } from "@llamaindex/openai";
import { Settings } from "../Settings.js";
import { OpenAI } from "../llm/openai.js";
import { LLMAgent, LLMAgentWorker, type LLMAgentParams } from "./llm.js";
// This is likely not necessary anymore but leaving it here just incase it's in use elsewhere
@@ -13,8 +13,8 @@ import { getAppBaseUrl, getProjectId, initService } from "./utils.js";
import { PipelinesService, ProjectsService } from "@llamaindex/cloud/api";
import { SentenceSplitter } from "@llamaindex/core/node-parser";
import { getEnv } from "@llamaindex/env";
import { OpenAIEmbedding } from "@llamaindex/openai";
import { Settings } from "../Settings.js";
import { OpenAIEmbedding } from "../embeddings/OpenAIEmbedding.js";
export class LlamaCloudIndex {
params: CloudConstructorParams;
+1 -1
View File
@@ -5,7 +5,7 @@ import type {
} from "@llamaindex/cloud/api";
import { SentenceSplitter } from "@llamaindex/core/node-parser";
import { BaseNode, type TransformComponent } from "@llamaindex/core/schema";
import { OpenAIEmbedding } from "../embeddings/OpenAIEmbedding.js";
import { OpenAIEmbedding } from "@llamaindex/openai";
export type GetPipelineCreateParams = {
pipelineName: string;
@@ -1,152 +1 @@
import { BaseEmbedding } from "@llamaindex/core/embeddings";
import { Tokenizers } from "@llamaindex/env";
import type { ClientOptions as OpenAIClientOptions } from "openai";
import type { AzureOpenAIConfig } from "../llm/azure.js";
import {
getAzureConfigFromEnv,
getAzureModel,
shouldUseAzure,
} from "../llm/azure.js";
import type { OpenAISession } from "../llm/openai.js";
import { getOpenAISession } from "../llm/openai.js";
export const ALL_OPENAI_EMBEDDING_MODELS = {
"text-embedding-ada-002": {
dimensions: 1536,
maxTokens: 8192,
tokenizer: Tokenizers.CL100K_BASE,
},
"text-embedding-3-small": {
dimensions: 1536,
dimensionOptions: [512, 1536],
maxTokens: 8192,
tokenizer: Tokenizers.CL100K_BASE,
},
"text-embedding-3-large": {
dimensions: 3072,
dimensionOptions: [256, 1024, 3072],
maxTokens: 8192,
tokenizer: Tokenizers.CL100K_BASE,
},
};
type ModelKeys = keyof typeof ALL_OPENAI_EMBEDDING_MODELS;
export class OpenAIEmbedding extends BaseEmbedding {
/** embeddding model. defaults to "text-embedding-ada-002" */
model: string;
/** number of dimensions of the resulting vector, for models that support choosing fewer dimensions. undefined will default to model default */
dimensions?: number | undefined;
// OpenAI session params
/** api key */
apiKey?: string | undefined = undefined;
/** maximum number of retries, default 10 */
maxRetries: number;
/** timeout in ms, default 60 seconds */
timeout?: number | undefined;
/** other session options for OpenAI */
additionalSessionOptions?:
| Omit<Partial<OpenAIClientOptions>, "apiKey" | "maxRetries" | "timeout">
| undefined;
/** session object */
session: OpenAISession;
/**
* OpenAI Embedding
* @param init - initial parameters
*/
constructor(init?: Partial<OpenAIEmbedding> & { azure?: AzureOpenAIConfig }) {
super();
this.model = init?.model ?? "text-embedding-ada-002";
this.dimensions = init?.dimensions; // if no dimensions provided, will be undefined/not sent to OpenAI
this.embedBatchSize = init?.embedBatchSize ?? 10;
this.maxRetries = init?.maxRetries ?? 10;
this.timeout = init?.timeout ?? 60 * 1000; // Default is 60 seconds
this.additionalSessionOptions = init?.additionalSessionOptions;
// find metadata for model
const key = Object.keys(ALL_OPENAI_EMBEDDING_MODELS).find(
(key) => key === this.model,
) as ModelKeys | undefined;
if (key) {
this.embedInfo = ALL_OPENAI_EMBEDDING_MODELS[key];
}
if (init?.azure || shouldUseAzure()) {
const azureConfig = {
...getAzureConfigFromEnv({
model: getAzureModel(this.model),
}),
...init?.azure,
};
this.apiKey = azureConfig.apiKey;
this.session =
init?.session ??
getOpenAISession({
azure: true,
maxRetries: this.maxRetries,
timeout: this.timeout,
...this.additionalSessionOptions,
...azureConfig,
});
} else {
this.apiKey = init?.apiKey ?? undefined;
this.session =
init?.session ??
getOpenAISession({
apiKey: this.apiKey,
maxRetries: this.maxRetries,
timeout: this.timeout,
...this.additionalSessionOptions,
});
}
}
/**
* Get embeddings for a batch of texts
* @param texts
* @param options
*/
private async getOpenAIEmbedding(input: string[]): Promise<number[][]> {
// TODO: ensure this for every sub class by calling it in the base class
input = this.truncateMaxTokens(input);
const { data } = await this.session.openai.embeddings.create(
this.dimensions
? {
model: this.model,
dimensions: this.dimensions, // only sent to OpenAI if set by user
input,
}
: {
model: this.model,
input,
},
);
return data.map((d) => d.embedding);
}
/**
* Get embeddings for a batch of texts
* @param texts
*/
getTextEmbeddings = async (texts: string[]): Promise<number[][]> => {
return this.getOpenAIEmbedding(texts);
};
/**
* Get embeddings for a single text
* @param texts
*/
async getTextEmbedding(text: string): Promise<number[]> {
return (await this.getOpenAIEmbedding([text]))[0]!;
}
}
export * from "@llamaindex/openai";
@@ -1,5 +1,5 @@
import { getEnv } from "@llamaindex/env";
import { OpenAIEmbedding } from "./OpenAIEmbedding.js";
import { OpenAIEmbedding } from "@llamaindex/openai";
export class FireworksEmbedding extends OpenAIEmbedding {
constructor(init?: Partial<OpenAIEmbedding>) {
@@ -1,5 +1,5 @@
import { getEnv } from "@llamaindex/env";
import { OpenAIEmbedding } from "./OpenAIEmbedding.js";
import { OpenAIEmbedding } from "@llamaindex/openai";
export class TogetherEmbedding extends OpenAIEmbedding {
constructor(init?: Partial<OpenAIEmbedding>) {
@@ -1,7 +1,7 @@
import type { LLM } from "@llamaindex/core/llms";
import type { BaseNode } from "@llamaindex/core/schema";
import { MetadataMode, TextNode } from "@llamaindex/core/schema";
import { OpenAI } from "../llm/index.js";
import { OpenAI } from "@llamaindex/openai";
import {
defaultKeywordExtractorPromptTemplate,
defaultQuestionAnswerPromptTemplate,
@@ -1,6 +1,6 @@
import type { BaseEmbedding } from "@llamaindex/core/embeddings";
import { AsyncLocalStorage } from "@llamaindex/env";
import { OpenAIEmbedding } from "../../embeddings/OpenAIEmbedding.js";
import { OpenAIEmbedding } from "@llamaindex/openai";
const embeddedModelAsyncLocalStorage = new AsyncLocalStorage<BaseEmbedding>();
let globalEmbeddedModel: BaseEmbedding | null = null;
+1 -1
View File
@@ -1,5 +1,5 @@
import { getEnv } from "@llamaindex/env";
import { OpenAI } from "./openai.js";
import { OpenAI } from "@llamaindex/openai";
const ENV_VARIABLE_NAME = "DEEPINFRA_API_TOKEN";
const DEFAULT_MODEL = "mistralai/Mixtral-8x22B-Instruct-v0.1";
+1 -1
View File
@@ -1,5 +1,5 @@
import { getEnv } from "@llamaindex/env";
import { OpenAI } from "./openai.js";
import { OpenAI } from "@llamaindex/openai";
export const DEEPSEEK_MODELS = {
"deepseek-coder": { contextWindow: 128000 },
+1 -1
View File
@@ -1,5 +1,5 @@
import { getEnv } from "@llamaindex/env";
import { OpenAI } from "./openai.js";
import { OpenAI } from "@llamaindex/openai";
export class FireworksLLM extends OpenAI {
constructor(init?: Partial<OpenAI>) {
+1 -1
View File
@@ -1,6 +1,6 @@
import { getEnv } from "@llamaindex/env";
import { OpenAI } from "@llamaindex/openai";
import GroqSDK, { type ClientOptions } from "groq-sdk";
import { OpenAI } from "./openai.js";
export class Groq extends OpenAI {
constructor(
-1
View File
@@ -11,7 +11,6 @@ export {
GEMINI_MODEL,
type GoogleGeminiSessionOptions,
} from "./gemini/types.js";
export { Groq } from "./groq.js";
export { HuggingFaceInferenceAPI, HuggingFaceLLM } from "./huggingface.js";
export {
+1 -482
View File
@@ -1,482 +1 @@
import { getEnv } from "@llamaindex/env";
import _ from "lodash";
import type OpenAILLM from "openai";
import type {
ClientOptions,
ClientOptions as OpenAIClientOptions,
} from "openai";
import { AzureOpenAI, OpenAI as OrigOpenAI } from "openai";
import type { ChatModel } from "openai/resources/chat/chat";
import {
type BaseTool,
type ChatMessage,
type ChatResponse,
type ChatResponseChunk,
type LLM,
type LLMChatParamsNonStreaming,
type LLMChatParamsStreaming,
type LLMMetadata,
type MessageType,
type PartialToolCall,
ToolCallLLM,
type ToolCallLLMMessageOptions,
} from "@llamaindex/core/llms";
import {
extractText,
wrapEventCaller,
wrapLLMEvent,
} from "@llamaindex/core/utils";
import { Tokenizers } from "@llamaindex/env";
import type {
ChatCompletionAssistantMessageParam,
ChatCompletionMessageToolCall,
ChatCompletionRole,
ChatCompletionSystemMessageParam,
ChatCompletionTool,
ChatCompletionToolMessageParam,
ChatCompletionUserMessageParam,
} from "openai/resources/chat/completions";
import type { ChatCompletionMessageParam } from "openai/resources/index.js";
import type { AzureOpenAIConfig } from "./azure.js";
import {
getAzureConfigFromEnv,
getAzureModel,
shouldUseAzure,
} from "./azure.js";
export class OpenAISession {
openai: Pick<OrigOpenAI, "chat" | "embeddings">;
constructor(options: ClientOptions & { azure?: boolean } = {}) {
if (options.azure) {
this.openai = new AzureOpenAI(options as AzureOpenAIConfig);
} else {
if (!options.apiKey) {
options.apiKey = getEnv("OPENAI_API_KEY");
}
if (!options.apiKey) {
throw new Error("Set OpenAI Key in OPENAI_API_KEY env variable"); // Overriding OpenAI package's error message
}
this.openai = new OrigOpenAI({
...options,
});
}
}
}
// I'm not 100% sure this is necessary vs. just starting a new session
// every time we make a call. They say they try to reuse connections
// so in theory this is more efficient, but we should test it in the future.
const defaultOpenAISession: {
session: OpenAISession;
options: ClientOptions;
}[] = [];
/**
* Get a session for the OpenAI API. If one already exists with the same options,
* it will be returned. Otherwise, a new session will be created.
* @param options
* @returns
*/
export function getOpenAISession(
options: ClientOptions & { azure?: boolean } = {},
) {
let session = defaultOpenAISession.find((session) => {
return _.isEqual(session.options, options);
})?.session;
if (!session) {
session = new OpenAISession(options);
defaultOpenAISession.push({ session, options });
}
return session;
}
export const GPT4_MODELS = {
"gpt-4": { contextWindow: 8192 },
"gpt-4-32k": { contextWindow: 32768 },
"gpt-4-32k-0613": { contextWindow: 32768 },
"gpt-4-turbo": { contextWindow: 128000 },
"gpt-4-turbo-preview": { contextWindow: 128000 },
"gpt-4-1106-preview": { contextWindow: 128000 },
"gpt-4-0125-preview": { contextWindow: 128000 },
"gpt-4-vision-preview": { contextWindow: 128000 },
"gpt-4o": { contextWindow: 128000 },
"gpt-4o-2024-05-13": { contextWindow: 128000 },
"gpt-4o-mini": { contextWindow: 128000 },
"gpt-4o-mini-2024-07-18": { contextWindow: 128000 },
"gpt-4o-2024-08-06": { contextWindow: 128000 },
"gpt-4o-2024-09-14": { contextWindow: 128000 },
"gpt-4o-2024-10-14": { contextWindow: 128000 },
"gpt-4-0613": { contextWindow: 128000 },
"gpt-4-turbo-2024-04-09": { contextWindow: 128000 },
"gpt-4-0314": { contextWindow: 128000 },
"gpt-4-32k-0314": { contextWindow: 32768 },
};
// NOTE we don't currently support gpt-3.5-turbo-instruct and don't plan to in the near future
export const GPT35_MODELS = {
"gpt-3.5-turbo": { contextWindow: 16385 },
"gpt-3.5-turbo-0613": { contextWindow: 4096 },
"gpt-3.5-turbo-16k": { contextWindow: 16385 },
"gpt-3.5-turbo-16k-0613": { contextWindow: 16385 },
"gpt-3.5-turbo-1106": { contextWindow: 16385 },
"gpt-3.5-turbo-0125": { contextWindow: 16385 },
"gpt-3.5-turbo-0301": { contextWindow: 16385 },
};
/**
* We currently support GPT-3.5 and GPT-4 models
*/
export const ALL_AVAILABLE_OPENAI_MODELS = {
...GPT4_MODELS,
...GPT35_MODELS,
} satisfies Record<ChatModel, { contextWindow: number }>;
export function isFunctionCallingModel(llm: LLM): llm is OpenAI {
let model: string;
if (llm instanceof OpenAI) {
model = llm.model;
} else if ("model" in llm && typeof llm.model === "string") {
model = llm.model;
} else {
return false;
}
const isChatModel = Object.keys(ALL_AVAILABLE_OPENAI_MODELS).includes(model);
const isOld = model.includes("0314") || model.includes("0301");
return isChatModel && !isOld;
}
export type OpenAIAdditionalMetadata = {};
export type OpenAIAdditionalChatOptions = Omit<
Partial<OpenAILLM.Chat.ChatCompletionCreateParams>,
| "max_tokens"
| "messages"
| "model"
| "temperature"
| "top_p"
| "stream"
| "tools"
| "toolChoice"
>;
export class OpenAI extends ToolCallLLM<OpenAIAdditionalChatOptions> {
model:
| ChatModel
// string & {} is a hack to allow any string, but still give autocomplete
| (string & {});
temperature: number;
topP: number;
maxTokens?: number | undefined;
additionalChatOptions?: OpenAIAdditionalChatOptions | undefined;
// OpenAI session params
apiKey?: string | undefined = undefined;
maxRetries: number;
timeout?: number;
session: OpenAISession;
additionalSessionOptions?:
| undefined
| Omit<Partial<OpenAIClientOptions>, "apiKey" | "maxRetries" | "timeout">;
constructor(
init?: Partial<OpenAI> & {
azure?: AzureOpenAIConfig;
},
) {
super();
this.model = init?.model ?? "gpt-4o";
this.temperature = init?.temperature ?? 0.1;
this.topP = init?.topP ?? 1;
this.maxTokens = init?.maxTokens ?? undefined;
this.maxRetries = init?.maxRetries ?? 10;
this.timeout = init?.timeout ?? 60 * 1000; // Default is 60 seconds
this.additionalChatOptions = init?.additionalChatOptions;
this.additionalSessionOptions = init?.additionalSessionOptions;
if (init?.azure || shouldUseAzure()) {
const azureConfig = {
...getAzureConfigFromEnv({
model: getAzureModel(this.model),
}),
...init?.azure,
};
this.apiKey = azureConfig.apiKey;
this.session =
init?.session ??
getOpenAISession({
azure: true,
maxRetries: this.maxRetries,
timeout: this.timeout,
...this.additionalSessionOptions,
...azureConfig,
});
} else {
this.apiKey = init?.apiKey ?? undefined;
this.session =
init?.session ??
getOpenAISession({
apiKey: this.apiKey,
maxRetries: this.maxRetries,
timeout: this.timeout,
...this.additionalSessionOptions,
});
}
}
get supportToolCall() {
return isFunctionCallingModel(this);
}
get metadata(): LLMMetadata & OpenAIAdditionalMetadata {
const contextWindow =
ALL_AVAILABLE_OPENAI_MODELS[
this.model as keyof typeof ALL_AVAILABLE_OPENAI_MODELS
]?.contextWindow ?? 1024;
return {
model: this.model,
temperature: this.temperature,
topP: this.topP,
maxTokens: this.maxTokens,
contextWindow,
tokenizer: Tokenizers.CL100K_BASE,
};
}
static toOpenAIRole(messageType: MessageType): ChatCompletionRole {
switch (messageType) {
case "user":
return "user";
case "assistant":
return "assistant";
case "system":
return "system";
default:
return "user";
}
}
static toOpenAIMessage(
messages: ChatMessage<ToolCallLLMMessageOptions>[],
): ChatCompletionMessageParam[] {
return messages.map((message) => {
const options = message.options ?? {};
if ("toolResult" in options) {
return {
tool_call_id: options.toolResult.id,
role: "tool",
content: extractText(message.content),
} satisfies ChatCompletionToolMessageParam;
} else if ("toolCall" in options) {
return {
role: "assistant",
content: extractText(message.content),
tool_calls: options.toolCall.map((toolCall) => {
return {
id: toolCall.id,
type: "function",
function: {
name: toolCall.name,
arguments:
typeof toolCall.input === "string"
? toolCall.input
: JSON.stringify(toolCall.input),
},
};
}),
} satisfies ChatCompletionAssistantMessageParam;
} else if (message.role === "user") {
return {
role: "user",
content: message.content,
} satisfies ChatCompletionUserMessageParam;
}
const response:
| ChatCompletionSystemMessageParam
| ChatCompletionUserMessageParam
| ChatCompletionMessageToolCall = {
// fixme(alex): type assertion
role: OpenAI.toOpenAIRole(message.role) as never,
// fixme: should not extract text, but assert content is string
content: extractText(message.content),
};
return response;
});
}
chat(
params: LLMChatParamsStreaming<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<AsyncIterable<ChatResponseChunk<ToolCallLLMMessageOptions>>>;
chat(
params: LLMChatParamsNonStreaming<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<ChatResponse<ToolCallLLMMessageOptions>>;
@wrapEventCaller
@wrapLLMEvent
async chat(
params:
| LLMChatParamsNonStreaming<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>
| LLMChatParamsStreaming<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<
| ChatResponse<ToolCallLLMMessageOptions>
| AsyncIterable<ChatResponseChunk<ToolCallLLMMessageOptions>>
> {
const { messages, stream, tools, additionalChatOptions } = params;
const baseRequestParams = <OpenAILLM.Chat.ChatCompletionCreateParams>{
model: this.model,
temperature: this.temperature,
max_tokens: this.maxTokens,
tools: tools?.map(OpenAI.toTool),
messages: OpenAI.toOpenAIMessage(messages),
top_p: this.topP,
...Object.assign({}, this.additionalChatOptions, additionalChatOptions),
};
if (
Array.isArray(baseRequestParams.tools) &&
baseRequestParams.tools.length === 0
) {
// remove empty tools array to avoid OpenAI error
delete baseRequestParams.tools;
}
// Streaming
if (stream) {
return this.streamChat(baseRequestParams);
}
// Non-streaming
const response = await this.session.openai.chat.completions.create({
...baseRequestParams,
stream: false,
});
const content = response.choices[0]!.message?.content ?? "";
return {
raw: response,
message: {
content,
role: response.choices[0]!.message.role,
options: response.choices[0]!.message?.tool_calls
? {
toolCall: response.choices[0]!.message.tool_calls.map(
(toolCall) => ({
id: toolCall.id,
name: toolCall.function.name,
input: toolCall.function.arguments,
}),
),
}
: {},
},
};
}
// todo: this wrapper is ugly, refactor it
@wrapEventCaller
protected async *streamChat(
baseRequestParams: OpenAILLM.Chat.ChatCompletionCreateParams,
): AsyncIterable<ChatResponseChunk<ToolCallLLMMessageOptions>> {
const stream: AsyncIterable<OpenAILLM.Chat.ChatCompletionChunk> =
await this.session.openai.chat.completions.create({
...baseRequestParams,
stream: true,
});
// TODO: add callback to streamConverter and use streamConverter here
// this will be used to keep track of the current tool call, make sure input are valid json object.
let currentToolCall: PartialToolCall | null = null;
const toolCallMap = new Map<string, PartialToolCall>();
for await (const part of stream) {
if (part.choices.length === 0) continue;
const choice = part.choices[0]!;
// skip parts that don't have any content
if (!(choice.delta.content || choice.delta.tool_calls)) continue;
let shouldEmitToolCall: PartialToolCall | null = null;
if (
choice.delta.tool_calls?.[0]!.id &&
currentToolCall &&
choice.delta.tool_calls?.[0].id !== currentToolCall.id
) {
shouldEmitToolCall = {
...currentToolCall,
input: JSON.parse(currentToolCall.input),
};
}
if (choice.delta.tool_calls?.[0]!.id) {
currentToolCall = {
name: choice.delta.tool_calls[0].function!.name!,
id: choice.delta.tool_calls[0].id,
input: choice.delta.tool_calls[0].function!.arguments!,
};
toolCallMap.set(choice.delta.tool_calls[0].id, currentToolCall);
} else {
if (choice.delta.tool_calls?.[0]!.function?.arguments) {
currentToolCall!.input +=
choice.delta.tool_calls[0].function.arguments;
}
}
const isDone: boolean = choice.finish_reason !== null;
if (isDone && currentToolCall) {
// for the last one, we need to emit the tool call
shouldEmitToolCall = {
...currentToolCall,
input: JSON.parse(currentToolCall.input),
};
}
yield {
raw: part,
options: shouldEmitToolCall
? { toolCall: [shouldEmitToolCall] }
: currentToolCall
? {
toolCall: [currentToolCall],
}
: {},
delta: choice.delta.content ?? "",
};
}
toolCallMap.clear();
return;
}
static toTool(tool: BaseTool): ChatCompletionTool {
return {
type: "function",
function: tool.metadata.parameters
? {
name: tool.metadata.name,
description: tool.metadata.description,
parameters: tool.metadata.parameters,
}
: {
name: tool.metadata.name,
description: tool.metadata.description,
},
};
}
}
export * from "@llamaindex/openai";
+1 -1
View File
@@ -1,5 +1,5 @@
import { getEnv } from "@llamaindex/env";
import { OpenAI } from "./openai.js";
import { OpenAI } from "@llamaindex/openai";
export class TogetherLLM extends OpenAI {
constructor(init?: Partial<OpenAI>) {
@@ -0,0 +1,21 @@
import { DEFAULT_NAMESPACE } from "@llamaindex/core/global";
import { PostgresKVStore } from "../kvStore/PostgresKVStore.js";
import { KVDocumentStore } from "./KVDocumentStore.js";
const DEFAULT_TABLE_NAME = "llamaindex_doc_store";
export class PostgresDocumentStore extends KVDocumentStore {
constructor(config?: {
schemaName?: string;
tableName?: string;
connectionString?: string;
namespace?: string;
}) {
const kvStore = new PostgresKVStore({
schemaName: config?.schemaName,
tableName: config?.tableName || DEFAULT_TABLE_NAME,
});
const namespace = config?.namespace || DEFAULT_NAMESPACE;
super(kvStore, namespace);
}
}
+3
View File
@@ -1,10 +1,13 @@
export { SimpleChatStore } from "./chatStore/SimpleChatStore.js";
export * from "./chatStore/types.js";
export { PostgresDocumentStore } from "./docStore/PostgresDocumentStore.js";
export { SimpleDocumentStore } from "./docStore/SimpleDocumentStore.js";
export * from "./docStore/types.js";
export * from "./FileSystem.js";
export { PostgresIndexStore } from "./indexStore/PostgresIndexStore.js";
export { SimpleIndexStore } from "./indexStore/SimpleIndexStore.js";
export * from "./indexStore/types.js";
export { PostgresKVStore } from "./kvStore/PostgresKVStore.js";
export { SimpleKVStore } from "./kvStore/SimpleKVStore.js";
export * from "./kvStore/types.js";
export * from "./StorageContext.js";
@@ -0,0 +1,21 @@
import { DEFAULT_NAMESPACE } from "@llamaindex/core/global";
import { PostgresKVStore } from "../kvStore/PostgresKVStore.js";
import { KVIndexStore } from "./KVIndexStore.js";
const DEFAULT_TABLE_NAME = "llamaindex_index_store";
export class PostgresIndexStore extends KVIndexStore {
constructor(config?: {
schemaName?: string;
tableName?: string;
connectionString?: string;
namespace?: string;
}) {
const kvStore = new PostgresKVStore({
schemaName: config?.schemaName,
tableName: config?.tableName || DEFAULT_TABLE_NAME,
});
const namespace = config?.namespace || DEFAULT_NAMESPACE;
super(kvStore, namespace);
}
}
@@ -0,0 +1,140 @@
import { DEFAULT_COLLECTION } from "@llamaindex/core/global";
import type pg from "pg";
import { BaseKVStore } from "./types.js";
export type DataType = Record<string, Record<string, any>>;
const DEFAULT_SCHEMA_NAME = "public";
const DEFAULT_TABLE_NAME = "llamaindex_kv_store";
export class PostgresKVStore extends BaseKVStore {
private schemaName: string;
private tableName: string;
private connectionString: string | undefined = undefined;
private db?: pg.Client;
constructor(config?: {
schemaName?: string | undefined;
tableName?: string | undefined;
connectionString?: string | undefined;
}) {
super();
this.schemaName = config?.schemaName || DEFAULT_SCHEMA_NAME;
this.tableName = config?.tableName || DEFAULT_TABLE_NAME;
this.connectionString = config?.connectionString;
}
private async getDb(): Promise<pg.Client> {
if (!this.db) {
try {
const pg = await import("pg");
const { Client } = pg.default ? pg.default : pg;
const db = new Client({ connectionString: this.connectionString });
await db.connect();
await this.checkSchema(db);
this.db = db;
} catch (err) {
console.error(err);
return Promise.reject(err instanceof Error ? err : new Error(`${err}`));
}
}
return Promise.resolve(this.db);
}
private async checkSchema(db: pg.Client) {
await db.query(`CREATE SCHEMA IF NOT EXISTS ${this.schemaName}`);
const tbl = `CREATE TABLE IF NOT EXISTS ${this.schemaName}.${this.tableName} (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
collection VARCHAR,
key VARCHAR,
value JSONB DEFAULT '{}'
)`;
await db.query(tbl);
const idxs = `CREATE INDEX IF NOT EXISTS idx_${this.tableName}_collection ON ${this.schemaName}.${this.tableName} (collection);
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_key ON ${this.schemaName}.${this.tableName} (key);`;
await db.query(idxs);
return db;
}
client() {
return this.getDb();
}
async put(
key: string,
val: any,
collection: string = DEFAULT_COLLECTION,
): Promise<void> {
const db = await this.getDb();
try {
await db.query("BEGIN");
const sql = `
INSERT INTO ${this.schemaName}.${this.tableName}
(collection, key, value)
VALUES ($1, $2, $3)
ON CONFLICT (id) DO UPDATE SET
collection = EXCLUDED.collection,
key = EXCLUDED.key,
value = EXCLUDED.value
RETURNING id
`;
const values = [collection, key, val];
await db.query(sql, values);
await db.query("COMMIT");
} catch (error) {
await db.query("ROLLBACK");
throw error;
}
}
async get(
key: string,
collection: string = DEFAULT_COLLECTION,
): Promise<any> {
const db = await this.getDb();
try {
await db.query("BEGIN");
const sql = `SELECT * FROM ${this.schemaName}.${this.tableName} WHERE key = $1 AND collection = $2`;
const result = await db.query(sql, [key, collection]);
await db.query("COMMIT");
return result.rows[0].value;
} catch (error) {
await db.query("ROLLBACK");
throw error;
}
}
async getAll(collection: string = DEFAULT_COLLECTION): Promise<DataType> {
const db = await this.getDb();
try {
await db.query("BEGIN");
const sql = `SELECT * FROM ${this.schemaName}.${this.tableName} WHERE collection = $1`;
const result = await db.query(sql, [collection]);
await db.query("COMMIT");
return result.rows.reduce((acc, row) => {
acc[row.key] = row.value;
return acc;
}, {});
} catch (error) {
await db.query("ROLLBACK");
throw error;
}
}
async delete(
key: string,
collection: string = DEFAULT_COLLECTION,
): Promise<boolean> {
const db = await this.getDb();
try {
await db.query("BEGIN");
const sql = `DELETE FROM ${this.schemaName}.${this.tableName} WHERE key = $1 AND collection = $2`;
const result = await db.query(sql, [key, collection]);
await db.query("COMMIT");
return !!result.rowCount && result.rowCount > 0;
} catch (error) {
await db.query("ROLLBACK");
throw error;
}
}
}
@@ -1,85 +0,0 @@
import { Document } from "llamaindex";
import { PGVectorStore } from "llamaindex/vector-store/PGVectorStore";
import pg from "pg";
import { registerTypes } from "pgvector/pg";
import { describe, expect, test } from "vitest";
import { VectorStoreQueryMode } from "../../src/index.js";
describe("pg - init", () => {
test("init with client", async () => {
const client = new pg.Client({
database: "llamaindex_node_test",
});
await client.connect();
await client.query("CREATE EXTENSION IF NOT EXISTS vector");
await registerTypes(client);
const vectorStore = new PGVectorStore(client);
expect(await vectorStore.client()).toBe(client);
});
test("init with pool", async () => {
const pool = new pg.Pool({
database: "llamaindex_node_test",
});
await pool.query("CREATE EXTENSION IF NOT EXISTS vector");
const client = await pool.connect();
await registerTypes(client);
const vectorStore = new PGVectorStore(client);
expect(await vectorStore.client()).toBe(client);
});
test("init without client", async () => {
const vectorStore = new PGVectorStore({
database: "llamaindex_node_test",
});
expect(await vectorStore.client()).toBeDefined();
});
});
describe("pg - save data", () => {
test("simple node", async () => {
const dimensions = 3;
const schemaName =
"llamaindex_vector_store_test_" + Math.random().toString(36).substring(7);
const nodeId = "5bb16627-f6c0-459c-bb18-71642813ef21";
const node = new Document({
text: "hello world",
id_: nodeId,
embedding: [0.1, 0.2, 0.3],
});
const vectorStore = new PGVectorStore({
database: "llamaindex_node_test",
dimensions,
schemaName,
});
await vectorStore.add([node]);
{
const result = await vectorStore.query({
mode: VectorStoreQueryMode.DEFAULT,
similarityTopK: 1,
queryEmbedding: [1, 2, 3],
});
const actualJSON = result.nodes![0]!.toJSON();
expect(actualJSON).toEqual({
...node.toJSON(),
hash: actualJSON.hash,
metadata: actualJSON.metadata,
});
expect(result.ids).toEqual([nodeId]);
expect(result.similarities).toEqual([1]);
}
await vectorStore.delete(nodeId);
{
const result = await vectorStore.query({
mode: VectorStoreQueryMode.DEFAULT,
similarityTopK: 1,
queryEmbedding: [1, 2, 3],
});
expect(result.nodes).toEqual([]);
}
});
});
+16
View File
@@ -0,0 +1,16 @@
# @llamaindex/openai
## 0.1.2
### Patch Changes
- Updated dependencies [11feef8]
- @llamaindex/core@0.2.0
## 0.1.1
### Patch Changes
- 7edeb1c: feat: decouple openai from `llamaindex` module
This should be a non-breaking change, but just you can now only install `@llamaindex/openai` to reduce the bundle size in the future
+41
View File
@@ -0,0 +1,41 @@
{
"name": "@llamaindex/openai",
"description": "OpenAI Adapter for LlamaIndex",
"version": "0.1.2",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"exports": {
".": {
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
},
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
},
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/run-llama/LlamaIndexTS.git",
"directory": "packages/llm/openai"
},
"scripts": {
"build": "bunchee",
"dev": "bunchee --watch"
},
"devDependencies": {
"bunchee": "5.3.2"
},
"dependencies": {
"@llamaindex/core": "workspace:*",
"@llamaindex/env": "workspace:*",
"openai": "^4.60.0",
"remeda": "^2.12.0"
}
}
+152
View File
@@ -0,0 +1,152 @@
import { BaseEmbedding } from "@llamaindex/core/embeddings";
import { Tokenizers } from "@llamaindex/env";
import type { ClientOptions as OpenAIClientOptions } from "openai";
import type { AzureOpenAIConfig } from "./azure.js";
import {
getAzureConfigFromEnv,
getAzureModel,
shouldUseAzure,
} from "./azure.js";
import type { OpenAISession } from "./llm.js";
import { getOpenAISession } from "./llm.js";
export const ALL_OPENAI_EMBEDDING_MODELS = {
"text-embedding-ada-002": {
dimensions: 1536,
maxTokens: 8192,
tokenizer: Tokenizers.CL100K_BASE,
},
"text-embedding-3-small": {
dimensions: 1536,
dimensionOptions: [512, 1536],
maxTokens: 8192,
tokenizer: Tokenizers.CL100K_BASE,
},
"text-embedding-3-large": {
dimensions: 3072,
dimensionOptions: [256, 1024, 3072],
maxTokens: 8192,
tokenizer: Tokenizers.CL100K_BASE,
},
};
type ModelKeys = keyof typeof ALL_OPENAI_EMBEDDING_MODELS;
export class OpenAIEmbedding extends BaseEmbedding {
/** embeddding model. defaults to "text-embedding-ada-002" */
model: string;
/** number of dimensions of the resulting vector, for models that support choosing fewer dimensions. undefined will default to model default */
dimensions?: number | undefined;
// OpenAI session params
/** api key */
apiKey?: string | undefined = undefined;
/** maximum number of retries, default 10 */
maxRetries: number;
/** timeout in ms, default 60 seconds */
timeout?: number | undefined;
/** other session options for OpenAI */
additionalSessionOptions?:
| Omit<Partial<OpenAIClientOptions>, "apiKey" | "maxRetries" | "timeout">
| undefined;
/** session object */
session: OpenAISession;
/**
* OpenAI Embedding
* @param init - initial parameters
*/
constructor(init?: Partial<OpenAIEmbedding> & { azure?: AzureOpenAIConfig }) {
super();
this.model = init?.model ?? "text-embedding-ada-002";
this.dimensions = init?.dimensions; // if no dimensions provided, will be undefined/not sent to OpenAI
this.embedBatchSize = init?.embedBatchSize ?? 10;
this.maxRetries = init?.maxRetries ?? 10;
this.timeout = init?.timeout ?? 60 * 1000; // Default is 60 seconds
this.additionalSessionOptions = init?.additionalSessionOptions;
// find metadata for model
const key = Object.keys(ALL_OPENAI_EMBEDDING_MODELS).find(
(key) => key === this.model,
) as ModelKeys | undefined;
if (key) {
this.embedInfo = ALL_OPENAI_EMBEDDING_MODELS[key];
}
if (init?.azure || shouldUseAzure()) {
const azureConfig = {
...getAzureConfigFromEnv({
model: getAzureModel(this.model),
}),
...init?.azure,
};
this.apiKey = azureConfig.apiKey;
this.session =
init?.session ??
getOpenAISession({
azure: true,
maxRetries: this.maxRetries,
timeout: this.timeout,
...this.additionalSessionOptions,
...azureConfig,
});
} else {
this.apiKey = init?.apiKey ?? undefined;
this.session =
init?.session ??
getOpenAISession({
apiKey: this.apiKey,
maxRetries: this.maxRetries,
timeout: this.timeout,
...this.additionalSessionOptions,
});
}
}
/**
* Get embeddings for a batch of texts
* @param texts
* @param options
*/
private async getOpenAIEmbedding(input: string[]): Promise<number[][]> {
// TODO: ensure this for every sub class by calling it in the base class
input = this.truncateMaxTokens(input);
const { data } = await this.session.openai.embeddings.create(
this.dimensions
? {
model: this.model,
dimensions: this.dimensions, // only sent to OpenAI if set by user
input,
}
: {
model: this.model,
input,
},
);
return data.map((d) => d.embedding);
}
/**
* Get embeddings for a batch of texts
* @param texts
*/
getTextEmbeddings = async (texts: string[]): Promise<number[][]> => {
return this.getOpenAIEmbedding(texts);
};
/**
* Get embeddings for a single text
* @param texts
*/
async getTextEmbedding(text: string): Promise<number[]> {
return (await this.getOpenAIEmbedding([text]))[0]!;
}
}
+13
View File
@@ -0,0 +1,13 @@
export { ALL_OPENAI_EMBEDDING_MODELS, OpenAIEmbedding } from "./embedding";
export {
ALL_AVAILABLE_OPENAI_MODELS,
GPT35_MODELS,
GPT4_MODELS,
O1_MODELS,
OpenAI,
OpenAISession,
type OpenAIAdditionalChatOptions,
type OpenAIAdditionalMetadata,
} from "./llm";
export { type AzureOpenAIConfig } from "./azure";
+502
View File
@@ -0,0 +1,502 @@
import { getEnv } from "@llamaindex/env";
import type OpenAILLM from "openai";
import type {
ClientOptions,
ClientOptions as OpenAIClientOptions,
} from "openai";
import { AzureOpenAI, OpenAI as OrigOpenAI } from "openai";
import type { ChatModel } from "openai/resources/chat/chat";
import { isDeepEqual } from "remeda";
import {
type BaseTool,
type ChatMessage,
type ChatResponse,
type ChatResponseChunk,
type LLM,
type LLMChatParamsNonStreaming,
type LLMChatParamsStreaming,
type LLMMetadata,
type MessageType,
type PartialToolCall,
ToolCallLLM,
type ToolCallLLMMessageOptions,
} from "@llamaindex/core/llms";
import {
extractText,
wrapEventCaller,
wrapLLMEvent,
} from "@llamaindex/core/utils";
import { Tokenizers } from "@llamaindex/env";
import type {
ChatCompletionAssistantMessageParam,
ChatCompletionMessageToolCall,
ChatCompletionRole,
ChatCompletionSystemMessageParam,
ChatCompletionTool,
ChatCompletionToolMessageParam,
ChatCompletionUserMessageParam,
} from "openai/resources/chat/completions";
import type { ChatCompletionMessageParam } from "openai/resources/index.js";
import type { AzureOpenAIConfig } from "./azure.js";
import {
getAzureConfigFromEnv,
getAzureModel,
shouldUseAzure,
} from "./azure.js";
export class OpenAISession {
openai: Pick<OrigOpenAI, "chat" | "embeddings">;
constructor(options: ClientOptions & { azure?: boolean } = {}) {
if (options.azure) {
this.openai = new AzureOpenAI(options as AzureOpenAIConfig);
} else {
if (!options.apiKey) {
options.apiKey = getEnv("OPENAI_API_KEY");
}
if (!options.apiKey) {
throw new Error("Set OpenAI Key in OPENAI_API_KEY env variable"); // Overriding OpenAI package's error message
}
this.openai = new OrigOpenAI({
...options,
});
}
}
}
// I'm not 100% sure this is necessary vs. just starting a new session
// every time we make a call. They say they try to reuse connections
// so in theory this is more efficient, but we should test it in the future.
const defaultOpenAISession: {
session: OpenAISession;
options: ClientOptions;
}[] = [];
/**
* Get a session for the OpenAI API. If one already exists with the same options,
* it will be returned. Otherwise, a new session will be created.
* @param options
* @returns
*/
export function getOpenAISession(
options: ClientOptions & { azure?: boolean } = {},
) {
let session = defaultOpenAISession.find((session) => {
return isDeepEqual(session.options, options);
})?.session;
if (!session) {
session = new OpenAISession(options);
defaultOpenAISession.push({ session, options });
}
return session;
}
export const GPT4_MODELS = {
"chatgpt-4o-latest": {
contextWindow: 128000,
},
"gpt-4": { contextWindow: 8192 },
"gpt-4-32k": { contextWindow: 32768 },
"gpt-4-32k-0613": { contextWindow: 32768 },
"gpt-4-turbo": { contextWindow: 128000 },
"gpt-4-turbo-preview": { contextWindow: 128000 },
"gpt-4-1106-preview": { contextWindow: 128000 },
"gpt-4-0125-preview": { contextWindow: 128000 },
"gpt-4-vision-preview": { contextWindow: 128000 },
"gpt-4o": { contextWindow: 128000 },
"gpt-4o-2024-05-13": { contextWindow: 128000 },
"gpt-4o-mini": { contextWindow: 128000 },
"gpt-4o-mini-2024-07-18": { contextWindow: 128000 },
"gpt-4o-2024-08-06": { contextWindow: 128000 },
"gpt-4o-2024-09-14": { contextWindow: 128000 },
"gpt-4o-2024-10-14": { contextWindow: 128000 },
"gpt-4-0613": { contextWindow: 128000 },
"gpt-4-turbo-2024-04-09": { contextWindow: 128000 },
"gpt-4-0314": { contextWindow: 128000 },
"gpt-4-32k-0314": { contextWindow: 32768 },
};
// NOTE we don't currently support gpt-3.5-turbo-instruct and don't plan to in the near future
export const GPT35_MODELS = {
"gpt-3.5-turbo": { contextWindow: 16385 },
"gpt-3.5-turbo-0613": { contextWindow: 4096 },
"gpt-3.5-turbo-16k": { contextWindow: 16385 },
"gpt-3.5-turbo-16k-0613": { contextWindow: 16385 },
"gpt-3.5-turbo-1106": { contextWindow: 16385 },
"gpt-3.5-turbo-0125": { contextWindow: 16385 },
"gpt-3.5-turbo-0301": { contextWindow: 16385 },
};
export const O1_MODELS = {
"o1-preview": {
contextWindow: 128000,
},
"o1-preview-2024-09-12": {
contextWindow: 128000,
},
"o1-mini": {
contextWindow: 128000,
},
"o1-mini-2024-09-12": {
contextWindow: 128000,
},
};
/**
* We currently support GPT-3.5 and GPT-4 models
*/
export const ALL_AVAILABLE_OPENAI_MODELS = {
...GPT4_MODELS,
...GPT35_MODELS,
...O1_MODELS,
} satisfies Record<ChatModel, { contextWindow: number }>;
export function isFunctionCallingModel(llm: LLM): llm is OpenAI {
let model: string;
if (llm instanceof OpenAI) {
model = llm.model;
} else if ("model" in llm && typeof llm.model === "string") {
model = llm.model;
} else {
return false;
}
const isChatModel = Object.keys(ALL_AVAILABLE_OPENAI_MODELS).includes(model);
const isOld = model.includes("0314") || model.includes("0301");
const isO1 = model.startsWith("o1");
return isChatModel && !isOld && !isO1;
}
export type OpenAIAdditionalMetadata = {};
export type OpenAIAdditionalChatOptions = Omit<
Partial<OpenAILLM.Chat.ChatCompletionCreateParams>,
| "max_tokens"
| "messages"
| "model"
| "temperature"
| "top_p"
| "stream"
| "tools"
| "toolChoice"
>;
export class OpenAI extends ToolCallLLM<OpenAIAdditionalChatOptions> {
model:
| ChatModel
// string & {} is a hack to allow any string, but still give autocomplete
| (string & {});
temperature: number;
topP: number;
maxTokens?: number | undefined;
additionalChatOptions?: OpenAIAdditionalChatOptions | undefined;
// OpenAI session params
apiKey?: string | undefined = undefined;
maxRetries: number;
timeout?: number;
session: OpenAISession;
additionalSessionOptions?:
| undefined
| Omit<Partial<OpenAIClientOptions>, "apiKey" | "maxRetries" | "timeout">;
constructor(
init?: Partial<OpenAI> & {
azure?: AzureOpenAIConfig;
},
) {
super();
this.model = init?.model ?? "gpt-4o";
this.temperature = init?.temperature ?? 0.1;
this.topP = init?.topP ?? 1;
this.maxTokens = init?.maxTokens ?? undefined;
this.maxRetries = init?.maxRetries ?? 10;
this.timeout = init?.timeout ?? 60 * 1000; // Default is 60 seconds
this.additionalChatOptions = init?.additionalChatOptions;
this.additionalSessionOptions = init?.additionalSessionOptions;
if (init?.azure || shouldUseAzure()) {
const azureConfig = {
...getAzureConfigFromEnv({
model: getAzureModel(this.model),
}),
...init?.azure,
};
this.apiKey = azureConfig.apiKey;
this.session =
init?.session ??
getOpenAISession({
azure: true,
maxRetries: this.maxRetries,
timeout: this.timeout,
...this.additionalSessionOptions,
...azureConfig,
});
} else {
this.apiKey = init?.apiKey ?? undefined;
this.session =
init?.session ??
getOpenAISession({
apiKey: this.apiKey,
maxRetries: this.maxRetries,
timeout: this.timeout,
...this.additionalSessionOptions,
});
}
}
get supportToolCall() {
return isFunctionCallingModel(this);
}
get metadata(): LLMMetadata & OpenAIAdditionalMetadata {
const contextWindow =
ALL_AVAILABLE_OPENAI_MODELS[
this.model as keyof typeof ALL_AVAILABLE_OPENAI_MODELS
]?.contextWindow ?? 1024;
return {
model: this.model,
temperature: this.temperature,
topP: this.topP,
maxTokens: this.maxTokens,
contextWindow,
tokenizer: Tokenizers.CL100K_BASE,
};
}
static toOpenAIRole(messageType: MessageType): ChatCompletionRole {
switch (messageType) {
case "user":
return "user";
case "assistant":
return "assistant";
case "system":
return "system";
default:
return "user";
}
}
static toOpenAIMessage(
messages: ChatMessage<ToolCallLLMMessageOptions>[],
): ChatCompletionMessageParam[] {
return messages.map((message) => {
const options = message.options ?? {};
if ("toolResult" in options) {
return {
tool_call_id: options.toolResult.id,
role: "tool",
content: extractText(message.content),
} satisfies ChatCompletionToolMessageParam;
} else if ("toolCall" in options) {
return {
role: "assistant",
content: extractText(message.content),
tool_calls: options.toolCall.map((toolCall) => {
return {
id: toolCall.id,
type: "function",
function: {
name: toolCall.name,
arguments:
typeof toolCall.input === "string"
? toolCall.input
: JSON.stringify(toolCall.input),
},
};
}),
} satisfies ChatCompletionAssistantMessageParam;
} else if (message.role === "user") {
return {
role: "user",
content: message.content,
} satisfies ChatCompletionUserMessageParam;
}
const response:
| ChatCompletionSystemMessageParam
| ChatCompletionUserMessageParam
| ChatCompletionMessageToolCall = {
// fixme(alex): type assertion
role: OpenAI.toOpenAIRole(message.role) as never,
// fixme: should not extract text, but assert content is string
content: extractText(message.content),
};
return response;
});
}
chat(
params: LLMChatParamsStreaming<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<AsyncIterable<ChatResponseChunk<ToolCallLLMMessageOptions>>>;
chat(
params: LLMChatParamsNonStreaming<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<ChatResponse<ToolCallLLMMessageOptions>>;
@wrapEventCaller
@wrapLLMEvent
async chat(
params:
| LLMChatParamsNonStreaming<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>
| LLMChatParamsStreaming<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<
| ChatResponse<ToolCallLLMMessageOptions>
| AsyncIterable<ChatResponseChunk<ToolCallLLMMessageOptions>>
> {
const { messages, stream, tools, additionalChatOptions } = params;
const baseRequestParams = <OpenAILLM.Chat.ChatCompletionCreateParams>{
model: this.model,
temperature: this.temperature,
max_tokens: this.maxTokens,
tools: tools?.map(OpenAI.toTool),
messages: OpenAI.toOpenAIMessage(messages),
top_p: this.topP,
...Object.assign({}, this.additionalChatOptions, additionalChatOptions),
};
if (
Array.isArray(baseRequestParams.tools) &&
baseRequestParams.tools.length === 0
) {
// remove empty tools array to avoid OpenAI error
delete baseRequestParams.tools;
}
// Streaming
if (stream) {
return this.streamChat(baseRequestParams);
}
// Non-streaming
const response = await this.session.openai.chat.completions.create({
...baseRequestParams,
stream: false,
});
const content = response.choices[0]!.message?.content ?? "";
return {
raw: response,
message: {
content,
role: response.choices[0]!.message.role,
options: response.choices[0]!.message?.tool_calls
? {
toolCall: response.choices[0]!.message.tool_calls.map(
(toolCall) => ({
id: toolCall.id,
name: toolCall.function.name,
input: toolCall.function.arguments,
}),
),
}
: {},
},
};
}
// todo: this wrapper is ugly, refactor it
@wrapEventCaller
protected async *streamChat(
baseRequestParams: OpenAILLM.Chat.ChatCompletionCreateParams,
): AsyncIterable<ChatResponseChunk<ToolCallLLMMessageOptions>> {
const stream: AsyncIterable<OpenAILLM.Chat.ChatCompletionChunk> =
await this.session.openai.chat.completions.create({
...baseRequestParams,
stream: true,
});
// TODO: add callback to streamConverter and use streamConverter here
// this will be used to keep track of the current tool call, make sure input are valid json object.
let currentToolCall: PartialToolCall | null = null;
const toolCallMap = new Map<string, PartialToolCall>();
for await (const part of stream) {
if (part.choices.length === 0) continue;
const choice = part.choices[0]!;
// skip parts that don't have any content
if (!(choice.delta.content || choice.delta.tool_calls)) continue;
let shouldEmitToolCall: PartialToolCall | null = null;
if (
choice.delta.tool_calls?.[0]!.id &&
currentToolCall &&
choice.delta.tool_calls?.[0].id !== currentToolCall.id
) {
shouldEmitToolCall = {
...currentToolCall,
input: JSON.parse(currentToolCall.input),
};
}
if (choice.delta.tool_calls?.[0]!.id) {
currentToolCall = {
name: choice.delta.tool_calls[0].function!.name!,
id: choice.delta.tool_calls[0].id,
input: choice.delta.tool_calls[0].function!.arguments!,
};
toolCallMap.set(choice.delta.tool_calls[0].id, currentToolCall);
} else {
if (choice.delta.tool_calls?.[0]!.function?.arguments) {
currentToolCall!.input +=
choice.delta.tool_calls[0].function.arguments;
}
}
const isDone: boolean = choice.finish_reason !== null;
if (isDone && currentToolCall) {
// for the last one, we need to emit the tool call
shouldEmitToolCall = {
...currentToolCall,
input: JSON.parse(currentToolCall.input),
};
}
yield {
raw: part,
options: shouldEmitToolCall
? { toolCall: [shouldEmitToolCall] }
: currentToolCall
? {
toolCall: [currentToolCall],
}
: {},
delta: choice.delta.content ?? "",
};
}
toolCallMap.clear();
return;
}
static toTool(tool: BaseTool): ChatCompletionTool {
return {
type: "function",
function: tool.metadata.parameters
? {
name: tool.metadata.name,
description: tool.metadata.description,
parameters: tool.metadata.parameters,
}
: {
name: tool.metadata.name,
description: tool.metadata.description,
},
};
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"outDir": "./lib"
},
"include": ["./src"],
"references": [
{
"path": "../../llamaindex/tsconfig.json"
},
{
"path": "../../env/tsconfig.json"
}
]
}
+101 -22
View File
@@ -139,7 +139,7 @@ importers:
specifier: ^1.4.1
version: 1.4.1
'@llamaindex/core':
specifier: ^0.1.0
specifier: ^0.2.0
version: link:../packages/core
'@notionhq/client':
specifier: ^2.2.15
@@ -152,7 +152,7 @@ importers:
version: 2.4.6
chromadb:
specifier: ^1.8.1
version: 1.8.1(@google/generative-ai@0.12.0)(cohere-ai@7.13.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0))(encoding@0.1.13))(encoding@0.1.13)(openai@4.57.0(encoding@0.1.13)(zod@3.23.8))
version: 1.8.1(@google/generative-ai@0.12.0)(cohere-ai@7.13.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0))(encoding@0.1.13))(encoding@0.1.13)(openai@4.60.0(encoding@0.1.13)(zod@3.23.8))
commander:
specifier: ^12.1.0
version: 12.1.0
@@ -163,11 +163,11 @@ importers:
specifier: ^1.0.14
version: 1.0.14
llamaindex:
specifier: ^0.5.0
specifier: ^0.6.0
version: link:../packages/llamaindex
mongodb:
specifier: ^6.7.0
version: 6.8.0(@aws-sdk/credential-providers@3.637.0)
version: 6.8.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))
pathe:
specifier: ^1.1.2
version: 1.1.2
@@ -276,7 +276,7 @@ importers:
version: 1.1.0(@types/react@18.3.5)(react@18.3.1)
ai:
specifier: ^3.3.21
version: 3.3.21(openai@4.57.0(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.4.38(typescript@5.5.4))(zod@3.23.8)
version: 3.3.21(openai@4.60.0(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.4.38(typescript@5.5.4))(zod@3.23.8)
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
@@ -516,6 +516,9 @@ importers:
'@llamaindex/env':
specifier: workspace:*
version: link:../env
'@llamaindex/openai':
specifier: workspace:*
version: link:../llm/openai
'@mistralai/mistralai':
specifier: ^1.0.4
version: 1.0.4(zod@3.23.8)
@@ -554,7 +557,7 @@ importers:
version: 4.7.0
chromadb:
specifier: 1.8.1
version: 1.8.1(@google/generative-ai@0.12.0)(cohere-ai@7.13.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0))(encoding@0.1.13))(encoding@0.1.13)(openai@4.57.0(encoding@0.1.13)(zod@3.23.8))
version: 1.8.1(@google/generative-ai@0.12.0)(cohere-ai@7.13.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0))(encoding@0.1.13))(encoding@0.1.13)(openai@4.60.0(encoding@0.1.13)(zod@3.23.8))
cohere-ai:
specifier: 7.13.0
version: 7.13.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0))(encoding@0.1.13)
@@ -581,13 +584,13 @@ importers:
version: 2.0.0
mongodb:
specifier: ^6.7.0
version: 6.8.0(@aws-sdk/credential-providers@3.637.0)
version: 6.8.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))
notion-md-crawler:
specifier: ^1.0.0
version: 1.0.0(encoding@0.1.13)
openai:
specifier: ^4.57.0
version: 4.57.0(encoding@0.1.13)(zod@3.23.8)
specifier: ^4.60.0
version: 4.60.0(encoding@0.1.13)(zod@3.23.8)
papaparse:
specifier: ^5.4.1
version: 5.4.1
@@ -652,9 +655,6 @@ importers:
'@faker-js/faker':
specifier: ^8.4.1
version: 8.4.1
'@llamaindex/core':
specifier: workspace:*
version: link:../../core
'@types/node':
specifier: ^22.5.1
version: 22.5.1
@@ -700,7 +700,7 @@ importers:
dependencies:
ai:
specifier: ^3.3.21
version: 3.3.21(openai@4.57.0(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.4.38(typescript@5.5.4))(zod@3.23.8)
version: 3.3.21(openai@4.60.0(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.4.38(typescript@5.5.4))(zod@3.23.8)
llamaindex:
specifier: workspace:*
version: link:../../..
@@ -840,6 +840,9 @@ importers:
typescript:
specifier: 5.5.4
version: 5.5.4
vite-plugin-wasm:
specifier: ^3.3.0
version: 3.3.0(vite@5.4.2(@types/node@22.5.1)(terser@5.31.6))
packages/llamaindex/tests:
devDependencies:
@@ -850,6 +853,25 @@ importers:
specifier: ^2.0.5
version: 2.0.5(@types/node@22.5.1)(terser@5.31.6)
packages/llm/openai:
dependencies:
'@llamaindex/core':
specifier: workspace:*
version: link:../../core
'@llamaindex/env':
specifier: workspace:*
version: link:../../env
openai:
specifier: ^4.60.0
version: 4.60.0(encoding@0.1.13)(zod@3.23.8)
remeda:
specifier: ^2.12.0
version: 2.12.0
devDependencies:
bunchee:
specifier: 5.3.2
version: 5.3.2(typescript@5.5.4)
packages/wasm-tools:
dependencies:
'@assemblyscript/loader':
@@ -8385,6 +8407,15 @@ packages:
zod:
optional: true
openai@4.60.0:
resolution: {integrity: sha512-U/wNmrUPdfsvU1GrKRP5mY5YHR3ev6vtdfNID6Sauz+oquWD8r+cXPL1xiUlYniosPKajy33muVHhGS/9/t6KA==}
hasBin: true
peerDependencies:
zod: ^3.23.8
peerDependenciesMeta:
zod:
optional: true
opener@1.5.2:
resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
hasBin: true
@@ -9460,6 +9491,9 @@ packages:
remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
remeda@2.12.0:
resolution: {integrity: sha512-VAlyhh1os8boCA9/7yN9sXzo0tfCeOwScGXztwBspS0DXQmbIN8xTBfEABvbAW8rMJMPzqxQ1UymHquuESh/pg==}
renderkid@3.0.0:
resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==}
@@ -10444,6 +10478,10 @@ packages:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
type-fest@4.26.1:
resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==}
engines: {node: '>=16'}
type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
@@ -10689,6 +10727,11 @@ packages:
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
vite-plugin-wasm@3.3.0:
resolution: {integrity: sha512-tVhz6w+W9MVsOCHzxo6SSMSswCeIw4HTrXEi6qL3IRzATl83jl09JVO1djBqPSwfjgnpVHNLYcaMbaDX5WB/pg==}
peerDependencies:
vite: ^2 || ^3 || ^4 || ^5
vite@5.4.2:
resolution: {integrity: sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==}
engines: {node: ^18.0.0 || >=20.0.0}
@@ -14746,7 +14789,7 @@ snapshots:
'@smithy/is-array-buffer@2.2.0':
dependencies:
tslib: 2.6.3
tslib: 2.7.0
'@smithy/is-array-buffer@3.0.0':
dependencies:
@@ -15958,7 +16001,7 @@ snapshots:
clean-stack: 2.2.0
indent-string: 4.0.0
ai@3.3.21(openai@4.57.0(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.4.38(typescript@5.5.4))(zod@3.23.8):
ai@3.3.21(openai@4.60.0(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.4.38(typescript@5.5.4))(zod@3.23.8):
dependencies:
'@ai-sdk/provider': 0.0.22
'@ai-sdk/provider-utils': 1.0.17(zod@3.23.8)
@@ -15975,7 +16018,7 @@ snapshots:
secure-json-parse: 2.7.0
zod-to-json-schema: 3.23.2(zod@3.23.8)
optionalDependencies:
openai: 4.57.0(zod@3.23.8)
openai: 4.60.0(zod@3.23.8)
react: 18.3.1
sswr: 2.1.0(svelte@4.2.19)
svelte: 4.2.19
@@ -16652,14 +16695,14 @@ snapshots:
chownr@2.0.0: {}
chromadb@1.8.1(@google/generative-ai@0.12.0)(cohere-ai@7.13.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0))(encoding@0.1.13))(encoding@0.1.13)(openai@4.57.0(encoding@0.1.13)(zod@3.23.8)):
chromadb@1.8.1(@google/generative-ai@0.12.0)(cohere-ai@7.13.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0))(encoding@0.1.13))(encoding@0.1.13)(openai@4.60.0(encoding@0.1.13)(zod@3.23.8)):
dependencies:
cliui: 8.0.1
isomorphic-fetch: 3.0.0(encoding@0.1.13)
optionalDependencies:
'@google/generative-ai': 0.12.0
cohere-ai: 7.13.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0))(encoding@0.1.13)
openai: 4.57.0(encoding@0.1.13)(zod@3.23.8)
openai: 4.60.0(encoding@0.1.13)(zod@3.23.8)
transitivePeerDependencies:
- encoding
@@ -17747,6 +17790,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.8.2(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.3.0(eslint@8.57.0)(typescript@5.5.4)
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0):
dependencies:
array-includes: 3.1.8
@@ -17757,7 +17810,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0)
eslint-module-utils: 2.8.2(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -20485,7 +20538,7 @@ snapshots:
optionalDependencies:
'@aws-sdk/credential-providers': 3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0))
mongodb@6.8.0(@aws-sdk/credential-providers@3.637.0):
mongodb@6.8.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0))):
dependencies:
'@mongodb-js/saslprep': 1.1.7
bson: 6.8.0
@@ -20915,7 +20968,23 @@ snapshots:
transitivePeerDependencies:
- encoding
openai@4.57.0(zod@3.23.8):
openai@4.60.0(encoding@0.1.13)(zod@3.23.8):
dependencies:
'@types/node': 18.19.47
'@types/node-fetch': 2.6.11
'@types/qs': 6.9.15
abort-controller: 3.0.0
agentkeepalive: 4.5.0
form-data-encoder: 1.7.2
formdata-node: 4.4.1
node-fetch: 2.7.0(encoding@0.1.13)
qs: 6.13.0
optionalDependencies:
zod: 3.23.8
transitivePeerDependencies:
- encoding
openai@4.60.0(zod@3.23.8):
dependencies:
'@types/node': 18.19.47
'@types/node-fetch': 2.6.11
@@ -22123,6 +22192,10 @@ snapshots:
mdast-util-to-markdown: 2.1.0
unified: 11.0.5
remeda@2.12.0:
dependencies:
type-fest: 4.26.1
renderkid@3.0.0:
dependencies:
css-select: 4.3.0
@@ -23220,6 +23293,8 @@ snapshots:
type-fest@2.19.0: {}
type-fest@4.26.1: {}
type-is@1.6.18:
dependencies:
media-typer: 0.3.0
@@ -23340,7 +23415,7 @@ snapshots:
union@0.5.0:
dependencies:
qs: 6.11.2
qs: 6.13.0
unique-string@3.0.0:
dependencies:
@@ -23522,6 +23597,10 @@ snapshots:
- supports-color
- terser
vite-plugin-wasm@3.3.0(vite@5.4.2(@types/node@22.5.1)(terser@5.31.6)):
dependencies:
vite: 5.4.2(@types/node@22.5.1)(terser@5.31.6)
vite@5.4.2(@types/node@22.5.1)(terser@5.31.6):
dependencies:
esbuild: 0.21.5
+1
View File
@@ -1,6 +1,7 @@
packages:
- "apps/*"
- "packages/*"
- "packages/llm/*"
- "packages/core/tests"
- "packages/llamaindex/tests"
- "packages/llamaindex/e2e"
+3
View File
@@ -26,6 +26,9 @@
{
"path": "./packages/community/tsconfig.json"
},
{
"path": "./packages/llm/openai/tsconfig.json"
},
{
"path": "./packages/cloud/tsconfig.json"
},