Compare commits

...

13 Commits

Author SHA1 Message Date
Marcus Schiesser 701e0ac2be release 0.2.8 2024-04-12 12:43:45 +08:00
Alex Yang a285f8ba3a feat: improve ToolsFactory type (#713) 2024-04-11 21:26:14 -05:00
Alex Yang 663821cdf6 test: add openai agent stream chat (#712) 2024-04-11 19:21:02 -05:00
Alex Yang c4b95494ac fix: memory type (#711) 2024-04-11 18:11:33 -05:00
Marcus Schiesser 980fb4e5a3 release llamaindex@0.2.7 2024-04-11 14:57:01 +08:00
Alex Yang 96f8f40291 fix: agent stream (#710) 2024-04-10 23:22:11 -05:00
Alex Yang 1c698df6e0 fix: package.json version 2024-04-10 19:49:16 -05:00
Alex Yang 298cb433be feat: improve base tool type (#709) 2024-04-10 19:40:47 -05:00
Yi Ding 63af7dd99d Fix protobuf (#708) 2024-04-10 17:20:32 -07:00
Alex Yang af5df1d083 feat: add llm-stream event (#707) 2024-04-10 09:26:26 -05:00
Marcus Schiesser a3b44093c2 fix: agent streaming with new OpenAI models (#706)
Co-authored-by: Alex Yang <himself65@outlook.com>
2024-04-10 08:38:54 -05:00
Alex Yang c80bf3311f fix: response.raw should be null (#705) 2024-04-10 02:54:36 -05:00
Alex Yang 7940d249b0 test: coverage on mock mode (#704) 2024-04-10 02:40:37 -05:00
67 changed files with 4723 additions and 755 deletions
+12 -1
View File
@@ -1,6 +1,12 @@
name: Run Tests
on: [push, pull_request]
on:
push:
branches:
- main
pull_request:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -8,6 +14,11 @@ concurrency:
jobs:
e2e:
strategy:
fail-fast: false
matrix:
node-version: [18.x, 20.x, 21.x]
name: E2E on Node.js ${{ matrix.node-version }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+2 -1
View File
@@ -10,8 +10,9 @@
"name": "Debug Example",
"skipFiles": ["<node_internals>/**"],
"runtimeExecutable": "pnpm",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}/examples",
"runtimeArgs": ["ts-node", "${fileBasename}"]
"runtimeArgs": ["npx", "tsx", "${file}"]
}
]
}
+1 -1
View File
@@ -6,7 +6,7 @@ This page shows how to track LLM cost using APIs.
The callback manager is a class that manages the callback functions.
You can register `llm-start`, and `llm-end` callbacks to the callback manager for tracking the cost.
You can register `llm-start`, `llm-end`, and `llm-stream` callbacks to the callback manager for tracking the cost.
import CodeBlock from "@theme/CodeBlock";
import CodeSource from "!raw-loader!../../../../examples/recipes/cost-analysis";
+1 -1
View File
@@ -32,7 +32,7 @@
"@docusaurus/theme-classic": "^3.2.1",
"@docusaurus/types": "^3.2.1",
"@tsconfig/docusaurus": "^2.0.3",
"@types/node": "^18.19.31",
"@types/node": "^20.12.7",
"docusaurus-plugin-typedoc": "^0.22.0",
"typedoc": "^0.25.13",
"typedoc-plugin-markdown": "^3.17.1",
+6 -6
View File
@@ -1,13 +1,13 @@
import { FunctionTool, OpenAIAgent } from "llamaindex";
// Define a function to sum two numbers
function sumNumbers({ a, b }: { a: number; b: number }): number {
return a + b;
function sumNumbers({ a, b }: { a: number; b: number }) {
return `${a + b}`;
}
// Define a function to divide two numbers
function divideNumbers({ a, b }: { a: number; b: number }): number {
return a / b;
function divideNumbers({ a, b }: { a: number; b: number }) {
return `${a / b}`;
}
// Define the parameters of the sum function as a JSON schema
@@ -24,7 +24,7 @@ const sumJSON = {
},
},
required: ["a", "b"],
};
} as const;
const divideJSON = {
type: "object",
@@ -39,7 +39,7 @@ const divideJSON = {
},
},
required: ["a", "b"],
};
} as const;
async function main() {
// Create a function tool from the sum function
+6 -6
View File
@@ -1,13 +1,13 @@
import { Anthropic, FunctionTool, ReActAgent } from "llamaindex";
// Define a function to sum two numbers
function sumNumbers({ a, b }: { a: number; b: number }): number {
return a + b;
function sumNumbers({ a, b }: { a: number; b: number }) {
return `${a + b}`;
}
// Define a function to divide two numbers
function divideNumbers({ a, b }: { a: number; b: number }): number {
return a / b;
function divideNumbers({ a, b }: { a: number; b: number }) {
return `${a / b}`;
}
// Define the parameters of the sum function as a JSON schema
@@ -24,7 +24,7 @@ const sumJSON = {
},
},
required: ["a", "b"],
};
} as const;
const divideJSON = {
type: "object",
@@ -39,7 +39,7 @@ const divideJSON = {
},
},
required: ["a", "b"],
};
} as const;
async function main() {
// Create a function tool from the sum function
+8 -8
View File
@@ -1,13 +1,13 @@
import { FunctionTool, OpenAIAgent } from "llamaindex";
// Define a function to sum two numbers
function sumNumbers({ a, b }: { a: number; b: number }): number {
return a + b;
function sumNumbers({ a, b }: { a: number; b: number }) {
return `${a + b}`;
}
// Define a function to divide two numbers
function divideNumbers({ a, b }: { a: number; b: number }): number {
return a / b;
function divideNumbers({ a, b }: { a: number; b: number }) {
return `${a / b}`;
}
// Define the parameters of the sum function as a JSON schema
@@ -24,22 +24,22 @@ const sumJSON = {
},
},
required: ["a", "b"],
};
} as const;
const divideJSON = {
type: "object",
properties: {
a: {
type: "number",
description: "The dividend a to divide",
description: "The dividend",
},
b: {
type: "number",
description: "The divisor b to divide by",
description: "The divisor",
},
},
required: ["a", "b"],
};
} as const;
async function main() {
// Create a function tool from the sum function
+6 -6
View File
@@ -1,13 +1,13 @@
import { FunctionTool, ReActAgent } from "llamaindex";
// Define a function to sum two numbers
function sumNumbers({ a, b }: { a: number; b: number }): number {
return a + b;
function sumNumbers({ a, b }: { a: number; b: number }) {
return `${a + b}`;
}
// Define a function to divide two numbers
function divideNumbers({ a, b }: { a: number; b: number }): number {
return a / b;
function divideNumbers({ a, b }: { a: number; b: number }) {
return `${a / b}`;
}
// Define the parameters of the sum function as a JSON schema
@@ -24,7 +24,7 @@ const sumJSON = {
},
},
required: ["a", "b"],
};
} as const;
const divideJSON = {
type: "object",
@@ -39,7 +39,7 @@ const divideJSON = {
},
},
required: ["a", "b"],
};
} as const;
async function main() {
// Create a function tool from the sum function
+8 -8
View File
@@ -1,13 +1,13 @@
import { FunctionTool, OpenAIAgent } from "llamaindex";
// Define a function to sum two numbers
function sumNumbers({ a, b }: { a: number; b: number }): number {
return a + b;
function sumNumbers({ a, b }: { a: number; b: number }) {
return `${a + b}`;
}
// Define a function to divide two numbers
function divideNumbers({ a, b }: { a: number; b: number }): number {
return a / b;
function divideNumbers({ a, b }: { a: number; b: number }) {
return `${a / b}`;
}
// Define the parameters of the sum function as a JSON schema
@@ -24,7 +24,7 @@ const sumJSON = {
},
},
required: ["a", "b"],
};
} as const;
const divideJSON = {
type: "object",
@@ -39,18 +39,18 @@ const divideJSON = {
},
},
required: ["a", "b"],
};
} as const;
async function main() {
// Create a function tool from the sum function
const functionTool = new FunctionTool(sumNumbers, {
const functionTool = FunctionTool.from(sumNumbers, {
name: "sumNumbers",
description: "Use this function to sum two numbers",
parameters: sumJSON,
});
// Create a function tool from the divide function
const functionTool2 = new FunctionTool(divideNumbers, {
const functionTool2 = FunctionTool.from(divideNumbers, {
name: "divideNumbers",
description: "Use this function to divide two numbers",
parameters: divideJSON,
+5 -4
View File
@@ -5,21 +5,22 @@
"dependencies": {
"@aws-crypto/sha256-js": "^5.2.0",
"@datastax/astra-db-ts": "^0.1.4",
"@notionhq/client": "^2.2.14",
"@notionhq/client": "^2.2.15",
"@pinecone-database/pinecone": "^1.1.3",
"@zilliz/milvus2-sdk-node": "^2.3.5",
"chromadb": "^1.8.1",
"commander": "^11.1.0",
"dotenv": "^16.4.5",
"js-tiktoken": "^1.0.10",
"llamaindex": "workspace:latest",
"llamaindex": "latest",
"mongodb": "^6.5.0",
"pathe": "^1.1.2"
},
"devDependencies": {
"@types/node": "^18.19.31",
"@types/node": "^20.12.7",
"ts-node": "^10.9.2",
"typescript": "^5.4.4"
"tsx": "^4.7.2",
"typescript": "^5.4.5"
},
"scripts": {
"lint": "eslint ."
+1 -1
View File
@@ -15,7 +15,7 @@
"llamaindex": "latest"
},
"devDependencies": {
"@types/node": "^20.11.14",
"@types/node": "^20.12.7",
"ts-node": "^10.9.2",
"typescript": "^5.4.3"
}
+19 -11
View File
@@ -6,7 +6,8 @@ import { extractText } from "llamaindex/llm/utils";
const encoding = encodingForModel("gpt-4-0125-preview");
const llm = new OpenAI({
model: "gpt-4-0125-preview",
// currently is "gpt-4-turbo-2024-04-09"
model: "gpt-4-turbo",
});
let tokenCount = 0;
@@ -19,18 +20,25 @@ Settings.callbackManager.on("llm-start", (event) => {
console.log("Token count:", tokenCount);
// https://openai.com/pricing
// $10.00 / 1M tokens
console.log(`Price: $${(tokenCount / 1_000_000) * 10}`);
});
Settings.callbackManager.on("llm-end", (event) => {
const { response } = event.detail.payload;
tokenCount += encoding.encode(extractText(response.message.content)).length;
console.log("Token count:", tokenCount);
// https://openai.com/pricing
// $30.00 / 1M tokens
console.log(`Price: $${(tokenCount / 1_000_000) * 30}`);
console.log(`Total Price: $${(tokenCount / 1_000_000) * 10}`);
});
const question = "Hello, how are you?";
Settings.callbackManager.on("llm-stream", (event) => {
const { chunk } = event.detail.payload;
const { delta } = chunk;
tokenCount += encoding.encode(extractText(delta)).length;
if (tokenCount > 20) {
// This is just an example, you can set your own limit or handle it differently
throw new Error("Token limit exceeded!");
}
});
Settings.callbackManager.on("llm-end", () => {
// https://openai.com/pricing
// $30.00 / 1M tokens
console.log(`Total Price: $${(tokenCount / 1_000_000) * 30}`);
});
const question = "Hello, how are you? Please response about 50 tokens.";
console.log("Question:", question);
void llm
.chat({
-1
View File
@@ -2,7 +2,6 @@ import { OpenAI } from "llamaindex";
async function main() {
const llm = new OpenAI({ model: "gpt-4-turbo" });
const args: Parameters<typeof llm.chat>[0] = {
additionalChatOptions: {
tool_choice: "auto",
+3 -2
View File
@@ -27,13 +27,14 @@
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
"turbo": "^1.13.2",
"typescript": "^5.4.4"
"typescript": "^5.4.5"
},
"packageManager": "pnpm@8.15.6+sha256.01c01eeb990e379b31ef19c03e9d06a14afa5250b82e81303f88721c99ff2e6f",
"pnpm": {
"overrides": {
"trim": "1.0.1",
"@babel/traverse": "7.23.2"
"@babel/traverse": "7.23.2",
"protobufjs": "7.2.6"
}
},
"lint-staged": {
+20
View File
@@ -1,5 +1,25 @@
# llamaindex
## 0.2.8
### Patch Changes
- Add ToolsFactory to generate agent tools
## 0.2.7
### Patch Changes
- 96f8f40: fix: agent stream
- Updated dependencies
- @llamaindex/env@0.0.7
## 0.2.6
### Patch Changes
- a3b4409: Fix agent streaming with new OpenAI models
## 0.2.5
### Patch Changes
@@ -0,0 +1,33 @@
import { BaseNode, SimilarityType, type BaseEmbedding } from "llamaindex";
export class OpenAIEmbedding implements BaseEmbedding {
embedBatchSize = 512;
async getQueryEmbedding(query: string) {
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;
}
async transform(nodes: BaseNode[], _options?: any): Promise<BaseNode[]> {
return nodes;
}
}
+52 -19
View File
@@ -1,4 +1,3 @@
import { faker } from "@faker-js/faker";
import type {
ChatResponse,
ChatResponseChunk,
@@ -9,6 +8,9 @@ import type {
LLMCompletionParamsNonStreaming,
LLMCompletionParamsStreaming,
} from "llamaindex/llm/types";
import { extractText } from "llamaindex/llm/utils";
import { strictEqual } from "node:assert";
import { llmCompleteMockStorage } from "../../node/utils.js";
export function getOpenAISession() {
return {};
@@ -40,24 +42,39 @@ export class OpenAI implements LLM {
| LLMChatParamsStreaming<Record<string, unknown>>
| LLMChatParamsNonStreaming<Record<string, unknown>>,
): unknown {
if (params.stream) {
return {
[Symbol.asyncIterator]: async function* () {
yield {
delta: faker.word.words(),
} satisfies ChatResponseChunk;
},
};
if (llmCompleteMockStorage.llmEventStart.length > 0) {
const chatMessage =
llmCompleteMockStorage.llmEventStart.shift()!["messages"];
strictEqual(chatMessage.length, params.messages.length);
for (let i = 0; i < chatMessage.length; i++) {
strictEqual(chatMessage[i].role, params.messages[i].role);
strictEqual(chatMessage[i].content, params.messages[i].content);
}
if (llmCompleteMockStorage.llmEventEnd.length > 0) {
const { id, response } = llmCompleteMockStorage.llmEventEnd.shift()!;
if (params.stream) {
return {
[Symbol.asyncIterator]: async function* () {
while (true) {
const idx = llmCompleteMockStorage.llmEventStream.findIndex(
(e) => e.id === id,
);
if (idx === -1) {
break;
}
const chunk = llmCompleteMockStorage.llmEventStream[idx].chunk;
llmCompleteMockStorage.llmEventStream.splice(idx, 1);
yield chunk;
}
},
};
} else {
return response;
}
}
}
return {
get raw(): never {
throw new Error("not implemented");
},
message: {
content: faker.lorem.paragraph(),
role: "assistant",
},
} satisfies ChatResponse;
throw new Error("Method not implemented.");
}
complete(
params: LLMCompletionParamsStreaming,
@@ -65,7 +82,23 @@ export class OpenAI implements LLM {
complete(
params: LLMCompletionParamsNonStreaming,
): Promise<CompletionResponse>;
async complete(params: unknown): Promise<unknown> {
async complete(
params: LLMCompletionParamsStreaming | LLMCompletionParamsNonStreaming,
): Promise<AsyncIterable<CompletionResponse> | CompletionResponse> {
if (llmCompleteMockStorage.llmEventStart.length > 0) {
const chatMessage =
llmCompleteMockStorage.llmEventStart.shift()!["messages"];
strictEqual(chatMessage.length, 1);
strictEqual(chatMessage[0].role, "user");
strictEqual(chatMessage[0].content, params.prompt);
}
if (llmCompleteMockStorage.llmEventEnd.length > 0) {
const response = llmCompleteMockStorage.llmEventEnd.shift()!["response"];
return {
raw: response,
text: extractText(response.message.content),
} satisfies CompletionResponse;
}
throw new Error("Method not implemented.");
}
}
-139
View File
@@ -1,139 +0,0 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
import { consola } from "consola";
import {
OpenAI,
OpenAIAgent,
Settings,
type LLM,
type LLMEndEvent,
type LLMStartEvent,
} from "llamaindex";
import { ok } from "node:assert";
import type { WriteStream } from "node:fs";
import { createWriteStream } from "node:fs";
import { mkdir } from "node:fs/promises";
import { join } from "node:path";
import { after, before, beforeEach, describe, test } from "node:test";
import { inspect } from "node:util";
let llm: LLM;
let fsStream: WriteStream;
before(async () => {
const logUrl = new URL(
join(
"..",
"logs",
`basic.e2e.${new Date().toISOString().replace(/:/g, "-").replace(/\./g, "-")}.log`,
),
import.meta.url,
);
await mkdir(new URL(".", logUrl), { recursive: true });
fsStream = createWriteStream(logUrl, {
encoding: "utf-8",
});
});
after(() => {
fsStream.end();
});
beforeEach((s) => {
fsStream.write("start: " + s.name + "\n");
});
const llmEventStartHandler = (event: LLMStartEvent) => {
const { payload } = event.detail;
fsStream.write(
"llmEventStart: " +
inspect(payload, {
depth: Infinity,
}) +
"\n",
);
};
const llmEventEndHandler = (event: LLMEndEvent) => {
const { payload } = event.detail;
fsStream.write(
"llmEventEnd: " +
inspect(payload, {
depth: Infinity,
}) +
"\n",
);
};
before(() => {
Settings.llm = new OpenAI({
model: "gpt-3.5-turbo",
});
llm = Settings.llm;
Settings.callbackManager.on("llm-start", llmEventStartHandler);
Settings.callbackManager.on("llm-end", llmEventEndHandler);
});
after(() => {
Settings.callbackManager.off("llm-start", llmEventStartHandler);
Settings.callbackManager.off("llm-end", llmEventEndHandler);
});
describe("llm", () => {
test("llm.chat", async () => {
const response = await llm.chat({
messages: [
{
content: "Hello",
role: "user",
},
],
});
consola.debug("response:", response);
ok(typeof response.message.content === "string");
});
test("stream llm.chat", async () => {
const iter = await llm.chat({
stream: true,
messages: [
{
content: "hello",
role: "user",
},
],
});
for await (const chunk of iter) {
consola.debug("chunk:", chunk);
ok(typeof chunk.delta === "string");
}
});
});
describe("agent", () => {
test("agent.chat", async () => {
const agent = new OpenAIAgent({
tools: [
{
call: async () => {
return "35 degrees and sunny in San Francisco";
},
metadata: {
name: "Weather",
description: "Get the weather",
parameters: {
type: "object",
properties: {
location: { type: "string" },
},
required: ["location"],
},
},
},
],
});
const result = await agent.chat({
message: "What is the weather in San Francisco?",
});
consola.debug("response:", result.response);
ok(typeof result.response === "string");
});
});
+292
View File
@@ -0,0 +1,292 @@
import { consola } from "consola";
import {
Document,
FunctionTool,
OpenAI,
OpenAIAgent,
QueryEngineTool,
Settings,
SubQuestionQueryEngine,
VectorStoreIndex,
type LLM,
} from "llamaindex";
import { ok } from "node:assert";
import { beforeEach, test } from "node:test";
import { mockLLMEvent } from "./utils.js";
let llm: LLM;
beforeEach(async () => {
Settings.llm = new OpenAI({
model: "gpt-3.5-turbo",
});
llm = Settings.llm;
});
function sumNumbers({ a, b }: { a: number; b: number }) {
return `${a + b}`;
}
function divideNumbers({ a, b }: { a: number; b: number }) {
return `${a / b}`;
}
await test("llm", async (t) => {
await mockLLMEvent(t, "llm");
await t.test("llm.chat", async () => {
const response = await llm.chat({
messages: [
{
content: "Hello",
role: "user",
},
],
});
consola.debug("response:", response);
ok(typeof response.message.content === "string");
});
await t.test("stream llm.chat", async () => {
const iter = await llm.chat({
stream: true,
messages: [
{
content: "hello",
role: "user",
},
],
});
for await (const chunk of iter) {
consola.debug("chunk:", chunk);
ok(typeof chunk.delta === "string");
}
});
});
await test("gpt-4-turbo", async (t) => {
const llm = new OpenAI({ model: "gpt-4-turbo" });
Settings.llm = llm;
await mockLLMEvent(t, "gpt-4-turbo");
await t.test("agent", async () => {
const agent = new OpenAIAgent({
llm,
tools: [
{
call: async () => {
return "45 degrees and sunny in San Jose";
},
metadata: {
name: "Weather",
description: "Get the weather",
parameters: {
type: "object",
properties: {
location: { type: "string" },
},
required: ["location"],
},
},
},
],
});
const { response } = await agent.chat({
message: "What is the weather in San Jose?",
});
consola.debug("response:", response);
ok(typeof response === "string");
ok(response.includes("45"));
});
});
await test("agent", async (t) => {
await mockLLMEvent(t, "agent");
await t.test("chat", async () => {
const agent = new OpenAIAgent({
tools: [
{
call: async () => {
return "35 degrees and sunny in San Francisco";
},
metadata: {
name: "Weather",
description: "Get the weather",
parameters: {
type: "object",
properties: {
location: { type: "string" },
},
required: ["location"],
},
},
},
],
});
const result = await agent.chat({
message: "What is the weather in San Francisco?",
});
consola.debug("response:", result.response);
ok(typeof result.response === "string");
ok(result.response.includes("35"));
});
await t.test("async function", async () => {
const uniqueId = "123456789";
const showUniqueId = FunctionTool.from<{
firstName: string;
lastName: string;
}>(
async ({ firstName, lastName }) => {
ok(typeof firstName === "string");
ok(typeof lastName === "string");
const fullName = firstName + lastName;
ok(fullName.toLowerCase().includes("alex"));
ok(fullName.toLowerCase().includes("yang"));
return uniqueId;
},
{
name: "unique_id",
description: "show user unique id",
parameters: {
type: "object",
properties: {
firstName: { type: "string" },
lastName: { type: "string" },
},
required: ["firstName", "lastName"],
},
},
);
const agent = new OpenAIAgent({
tools: [showUniqueId],
});
const { response } = await agent.chat({
message: "My name is Alex Yang. What is my unique id?",
});
consola.debug("response:", response);
ok(response.includes(uniqueId));
});
await t.test("sum numbers", async () => {
const sumFunctionTool = new FunctionTool(sumNumbers, {
name: "sumNumbers",
description: "Use this function to sum two numbers",
parameters: {
type: "object",
properties: {
a: {
type: "number",
description: "The first number",
},
b: {
type: "number",
description: "The second number",
},
},
required: ["a", "b"],
},
});
const openaiAgent = new OpenAIAgent({
tools: [sumFunctionTool],
});
const response = await openaiAgent.chat({
message: "how much is 1 + 1?",
});
ok(response.response.includes("2"));
});
});
await test("agent stream", async (t) => {
await mockLLMEvent(t, "agent_stream");
await t.test("sum numbers stream", async () => {
const sumJSON = {
type: "object",
properties: {
a: {
type: "number",
description: "The first number",
},
b: {
type: "number",
description: "The second number",
},
},
required: ["a", "b"],
} as const;
const divideJSON = {
type: "object",
properties: {
a: {
type: "number",
description: "The dividend",
},
b: {
type: "number",
description: "The divisor",
},
},
required: ["a", "b"],
} as const;
const functionTool = FunctionTool.from(sumNumbers, {
name: "sumNumbers",
description: "Use this function to sum two numbers",
parameters: sumJSON,
});
const functionTool2 = FunctionTool.from(divideNumbers, {
name: "divideNumbers",
description: "Use this function to divide two numbers",
parameters: divideJSON,
});
const agent = new OpenAIAgent({
tools: [functionTool, functionTool2],
});
const { response } = await agent.chat({
message: "Divide 16 by 2 then add 20",
stream: true,
});
let message = "";
for await (const chunk of response) {
message += chunk.response;
}
ok(message.includes("28"));
});
});
await test("queryEngine", async (t) => {
await mockLLMEvent(t, "queryEngine_subquestion");
await t.test("subquestion", async () => {
const document = new Document({
text: "Bill Gates stole from Apple.\n Steve Jobs stole from Xerox.",
});
const index = await VectorStoreIndex.fromDocuments([document]);
const queryEngineTools = [
new QueryEngineTool({
queryEngine: index.asQueryEngine(),
metadata: {
name: "bill_gates_idea",
description: "Get what Bill Gates idea from.",
},
}),
];
const queryEngine = SubQuestionQueryEngine.fromDefaults({
queryEngineTools,
});
const { response } = await queryEngine.query({
query: "What did Bill Gates steal from?",
});
ok(response.includes("Apple"));
});
});
+394
View File
@@ -0,0 +1,394 @@
{
"llmEventStart": [
{
"id": "PRESERVE_0",
"messages": [
{
"content": "What is the weather in San Francisco?",
"role": "user"
}
]
},
{
"id": "PRESERVE_1",
"messages": [
{
"content": "What is the weather in San Francisco?",
"role": "user"
},
{
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "Weather",
"arguments": "{\"location\":\"San Francisco\"}"
}
}
]
}
},
{
"content": "35 degrees and sunny in San Francisco",
"role": "tool",
"options": {
"name": "Weather",
"tool_call_id": "HIDDEN"
}
}
]
},
{
"id": "PRESERVE_2",
"messages": [
{
"content": "My name is Alex Yang. What is my unique id?",
"role": "user"
}
]
},
{
"id": "PRESERVE_3",
"messages": [
{
"content": "My name is Alex Yang. What is my unique id?",
"role": "user"
},
{
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "unique_id",
"arguments": "{\"firstName\":\"Alex\",\"lastName\":\"Yang\"}"
}
}
]
}
},
{
"content": "123456789",
"role": "tool",
"options": {
"name": "unique_id",
"tool_call_id": "HIDDEN"
}
}
]
},
{
"id": "PRESERVE_4",
"messages": [
{
"content": "how much is 1 + 1?",
"role": "user"
}
]
},
{
"id": "PRESERVE_5",
"messages": [
{
"content": "how much is 1 + 1?",
"role": "user"
},
{
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "sumNumbers",
"arguments": "{\"a\":1,\"b\":1}"
}
}
]
}
},
{
"content": "2",
"role": "tool",
"options": {
"name": "sumNumbers",
"tool_call_id": "HIDDEN"
}
}
]
}
],
"llmEventEnd": [
{
"id": "PRESERVE_0",
"response": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "Weather",
"arguments": "{\"location\":\"San Francisco\"}"
}
}
]
},
"logprobs": null,
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 49,
"completion_tokens": 15,
"total_tokens": 64
},
"system_fingerprint": "HIDDEN"
},
"message": {
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "Weather",
"arguments": "{\"location\":\"San Francisco\"}"
}
}
]
}
}
}
},
{
"id": "PRESERVE_1",
"response": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "The weather in San Francisco is currently 35 degrees and sunny."
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 78,
"completion_tokens": 14,
"total_tokens": 92
},
"system_fingerprint": "HIDDEN"
},
"message": {
"content": "The weather in San Francisco is currently 35 degrees and sunny.",
"role": "assistant",
"options": {}
}
}
},
{
"id": "PRESERVE_2",
"response": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "unique_id",
"arguments": "{\"firstName\":\"Alex\",\"lastName\":\"Yang\"}"
}
}
]
},
"logprobs": null,
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 59,
"completion_tokens": 18,
"total_tokens": 77
},
"system_fingerprint": "HIDDEN"
},
"message": {
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "unique_id",
"arguments": "{\"firstName\":\"Alex\",\"lastName\":\"Yang\"}"
}
}
]
}
}
}
},
{
"id": "PRESERVE_3",
"response": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Your unique id is 123456789."
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 88,
"completion_tokens": 10,
"total_tokens": 98
},
"system_fingerprint": "HIDDEN"
},
"message": {
"content": "Your unique id is 123456789.",
"role": "assistant",
"options": {}
}
}
},
{
"id": "PRESERVE_4",
"response": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "sumNumbers",
"arguments": "{\"a\":1,\"b\":1}"
}
}
]
},
"logprobs": null,
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 70,
"completion_tokens": 18,
"total_tokens": 88
},
"system_fingerprint": "HIDDEN"
},
"message": {
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "sumNumbers",
"arguments": "{\"a\":1,\"b\":1}"
}
}
]
}
}
}
},
{
"id": "PRESERVE_5",
"response": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "The sum of 1 + 1 is 2."
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 97,
"completion_tokens": 13,
"total_tokens": 110
},
"system_fingerprint": "HIDDEN"
},
"message": {
"content": "The sum of 1 + 1 is 2.",
"role": "assistant",
"options": {}
}
}
}
],
"llmEventStream": []
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,136 @@
{
"llmEventStart": [
{
"id": "PRESERVE_0",
"messages": [
{
"content": "What is the weather in San Jose?",
"role": "user"
}
]
},
{
"id": "PRESERVE_1",
"messages": [
{
"content": "What is the weather in San Jose?",
"role": "user"
},
{
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "Weather",
"arguments": "{\"location\":\"San Jose\"}"
}
}
]
}
},
{
"content": "45 degrees and sunny in San Jose",
"role": "tool",
"options": {
"name": "Weather",
"tool_call_id": "HIDDEN"
}
}
]
}
],
"llmEventEnd": [
{
"id": "PRESERVE_0",
"response": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "Weather",
"arguments": "{\"location\":\"San Jose\"}"
}
}
]
},
"logprobs": null,
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 49,
"completion_tokens": 15,
"total_tokens": 64
},
"system_fingerprint": "HIDDEN"
},
"message": {
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "Weather",
"arguments": "{\"location\":\"San Jose\"}"
}
}
]
}
}
}
},
{
"id": "PRESERVE_1",
"response": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "The weather in San Jose is currently 45 degrees and sunny."
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 78,
"completion_tokens": 14,
"total_tokens": 92
},
"system_fingerprint": "HIDDEN"
},
"message": {
"content": "The weather in San Jose is currently 45 degrees and sunny.",
"role": "assistant",
"options": {}
}
}
}
],
"llmEventStream": []
}
+476
View File
@@ -0,0 +1,476 @@
{
"llmEventStart": [
{
"id": "PRESERVE_0",
"messages": [
{
"content": "Hello",
"role": "user"
}
]
},
{
"id": "PRESERVE_1",
"messages": [
{
"content": "hello",
"role": "user"
}
]
}
],
"llmEventEnd": [
{
"id": "PRESERVE_0",
"response": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I assist you today?"
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 8,
"completion_tokens": 9,
"total_tokens": 17
},
"system_fingerprint": "HIDDEN"
},
"message": {
"content": "Hello! How can I assist you today?",
"role": "assistant",
"options": {}
}
}
},
{
"id": "PRESERVE_1",
"response": {
"raw": [
{
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": "Hello"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": "Hello"
},
{
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": "!"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": "!"
},
{
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": " How"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": " How"
},
{
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": " can"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": " can"
},
{
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": " I"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": " I"
},
{
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": " assist"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": " assist"
},
{
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": " you"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": " you"
},
{
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": " today"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": " today"
},
{
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": "?"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": "?"
}
],
"message": {
"content": "Hello! How can I assist you today?",
"role": "assistant",
"options": {}
}
}
}
],
"llmEventStream": [
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": "Hello"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": "Hello"
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": "!"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": "!"
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": " How"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": " How"
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": " can"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": " can"
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": " I"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": " I"
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": " assist"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": " assist"
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": " you"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": " you"
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": " today"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": " today"
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion.chunk",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "HIDDEN",
"choices": [
{
"index": 0,
"delta": {
"content": "?"
},
"logprobs": null,
"finish_reason": null
}
]
},
"options": {},
"delta": "?"
}
}
]
}
@@ -0,0 +1,133 @@
{
"llmEventStart": [
{
"id": "PRESERVE_0",
"messages": [
{
"content": "Given a user question, and a list of tools, output a list of relevant sub-questions that when composed can help answer the full user question:\n\n# Example 1\n<Tools>\n```json\n{\n \"uber_10k\": \"Provides information about Uber financials for year 2021\",\n \"lyft_10k\": \"Provides information about Lyft financials for year 2021\"\n}\n```\n\n<User Question>\nCompare and contrast the revenue growth and EBITDA of Uber and Lyft for year 2021\n\n<Output>\n```json\n[\n {\n \"subQuestion\": \"What is the revenue growth of Uber\",\n \"toolName\": \"uber_10k\"\n },\n {\n \"subQuestion\": \"What is the EBITDA of Uber\",\n \"toolName\": \"uber_10k\"\n },\n {\n \"subQuestion\": \"What is the revenue growth of Lyft\",\n \"toolName\": \"lyft_10k\"\n },\n {\n \"subQuestion\": \"What is the EBITDA of Lyft\",\n \"toolName\": \"lyft_10k\"\n }\n]\n```\n\n# Example 2\n<Tools>\n```json\n{\n \"bill_gates_idea\": \"Get what Bill Gates idea from.\"\n}\n```\n\n<User Question>\nWhat did Bill Gates steal from?\n\n<Output>\n",
"role": "user"
}
]
},
{
"id": "PRESERVE_1",
"messages": [
{
"content": "Context information is below.\n---------------------\nBill Gates stole from Apple. Steve Jobs stole from Xerox.\n---------------------\nGiven the context information and not prior knowledge, answer the query.\nQuery: What is Bill Gates' idea\nAnswer:",
"role": "user"
}
]
},
{
"id": "PRESERVE_2",
"messages": [
{
"content": "Context information is below.\n---------------------\nSub question: What is Bill Gates' idea\nResponse: Bill Gates' idea was to steal from Apple.\n---------------------\nGiven the context information and not prior knowledge, answer the query.\nQuery: What did Bill Gates steal from?\nAnswer:",
"role": "user"
}
]
}
],
"llmEventEnd": [
{
"id": "PRESERVE_0",
"response": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "```json\n[\n {\n \"subQuestion\": \"What is Bill Gates' idea\",\n \"toolName\": \"bill_gates_idea\"\n }\n]\n```"
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 290,
"completion_tokens": 35,
"total_tokens": 325
},
"system_fingerprint": "HIDDEN"
},
"message": {
"content": "```json\n[\n {\n \"subQuestion\": \"What is Bill Gates' idea\",\n \"toolName\": \"bill_gates_idea\"\n }\n]\n```",
"role": "assistant",
"options": {}
}
}
},
{
"id": "PRESERVE_1",
"response": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Bill Gates' idea was to steal from Apple."
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 53,
"completion_tokens": 10,
"total_tokens": 63
},
"system_fingerprint": "HIDDEN"
},
"message": {
"content": "Bill Gates' idea was to steal from Apple.",
"role": "assistant",
"options": {}
}
}
},
{
"id": "PRESERVE_2",
"response": {
"raw": {
"id": "HIDDEN",
"object": "chat.completion",
"created": 114514,
"model": "gpt-3.5-turbo-0125",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Bill Gates stole from Apple."
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 62,
"completion_tokens": 6,
"total_tokens": 68
},
"system_fingerprint": "HIDDEN"
},
"message": {
"content": "Bill Gates stole from Apple.",
"role": "assistant",
"options": {}
}
}
}
],
"llmEventStream": []
}
+138
View File
@@ -0,0 +1,138 @@
import {
Settings,
type LLMEndEvent,
type LLMStartEvent,
type LLMStreamEvent,
} from "llamaindex";
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { type test } from "node:test";
import { fileURLToPath } from "node:url";
type MockStorage = {
llmEventStart: LLMStartEvent["detail"]["payload"][];
llmEventEnd: LLMEndEvent["detail"]["payload"][];
llmEventStream: LLMStreamEvent["detail"]["payload"][];
};
export const llmCompleteMockStorage: MockStorage = {
llmEventStart: [],
llmEventEnd: [],
llmEventStream: [],
};
export const testRootDir = fileURLToPath(new URL(".", import.meta.url));
export async function mockLLMEvent(
t: Parameters<NonNullable<Parameters<typeof test>[0]>>[0],
snapshotName: string,
) {
const idMap = new Map<string, string>();
let counter = 0;
const newLLMCompleteMockStorage: MockStorage = {
llmEventStart: [],
llmEventEnd: [],
llmEventStream: [],
};
function captureLLMStart(event: LLMStartEvent) {
idMap.set(event.detail.payload.id, `PRESERVE_${counter++}`);
newLLMCompleteMockStorage.llmEventStart.push({
...event.detail.payload,
// @ts-expect-error id is not UUID, but it is fine for testing
id: idMap.get(event.detail.payload.id)!,
});
}
function captureLLMEnd(event: LLMEndEvent) {
newLLMCompleteMockStorage.llmEventEnd.push({
...event.detail.payload,
// @ts-expect-error id is not UUID, but it is fine for testing
id: idMap.get(event.detail.payload.id)!,
});
}
function captureLLMStream(event: LLMStreamEvent) {
newLLMCompleteMockStorage.llmEventStream.push({
...event.detail.payload,
// @ts-expect-error id is not UUID, but it is fine for testing
id: idMap.get(event.detail.payload.id)!,
});
}
await readFile(join(testRootDir, "snapshot", `${snapshotName}.snap`), {
encoding: "utf-8",
})
.then((data) => {
const result = JSON.parse(data) as MockStorage;
result["llmEventEnd"].forEach((event) => {
llmCompleteMockStorage.llmEventEnd.push(event);
});
result["llmEventStart"].forEach((event) => {
llmCompleteMockStorage.llmEventStart.push(event);
});
result["llmEventStream"].forEach((event) => {
llmCompleteMockStorage.llmEventStream.push(event);
});
})
.catch((error) => {
if (error.code === "ENOENT") {
console.warn("Snapshot file not found, will create a new one");
return;
}
});
Settings.callbackManager.on("llm-start", captureLLMStart);
Settings.callbackManager.on("llm-end", captureLLMEnd);
Settings.callbackManager.on("llm-stream", captureLLMStream);
t.after(async () => {
Settings.callbackManager.off("llm-stream", captureLLMStream);
Settings.callbackManager.off("llm-end", captureLLMEnd);
Settings.callbackManager.off("llm-start", captureLLMStart);
// eslint-disable-next-line turbo/no-undeclared-env-vars
if (process.env.UPDATE_SNAPSHOT === "1") {
const data = JSON.stringify(newLLMCompleteMockStorage, null, 2)
.replace(/"id": "(?!PRESERVE_).*"/g, '"id": "HIDDEN"')
.replace(/"created": \d+/g, `"created": 114514`)
.replace(
/"system_fingerprint": ".*"/g,
'"system_fingerprint": "HIDDEN"',
)
.replace(/"tool_call_id": ".*"/g, '"tool_call_id": "HIDDEN"');
await writeFile(
join(testRootDir, "snapshot", `${snapshotName}.snap`),
data,
);
return;
}
if (
newLLMCompleteMockStorage.llmEventEnd.length !==
llmCompleteMockStorage.llmEventEnd.length
) {
throw new Error("New LLMEndEvent does not match, please update snapshot");
}
if (
newLLMCompleteMockStorage.llmEventStart.length !==
llmCompleteMockStorage.llmEventStart.length
) {
throw new Error(
"New LLMStartEvent does not match, please update snapshot",
);
}
if (
newLLMCompleteMockStorage.llmEventStream.length !==
llmCompleteMockStorage.llmEventStream.length
) {
throw new Error(
"New LLMStreamEvent does not match, please update snapshot",
);
}
});
// cleanup
t.after(() => {
llmCompleteMockStorage.llmEventEnd = [];
llmCompleteMockStorage.llmEventStart = [];
llmCompleteMockStorage.llmEventStream = [];
});
}
+2 -1
View File
@@ -5,7 +5,8 @@
"type": "module",
"scripts": {
"e2e": "node --import tsx --import ./mock-register.js --test ./node/*.e2e.ts",
"e2e:nomock": "node --import tsx --test ./node/*.e2e.ts"
"e2e:nomock": "node --import tsx --test ./node/*.e2e.ts",
"e2e:updatesnap": "UPDATE_SNAPSHOT=1 node --import tsx --test ./node/*.e2e.ts"
},
"devDependencies": {
"@faker-js/faker": "^8.4.1",
+6 -5
View File
@@ -1,6 +1,6 @@
{
"name": "llamaindex",
"version": "0.2.5",
"version": "0.2.8",
"expectedMinorVersion": "2",
"license": "MIT",
"type": "module",
@@ -12,18 +12,19 @@
"@llamaindex/cloud": "0.0.5",
"@llamaindex/env": "workspace:*",
"@mistralai/mistralai": "^0.1.3",
"@notionhq/client": "^2.2.14",
"@notionhq/client": "^2.2.15",
"@pinecone-database/pinecone": "^2.2.0",
"@qdrant/js-client-rest": "^1.8.2",
"@types/lodash": "^4.17.0",
"@types/node": "^18.19.31",
"@types/node": "^20.12.7",
"@types/papaparse": "^5.3.14",
"@types/pg": "^8.11.5",
"@xenova/transformers": "^2.16.1",
"@zilliz/milvus2-sdk-node": "^2.3.5",
"ajv": "^8.12.0",
"assemblyai": "^4.3.4",
"chromadb": "~1.7.3",
"cohere-ai": "^7.9.2",
"cohere-ai": "^7.9.3",
"js-tiktoken": "^1.0.10",
"lodash": "^4.17.21",
"magic-bytes.js": "^1.10.0",
@@ -50,7 +51,7 @@
"concurrently": "^8.2.2",
"glob": "^10.3.12",
"madge": "^6.1.0",
"typescript": "^5.4.4"
"typescript": "^5.4.5"
},
"engines": {
"node": ">=18.0.0"
+2 -1
View File
@@ -1,6 +1,7 @@
import { Settings } from "../../Settings.js";
import type { ChatMessage } from "../../llm/index.js";
import { OpenAI } from "../../llm/index.js";
import type { BaseMemory } from "../../memory/types.js";
import type { ObjectRetriever } from "../../objects/base.js";
import type { BaseTool } from "../../types.js";
import { AgentRunner } from "../runner/base.js";
@@ -9,7 +10,7 @@ import { OpenAIAgentWorker } from "./worker.js";
type OpenAIAgentParams = {
tools?: BaseTool[];
llm?: OpenAI;
memory?: any;
memory?: BaseMemory;
prefixMessages?: ChatMessage[];
maxFunctionCalls?: number;
defaultToolChoice?: string;
+61 -34
View File
@@ -1,4 +1,4 @@
import { randomUUID } from "@llamaindex/env";
import { pipeline, randomUUID } from "@llamaindex/env";
import type { ChatCompletionToolChoiceOption } from "openai/resources/chat/completions";
import { Response } from "../../Response.js";
import { Settings } from "../../Settings.js";
@@ -14,12 +14,9 @@ import {
type ChatResponseChunk,
type LLMChatParamsBase,
type OpenAIAdditionalChatOptions,
type OpenAIAdditionalMessageOptions,
} from "../../llm/index.js";
import {
extractText,
streamConverter,
streamReducer,
} from "../../llm/utils.js";
import { extractText } from "../../llm/utils.js";
import { ChatMemoryBuffer } from "../../memory/ChatMemoryBuffer.js";
import type { ObjectRetriever } from "../../objects/base.js";
import type { ToolOutput } from "../../tools/types.js";
@@ -49,7 +46,7 @@ async function callFunction(
// Call tool
// Use default error message
const output = await callToolWithErrorHandling(tool, argumentDict, null);
const output = await callToolWithErrorHandling(tool, argumentDict);
if (Settings.debug) {
console.log(`Got output ${output}`);
@@ -181,40 +178,70 @@ export class OpenAIAgentWorker
stream: true,
...llmChatParams,
});
// read first chunk from stream to find out if we need to call tools
const iterator = stream[Symbol.asyncIterator]();
let { value } = await iterator.next();
let content = value.delta;
const hasToolCalls = value.options?.toolCalls.length > 0;
const responseChunkStream = new ReadableStream<
ChatResponseChunk<OpenAIAdditionalMessageOptions>
>({
async start(controller) {
for await (const chunk of stream) {
controller.enqueue(chunk);
}
controller.close();
},
});
const [pipStream, finalStream] = responseChunkStream.tee();
const reader = pipStream.getReader();
const { value } = await reader.read();
reader.releaseLock();
if (value === undefined) {
throw new Error("first chunk value is undefined, this should not happen");
}
// check if first chunk has tool calls, if so, this is a function call
// otherwise, it's a regular message
const hasToolCalls: boolean =
!!value.options?.toolCalls?.length &&
value.options?.toolCalls?.length > 0;
if (hasToolCalls) {
// consume stream until we have all the tool calls and return a non-streamed response
for await (value of stream) {
content += value.delta;
}
return this._processMessage(task, {
content,
content: await pipeline(finalStream, async (iterator) => {
let content = "";
for await (const value of iterator) {
content += value.delta;
}
return content;
}),
role: "assistant",
options: value.options,
});
} else {
const [responseStream, chunkStream] = finalStream.tee();
let content = "";
return new StreamingAgentChatResponse(
responseStream.pipeThrough<Response>({
readable: new ReadableStream({
async start(controller) {
for await (const chunk of chunkStream) {
controller.enqueue(new Response(chunk.delta));
}
controller.close();
},
}),
writable: new WritableStream({
write(chunk) {
content += chunk.delta;
},
close() {
task.extraState.newMemory.put({
content,
role: "assistant",
});
},
}),
}),
task.extraState.sources,
);
}
const newStream = streamConverter.bind(this)(
streamReducer({
stream,
initialValue: content,
reducer: (accumulator, part) => (accumulator += part.delta),
finished: (accumulator) => {
task.extraState.newMemory.put({
content: accumulator,
role: "assistant",
});
},
}),
(r: ChatResponseChunk) => new Response(r.delta),
);
return new StreamingAgentChatResponse(newStream, task.extraState.sources);
}
private async _getAgentResponse(
+2 -1
View File
@@ -1,4 +1,5 @@
import type { ChatMessage, LLM } from "../../llm/index.js";
import type { BaseMemory } from "../../memory/types.js";
import type { ObjectRetriever } from "../../objects/base.js";
import type { BaseTool } from "../../types.js";
import { AgentRunner } from "../runner/base.js";
@@ -7,7 +8,7 @@ import { ReActAgentWorker } from "./worker.js";
type ReActAgentParams = {
tools: BaseTool[];
llm?: LLM;
memory?: any;
memory?: BaseMemory;
prefixMessages?: ChatMessage[];
maxInteractions?: number;
defaultToolChoice?: string;
+1 -1
View File
@@ -194,7 +194,7 @@ export class ReActAgentWorker implements AgentWorker<ChatParams> {
const tool = toolsDict[actionReasoningStep.action];
const toolOutput = await tool?.call?.(actionReasoningStep.actionInput);
const toolOutput = await tool.call!(actionReasoningStep.actionInput);
task.extraState.sources.push(
new ToolOutput(
+1 -1
View File
@@ -161,7 +161,7 @@ export class AgentRunner extends BaseAgentRunner {
const task = this.state.getTask(taskId);
const curStep = step || this.state.getStepQueue(taskId).shift();
let curStepOutput;
let curStepOutput: TaskStepOutput;
if (!curStep) {
throw new Error(`No step found for task ${taskId}`);
+3 -2
View File
@@ -4,6 +4,7 @@ import type {
StreamingAgentChatResponse,
} from "../engines/chat/index.js";
import type { BaseMemory } from "../memory/types.js";
import type { QueryEngineParamsNonStreaming } from "../types.js";
export interface AgentWorker<ExtraParams extends object = object> {
@@ -72,7 +73,7 @@ export abstract class BaseAgent implements BaseChatEngine, BaseQueryEngine {
type TaskParams = {
taskId: string;
input: string;
memory: any;
memory: BaseMemory;
extraState: Record<string, any>;
};
@@ -84,7 +85,7 @@ export class Task {
taskId: string;
input: string;
memory: any;
memory: BaseMemory;
extraState: Record<string, any>;
constructor({ taskId, input, memory, extraState }: TaskParams) {
@@ -5,7 +5,11 @@ import {
EventCaller,
getEventCaller,
} from "../internal/context/EventCaller.js";
import type { LLMEndEvent, LLMStartEvent } from "../llm/types.js";
import type {
LLMEndEvent,
LLMStartEvent,
LLMStreamEvent,
} from "../llm/types.js";
export class LlamaIndexCustomEvent<T = any> extends CustomEvent<T> {
reason: EventCaller | null;
@@ -44,6 +48,7 @@ export interface LlamaIndexEventMaps {
stream: CustomEvent<StreamCallbackResponse>;
"llm-start": LLMStartEvent;
"llm-end": LLMEndEvent;
"llm-stream": LLMStreamEvent;
}
//#region @deprecated remove in the next major version
+1 -1
View File
@@ -366,7 +366,7 @@ export class Portkey extends BaseLLM {
idx_counter++;
yield { delta: part.choices[0].delta?.content ?? "" };
yield { raw: part, delta: part.choices[0].delta?.content ?? "" };
}
return;
}
+4 -1
View File
@@ -212,7 +212,10 @@ export class Anthropic extends BaseLLM {
if (typeof content !== "string") continue;
idx_counter++;
yield { delta: content };
yield {
raw: part,
delta: content,
};
}
return;
}
+1
View File
@@ -41,6 +41,7 @@ export abstract class BaseLLM<
});
return streamConverter(stream, (chunk) => {
return {
raw: null,
text: chunk.delta,
};
});
+1
View File
@@ -137,6 +137,7 @@ export class MistralAI extends BaseLLM {
idx_counter++;
yield {
raw: part,
delta: part.choices[0].delta.content ?? "",
};
}
+3 -1
View File
@@ -14,11 +14,13 @@ import type {
const messageAccessor = (data: any): ChatResponseChunk => {
return {
raw: data,
delta: data.message.content,
};
};
const completionAccessor = (data: any): CompletionResponse => {
return { text: data.response };
return { text: data.response, raw: null };
};
// https://github.com/jmorganca/ollama
+8 -2
View File
@@ -391,6 +391,8 @@ export class OpenAI extends BaseLLM<
for await (const part of stream) {
if (!part.choices.length) continue;
const choice = part.choices[0];
// skip parts that don't have any content
if (!(choice.delta.content || choice.delta.tool_calls)) continue;
updateToolCalls(toolCalls, choice.delta.tool_calls);
const isDone: boolean = choice.finish_reason !== null;
@@ -402,6 +404,7 @@ export class OpenAI extends BaseLLM<
});
yield {
raw: part,
// add tool calls to final chunk
options: toolCalls.length > 0 ? { toolCalls: toolCalls } : {},
delta: choice.delta.content ?? "",
@@ -444,8 +447,11 @@ function updateToolCalls(
return toolCall;
}
if (toolCallDeltas) {
toolCallDeltas?.forEach((toolCall, i) => {
toolCalls[i] = augmentToolCall(toolCalls[i], toolCall);
toolCallDeltas?.forEach((toolCall) => {
toolCalls[toolCall.index] = augmentToolCall(
toolCalls[toolCall.index],
toolCall,
);
});
}
}
+18 -2
View File
@@ -22,6 +22,13 @@ export type LLMEndEvent = LLMBaseEvent<
response: ChatResponse;
}
>;
export type LLMStreamEvent = LLMBaseEvent<
"llm-stream",
{
id: UUID;
chunk: ChatResponseChunk;
}
>;
/**
* @internal
@@ -126,8 +133,10 @@ export interface ChatResponse<
message: ChatMessage<AdditionalMessageOptions>;
/**
* Raw response from the LLM
*
* If LLM response an iterable of chunks, this will be an array of those chunks
*/
raw: object;
raw: object | null;
}
export type ChatResponseChunk<
@@ -138,17 +147,24 @@ export type ChatResponseChunk<
> =
AdditionalMessageOptions extends Record<string, unknown>
? {
raw: object | null;
delta: string;
options?: AdditionalMessageOptions;
}
: {
raw: object | null;
delta: string;
options: AdditionalMessageOptions;
};
export interface CompletionResponse {
text: string;
raw?: Record<string, any>;
/**
* Raw response from the LLM
*
* It's possible that this is `null` if the LLM response an iterable of chunks
*/
raw: object | null;
}
export type LLMMetadata = {
+18 -3
View File
@@ -2,6 +2,7 @@ import { AsyncLocalStorage, randomUUID } from "@llamaindex/env";
import { getCallbackManager } from "../internal/settings/CallbackManager.js";
import type {
ChatResponse,
ChatResponseChunk,
LLM,
LLMChat,
MessageContent,
@@ -83,13 +84,14 @@ export function wrapLLMEvent(
[Symbol.asyncIterator]: response[Symbol.asyncIterator].bind(response),
};
response[Symbol.asyncIterator] = async function* () {
const finalResponse: ChatResponse = {
raw: response,
const finalResponse = {
raw: [] as ChatResponseChunk[],
message: {
content: "",
role: "assistant",
options: {},
},
};
} satisfies ChatResponse;
let firstOne = false;
for await (const chunk of originalAsyncIterator) {
if (!firstOne) {
@@ -98,6 +100,19 @@ export function wrapLLMEvent(
} else {
finalResponse.message.content += chunk.delta;
}
if (chunk.options) {
finalResponse.message.options = {
...finalResponse.message.options,
...chunk.options,
};
}
getCallbackManager().dispatchEvent("llm-stream", {
payload: {
id,
chunk,
},
});
finalResponse.raw.push(chunk);
yield chunk;
}
snapshot(() => {
+16 -24
View File
@@ -1,18 +1,11 @@
import type { JSONSchemaType } from "ajv";
import type { BaseQueryEngine, BaseTool, ToolMetadata } from "../types.js";
export type QueryEngineToolParams = {
queryEngine: BaseQueryEngine;
metadata: ToolMetadata;
};
type QueryEngineCallParams = {
query: string;
};
const DEFAULT_NAME = "query_engine_tool";
const DEFAULT_DESCRIPTION =
"Useful for running a natural language query against a knowledge base and get back a natural language response.";
const DEFAULT_PARAMETERS = {
const DEFAULT_PARAMETERS: JSONSchemaType<QueryEngineParam> = {
type: "object",
properties: {
query: {
@@ -23,9 +16,18 @@ const DEFAULT_PARAMETERS = {
required: ["query"],
};
export class QueryEngineTool implements BaseTool {
export type QueryEngineToolParams = {
queryEngine: BaseQueryEngine;
metadata: ToolMetadata<JSONSchemaType<QueryEngineParam>>;
};
export type QueryEngineParam = {
query: string;
};
export class QueryEngineTool implements BaseTool<QueryEngineParam> {
private queryEngine: BaseQueryEngine;
metadata: ToolMetadata;
metadata: ToolMetadata<JSONSchemaType<QueryEngineParam>>;
constructor({ queryEngine, metadata }: QueryEngineToolParams) {
this.queryEngine = queryEngine;
@@ -36,18 +38,8 @@ export class QueryEngineTool implements BaseTool {
};
}
async call(...args: QueryEngineCallParams[]): Promise<any> {
let queryStr: string;
if (args && args.length > 0) {
queryStr = String(args[0].query);
} else {
throw new Error(
"Cannot call query engine without specifying `input` parameter.",
);
}
const response = await this.queryEngine.query({ query: queryStr });
async call({ query }: QueryEngineParam) {
const response = await this.queryEngine.query({ query });
return response.response;
}
-33
View File
@@ -1,33 +0,0 @@
import type { BaseTool } from "../types.js";
import { WikipediaTool } from "./WikipediaTool.js";
enum Tools {
Wikipedia = "wikipedia.WikipediaToolSpec",
}
type ToolConfig = { [key in Tools]: Record<string, any> };
export class ToolFactory {
private static async createTool(
key: Tools,
options: Record<string, any>,
): Promise<BaseTool> {
if (key === Tools.Wikipedia) {
const tool = new WikipediaTool();
return tool;
}
throw new Error(
`Sorry! Tool ${key} is not supported yet. Options: ${options}`,
);
}
public static async createTools(config: ToolConfig): Promise<BaseTool[]> {
const tools: BaseTool[] = [];
for (const [key, value] of Object.entries(config as ToolConfig)) {
const tool = await ToolFactory.createTool(key as Tools, value);
tools.push(tool);
}
return tools;
}
}
+43
View File
@@ -0,0 +1,43 @@
import { WikipediaTool } from "./WikipediaTool.js";
export namespace ToolsFactory {
type ToolsMap = {
[Tools.Wikipedia]: typeof WikipediaTool;
};
export enum Tools {
Wikipedia = "wikipedia.WikipediaToolSpec",
}
export async function createTool<Tool extends Tools>(
key: Tool,
...params: ConstructorParameters<ToolsMap[Tool]>
): Promise<InstanceType<ToolsMap[Tool]>> {
if (key === Tools.Wikipedia) {
return new WikipediaTool(...params) as InstanceType<ToolsMap[Tool]>;
}
throw new Error(
`Sorry! Tool ${key} is not supported yet. Options: ${params}`,
);
}
export async function createTools<const Tool extends Tools>(record: {
[key in Tool]: ConstructorParameters<ToolsMap[Tool]>[1] extends any // backward compatibility for `create-llama` script // if parameters are an array, use them as is
? ConstructorParameters<ToolsMap[Tool]>[0]
: ConstructorParameters<ToolsMap[Tool]>;
}): Promise<InstanceType<ToolsMap[Tool]>[]> {
const tools: InstanceType<ToolsMap[Tool]>[] = [];
for (const key in record) {
const params = record[key];
tools.push(
await createTool(
key,
// @ts-expect-error
Array.isArray(params) ? params : [params],
),
);
}
return tools;
}
}
+15 -9
View File
@@ -1,16 +1,17 @@
import type { JSONSchemaType } from "ajv";
import { default as wiki } from "wikipedia";
import type { BaseTool, ToolMetadata } from "../types.js";
export type WikipediaToolParams = {
metadata?: ToolMetadata;
};
type WikipediaCallParams = {
type WikipediaParameter = {
query: string;
lang?: string;
};
const DEFAULT_META_DATA: ToolMetadata = {
export type WikipediaToolParams = {
metadata?: ToolMetadata<JSONSchemaType<WikipediaParameter>>;
};
const DEFAULT_META_DATA: ToolMetadata<JSONSchemaType<WikipediaParameter>> = {
name: "wikipedia_tool",
description: "A tool that uses a query engine to search Wikipedia.",
parameters: {
@@ -20,14 +21,19 @@ const DEFAULT_META_DATA: ToolMetadata = {
type: "string",
description: "The query to search for",
},
lang: {
type: "string",
description: "The language to search in",
nullable: true,
},
},
required: ["query"],
},
};
export class WikipediaTool implements BaseTool {
export class WikipediaTool implements BaseTool<WikipediaParameter> {
private readonly DEFAULT_LANG = "en";
metadata: ToolMetadata;
metadata: ToolMetadata<JSONSchemaType<WikipediaParameter>>;
constructor(params?: WikipediaToolParams) {
this.metadata = params?.metadata || DEFAULT_META_DATA;
@@ -46,7 +52,7 @@ export class WikipediaTool implements BaseTool {
async call({
query,
lang = this.DEFAULT_LANG,
}: WikipediaCallParams): Promise<string> {
}: WikipediaParameter): Promise<string> {
const searchResult = await wiki.default.search(query);
if (searchResult.results.length === 0) return "No search results.";
return await this.loadData(searchResult.results[0].title, lang);
+21 -23
View File
@@ -1,32 +1,30 @@
import type { JSONSchemaType } from "ajv";
import type { BaseTool, ToolMetadata } from "../types.js";
type Metadata = {
name: string;
description: string;
parameters: ToolMetadata["parameters"];
};
export class FunctionTool<T, R extends string | Promise<string>>
implements BaseTool<T>
{
constructor(
private readonly _fn: (input: T) => R,
private readonly _metadata: ToolMetadata<JSONSchemaType<T>>,
) {}
export class FunctionTool<T = any> implements BaseTool {
private _fn: (...args: any[]) => any;
private _metadata: ToolMetadata;
constructor(fn: (...args: any[]) => any, metadata: Metadata) {
this._fn = fn;
this._metadata = metadata as ToolMetadata;
static from<T>(
fn: (input: T) => string | Promise<string>,
schema: ToolMetadata<JSONSchemaType<T>>,
): FunctionTool<T, string | Promise<string>>;
static from<T, R extends string | Promise<string>>(
fn: (input: T) => R,
schema: ToolMetadata<JSONSchemaType<T>>,
): FunctionTool<T, R> {
return new FunctionTool(fn, schema);
}
static fromDefaults<T = any>(
fn: (...args: any[]) => any,
metadata?: Metadata,
): FunctionTool<T> {
return new FunctionTool(fn, metadata!);
get metadata(): BaseTool<T>["metadata"] {
return this._metadata as BaseTool<T>["metadata"];
}
get metadata(): ToolMetadata {
return this._metadata;
}
async call(...args: any[]): Promise<any> {
return this._fn(...args);
call(input: T) {
return this._fn(input);
}
}
-1
View File
@@ -1,5 +1,4 @@
export * from "./QueryEngineTool.js";
export * from "./ToolFactory.js";
export * from "./WikipediaTool.js";
export * from "./functionTool.js";
export * from "./types.js";
+10 -16
View File
@@ -1,30 +1,24 @@
import type { BaseTool } from "../types.js";
import { ToolOutput } from "./types.js";
/**
* Call tool with error handling.
* @param tool: tool
* @param inputDict: input dict
* @param errorMessage: error message
* @param raiseError: raise error
* @returns: tool output
*/
export async function callToolWithErrorHandling(
tool: BaseTool,
inputDict: { [key: string]: any },
errorMessage: string | null = null,
raiseError: boolean = false,
): Promise<ToolOutput> {
if (!tool.call) {
return new ToolOutput(
"Error: Tool does not have a call function.",
tool.metadata.name,
{ kwargs: inputDict },
null,
);
}
try {
const value = await tool.call?.(inputDict);
const value = await tool.call(inputDict);
return new ToolOutput(value, tool.metadata.name, inputDict, value);
} catch (e) {
if (raiseError) {
throw e;
}
errorMessage = errorMessage || `Error: ${e}`;
return new ToolOutput(
errorMessage,
`Error: ${e}`,
tool.metadata.name,
{ kwargs: inputDict },
e,
+36 -16
View File
@@ -1,6 +1,7 @@
/**
* Top level types to avoid circular dependencies
*/
import { type JSONSchemaType } from "ajv";
import type { Response } from "./Response.js";
/**
@@ -30,14 +31,46 @@ export interface BaseQueryEngine {
query(params: QueryEngineParamsNonStreaming): Promise<Response>;
}
type Known =
| { [key: string]: Known }
| [Known, ...Known[]]
| Known[]
| number
| string
| boolean
| null;
export type ToolMetadata<
Parameters extends Record<string, unknown> = Record<string, unknown>,
> = {
description: string;
name: string;
/**
* OpenAI uses JSON Schema to describe the parameters that a tool can take.
* @link https://json-schema.org/understanding-json-schema
*/
parameters?: Parameters;
};
/**
* Simple Tool interface. Likely to change.
*/
export interface BaseTool {
call?: (...args: any[]) => any;
metadata: ToolMetadata;
export interface BaseTool<Input = any> {
/**
* This could be undefined if the implementation is not provided,
* which might be the case when communicating with a llm.
*
* @return string - the output of the tool, should be string in any case for LLM input.
*/
call?: (input: Input) => string | Promise<string>;
metadata: // if user input any, we cannot check the schema
Input extends Known ? ToolMetadata<JSONSchemaType<Input>> : ToolMetadata;
}
export type ToolWithCall<Input = unknown> = Omit<BaseTool<Input>, "call"> & {
call: NonNullable<Pick<BaseTool<Input>, "call">["call"]>;
};
/**
* An OutputParser is used to extract structured data from the raw output of the LLM.
*/
@@ -55,19 +88,6 @@ export interface StructuredOutput<T> {
parsedOutput: T;
}
export type ToolParameters = {
type: string | "object";
properties: Record<string, { type: string; description?: string }>;
required?: string[];
};
export interface ToolMetadata {
description: string;
name: string;
parameters?: ToolParameters;
argsKwargs?: Record<string, any>;
}
export type ToolMetadataOnlyDescription = Pick<ToolMetadata, "description">;
export class QueryBundle {
@@ -1,61 +0,0 @@
import { Settings } from "llamaindex";
import { OpenAIAgent } from "llamaindex/agent/index";
import { OpenAI } from "llamaindex/llm/index";
import { FunctionTool } from "llamaindex/tools/index";
import { beforeEach, describe, expect, it } from "vitest";
import { mockLlmToolCallGeneration } from "../utility/mockOpenAI.js";
// Define a function to sum two numbers
function sumNumbers({ a, b }: { a: number; b: number }): number {
return a + b;
}
const sumJSON = {
type: "object",
properties: {
a: {
type: "number",
description: "The first number",
},
b: {
type: "number",
description: "The second number",
},
},
required: ["a", "b"],
};
describe("OpenAIAgent", () => {
let openaiAgent: OpenAIAgent;
beforeEach(() => {
const languageModel = new OpenAI({
model: "gpt-3.5-turbo",
});
Settings.llm = languageModel;
mockLlmToolCallGeneration({
languageModel,
});
const sumFunctionTool = new FunctionTool(sumNumbers, {
name: "sumNumbers",
description: "Use this function to sum two numbers",
parameters: sumJSON,
});
openaiAgent = new OpenAIAgent({
tools: [sumFunctionTool],
llm: languageModel,
});
});
it("should be able to chat with agent", async () => {
const response = await openaiAgent.chat({
message: "how much is 1 + 1?",
});
expect(String(response)).toEqual("The sum is 2");
});
});
@@ -17,7 +17,7 @@ describe("ObjectIndex", () => {
});
test("test_object_with_tools", async () => {
const tool1 = new FunctionTool((x: any) => x, {
const tool1 = new FunctionTool(({ x }: { x: string }) => x, {
name: "test_tool",
description: "test tool",
parameters: {
@@ -27,10 +27,11 @@ describe("ObjectIndex", () => {
type: "string",
},
},
required: ["x"],
},
});
const tool2 = new FunctionTool((x: any) => x, {
const tool2 = new FunctionTool(({ x }: { x: string }) => x, {
name: "test_tool_2",
description: "test tool 2",
parameters: {
@@ -40,6 +41,7 @@ describe("ObjectIndex", () => {
type: "string",
},
},
required: ["x"],
},
});
@@ -62,7 +64,7 @@ describe("ObjectIndex", () => {
});
test("add a new object", async () => {
const tool1 = new FunctionTool((x: any) => x, {
const tool1 = new FunctionTool(({ x }: { x: string }) => x, {
name: "test_tool",
description: "test tool",
parameters: {
@@ -72,10 +74,11 @@ describe("ObjectIndex", () => {
type: "string",
},
},
required: ["x"],
},
});
const tool2 = new FunctionTool((x: any) => x, {
const tool2 = new FunctionTool(({ x }: { x: string }) => x, {
name: "test_tool_2",
description: "test tool 2",
parameters: {
@@ -85,6 +88,7 @@ describe("ObjectIndex", () => {
type: "string",
},
},
required: ["x"],
},
});
+32
View File
@@ -0,0 +1,32 @@
import { ToolsFactory } from "llamaindex/tools/ToolsFactory";
import { WikipediaTool } from "llamaindex/tools/WikipediaTool";
import { assertType, describe, test } from "vitest";
describe("ToolsFactory", async () => {
test("createTool", async () => {
await ToolsFactory.createTool(ToolsFactory.Tools.Wikipedia, {
metadata: {
name: "wikipedia_tool",
description: "A tool that uses a query engine to search Wikipedia.",
},
});
});
test("createTools", async () => {
await ToolsFactory.createTools({
[ToolsFactory.Tools.Wikipedia]: {
metadata: {
name: "wikipedia_tool",
description: "A tool that uses a query engine to search Wikipedia.",
},
},
});
});
test("type", () => {
assertType<
(
key: ToolsFactory.Tools.Wikipedia,
params: ConstructorParameters<typeof WikipediaTool>[0],
) => Promise<WikipediaTool>
>(ToolsFactory.createTool<ToolsFactory.Tools.Wikipedia>);
});
});
-46
View File
@@ -1,46 +0,0 @@
import { FunctionTool, ToolOutput } from "llamaindex/tools/index";
import { callToolWithErrorHandling } from "llamaindex/tools/utils";
import { describe, expect, it } from "vitest";
function sumNumbers({ a, b }: { a: number; b: number }): number {
return a + b;
}
const sumJSON = {
type: "object",
properties: {
a: {
type: "number",
description: "The first number",
},
b: {
type: "number",
description: "The second number",
},
},
required: ["a", "b"],
};
describe("Tools", () => {
it("should be able to call a tool with a common JSON", async () => {
const tool = new FunctionTool(sumNumbers, {
name: "sumNumbers",
description: "Use this function to sum two numbers",
parameters: sumJSON,
});
const response = await callToolWithErrorHandling(tool, {
a: 1,
b: 2,
});
expect(response).toEqual(
new ToolOutput(
response.content,
tool.metadata.name,
{ a: 1, b: 2 },
response.content,
),
);
});
});
+8
View File
@@ -0,0 +1,8 @@
# @llamaindex/edge
## 0.2.7
### Patch Changes
- Updated dependencies
- @llamaindex/env@0.0.7
@@ -1,5 +1,11 @@
# test-edge-runtime
## 0.1.2
### Patch Changes
- @llamaindex/edge@0.2.7
## 0.1.1
### Patch Changes
@@ -1,6 +1,6 @@
{
"name": "test-edge-runtime",
"version": "0.1.1",
"version": "0.1.2",
"private": true,
"scripts": {
"dev": "next dev",
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@llamaindex/edge",
"version": "0.2.5",
"version": "0.2.8",
"license": "MIT",
"type": "module",
"dependencies": {
@@ -11,18 +11,19 @@
"@llamaindex/cloud": "0.0.5",
"@llamaindex/env": "workspace:*",
"@mistralai/mistralai": "^0.1.3",
"@notionhq/client": "^2.2.14",
"@notionhq/client": "^2.2.15",
"@pinecone-database/pinecone": "^2.2.0",
"@qdrant/js-client-rest": "^1.8.2",
"@types/lodash": "^4.17.0",
"@types/node": "^18.19.31",
"@types/node": "^20.12.7",
"@types/papaparse": "^5.3.14",
"@types/pg": "^8.11.5",
"@xenova/transformers": "^2.16.1",
"@zilliz/milvus2-sdk-node": "^2.3.5",
"ajv": "^8.12.0",
"assemblyai": "^4.3.4",
"chromadb": "~1.7.3",
"cohere-ai": "^7.9.2",
"cohere-ai": "^7.9.3",
"js-tiktoken": "^1.0.10",
"lodash": "^4.17.21",
"magic-bytes.js": "^1.10.0",
+6
View File
@@ -1,5 +1,11 @@
# @llamaindex/env
## 0.0.7
### Patch Changes
- Add polyfill for pipeline
## 0.0.6
### Patch Changes
+6 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@llamaindex/env",
"description": "environment wrapper",
"version": "0.0.6",
"version": "0.0.7",
"type": "module",
"types": "dist/type/index.d.ts",
"main": "dist/cjs/index.js",
@@ -56,16 +56,18 @@
"@aws-crypto/sha256-js": "^5.2.0",
"@swc/cli": "^0.3.9",
"@swc/core": "^1.4.2",
"concurrently": "^8.2.2",
"pathe": "^1.1.2",
"concurrently": "^8.2.2"
"readable-stream": "^4.5.2"
},
"dependencies": {
"@types/lodash": "^4.14.202",
"@types/node": "^20.11.20",
"@types/node": "^20.12.7",
"lodash": "^4.17.21"
},
"peerDependencies": {
"@aws-crypto/sha256-js": "^5.2.0",
"pathe": "^1.1.2"
"pathe": "^1.1.2",
"readable-stream": "^4.5.2"
}
}
+3 -1
View File
@@ -1,8 +1,10 @@
import { Sha256 } from "@aws-crypto/sha256-js";
import pathe from "pathe";
import { InMemoryFileSystem, type CompleteFileSystem } from "./type.js";
// @ts-expect-error
import { pipeline } from "readable-stream";
export { pathe as path };
export { pathe as path, pipeline };
export interface SHA256 {
update(data: string | Uint8Array): void;
+2 -1
View File
@@ -3,6 +3,7 @@ import { createHash, randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import { EOL } from "node:os";
import path from "node:path";
import { pipeline } from "node:stream/promises";
import type { SHA256 } from "./index.polyfill.js";
import type { CompleteFileSystem } from "./type.js";
@@ -36,4 +37,4 @@ export const defaultFS: CompleteFileSystem = {
export type * from "./type.js";
export { AsyncLocalStorage, CustomEvent, getEnv } from "./utils.js";
export { EOL, ok, path, randomUUID };
export { EOL, ok, path, pipeline, randomUUID };
+21
View File
@@ -1,5 +1,26 @@
# @llamaindex/experimental
## 0.0.11
### Patch Changes
- Updated dependencies
- llamaindex@0.2.8
## 0.0.10
### Patch Changes
- Updated dependencies [96f8f40]
- llamaindex@0.2.7
## 0.0.9
### Patch Changes
- Updated dependencies [a3b4409]
- llamaindex@0.2.6
## 0.0.8
### Patch Changes
+1 -1
View File
@@ -7,7 +7,7 @@
"llamaindex": "workspace:*"
},
"devDependencies": {
"@types/node": "^18.19.10",
"@types/node": "^20.12.7",
"ts-node": "^10.9.2",
"typescript": "^5.4.3"
},
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@llamaindex/experimental",
"description": "Experimental package for LlamaIndexTS",
"version": "0.0.8",
"version": "0.0.11",
"type": "module",
"types": "dist/type/index.d.ts",
"main": "dist/cjs/index.js",
@@ -63,7 +63,7 @@
},
"dependencies": {
"@types/lodash": "^4.14.202",
"@types/node": "^20.11.20",
"@types/node": "^20.12.7",
"jsonpath": "^1.1.1",
"llamaindex": "workspace:*",
"lodash": "^4.17.21"
+1 -1
View File
@@ -4,7 +4,7 @@
"license": "MIT",
"type": "module",
"dependencies": {
"@types/node": "^18.19.14",
"@types/node": "^20.12.7",
"@assemblyscript/loader": "^0.19.9"
},
"devDependencies": {
+239 -235
View File
File diff suppressed because it is too large Load Diff