mirror of
https://github.com/run-llama/LlamaIndexTS.git
synced 2026-07-10 15:53:42 -04:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b8173e4c4e | |||
| 67b5445fb9 | |||
| 87419ef5d1 | |||
| ad218160d8 | |||
| eeb90d7991 | |||
| 7b7329bd18 | |||
| b3acbb06f4 | |||
| 7db7562841 | |||
| 0e75b124c3 | |||
| d79a0b76f3 | |||
| c3eb4933fb | |||
| e3a956aedd | |||
| e562e479dc | |||
| 1900e019e3 | |||
| 317f140822 | |||
| cd829474d6 | |||
| b6c1500570 | |||
| d06a85bd34 | |||
| 6b9a2feac5 | |||
| bd08004afe | |||
| 0ecc4b2051 | |||
| f9f351229a | |||
| 72659a237b | |||
| 6cc3a36d44 | |||
| 6fe55d6e88 | |||
| 36f2903eb3 | |||
| 09464e6da7 | |||
| 955e084cf3 | |||
| 46ee0c8765 | |||
| da5391c018 | |||
| ce732beece | |||
| 889b70093c | |||
| 7211a27f01 | |||
| ba95ca3fb6 | |||
| ffdc507625 | |||
| 4016c55604 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"llamaindex": patch
|
||||
---
|
||||
|
||||
easier prompt customization for SimpleResponseBuilder
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"llamaindex": patch
|
||||
---
|
||||
|
||||
fix(cyclic): remove cyclic structures from transform hash
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"llamaindex": patch
|
||||
---
|
||||
|
||||
chore: improve extractors prompt
|
||||
@@ -4,6 +4,6 @@
|
||||
"ghcr.io/devcontainers/features/node:1": {},
|
||||
"ghcr.io/devcontainers-contrib/features/turborepo-npm:1": {},
|
||||
"ghcr.io/devcontainers-contrib/features/typescript:2": {},
|
||||
"ghcr.io/devcontainers-contrib/features/pnpm:2": {},
|
||||
},
|
||||
"ghcr.io/devcontainers-contrib/features/pnpm:2": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,12 @@ jobs:
|
||||
- name: Run Circular Dependency Check
|
||||
run: pnpm run circular-check
|
||||
working-directory: ./packages/core
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: typecheck-build-dist
|
||||
path: ./packages/core/dist
|
||||
if-no-files-found: error
|
||||
typecheck-examples:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
@@ -78,3 +78,15 @@ pnpm start
|
||||
That should start a webserver which will serve the docs on https://localhost:3000
|
||||
|
||||
Any changes you make should be reflected in the browser. If you need to regenerate the API docs and find that your TSDoc isn't getting the updates, feel free to remove apps/docs/api. It will automatically regenerate itself when you run pnpm start again.
|
||||
|
||||
## Publishing
|
||||
|
||||
To publish a new version of the library, run
|
||||
|
||||
```shell
|
||||
pnpm new-llamaindex
|
||||
pnpm new-create-llama
|
||||
pnpm release
|
||||
git push # push to the main branch
|
||||
git push --tags
|
||||
```
|
||||
|
||||
@@ -70,7 +70,7 @@ main();
|
||||
Then you can run it using
|
||||
|
||||
```bash
|
||||
pnpx ts-node example.ts
|
||||
pnpm dlx ts-node example.ts
|
||||
```
|
||||
|
||||
## Playground
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# Agents
|
||||
|
||||
A built-in agent that can take decisions and reasoning based on the tools provided to it.
|
||||
|
||||
## OpenAI Agent
|
||||
|
||||
```ts
|
||||
import { FunctionTool, OpenAIAgent } from "llamaindex";
|
||||
|
||||
// Define a function to sum two numbers
|
||||
function sumNumbers({ a, b }: { a: number; b: number }): number {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
// Define a function to divide two numbers
|
||||
function divideNumbers({ a, b }: { a: number; b: number }): number {
|
||||
return a / b;
|
||||
}
|
||||
|
||||
// Define the parameters of the sum function as a JSON schema
|
||||
const sumJSON = {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "The first number",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "The second number",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
};
|
||||
|
||||
// Define the parameters of the divide function as a JSON schema
|
||||
const divideJSON = {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "The dividend to divide",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "The divisor to divide by",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
};
|
||||
|
||||
async function main() {
|
||||
// Create a function tool from the sum function
|
||||
const sumFunctionTool = new FunctionTool(sumNumbers, {
|
||||
name: "sumNumbers",
|
||||
description: "Use this function to sum two numbers",
|
||||
parameters: sumJSON,
|
||||
});
|
||||
|
||||
// Create a function tool from the divide function
|
||||
const divideFunctionTool = new FunctionTool(divideNumbers, {
|
||||
name: "divideNumbers",
|
||||
description: "Use this function to divide two numbers"
|
||||
parameters: divideJSON,
|
||||
});
|
||||
|
||||
// Create an OpenAIAgent with the function tools
|
||||
const agent = new OpenAIAgent({
|
||||
tools: [sumFunctionTool, divideFunctionTool],
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
// Chat with the agent
|
||||
const response = await agent.chat({
|
||||
message: "How much is 5 + 5? then divide by 2",
|
||||
});
|
||||
|
||||
// Print the response
|
||||
console.log(String(response));
|
||||
}
|
||||
|
||||
main().then(() => {
|
||||
console.log("Done");
|
||||
});
|
||||
```
|
||||
@@ -35,7 +35,7 @@ LlamaIndex.TS help you prepare the knowledge base with a suite of data connector
|
||||
[**Data Loaders**](../modules/data_loader.md):
|
||||
A data connector (i.e. `Reader`) ingest data from different data sources and data formats into a simple `Document` representation (text and simple metadata).
|
||||
|
||||
[**Documents / Nodes**](../modules/documents_and_nodes.md): A `Document` is a generic container around any data source - for instance, a PDF, an API output, or retrieved data from a database. A `Node` is the atomic unit of data in LlamaIndex and represents a "chunk" of a source `Document`. It's a rich representation that includes metadata and relationships (to other nodes) to enable accurate and expressive retrieval operations.
|
||||
[**Documents / Nodes**](../modules/documents_and_nodes/index.md): A `Document` is a generic container around any data source - for instance, a PDF, an API output, or retrieved data from a database. A `Node` is the atomic unit of data in LlamaIndex and represents a "chunk" of a source `Document`. It's a rich representation that includes metadata and relationships (to other nodes) to enable accurate and expressive retrieval operations.
|
||||
|
||||
[**Data Indexes**](../modules/data_index.md):
|
||||
Once you've ingested your data, LlamaIndex helps you index data into a format that's easy to retrieve.
|
||||
@@ -69,7 +69,7 @@ A response synthesizer generates a response from an LLM, using a user query and
|
||||
|
||||
#### Pipelines
|
||||
|
||||
[**Query Engines**](../modules/query_engine.md):
|
||||
[**Query Engines**](../modules/query_engines):
|
||||
A query engine is an end-to-end pipeline that allow you to ask question over your data.
|
||||
It takes in a natural language query, and returns a response, along with reference context retrieved and passed to the LLM.
|
||||
|
||||
|
||||
@@ -58,6 +58,6 @@ Our examples use OpenAI by default. You'll need to set up your Open AI key like
|
||||
export OPENAI_API_KEY="sk-......" # Replace with your key from https://platform.openai.com/account/api-keys
|
||||
```
|
||||
|
||||
If you want to have it automatically loaded every time, add it to your .zshrc/.bashrc.
|
||||
If you want to have it automatically loaded every time, add it to your `.zshrc/.bashrc`.
|
||||
|
||||
WARNING: do not check in your OpenAI key into version control.
|
||||
|
||||
@@ -36,9 +36,9 @@ async function main() {
|
||||
|
||||
// Query the index
|
||||
const queryEngine = index.asQueryEngine();
|
||||
const response = await queryEngine.query(
|
||||
"What did the author do in college?",
|
||||
);
|
||||
const response = await queryEngine.query({
|
||||
query: "What did the author do in college?",
|
||||
});
|
||||
|
||||
// Output response
|
||||
console.log(response.toString());
|
||||
|
||||
@@ -37,9 +37,9 @@ For more complex applications, our lower-level APIs allow advanced users to cust
|
||||
|
||||
`npm install llamaindex`
|
||||
|
||||
Our documentation includes [Installation Instructions](./installation.mdx) and a [Starter Tutorial](./starter.md) to build your first application.
|
||||
Our documentation includes [Installation Instructions](./getting_started/installation.mdx) and a [Starter Tutorial](./getting_started/starter.md) to build your first application.
|
||||
|
||||
Once you're up and running, [High-Level Concepts](./getting_started/concepts.md) has an overview of LlamaIndex's modular architecture. For more hands-on practical examples, look through our [End-to-End Tutorials](./end_to_end.md).
|
||||
Once you're up and running, [High-Level Concepts](./getting_started/concepts.md) has an overview of LlamaIndex's modular architecture. For more hands-on practical examples, look through our Examples section on the sidebar.
|
||||
|
||||
## 🗺️ Ecosystem
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
label: "Agents"
|
||||
@@ -0,0 +1,14 @@
|
||||
# Agents
|
||||
|
||||
An “agent” is an automated reasoning and decision engine. It takes in a user input/query and can make internal decisions for executing that query in order to return the correct result. The key agent components can include, but are not limited to:
|
||||
|
||||
- Breaking down a complex question into smaller ones
|
||||
- Choosing an external Tool to use + coming up with parameters for calling the Tool
|
||||
- Planning out a set of tasks
|
||||
- Storing previously completed tasks in a memory module
|
||||
|
||||
## Getting Started
|
||||
|
||||
LlamaIndex.TS comes with a few built-in agents, but you can also create your own. The built-in agents include:
|
||||
|
||||
- [OpenAI Agent](./openai.mdx)
|
||||
@@ -0,0 +1,183 @@
|
||||
# OpenAI Agent
|
||||
|
||||
OpenAI API that supports function calling, it’s never been easier to build your own agent!
|
||||
|
||||
In this notebook tutorial, we showcase how to write your own OpenAI agent
|
||||
|
||||
## Setup
|
||||
|
||||
First, you need to install the `llamaindex` package. You can do this by running the following command in your terminal:
|
||||
|
||||
```bash
|
||||
pnpm i llamaindex
|
||||
```
|
||||
|
||||
Then we can define a function to sum two numbers and another function to divide two numbers.
|
||||
|
||||
```ts
|
||||
function sumNumbers({ a, b }: { a: number; b: number }): number {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
// Define a function to divide two numbers
|
||||
function divideNumbers({ a, b }: { a: number; b: number }): number {
|
||||
return a / b;
|
||||
}
|
||||
```
|
||||
|
||||
## Create a function tool
|
||||
|
||||
Now we can create a function tool from the sum function and another function tool from the divide function.
|
||||
|
||||
For the parameters of the sum function, we can define a JSON schema.
|
||||
|
||||
### JSON Schema
|
||||
|
||||
```ts
|
||||
const sumJSON = {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "The first number",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "The second number",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
};
|
||||
|
||||
const divideJSON = {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "The dividend a to divide",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "The divisor b to divide by",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
};
|
||||
|
||||
const sumFunctionTool = new FunctionTool(sumNumbers, {
|
||||
name: "sumNumbers",
|
||||
description: "Use this function to sum two numbers",
|
||||
parameters: sumJSON,
|
||||
});
|
||||
|
||||
const divideFunctionTool = new FunctionTool(divideNumbers, {
|
||||
name: "divideNumbers",
|
||||
description: "Use this function to divide two numbers",
|
||||
parameters: divideJSON,
|
||||
});
|
||||
```
|
||||
|
||||
## Create an OpenAIAgent
|
||||
|
||||
Now we can create an OpenAIAgent with the function tools.
|
||||
|
||||
```ts
|
||||
const worker = new OpenAIAgent({
|
||||
tools: [sumFunctionTool, divideFunctionTool],
|
||||
verbose: true,
|
||||
});
|
||||
```
|
||||
|
||||
## Chat with the agent
|
||||
|
||||
Now we can chat with the agent.
|
||||
|
||||
```ts
|
||||
const response = await worker.chat({
|
||||
message: "How much is 5 + 5? then divide by 2",
|
||||
});
|
||||
|
||||
console.log(String(response));
|
||||
```
|
||||
|
||||
## Full code
|
||||
|
||||
```ts
|
||||
import { FunctionTool, OpenAIAgent } from "llamaindex";
|
||||
|
||||
// Define a function to sum two numbers
|
||||
function sumNumbers({ a, b }: { a: number; b: number }): number {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
// Define a function to divide two numbers
|
||||
function divideNumbers({ a, b }: { a: number; b: number }): number {
|
||||
return a / b;
|
||||
}
|
||||
|
||||
// Define the parameters of the sum function as a JSON schema
|
||||
const sumJSON = {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "The first number",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "The second number",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
};
|
||||
|
||||
// Define the parameters of the divide function as a JSON schema
|
||||
const divideJSON = {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "The argument a to divide",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "The argument b to divide",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
};
|
||||
|
||||
async function main() {
|
||||
// Create a function tool from the sum function
|
||||
const sumFunctionTool = new FunctionTool(sumNumbers, {
|
||||
name: "sumNumbers",
|
||||
description: "Use this function to sum two numbers",
|
||||
parameters: sumJSON,
|
||||
});
|
||||
|
||||
// Create a function tool from the divide function
|
||||
const divideFunctionTool = new FunctionTool(divideNumbers, {
|
||||
name: "divideNumbers",
|
||||
description: "Use this function to divide two numbers",
|
||||
parameters: divideJSON,
|
||||
});
|
||||
|
||||
// Create an OpenAIAgent with the function tools
|
||||
const agent = new OpenAIAgent({
|
||||
tools: [sumFunctionTool, divideFunctionTool],
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
// Chat with the agent
|
||||
const response = await agent.chat({
|
||||
message: "How much is 5 + 5? then divide by 2",
|
||||
});
|
||||
|
||||
// Print the response
|
||||
console.log(String(response));
|
||||
}
|
||||
|
||||
main().then(() => {
|
||||
console.log("Done");
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,128 @@
|
||||
# OpenAI Agent + QueryEngineTool
|
||||
|
||||
QueryEngineTool is a tool that allows you to query a vector index. In this example, we will create a vector index from a set of documents and then create a QueryEngineTool from the vector index. We will then create an OpenAIAgent with the QueryEngineTool and chat with the agent.
|
||||
|
||||
## Setup
|
||||
|
||||
First, you need to install the `llamaindex` package. You can do this by running the following command in your terminal:
|
||||
|
||||
```bash
|
||||
pnpm i llamaindex
|
||||
```
|
||||
|
||||
Then you can import the necessary classes and functions.
|
||||
|
||||
```ts
|
||||
import {
|
||||
OpenAIAgent,
|
||||
SimpleDirectoryReader,
|
||||
VectorStoreIndex,
|
||||
QueryEngineTool,
|
||||
} from "llamaindex";
|
||||
```
|
||||
|
||||
## Create a vector index
|
||||
|
||||
Now we can create a vector index from a set of documents.
|
||||
|
||||
```ts
|
||||
// Load the documents
|
||||
const documents = await new SimpleDirectoryReader().loadData({
|
||||
directoryPath: "node_modules/llamaindex/examples/",
|
||||
});
|
||||
|
||||
// Create a vector index from the documents
|
||||
const vectorIndex = await VectorStoreIndex.fromDocuments(documents);
|
||||
```
|
||||
|
||||
## Create a QueryEngineTool
|
||||
|
||||
Now we can create a QueryEngineTool from the vector index.
|
||||
|
||||
```ts
|
||||
// Create a query engine from the vector index
|
||||
const abramovQueryEngine = vectorIndex.asQueryEngine();
|
||||
|
||||
// Create a QueryEngineTool with the query engine
|
||||
const queryEngineTool = new QueryEngineTool({
|
||||
queryEngine: abramovQueryEngine,
|
||||
metadata: {
|
||||
name: "abramov_query_engine",
|
||||
description: "A query engine for the Abramov documents",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Create an OpenAIAgent
|
||||
|
||||
```ts
|
||||
// Create an OpenAIAgent with the query engine tool tools
|
||||
|
||||
const agent = new OpenAIAgent({
|
||||
tools: [queryEngineTool],
|
||||
verbose: true,
|
||||
});
|
||||
```
|
||||
|
||||
## Chat with the agent
|
||||
|
||||
Now we can chat with the agent.
|
||||
|
||||
```ts
|
||||
const response = await agent.chat({
|
||||
message: "What was his salary?",
|
||||
});
|
||||
|
||||
console.log(String(response));
|
||||
```
|
||||
|
||||
## Full code
|
||||
|
||||
```ts
|
||||
import {
|
||||
OpenAIAgent,
|
||||
SimpleDirectoryReader,
|
||||
VectorStoreIndex,
|
||||
QueryEngineTool,
|
||||
} from "llamaindex";
|
||||
|
||||
async function main() {
|
||||
// Load the documents
|
||||
const documents = await new SimpleDirectoryReader().loadData({
|
||||
directoryPath: "node_modules/llamaindex/examples/",
|
||||
});
|
||||
|
||||
// Create a vector index from the documents
|
||||
const vectorIndex = await VectorStoreIndex.fromDocuments(documents);
|
||||
|
||||
// Create a query engine from the vector index
|
||||
const abramovQueryEngine = vectorIndex.asQueryEngine();
|
||||
|
||||
// Create a QueryEngineTool with the query engine
|
||||
const queryEngineTool = new QueryEngineTool({
|
||||
queryEngine: abramovQueryEngine,
|
||||
metadata: {
|
||||
name: "abramov_query_engine",
|
||||
description: "A query engine for the Abramov documents",
|
||||
},
|
||||
});
|
||||
|
||||
// Create an OpenAIAgent with the function tools
|
||||
const agent = new OpenAIAgent({
|
||||
tools: [queryEngineTool],
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
// Chat with the agent
|
||||
const response = await agent.chat({
|
||||
message: "What was his salary?",
|
||||
});
|
||||
|
||||
// Print the response
|
||||
console.log(String(response));
|
||||
}
|
||||
|
||||
main().then(() => {
|
||||
console.log("Done");
|
||||
});
|
||||
```
|
||||
@@ -36,6 +36,6 @@ You can learn more about Tools by taking a look at the LlamaIndex Python documen
|
||||
|
||||
## API Reference
|
||||
|
||||
- [RetrieverQueryEngine](../api/classes/RetrieverQueryEngine.md)
|
||||
- [SubQuestionQueryEngine](../api/classes/SubQuestionQueryEngine.md)
|
||||
- [QueryEngineTool](../api/interfaces//QueryEngineTool.md)
|
||||
- [RetrieverQueryEngine](../../api/classes/RetrieverQueryEngine.md)
|
||||
- [SubQuestionQueryEngine](../../api/classes/SubQuestionQueryEngine.md)
|
||||
- [QueryEngineTool](../../api/interfaces/QueryEngineTool.md)
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
# Metadata Filtering
|
||||
|
||||
Metadata filtering is a way to filter the documents that are returned by a query based on the metadata associated with the documents. This is useful when you want to filter the documents based on some metadata that is not part of the document text.
|
||||
|
||||
You can also check our multi-tenancy blog post to see how metadata filtering can be used in a multi-tenant environment. [https://blog.llamaindex.ai/building-multi-tenancy-rag-system-with-llamaindex-0d6ab4e0c44b] (the article uses the Python version of LlamaIndex, but the concepts are the same).
|
||||
|
||||
## Setup
|
||||
|
||||
Firstly if you haven't already, you need to install the `llamaindex` package:
|
||||
|
||||
```bash
|
||||
pnpm i llamaindex
|
||||
```
|
||||
|
||||
Then you can import the necessary modules from `llamaindex`:
|
||||
|
||||
```ts
|
||||
import {
|
||||
ChromaVectorStore,
|
||||
Document,
|
||||
VectorStoreIndex,
|
||||
storageContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
|
||||
const collectionName = "dog_colors";
|
||||
```
|
||||
|
||||
## Creating documents with metadata
|
||||
|
||||
You can create documents with metadata using the `Document` class:
|
||||
|
||||
```ts
|
||||
const docs = [
|
||||
new Document({
|
||||
text: "The dog is brown",
|
||||
metadata: {
|
||||
color: "brown",
|
||||
dogId: "1",
|
||||
},
|
||||
}),
|
||||
new Document({
|
||||
text: "The dog is red",
|
||||
metadata: {
|
||||
color: "red",
|
||||
dogId: "2",
|
||||
},
|
||||
}),
|
||||
];
|
||||
```
|
||||
|
||||
## Creating a ChromaDB vector store
|
||||
|
||||
You can create a `ChromaVectorStore` to store the documents:
|
||||
|
||||
```ts
|
||||
const chromaVS = new ChromaVectorStore({ collectionName });
|
||||
const serviceContext = await storageContextFromDefaults({
|
||||
vectorStore: chromaVS,
|
||||
});
|
||||
|
||||
const index = await VectorStoreIndex.fromDocuments(docs, {
|
||||
storageContext: serviceContext,
|
||||
});
|
||||
```
|
||||
|
||||
## Querying the index with metadata filtering
|
||||
|
||||
Now you can query the index with metadata filtering using the `preFilters` option:
|
||||
|
||||
```ts
|
||||
const queryEngine = index.asQueryEngine({
|
||||
preFilters: {
|
||||
filters: [
|
||||
{
|
||||
key: "dogId",
|
||||
value: "2",
|
||||
filterType: "ExactMatch",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const response = await queryEngine.query({
|
||||
query: "What is the color of the dog?",
|
||||
});
|
||||
|
||||
console.log(response.toString());
|
||||
```
|
||||
|
||||
## Full Code
|
||||
|
||||
```ts
|
||||
import {
|
||||
ChromaVectorStore,
|
||||
Document,
|
||||
VectorStoreIndex,
|
||||
storageContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
|
||||
const collectionName = "dog_colors";
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const docs = [
|
||||
new Document({
|
||||
text: "The dog is brown",
|
||||
metadata: {
|
||||
color: "brown",
|
||||
dogId: "1",
|
||||
},
|
||||
}),
|
||||
new Document({
|
||||
text: "The dog is red",
|
||||
metadata: {
|
||||
color: "red",
|
||||
dogId: "2",
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
console.log("Creating ChromaDB vector store");
|
||||
const chromaVS = new ChromaVectorStore({ collectionName });
|
||||
const ctx = await storageContextFromDefaults({ vectorStore: chromaVS });
|
||||
|
||||
console.log("Embedding documents and adding to index");
|
||||
const index = await VectorStoreIndex.fromDocuments(docs, {
|
||||
storageContext: ctx,
|
||||
});
|
||||
|
||||
console.log("Querying index");
|
||||
const queryEngine = index.asQueryEngine({
|
||||
preFilters: {
|
||||
filters: [
|
||||
{
|
||||
key: "dogId",
|
||||
value: "2",
|
||||
filterType: "ExactMatch",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const response = await queryEngine.query({
|
||||
query: "What is the color of the dog?",
|
||||
});
|
||||
console.log(response.toString());
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
@@ -6,6 +6,6 @@
|
||||
"composite": true,
|
||||
"incremental": true,
|
||||
"outDir": "./lib",
|
||||
"tsBuildInfoFile": "./lib/.tsbuildinfo",
|
||||
},
|
||||
"tsBuildInfoFile": "./lib/.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { FunctionTool, OpenAIAgent } from "llamaindex";
|
||||
|
||||
// Define a function to sum two numbers
|
||||
function sumNumbers({ a, b }: { a: number; b: number }): number {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
// Define a function to divide two numbers
|
||||
function divideNumbers({ a, b }: { a: number; b: number }): number {
|
||||
return a / b;
|
||||
}
|
||||
|
||||
// Define the parameters of the sum function as a JSON schema
|
||||
const sumJSON = {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "The first number",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "The second number",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
};
|
||||
|
||||
const divideJSON = {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "The dividend a to divide",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "The divisor b to divide by",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
};
|
||||
|
||||
async function main() {
|
||||
// Create a function tool from the sum function
|
||||
const functionTool = new FunctionTool(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, {
|
||||
name: "divideNumbers",
|
||||
description: "Use this function to divide two numbers",
|
||||
parameters: divideJSON,
|
||||
});
|
||||
|
||||
// Create an OpenAIAgent with the function tools
|
||||
const agent = new OpenAIAgent({
|
||||
tools: [functionTool, functionTool2],
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
// Chat with the agent
|
||||
const response = await agent.chat({
|
||||
message: "How much is 5 + 5? then divide by 2",
|
||||
});
|
||||
|
||||
// Print the response
|
||||
console.log(String(response));
|
||||
}
|
||||
|
||||
main().then(() => {
|
||||
console.log("Done");
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
OpenAIAgent,
|
||||
QueryEngineTool,
|
||||
SimpleDirectoryReader,
|
||||
VectorStoreIndex,
|
||||
} from "llamaindex";
|
||||
|
||||
async function main() {
|
||||
// Load the documents
|
||||
const documents = await new SimpleDirectoryReader().loadData({
|
||||
directoryPath: "node_modules/llamaindex/examples/",
|
||||
});
|
||||
|
||||
// Create a vector index from the documents
|
||||
const vectorIndex = await VectorStoreIndex.fromDocuments(documents);
|
||||
|
||||
// Create a query engine from the vector index
|
||||
const abramovQueryEngine = vectorIndex.asQueryEngine();
|
||||
|
||||
// Create a QueryEngineTool with the query engine
|
||||
const queryEngineTool = new QueryEngineTool({
|
||||
queryEngine: abramovQueryEngine,
|
||||
metadata: {
|
||||
name: "abramov_query_engine",
|
||||
description: "A query engine for the Abramov documents",
|
||||
},
|
||||
});
|
||||
|
||||
// Create an OpenAIAgent with the function tools
|
||||
const agent = new OpenAIAgent({
|
||||
tools: [queryEngineTool],
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
// Chat with the agent
|
||||
const response = await agent.chat({
|
||||
message: "What was his salary?",
|
||||
});
|
||||
|
||||
// Print the response
|
||||
console.log(String(response));
|
||||
}
|
||||
|
||||
main().then(() => {
|
||||
console.log("Done");
|
||||
});
|
||||
@@ -14,18 +14,27 @@ Here are two sample scripts which work well with the sample data in the Astra Po
|
||||
|
||||
- `ASTRA_DB_APPLICATION_TOKEN`: The generated app token for your Astra database
|
||||
- `ASTRA_DB_ENDPOINT`: The API endpoint for your Astra database
|
||||
- `ASTRA_DB_NAMESPACE`: (Optional) The namespace where your collection is stored defaults to `default_keyspace`
|
||||
- `OPENAI_API_KEY`: Your OpenAI key
|
||||
|
||||
2. `cd` Into the `examples` directory
|
||||
3. run `npm i`
|
||||
|
||||
## Load the data
|
||||
## Example load and query
|
||||
|
||||
Loads and queries a simple vectorstore with some documents about Astra DB
|
||||
|
||||
run `ts-node astradb/example`
|
||||
|
||||
## Movie Reviews Example
|
||||
|
||||
### Load the data
|
||||
|
||||
This sample loads the same dataset of movie reviews as the Astra Portal sample dataset. (Feel free to load the data in your the Astra Data Explorer to compare)
|
||||
|
||||
run `ts-node astradb/load`
|
||||
|
||||
## Use RAG to Query the data
|
||||
### Use RAG to Query the data
|
||||
|
||||
Check out your data in the Astra Data Explorer and change the sample query as you see fit.
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
AstraDBVectorStore,
|
||||
Document,
|
||||
storageContextFromDefaults,
|
||||
VectorStoreIndex,
|
||||
} from "llamaindex";
|
||||
|
||||
const collectionName = "test_collection";
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const docs = [
|
||||
new Document({
|
||||
text: "AstraDB is built on Apache Cassandra",
|
||||
metadata: {
|
||||
id: 123,
|
||||
foo: "bar",
|
||||
},
|
||||
}),
|
||||
new Document({
|
||||
text: "AstraDB is a NoSQL DB",
|
||||
metadata: {
|
||||
id: 456,
|
||||
foo: "baz",
|
||||
},
|
||||
}),
|
||||
new Document({
|
||||
text: "AstraDB supports vector search",
|
||||
metadata: {
|
||||
id: 789,
|
||||
foo: "qux",
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const astraVS = new AstraDBVectorStore();
|
||||
await astraVS.create(collectionName, {
|
||||
vector: { dimension: 1536, metric: "cosine" },
|
||||
});
|
||||
await astraVS.connect(collectionName);
|
||||
|
||||
const ctx = await storageContextFromDefaults({ vectorStore: astraVS });
|
||||
const index = await VectorStoreIndex.fromDocuments(docs, {
|
||||
storageContext: ctx,
|
||||
});
|
||||
|
||||
const queryEngine = index.asQueryEngine();
|
||||
const response = await queryEngine.query({
|
||||
query: "Describe AstraDB.",
|
||||
});
|
||||
|
||||
console.log(response.toString());
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -10,9 +10,9 @@ const collectionName = "movie_reviews";
|
||||
async function main() {
|
||||
try {
|
||||
const reader = new PapaCSVReader(false);
|
||||
const docs = await reader.loadData("../data/movie_reviews.csv");
|
||||
const docs = await reader.loadData("./data/movie_reviews.csv");
|
||||
|
||||
const astraVS = new AstraDBVectorStore();
|
||||
const astraVS = new AstraDBVectorStore({ contentKey: "reviewtext" });
|
||||
await astraVS.create(collectionName, {
|
||||
vector: { dimension: 1536, metric: "cosine" },
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ const collectionName = "movie_reviews";
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const astraVS = new AstraDBVectorStore();
|
||||
const astraVS = new AstraDBVectorStore({ contentKey: "reviewtext" });
|
||||
await astraVS.connect(collectionName);
|
||||
|
||||
const ctx = serviceContextFromDefaults();
|
||||
@@ -19,7 +19,7 @@ async function main() {
|
||||
const queryEngine = await index.asQueryEngine({ retriever });
|
||||
|
||||
const results = await queryEngine.query({
|
||||
query: "What is the best reviewed movie?",
|
||||
query: 'How was "La Sapienza" reviewed?',
|
||||
});
|
||||
|
||||
console.log(results.response);
|
||||
|
||||
@@ -6,7 +6,7 @@ Export your OpenAI API Key using `export OPEN_API_KEY=insert your api key here`
|
||||
|
||||
If you haven't installed chromadb, run `pip install chromadb`. Start the server using `chroma run`.
|
||||
|
||||
Now, open a new terminal window and inside `examples`, run `pnpx ts-node chromadb/test.ts`.
|
||||
Now, open a new terminal window and inside `examples`, run `pnpm dlx ts-node chromadb/test.ts`.
|
||||
|
||||
Here's the output for the input query `Tell me about Godfrey Cheshire's rating of La Sapienza.`:
|
||||
|
||||
|
||||
+17
-10
@@ -1,4 +1,9 @@
|
||||
import { Document, SubQuestionQueryEngine, VectorStoreIndex } from "llamaindex";
|
||||
import {
|
||||
Document,
|
||||
QueryEngineTool,
|
||||
SubQuestionQueryEngine,
|
||||
VectorStoreIndex,
|
||||
} from "llamaindex";
|
||||
|
||||
import essay from "./essay";
|
||||
|
||||
@@ -6,16 +11,18 @@ import essay from "./essay";
|
||||
const document = new Document({ text: essay, id_: essay });
|
||||
const index = await VectorStoreIndex.fromDocuments([document]);
|
||||
|
||||
const queryEngine = SubQuestionQueryEngine.fromDefaults({
|
||||
queryEngineTools: [
|
||||
{
|
||||
queryEngine: index.asQueryEngine(),
|
||||
metadata: {
|
||||
name: "pg_essay",
|
||||
description: "Paul Graham essay on What I Worked On",
|
||||
},
|
||||
const queryEngineTools = [
|
||||
new QueryEngineTool({
|
||||
queryEngine: index.asQueryEngine(),
|
||||
metadata: {
|
||||
name: "pg_essay",
|
||||
description: "Paul Graham essay on What I Worked On",
|
||||
},
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const queryEngine = SubQuestionQueryEngine.fromDefaults({
|
||||
queryEngineTools,
|
||||
});
|
||||
|
||||
const response = await queryEngine.query({
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
"outDir": "./lib",
|
||||
"tsBuildInfoFile": "./lib/.tsbuildinfo",
|
||||
"incremental": true,
|
||||
"composite": true,
|
||||
"composite": true
|
||||
},
|
||||
"ts-node": {
|
||||
"files": true,
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
},
|
||||
"module": "commonjs"
|
||||
}
|
||||
},
|
||||
"include": ["./**/*.ts"],
|
||||
"include": ["./**/*.ts"]
|
||||
}
|
||||
|
||||
+7
-7
@@ -18,20 +18,20 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.27.1",
|
||||
"@turbo/gen": "^1.11.3",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@turbo/gen": "^1.12.2",
|
||||
"@types/jest": "^29.5.12",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"husky": "^9.0.6",
|
||||
"husky": "^9.0.10",
|
||||
"jest": "^29.7.0",
|
||||
"lint-staged": "^15.2.0",
|
||||
"prettier": "^3.2.4",
|
||||
"lint-staged": "^15.2.2",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"ts-jest": "^29.1.2",
|
||||
"turbo": "^1.11.3",
|
||||
"turbo": "^1.12.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"packageManager": "pnpm@8.14.3+sha256.2d0363bb6c314daa67087ef07743eea1ba2e2d360c835e8fec6b5575e4ed9484",
|
||||
"packageManager": "pnpm@8.15.1",
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"trim": "1.0.1",
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# llamaindex
|
||||
|
||||
## 0.1.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b6c1500: feat(embedBatchSize): add batching for embeddings
|
||||
- 6cc3a36: fix: update `VectorIndexRetriever` constructor parameters' type.
|
||||
- cd82947: feat(queryEngineTool): add query engine tool to agents
|
||||
|
||||
## 0.1.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 09464e6: add OpenAIAgent (thanks @EmanuelCampos)
|
||||
|
||||
## 0.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d903da6: easier prompt customization for SimpleResponseBuilder
|
||||
- ab9d941: fix(cyclic): remove cyclic structures from transform hash
|
||||
- 177b446: chore: improve extractors prompt
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+15
-20
@@ -1,26 +1,26 @@
|
||||
{
|
||||
"name": "llamaindex",
|
||||
"private": true,
|
||||
"version": "0.1.7",
|
||||
"version": "0.1.10",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.12.4",
|
||||
"@anthropic-ai/sdk": "^0.13.0",
|
||||
"@datastax/astra-db-ts": "^0.1.4",
|
||||
"@mistralai/mistralai": "^0.0.10",
|
||||
"@notionhq/client": "^2.2.14",
|
||||
"@pinecone-database/pinecone": "^1.1.3",
|
||||
"@qdrant/js-client-rest": "^1.7.0",
|
||||
"@xenova/transformers": "^2.14.1",
|
||||
"assemblyai": "^4.2.1",
|
||||
"@xenova/transformers": "^2.15.0",
|
||||
"assemblyai": "^4.2.2",
|
||||
"chromadb": "~1.7.3",
|
||||
"file-type": "^18.7.0",
|
||||
"js-tiktoken": "^1.0.8",
|
||||
"js-tiktoken": "^1.0.10",
|
||||
"lodash": "^4.17.21",
|
||||
"mammoth": "^1.6.0",
|
||||
"md-utils-ts": "^2.0.0",
|
||||
"mongodb": "^6.3.0",
|
||||
"notion-md-crawler": "^0.0.2",
|
||||
"openai": "^4.26.0",
|
||||
"openai": "^4.26.1",
|
||||
"papaparse": "^5.4.1",
|
||||
"pathe": "^1.1.2",
|
||||
"pdf2json": "^3.0.5",
|
||||
@@ -29,18 +29,18 @@
|
||||
"portkey-ai": "^0.1.16",
|
||||
"rake-modified": "^1.0.8",
|
||||
"replicate": "^0.25.2",
|
||||
"string-strip-html": "^13.4.5",
|
||||
"string-strip-html": "^13.4.6",
|
||||
"wink-nlp": "^1.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aws-crypto/sha256-js": "^5.2.0",
|
||||
"@types/edit-json-file": "^1.7.3",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/node": "^18.19.10",
|
||||
"@types/node": "^18.19.14",
|
||||
"@types/papaparse": "^5.3.14",
|
||||
"@types/pg": "^8.11.0",
|
||||
"bunchee": "^4.4.3",
|
||||
"bunchee": "^4.4.6",
|
||||
"edit-json-file": "^1.8.0",
|
||||
"madge": "^6.1.0",
|
||||
"typescript": "^5.3.3"
|
||||
@@ -118,11 +118,6 @@
|
||||
"import": "./dist/Response.mjs",
|
||||
"require": "./dist/Response.js"
|
||||
},
|
||||
"./Retriever": {
|
||||
"types": "./dist/Retriever.d.mts",
|
||||
"import": "./dist/Retriever.mjs",
|
||||
"require": "./dist/Retriever.js"
|
||||
},
|
||||
"./ServiceContext": {
|
||||
"types": "./dist/ServiceContext.d.mts",
|
||||
"import": "./dist/ServiceContext.mjs",
|
||||
@@ -133,10 +128,10 @@
|
||||
"import": "./dist/TextSplitter.mjs",
|
||||
"require": "./dist/TextSplitter.js"
|
||||
},
|
||||
"./Tool": {
|
||||
"types": "./dist/Tool.d.mts",
|
||||
"import": "./dist/Tool.mjs",
|
||||
"require": "./dist/Tool.js"
|
||||
"./tools": {
|
||||
"types": "./dist/tools.d.mts",
|
||||
"import": "./dist/tools.mjs",
|
||||
"require": "./dist/tools.js"
|
||||
},
|
||||
"./readers/AssemblyAIReader": {
|
||||
"types": "./dist/readers/AssemblyAIReader.d.mts",
|
||||
@@ -200,7 +195,7 @@
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "jest",
|
||||
"build": "NODE_OPTIONS=\"--max-old-space-size=8192\" bunchee",
|
||||
"build": "rm -rf ./dist && NODE_OPTIONS=\"--max-old-space-size=8192\" bunchee",
|
||||
"postbuild": "pnpm run copy && pnpm run modify-package-json",
|
||||
"copy": "cp -r package.json CHANGELOG.md ../../README.md ../../LICENSE examples src dist/",
|
||||
"modify-package-json": "node ./scripts/modify-package-json.mjs",
|
||||
|
||||
@@ -74,9 +74,6 @@ export class SubQuestionOutputParser
|
||||
{
|
||||
parse(output: string): StructuredOutput<SubQuestion[]> {
|
||||
const parsed = parseJsonMarkdown(output);
|
||||
|
||||
// TODO add zod validation
|
||||
|
||||
return { rawOutput: output, parsedOutput: parsed };
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export class Response {
|
||||
this.sourceNodes = sourceNodes || [];
|
||||
}
|
||||
|
||||
getFormattedSources() {
|
||||
protected _getFormattedSources() {
|
||||
throw new Error("Not implemented yet");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./openai/base";
|
||||
export * from "./openai/worker";
|
||||
@@ -0,0 +1,55 @@
|
||||
import { CallbackManager } from "../../callbacks/CallbackManager";
|
||||
import { ChatMessage, OpenAI } from "../../llm";
|
||||
import { ObjectRetriever } from "../../objects/base";
|
||||
import { BaseTool } from "../../types";
|
||||
import { AgentRunner } from "../runner/base";
|
||||
import { OpenAIAgentWorker } from "./worker";
|
||||
|
||||
type OpenAIAgentParams = {
|
||||
tools: BaseTool[];
|
||||
llm?: OpenAI;
|
||||
memory?: any;
|
||||
prefixMessages?: ChatMessage[];
|
||||
verbose?: boolean;
|
||||
maxFunctionCalls?: number;
|
||||
defaultToolChoice?: string;
|
||||
callbackManager?: CallbackManager;
|
||||
toolRetriever?: ObjectRetriever<BaseTool>;
|
||||
};
|
||||
|
||||
/**
|
||||
* An agent that uses OpenAI's API to generate text.
|
||||
*
|
||||
* @category OpenAI
|
||||
*/
|
||||
export class OpenAIAgent extends AgentRunner {
|
||||
constructor({
|
||||
tools,
|
||||
llm,
|
||||
memory,
|
||||
prefixMessages,
|
||||
verbose,
|
||||
maxFunctionCalls = 5,
|
||||
defaultToolChoice = "auto",
|
||||
callbackManager,
|
||||
toolRetriever,
|
||||
}: OpenAIAgentParams) {
|
||||
const stepEngine = new OpenAIAgentWorker({
|
||||
tools,
|
||||
callbackManager,
|
||||
llm,
|
||||
prefixMessages,
|
||||
maxFunctionCalls,
|
||||
toolRetriever,
|
||||
verbose,
|
||||
});
|
||||
|
||||
super({
|
||||
agentWorker: stepEngine,
|
||||
memory,
|
||||
callbackManager,
|
||||
defaultToolChoice,
|
||||
chatHistory: prefixMessages,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export type OpenAIToolCall = ChatCompletionMessageToolCall;
|
||||
|
||||
export interface Function {
|
||||
arguments: string;
|
||||
name: string;
|
||||
type: "function";
|
||||
}
|
||||
|
||||
export interface ChatCompletionMessageToolCall {
|
||||
id: string;
|
||||
function: Function;
|
||||
type: "function";
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ToolMetadata } from "../../types";
|
||||
|
||||
export type OpenAIFunction = {
|
||||
type: "function";
|
||||
function: ToolMetadata;
|
||||
};
|
||||
|
||||
type OpenAiTool = {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: ToolMetadata["parameters"];
|
||||
};
|
||||
|
||||
export const toOpenAiTool = ({
|
||||
name,
|
||||
description,
|
||||
parameters,
|
||||
}: OpenAiTool): OpenAIFunction => {
|
||||
return {
|
||||
type: "function",
|
||||
function: {
|
||||
name: name,
|
||||
description: description,
|
||||
parameters,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,405 @@
|
||||
// Assuming that the necessary interfaces and classes (like BaseTool, OpenAI, ChatMessage, CallbackManager, etc.) are defined elsewhere
|
||||
|
||||
import { CallbackManager } from "../../callbacks/CallbackManager";
|
||||
import { AgentChatResponse, ChatResponseMode } from "../../engines/chat";
|
||||
import { randomUUID } from "../../env";
|
||||
import {
|
||||
ChatMessage,
|
||||
ChatResponse,
|
||||
ChatResponseChunk,
|
||||
OpenAI,
|
||||
} from "../../llm";
|
||||
import { ChatMemoryBuffer } from "../../memory/ChatMemoryBuffer";
|
||||
import { ObjectRetriever } from "../../objects/base";
|
||||
import { ToolOutput } from "../../tools/types";
|
||||
import { callToolWithErrorHandling } from "../../tools/utils";
|
||||
import { BaseTool } from "../../types";
|
||||
import { AgentWorker, Task, TaskStep, TaskStepOutput } from "../types";
|
||||
import { addUserStepToMemory, getFunctionByName } from "../utils";
|
||||
import { OpenAIToolCall } from "./types/chat";
|
||||
import { toOpenAiTool } from "./utils";
|
||||
|
||||
const DEFAULT_MAX_FUNCTION_CALLS = 5;
|
||||
|
||||
/**
|
||||
* Call function.
|
||||
* @param tools: tools
|
||||
* @param toolCall: tool call
|
||||
* @param verbose: verbose
|
||||
* @returns: void
|
||||
*/
|
||||
async function callFunction(
|
||||
tools: BaseTool[],
|
||||
toolCall: OpenAIToolCall,
|
||||
verbose: boolean = false,
|
||||
): Promise<[ChatMessage, ToolOutput]> {
|
||||
const id_ = toolCall.id;
|
||||
const functionCall = toolCall.function;
|
||||
const name = toolCall.function.name;
|
||||
const argumentsStr = toolCall.function.arguments;
|
||||
|
||||
if (verbose) {
|
||||
console.log("=== Calling Function ===");
|
||||
console.log(`Calling function: ${name} with args: ${argumentsStr}`);
|
||||
}
|
||||
|
||||
const tool = getFunctionByName(tools, name);
|
||||
const argumentDict = JSON.parse(argumentsStr);
|
||||
|
||||
// Call tool
|
||||
// Use default error message
|
||||
const output = await callToolWithErrorHandling(tool, argumentDict, null);
|
||||
|
||||
if (verbose) {
|
||||
console.log(`Got output ${output}`);
|
||||
console.log("==========================");
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
content: String(output),
|
||||
role: "tool",
|
||||
additionalKwargs: {
|
||||
name,
|
||||
tool_call_id: id_,
|
||||
},
|
||||
},
|
||||
output,
|
||||
];
|
||||
}
|
||||
|
||||
type OpenAIAgentWorkerParams = {
|
||||
tools: BaseTool[];
|
||||
llm?: OpenAI;
|
||||
prefixMessages?: ChatMessage[];
|
||||
verbose?: boolean;
|
||||
maxFunctionCalls?: number;
|
||||
callbackManager?: CallbackManager | undefined;
|
||||
toolRetriever?: ObjectRetriever<BaseTool>;
|
||||
};
|
||||
|
||||
type CallFunctionOutput = {
|
||||
message: ChatMessage;
|
||||
toolOutput: ToolOutput;
|
||||
};
|
||||
|
||||
/**
|
||||
* OpenAI agent worker.
|
||||
* This class is responsible for running the agent.
|
||||
*/
|
||||
export class OpenAIAgentWorker implements AgentWorker {
|
||||
private _llm: OpenAI;
|
||||
private _verbose: boolean;
|
||||
private _maxFunctionCalls: number;
|
||||
|
||||
public prefixMessages: ChatMessage[];
|
||||
public callbackManager: CallbackManager | undefined;
|
||||
|
||||
private _getTools: (input: string) => BaseTool[];
|
||||
|
||||
/**
|
||||
* Initialize.
|
||||
*/
|
||||
constructor({
|
||||
tools,
|
||||
llm,
|
||||
prefixMessages,
|
||||
verbose,
|
||||
maxFunctionCalls = DEFAULT_MAX_FUNCTION_CALLS,
|
||||
callbackManager,
|
||||
toolRetriever,
|
||||
}: OpenAIAgentWorkerParams) {
|
||||
this._llm = llm ?? new OpenAI({ model: "gpt-3.5-turbo-0613" });
|
||||
this._verbose = verbose || false;
|
||||
this._maxFunctionCalls = maxFunctionCalls;
|
||||
this.prefixMessages = prefixMessages || [];
|
||||
this.callbackManager = callbackManager || this._llm.callbackManager;
|
||||
|
||||
if (tools.length > 0 && toolRetriever) {
|
||||
throw new Error("Cannot specify both tools and tool_retriever");
|
||||
} else if (tools.length > 0) {
|
||||
this._getTools = () => tools;
|
||||
} else if (toolRetriever) {
|
||||
// @ts-ignore
|
||||
this._getTools = (message: string) => toolRetriever.retrieve(message);
|
||||
} else {
|
||||
this._getTools = () => [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all messages.
|
||||
* @param task: task
|
||||
* @returns: messages
|
||||
*/
|
||||
public getAllMessages(task: Task): ChatMessage[] {
|
||||
return [
|
||||
...this.prefixMessages,
|
||||
...task.memory.get(),
|
||||
...task.extraState.newMemory.get(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest tool calls.
|
||||
* @param task: task
|
||||
* @returns: tool calls
|
||||
*/
|
||||
public getLatestToolCalls(task: Task): OpenAIToolCall[] | null {
|
||||
const chatHistory: ChatMessage[] = task.extraState.newMemory.getAll();
|
||||
|
||||
if (chatHistory.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return chatHistory[chatHistory.length - 1].additionalKwargs?.toolCalls;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param task
|
||||
* @param openaiTools
|
||||
* @param toolChoice
|
||||
* @returns
|
||||
*/
|
||||
private _getLlmChatKwargs(
|
||||
task: Task,
|
||||
openaiTools: { [key: string]: any }[],
|
||||
toolChoice: string | { [key: string]: any } = "auto",
|
||||
): { [key: string]: any } {
|
||||
const llmChatKwargs: { [key: string]: any } = {
|
||||
messages: this.getAllMessages(task),
|
||||
};
|
||||
|
||||
if (openaiTools.length > 0) {
|
||||
llmChatKwargs.tools = openaiTools;
|
||||
llmChatKwargs.toolChoice = toolChoice;
|
||||
}
|
||||
|
||||
return llmChatKwargs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process message.
|
||||
* @param task: task
|
||||
* @param chatResponse: chat response
|
||||
* @returns: agent chat response
|
||||
*/
|
||||
private _processMessage(
|
||||
task: Task,
|
||||
chatResponse: ChatResponse,
|
||||
): AgentChatResponse | AsyncIterable<ChatResponseChunk> {
|
||||
const aiMessage = chatResponse.message;
|
||||
task.extraState.newMemory.put(aiMessage);
|
||||
return new AgentChatResponse(aiMessage.content, task.extraState.sources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get agent response.
|
||||
* @param task: task
|
||||
* @param mode: mode
|
||||
* @param llmChatKwargs: llm chat kwargs
|
||||
* @returns: agent chat response
|
||||
*/
|
||||
private async _getAgentResponse(
|
||||
task: Task,
|
||||
mode: ChatResponseMode,
|
||||
llmChatKwargs: any,
|
||||
): Promise<AgentChatResponse> {
|
||||
if (mode === ChatResponseMode.WAIT) {
|
||||
const chatResponse = (await this._llm.chat({
|
||||
stream: false,
|
||||
...llmChatKwargs,
|
||||
})) as unknown as ChatResponse;
|
||||
|
||||
return this._processMessage(task, chatResponse) as AgentChatResponse;
|
||||
} else {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call function.
|
||||
* @param tools: tools
|
||||
* @param toolCall: tool call
|
||||
* @param memory: memory
|
||||
* @param sources: sources
|
||||
* @returns: void
|
||||
*/
|
||||
async callFunction(
|
||||
tools: BaseTool[],
|
||||
toolCall: OpenAIToolCall,
|
||||
): Promise<CallFunctionOutput> {
|
||||
const functionCall = toolCall.function;
|
||||
|
||||
if (!functionCall) {
|
||||
throw new Error("Invalid tool_call object");
|
||||
}
|
||||
|
||||
const functionMessage = await callFunction(tools, toolCall, this._verbose);
|
||||
|
||||
const message = functionMessage[0];
|
||||
const toolOutput = functionMessage[1];
|
||||
|
||||
return {
|
||||
message,
|
||||
toolOutput,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize step.
|
||||
* @param task: task
|
||||
* @param kwargs: kwargs
|
||||
* @returns: task step
|
||||
*/
|
||||
initializeStep(task: Task, kwargs?: any): TaskStep {
|
||||
const sources: ToolOutput[] = [];
|
||||
|
||||
const newMemory = new ChatMemoryBuffer();
|
||||
|
||||
const taskState = {
|
||||
sources,
|
||||
nFunctionCalls: 0,
|
||||
newMemory,
|
||||
};
|
||||
|
||||
task.extraState = {
|
||||
...task.extraState,
|
||||
...taskState,
|
||||
};
|
||||
|
||||
return new TaskStep(task.taskId, randomUUID(), task.input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should continue.
|
||||
* @param toolCalls: tool calls
|
||||
* @param nFunctionCalls: number of function calls
|
||||
* @returns: boolean
|
||||
*/
|
||||
private _shouldContinue(
|
||||
toolCalls: OpenAIToolCall[] | null,
|
||||
nFunctionCalls: number,
|
||||
): boolean {
|
||||
if (nFunctionCalls > this._maxFunctionCalls) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (toolCalls?.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tools.
|
||||
* @param input: input
|
||||
* @returns: tools
|
||||
*/
|
||||
getTools(input: string): BaseTool[] {
|
||||
return this._getTools(input);
|
||||
}
|
||||
|
||||
private async _runStep(
|
||||
step: TaskStep,
|
||||
task: Task,
|
||||
mode: ChatResponseMode = ChatResponseMode.WAIT,
|
||||
toolChoice: string | { [key: string]: any } = "auto",
|
||||
): Promise<TaskStepOutput> {
|
||||
const tools = this.getTools(task.input);
|
||||
|
||||
if (step.input) {
|
||||
addUserStepToMemory(step, task.extraState.newMemory, this._verbose);
|
||||
}
|
||||
|
||||
const openaiTools = tools.map((tool) =>
|
||||
toOpenAiTool({
|
||||
name: tool.metadata.name,
|
||||
description: tool.metadata.description,
|
||||
parameters: tool.metadata.parameters,
|
||||
}),
|
||||
);
|
||||
|
||||
const llmChatKwargs = this._getLlmChatKwargs(task, openaiTools, toolChoice);
|
||||
|
||||
const agentChatResponse = await this._getAgentResponse(
|
||||
task,
|
||||
mode,
|
||||
llmChatKwargs,
|
||||
);
|
||||
|
||||
const latestToolCalls = this.getLatestToolCalls(task) || [];
|
||||
|
||||
let isDone: boolean;
|
||||
let newSteps: TaskStep[] = [];
|
||||
|
||||
if (
|
||||
!this._shouldContinue(latestToolCalls, task.extraState.nFunctionCalls)
|
||||
) {
|
||||
isDone = true;
|
||||
newSteps = [];
|
||||
} else {
|
||||
isDone = false;
|
||||
for (const toolCall of latestToolCalls) {
|
||||
const { message, toolOutput } = await this.callFunction(
|
||||
tools,
|
||||
toolCall,
|
||||
);
|
||||
|
||||
task.extraState.sources.push(toolOutput);
|
||||
task.extraState.newMemory.put(message);
|
||||
|
||||
task.extraState.nFunctionCalls += 1;
|
||||
}
|
||||
|
||||
newSteps = [step.getNextStep(randomUUID(), undefined)];
|
||||
}
|
||||
|
||||
return new TaskStepOutput(agentChatResponse, step, newSteps, isDone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run step.
|
||||
* @param step: step
|
||||
* @param task: task
|
||||
* @param kwargs: kwargs
|
||||
* @returns: task step output
|
||||
*/
|
||||
async runStep(
|
||||
step: TaskStep,
|
||||
task: Task,
|
||||
kwargs?: any,
|
||||
): Promise<TaskStepOutput> {
|
||||
const toolChoice = kwargs?.toolChoice || "auto";
|
||||
return this._runStep(step, task, ChatResponseMode.WAIT, toolChoice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream step.
|
||||
* @param step: step
|
||||
* @param task: task
|
||||
* @param kwargs: kwargs
|
||||
* @returns: task step output
|
||||
*/
|
||||
async streamStep(
|
||||
step: TaskStep,
|
||||
task: Task,
|
||||
kwargs?: any,
|
||||
): Promise<TaskStepOutput> {
|
||||
const toolChoice = kwargs?.toolChoice || "auto";
|
||||
return this._runStep(step, task, ChatResponseMode.STREAM, toolChoice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize task.
|
||||
* @param task: task
|
||||
* @param kwargs: kwargs
|
||||
* @returns: void
|
||||
*/
|
||||
finalizeTask(task: Task, kwargs?: any): void {
|
||||
task.memory.set(task.memory.get().concat(task.extraState.newMemory.get()));
|
||||
task.extraState.newMemory.reset();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import { CallbackManager } from "../../callbacks/CallbackManager";
|
||||
import {
|
||||
AgentChatResponse,
|
||||
ChatEngineAgentParams,
|
||||
ChatResponseMode,
|
||||
} from "../../engines/chat";
|
||||
import { ChatMessage, LLM } from "../../llm";
|
||||
import { ChatMemoryBuffer } from "../../memory/ChatMemoryBuffer";
|
||||
import { BaseMemory } from "../../memory/types";
|
||||
import { AgentWorker, Task, TaskStep, TaskStepOutput } from "../types";
|
||||
import { AgentState, BaseAgentRunner, TaskState } from "./types";
|
||||
|
||||
const validateStepFromArgs = (
|
||||
taskId: string,
|
||||
input: string,
|
||||
step?: any,
|
||||
kwargs?: any,
|
||||
): TaskStep | undefined => {
|
||||
if (step) {
|
||||
if (input) {
|
||||
throw new Error("Cannot specify both `step` and `input`");
|
||||
}
|
||||
return step;
|
||||
} else {
|
||||
return new TaskStep(taskId, step, input, kwargs);
|
||||
}
|
||||
};
|
||||
|
||||
type AgentRunnerParams = {
|
||||
agentWorker: AgentWorker;
|
||||
chatHistory?: ChatMessage[];
|
||||
state?: AgentState;
|
||||
memory?: BaseMemory;
|
||||
llm?: LLM;
|
||||
callbackManager?: CallbackManager;
|
||||
initTaskStateKwargs?: Record<string, any>;
|
||||
deleteTaskOnFinish?: boolean;
|
||||
defaultToolChoice?: string;
|
||||
};
|
||||
|
||||
export class AgentRunner extends BaseAgentRunner {
|
||||
agentWorker: AgentWorker;
|
||||
state: AgentState;
|
||||
memory: BaseMemory;
|
||||
callbackManager: CallbackManager;
|
||||
initTaskStateKwargs: Record<string, any>;
|
||||
deleteTaskOnFinish: boolean;
|
||||
defaultToolChoice: string;
|
||||
|
||||
/**
|
||||
* Creates an AgentRunner.
|
||||
*/
|
||||
constructor(params: AgentRunnerParams) {
|
||||
super();
|
||||
|
||||
this.agentWorker = params.agentWorker;
|
||||
this.state = params.state ?? new AgentState();
|
||||
this.memory =
|
||||
params.memory ??
|
||||
new ChatMemoryBuffer({
|
||||
chatHistory: params.chatHistory,
|
||||
});
|
||||
this.callbackManager = params.callbackManager ?? new CallbackManager();
|
||||
this.initTaskStateKwargs = params.initTaskStateKwargs ?? {};
|
||||
this.deleteTaskOnFinish = params.deleteTaskOnFinish ?? false;
|
||||
this.defaultToolChoice = params.defaultToolChoice ?? "auto";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a task.
|
||||
* @param input
|
||||
* @param kwargs
|
||||
*/
|
||||
createTask(input: string, kwargs?: any): Task {
|
||||
let extraState;
|
||||
|
||||
if (!this.initTaskStateKwargs) {
|
||||
if (kwargs && "extraState" in kwargs) {
|
||||
if (extraState) {
|
||||
delete extraState["extraState"];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (kwargs && "extraState" in kwargs) {
|
||||
throw new Error(
|
||||
"Cannot specify both `extraState` and `initTaskStateKwargs`",
|
||||
);
|
||||
} else {
|
||||
extraState = this.initTaskStateKwargs;
|
||||
}
|
||||
}
|
||||
|
||||
const task = new Task({
|
||||
taskId: randomUUID(),
|
||||
input,
|
||||
memory: this.memory,
|
||||
extraState,
|
||||
...kwargs,
|
||||
});
|
||||
|
||||
const initialStep = this.agentWorker.initializeStep(task);
|
||||
|
||||
const taskState = new TaskState({
|
||||
task,
|
||||
stepQueue: [initialStep],
|
||||
});
|
||||
|
||||
this.state.taskDict[task.taskId] = taskState;
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the task.
|
||||
* @param taskId
|
||||
*/
|
||||
deleteTask(taskId: string): void {
|
||||
delete this.state.taskDict[taskId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of tasks.
|
||||
*/
|
||||
listTasks(): Task[] {
|
||||
return Object.values(this.state.taskDict).map(
|
||||
(taskState) => taskState.task,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the task.
|
||||
*/
|
||||
getTask(taskId: string): Task {
|
||||
return this.state.taskDict[taskId].task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the completed steps in the task.
|
||||
* @param taskId
|
||||
* @param kwargs
|
||||
*/
|
||||
getCompletedSteps(taskId: string): TaskStepOutput[] {
|
||||
return this.state.taskDict[taskId].completedSteps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next steps in the task.
|
||||
* @param taskId
|
||||
* @param kwargs
|
||||
*/
|
||||
getUpcomingSteps(taskId: string, kwargs: any): TaskStep[] {
|
||||
return this.state.taskDict[taskId].stepQueue;
|
||||
}
|
||||
|
||||
private async _runStep(
|
||||
taskId: string,
|
||||
step?: TaskStep,
|
||||
mode: ChatResponseMode = ChatResponseMode.WAIT,
|
||||
kwargs?: any,
|
||||
): Promise<TaskStepOutput> {
|
||||
const task = this.state.getTask(taskId);
|
||||
const curStep = step || this.state.getStepQueue(taskId).shift();
|
||||
|
||||
let curStepOutput;
|
||||
|
||||
if (!curStep) {
|
||||
throw new Error(`No step found for task ${taskId}`);
|
||||
}
|
||||
|
||||
if (mode === ChatResponseMode.WAIT) {
|
||||
curStepOutput = await this.agentWorker.runStep(curStep, task, kwargs);
|
||||
} else if (mode === ChatResponseMode.STREAM) {
|
||||
curStepOutput = await this.agentWorker.streamStep(curStep, task, kwargs);
|
||||
} else {
|
||||
throw new Error(`Invalid mode: ${mode}`);
|
||||
}
|
||||
|
||||
const nextSteps = curStepOutput.nextSteps;
|
||||
|
||||
this.state.addSteps(taskId, nextSteps);
|
||||
this.state.addCompletedStep(taskId, [curStepOutput]);
|
||||
|
||||
return curStepOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the next step in the task.
|
||||
* @param taskId
|
||||
* @param kwargs
|
||||
* @param step
|
||||
* @returns
|
||||
*/
|
||||
async runStep(
|
||||
taskId: string,
|
||||
input: string,
|
||||
step?: TaskStep,
|
||||
kwargs: any = {},
|
||||
): Promise<TaskStepOutput> {
|
||||
const curStep = validateStepFromArgs(taskId, input, step, kwargs);
|
||||
return this._runStep(taskId, curStep, ChatResponseMode.WAIT, kwargs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the step and returns the response.
|
||||
* @param taskId
|
||||
* @param input
|
||||
* @param step
|
||||
* @param kwargs
|
||||
*/
|
||||
async streamStep(
|
||||
taskId: string,
|
||||
input: string,
|
||||
step?: TaskStep,
|
||||
kwargs?: any,
|
||||
): Promise<TaskStepOutput> {
|
||||
const curStep = validateStepFromArgs(taskId, input, step, kwargs);
|
||||
return this._runStep(taskId, curStep, ChatResponseMode.STREAM, kwargs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the response and returns it.
|
||||
* @param taskId
|
||||
* @param kwargs
|
||||
* @param stepOutput
|
||||
* @returns
|
||||
*/
|
||||
async finalizeResponse(
|
||||
taskId: string,
|
||||
stepOutput: TaskStepOutput,
|
||||
kwargs?: any,
|
||||
): Promise<AgentChatResponse> {
|
||||
if (!stepOutput) {
|
||||
stepOutput =
|
||||
this.getCompletedSteps(taskId)[
|
||||
this.getCompletedSteps(taskId).length - 1
|
||||
];
|
||||
}
|
||||
if (!stepOutput.isLast) {
|
||||
throw new Error(
|
||||
"finalizeResponse can only be called on the last step output",
|
||||
);
|
||||
}
|
||||
|
||||
if (!(stepOutput.output instanceof AgentChatResponse)) {
|
||||
throw new Error(
|
||||
`When \`isLast\` is True, cur_step_output.output must be AGENT_CHAT_RESPONSE_TYPE: ${stepOutput.output}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.agentWorker.finalizeTask(this.getTask(taskId), kwargs);
|
||||
|
||||
if (this.deleteTaskOnFinish) {
|
||||
this.deleteTask(taskId);
|
||||
}
|
||||
|
||||
return stepOutput.output;
|
||||
}
|
||||
|
||||
protected async _chat({
|
||||
message,
|
||||
toolChoice,
|
||||
}: ChatEngineAgentParams & { mode: ChatResponseMode }) {
|
||||
const task = this.createTask(message as string);
|
||||
|
||||
let resultOutput;
|
||||
|
||||
while (true) {
|
||||
const curStepOutput = await this._runStep(
|
||||
task.taskId,
|
||||
undefined,
|
||||
ChatResponseMode.WAIT,
|
||||
{
|
||||
toolChoice,
|
||||
},
|
||||
);
|
||||
|
||||
if (curStepOutput.isLast) {
|
||||
resultOutput = curStepOutput;
|
||||
break;
|
||||
}
|
||||
|
||||
toolChoice = "auto";
|
||||
}
|
||||
|
||||
return this.finalizeResponse(task.taskId, resultOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the LLM and returns the response.
|
||||
* @param message
|
||||
* @param chatHistory
|
||||
* @param toolChoice
|
||||
* @returns
|
||||
*/
|
||||
public async chat({
|
||||
message,
|
||||
chatHistory,
|
||||
toolChoice,
|
||||
}: ChatEngineAgentParams): Promise<AgentChatResponse> {
|
||||
if (!toolChoice) {
|
||||
toolChoice = this.defaultToolChoice;
|
||||
}
|
||||
|
||||
const chatResponse = await this._chat({
|
||||
message,
|
||||
chatHistory,
|
||||
toolChoice,
|
||||
mode: ChatResponseMode.WAIT,
|
||||
});
|
||||
|
||||
return chatResponse;
|
||||
}
|
||||
|
||||
protected _getPromptModules(): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected _getPrompts(): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the agent.
|
||||
*/
|
||||
reset(): void {
|
||||
this.state = new AgentState();
|
||||
}
|
||||
|
||||
getCompletedStep(
|
||||
taskId: string,
|
||||
stepId: string,
|
||||
kwargs: any,
|
||||
): TaskStepOutput {
|
||||
const completedSteps = this.getCompletedSteps(taskId);
|
||||
for (const stepOutput of completedSteps) {
|
||||
if (stepOutput.taskStep.stepId === stepId) {
|
||||
return stepOutput;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Step ${stepId} not found in task ${taskId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undoes the step.
|
||||
* @param taskId
|
||||
*/
|
||||
undoStep(taskId: string): void {}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { AgentChatResponse } from "../../engines/chat";
|
||||
import { BaseAgent, Task, TaskStep, TaskStepOutput } from "../types";
|
||||
|
||||
export class TaskState {
|
||||
task!: Task;
|
||||
stepQueue!: TaskStep[];
|
||||
completedSteps!: TaskStepOutput[];
|
||||
|
||||
constructor(init?: Partial<TaskState>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseAgentRunner extends BaseAgent {
|
||||
constructor(init?: Partial<BaseAgentRunner>) {
|
||||
super();
|
||||
}
|
||||
|
||||
abstract createTask(input: string, kwargs: any): Task;
|
||||
abstract deleteTask(taskId: string): void;
|
||||
abstract getTask(taskId: string, kwargs: any): Task;
|
||||
abstract listTasks(kwargs: any): Task[];
|
||||
abstract getUpcomingSteps(taskId: string, kwargs: any): TaskStep[];
|
||||
abstract getCompletedSteps(taskId: string, kwargs: any): TaskStepOutput[];
|
||||
|
||||
getCompletedStep(
|
||||
taskId: string,
|
||||
stepId: string,
|
||||
kwargs: any,
|
||||
): TaskStepOutput {
|
||||
const completedSteps = this.getCompletedSteps(taskId, kwargs);
|
||||
for (const stepOutput of completedSteps) {
|
||||
if (stepOutput.taskStep.stepId === stepId) {
|
||||
return stepOutput;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Step ${stepId} not found in task ${taskId}`);
|
||||
}
|
||||
|
||||
abstract runStep(
|
||||
taskId: string,
|
||||
input: string,
|
||||
step: TaskStep,
|
||||
kwargs: any,
|
||||
): Promise<TaskStepOutput>;
|
||||
|
||||
abstract streamStep(
|
||||
taskId: string,
|
||||
input: string,
|
||||
step: TaskStep,
|
||||
kwargs?: any,
|
||||
): Promise<TaskStepOutput>;
|
||||
|
||||
abstract finalizeResponse(
|
||||
taskId: string,
|
||||
stepOutput: TaskStepOutput,
|
||||
kwargs?: any,
|
||||
): Promise<AgentChatResponse>;
|
||||
|
||||
abstract undoStep(taskId: string): void;
|
||||
}
|
||||
|
||||
export class AgentState {
|
||||
taskDict!: Record<string, TaskState>;
|
||||
|
||||
constructor(init?: Partial<AgentState>) {
|
||||
Object.assign(this, init);
|
||||
|
||||
if (!this.taskDict) {
|
||||
this.taskDict = {};
|
||||
}
|
||||
}
|
||||
|
||||
getTask(taskId: string): Task {
|
||||
return this.taskDict[taskId].task;
|
||||
}
|
||||
|
||||
getCompletedSteps(taskId: string): TaskStepOutput[] {
|
||||
return this.taskDict[taskId].completedSteps || [];
|
||||
}
|
||||
|
||||
getStepQueue(taskId: string): TaskStep[] {
|
||||
return this.taskDict[taskId].stepQueue || [];
|
||||
}
|
||||
|
||||
addSteps(taskId: string, steps: TaskStep[]): void {
|
||||
if (!this.taskDict[taskId].stepQueue) {
|
||||
this.taskDict[taskId].stepQueue = [];
|
||||
}
|
||||
|
||||
this.taskDict[taskId].stepQueue.push(...steps);
|
||||
}
|
||||
|
||||
addCompletedStep(taskId: string, stepOutputs: TaskStepOutput[]): void {
|
||||
if (!this.taskDict[taskId].completedSteps) {
|
||||
this.taskDict[taskId].completedSteps = [];
|
||||
}
|
||||
|
||||
this.taskDict[taskId].completedSteps.push(...stepOutputs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import { AgentChatResponse, ChatEngineAgentParams } from "../engines/chat";
|
||||
import { QueryEngineParamsNonStreaming } from "../types";
|
||||
|
||||
export interface AgentWorker {
|
||||
initializeStep(task: Task, kwargs?: any): TaskStep;
|
||||
runStep(step: TaskStep, task: Task, kwargs?: any): Promise<TaskStepOutput>;
|
||||
streamStep(step: TaskStep, task: Task, kwargs?: any): Promise<TaskStepOutput>;
|
||||
finalizeTask(task: Task, kwargs?: any): void;
|
||||
}
|
||||
|
||||
interface BaseChatEngine {
|
||||
chat(params: ChatEngineAgentParams): Promise<AgentChatResponse>;
|
||||
}
|
||||
|
||||
interface BaseQueryEngine {
|
||||
query(params: QueryEngineParamsNonStreaming): Promise<AgentChatResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
* BaseAgent is the base class for all agents.
|
||||
*/
|
||||
export abstract class BaseAgent implements BaseChatEngine, BaseQueryEngine {
|
||||
protected _getPrompts(): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected _getPromptModules(): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
abstract chat(params: ChatEngineAgentParams): Promise<AgentChatResponse>;
|
||||
abstract reset(): void;
|
||||
|
||||
/**
|
||||
* query is the main entrypoint for the agent. It takes a query and returns a response.
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
async query(
|
||||
params: QueryEngineParamsNonStreaming,
|
||||
): Promise<AgentChatResponse> {
|
||||
// Handle non-streaming query
|
||||
const agentResponse = await this.chat({
|
||||
message: params.query,
|
||||
chatHistory: [],
|
||||
});
|
||||
|
||||
return agentResponse;
|
||||
}
|
||||
}
|
||||
|
||||
type TaskParams = {
|
||||
taskId: string;
|
||||
input: string;
|
||||
memory: any;
|
||||
extraState: Record<string, any>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Task is a unit of work for the agent.
|
||||
* @param taskId: taskId
|
||||
*/
|
||||
export class Task {
|
||||
taskId: string;
|
||||
input: string;
|
||||
|
||||
memory: any;
|
||||
extraState: Record<string, any>;
|
||||
|
||||
constructor({ taskId, input, memory, extraState }: TaskParams) {
|
||||
this.taskId = taskId;
|
||||
this.input = input;
|
||||
this.memory = memory;
|
||||
this.extraState = extraState ?? {};
|
||||
}
|
||||
}
|
||||
|
||||
interface ITaskStep {
|
||||
taskId: string;
|
||||
stepId: string;
|
||||
input?: string | null;
|
||||
stepState: Record<string, any>;
|
||||
nextSteps: Record<string, TaskStep>;
|
||||
prevSteps: Record<string, TaskStep>;
|
||||
isReady: boolean;
|
||||
getNextStep(
|
||||
stepId: string,
|
||||
input?: string,
|
||||
stepState?: Record<string, any>,
|
||||
): TaskStep;
|
||||
linkStep(nextStep: TaskStep): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskStep is a unit of work for the agent.
|
||||
* @param taskId: taskId
|
||||
* @param stepId: stepId
|
||||
* @param input: input
|
||||
* @param stepState: stepState
|
||||
*/
|
||||
export class TaskStep implements ITaskStep {
|
||||
taskId: string;
|
||||
stepId: string;
|
||||
input?: string | null;
|
||||
stepState: Record<string, any> = {};
|
||||
nextSteps: Record<string, TaskStep> = {};
|
||||
prevSteps: Record<string, TaskStep> = {};
|
||||
isReady: boolean = true;
|
||||
|
||||
constructor(
|
||||
taskId: string,
|
||||
stepId: string,
|
||||
input?: string | null,
|
||||
stepState?: Record<string, any> | null,
|
||||
) {
|
||||
this.taskId = taskId;
|
||||
this.stepId = stepId;
|
||||
this.input = input;
|
||||
this.stepState = stepState ?? this.stepState;
|
||||
}
|
||||
|
||||
/*
|
||||
* getNextStep is a function that returns the next step.
|
||||
* @param stepId: stepId
|
||||
* @param input: input
|
||||
* @param stepState: stepState
|
||||
* @returns: TaskStep
|
||||
*/
|
||||
getNextStep(
|
||||
stepId: string,
|
||||
input?: string,
|
||||
stepState?: Record<string, unknown>,
|
||||
): TaskStep {
|
||||
return new TaskStep(
|
||||
this.taskId,
|
||||
stepId,
|
||||
input,
|
||||
stepState ?? this.stepState,
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* linkStep is a function that links the next step.
|
||||
* @param nextStep: nextStep
|
||||
* @returns: void
|
||||
*/
|
||||
linkStep(nextStep: TaskStep): void {
|
||||
this.nextSteps[nextStep.stepId] = nextStep;
|
||||
nextStep.prevSteps[this.stepId] = this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskStepOutput is a unit of work for the agent.
|
||||
* @param output: output
|
||||
* @param taskStep: taskStep
|
||||
* @param nextSteps: nextSteps
|
||||
* @param isLast: isLast
|
||||
*/
|
||||
export class TaskStepOutput {
|
||||
output: unknown;
|
||||
taskStep: TaskStep;
|
||||
nextSteps: TaskStep[];
|
||||
isLast: boolean;
|
||||
|
||||
constructor(
|
||||
output: unknown,
|
||||
taskStep: TaskStep,
|
||||
nextSteps: TaskStep[],
|
||||
isLast: boolean = false,
|
||||
) {
|
||||
this.output = output;
|
||||
this.taskStep = taskStep;
|
||||
this.nextSteps = nextSteps;
|
||||
this.isLast = isLast;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return String(this.output);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { ChatMessage } from "../llm";
|
||||
import { ChatMemoryBuffer } from "../memory/ChatMemoryBuffer";
|
||||
import { BaseTool } from "../types";
|
||||
import { TaskStep } from "./types";
|
||||
|
||||
/**
|
||||
* Adds the user's input to the memory.
|
||||
*
|
||||
* @param step - The step to add to the memory.
|
||||
* @param memory - The memory to add the step to.
|
||||
* @param verbose - Whether to print debug messages.
|
||||
*/
|
||||
export function addUserStepToMemory(
|
||||
step: TaskStep,
|
||||
memory: ChatMemoryBuffer,
|
||||
verbose: boolean = false,
|
||||
): void {
|
||||
if (!step.input) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userMessage: ChatMessage = {
|
||||
content: step.input,
|
||||
role: "user",
|
||||
};
|
||||
|
||||
memory.put(userMessage);
|
||||
|
||||
if (verbose) {
|
||||
console.log(`Added user message to memory!: ${userMessage.content}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get function by name.
|
||||
* @param tools: tools
|
||||
* @param name: name
|
||||
* @returns: tool
|
||||
*/
|
||||
export function getFunctionByName(tools: BaseTool[], name: string): BaseTool {
|
||||
const nameToTool: { [key: string]: BaseTool } = {};
|
||||
tools.forEach((tool) => {
|
||||
nameToTool[tool.metadata.name] = tool;
|
||||
});
|
||||
|
||||
if (!(name in nameToTool)) {
|
||||
throw new Error(`Tool with name ${name} not found`);
|
||||
}
|
||||
|
||||
return nameToTool[name];
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk";
|
||||
import { NodeWithScore } from "../Node";
|
||||
|
||||
/*
|
||||
@@ -39,14 +40,7 @@ export interface DefaultStreamToken {
|
||||
//OpenAI stream token schema is the default.
|
||||
//Note: Anthropic and Replicate also use similar token schemas.
|
||||
export type OpenAIStreamToken = DefaultStreamToken;
|
||||
export type AnthropicStreamToken = {
|
||||
completion: string;
|
||||
model: string;
|
||||
stop_reason: string | undefined;
|
||||
stop?: boolean | undefined;
|
||||
log_id?: string;
|
||||
};
|
||||
|
||||
export type AnthropicStreamToken = Anthropic.Completion;
|
||||
//
|
||||
//Callback Responses
|
||||
//
|
||||
|
||||
@@ -59,7 +59,9 @@ export class OpenAIEmbedding extends BaseEmbedding {
|
||||
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;
|
||||
|
||||
@@ -100,7 +102,9 @@ export class OpenAIEmbedding extends BaseEmbedding {
|
||||
}
|
||||
}
|
||||
|
||||
private async getOpenAIEmbedding(input: string) {
|
||||
private async getOpenAIEmbedding(
|
||||
input: string | string[],
|
||||
): Promise<number[]> {
|
||||
const { data } = await this.session.openai.embeddings.create({
|
||||
model: this.model,
|
||||
dimensions: this.dimensions, // only sent to OpenAI if set by user
|
||||
@@ -110,6 +114,11 @@ export class OpenAIEmbedding extends BaseEmbedding {
|
||||
return data[0].embedding;
|
||||
}
|
||||
|
||||
async getTextEmbeddings(texts: string[]): Promise<number[][]> {
|
||||
const embeddings = await this.getOpenAIEmbedding(texts);
|
||||
return Array(embeddings);
|
||||
}
|
||||
|
||||
async getTextEmbedding(text: string): Promise<number[]> {
|
||||
return this.getOpenAIEmbedding(text);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,11 @@ import { BaseNode, MetadataMode } from "../Node";
|
||||
import { TransformComponent } from "../ingestion";
|
||||
import { SimilarityType, similarity } from "./utils";
|
||||
|
||||
const DEFAULT_EMBED_BATCH_SIZE = 10;
|
||||
|
||||
export abstract class BaseEmbedding implements TransformComponent {
|
||||
embedBatchSize = DEFAULT_EMBED_BATCH_SIZE;
|
||||
|
||||
similarity(
|
||||
embedding1: number[],
|
||||
embedding2: number[],
|
||||
@@ -14,12 +18,66 @@ export abstract class BaseEmbedding implements TransformComponent {
|
||||
abstract getTextEmbedding(text: string): Promise<number[]>;
|
||||
abstract getQueryEmbedding(query: string): Promise<number[]>;
|
||||
|
||||
async transform(nodes: BaseNode[], _options?: any): Promise<BaseNode[]> {
|
||||
for (const node of nodes) {
|
||||
node.embedding = await this.getTextEmbedding(
|
||||
node.getContent(MetadataMode.EMBED),
|
||||
);
|
||||
/**
|
||||
* Get embeddings for a batch of texts
|
||||
* @param texts
|
||||
*/
|
||||
async getTextEmbeddings(texts: string[]): Promise<Array<number[]>> {
|
||||
const embeddings: number[][] = [];
|
||||
|
||||
for (const text of texts) {
|
||||
const embedding = await this.getTextEmbedding(text);
|
||||
embeddings.push(embedding);
|
||||
}
|
||||
|
||||
return embeddings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get embeddings for a batch of texts
|
||||
* @param texts
|
||||
* @param options
|
||||
*/
|
||||
async getTextEmbeddingsBatch(
|
||||
texts: string[],
|
||||
options?: {
|
||||
logProgress?: boolean;
|
||||
},
|
||||
): Promise<Array<number[]>> {
|
||||
const resultEmbeddings: Array<number[]> = [];
|
||||
const chunkSize = this.embedBatchSize;
|
||||
|
||||
const queue: string[] = texts;
|
||||
|
||||
const curBatch: string[] = [];
|
||||
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
curBatch.push(queue[i]);
|
||||
if (i == queue.length - 1 || curBatch.length == chunkSize) {
|
||||
const embeddings = await this.getTextEmbeddings(curBatch);
|
||||
|
||||
resultEmbeddings.push(...embeddings);
|
||||
|
||||
if (options?.logProgress) {
|
||||
console.log(`number[] progress: ${i} / ${queue.length}`);
|
||||
}
|
||||
|
||||
curBatch.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return resultEmbeddings;
|
||||
}
|
||||
|
||||
async transform(nodes: BaseNode[], _options?: any): Promise<BaseNode[]> {
|
||||
const texts = nodes.map((node) => node.getContent(MetadataMode.EMBED));
|
||||
|
||||
const embeddings = await this.getTextEmbeddingsBatch(texts);
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
nodes[i].embedding = embeddings[i];
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ChatHistory } from "../../ChatHistory";
|
||||
import { NodeWithScore } from "../../Node";
|
||||
import { BaseNode, NodeWithScore } from "../../Node";
|
||||
import { Response } from "../../Response";
|
||||
import { Event } from "../../callbacks/CallbackManager";
|
||||
import { ChatMessage } from "../../llm";
|
||||
import { MessageContent } from "../../llm/types";
|
||||
import { ToolOutput } from "../../tools/types";
|
||||
|
||||
/**
|
||||
* Represents the base parameters for ChatEngine.
|
||||
@@ -24,6 +25,10 @@ export interface ChatEngineParamsNonStreaming extends ChatEngineParamsBase {
|
||||
stream?: false | null;
|
||||
}
|
||||
|
||||
export interface ChatEngineAgentParams extends ChatEngineParamsBase {
|
||||
toolChoice?: string | Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A ChatEngine is used to handle back and forth chats between the application and the LLM.
|
||||
*/
|
||||
@@ -52,3 +57,32 @@ export interface Context {
|
||||
export interface ContextGenerator {
|
||||
generate(message: string, parentEvent?: Event): Promise<Context>;
|
||||
}
|
||||
|
||||
export enum ChatResponseMode {
|
||||
WAIT = "wait",
|
||||
STREAM = "stream",
|
||||
}
|
||||
|
||||
export class AgentChatResponse {
|
||||
response: string;
|
||||
sources: ToolOutput[];
|
||||
sourceNodes?: BaseNode[];
|
||||
|
||||
constructor(
|
||||
response: string,
|
||||
sources?: ToolOutput[],
|
||||
sourceNodes?: BaseNode[],
|
||||
) {
|
||||
this.response = response;
|
||||
this.sources = sources || [];
|
||||
this.sourceNodes = sourceNodes || [];
|
||||
}
|
||||
|
||||
protected _getFormattedSources() {
|
||||
throw new Error("Not implemented yet");
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.response ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
} from "../../synthesizers";
|
||||
import {
|
||||
BaseQueryEngine,
|
||||
BaseTool,
|
||||
QueryEngineParamsNonStreaming,
|
||||
QueryEngineParamsStreaming,
|
||||
QueryEngineTool,
|
||||
ToolMetadata,
|
||||
} from "../../types";
|
||||
import { BaseQuestionGenerator, SubQuestion } from "./types";
|
||||
@@ -27,28 +27,23 @@ import { BaseQuestionGenerator, SubQuestion } from "./types";
|
||||
export class SubQuestionQueryEngine implements BaseQueryEngine {
|
||||
responseSynthesizer: BaseSynthesizer;
|
||||
questionGen: BaseQuestionGenerator;
|
||||
queryEngines: Record<string, BaseQueryEngine>;
|
||||
queryEngines: BaseTool[];
|
||||
metadatas: ToolMetadata[];
|
||||
|
||||
constructor(init: {
|
||||
questionGen: BaseQuestionGenerator;
|
||||
responseSynthesizer: BaseSynthesizer;
|
||||
queryEngineTools: QueryEngineTool[];
|
||||
queryEngineTools: BaseTool[];
|
||||
}) {
|
||||
this.questionGen = init.questionGen;
|
||||
this.responseSynthesizer =
|
||||
init.responseSynthesizer ?? new ResponseSynthesizer();
|
||||
this.queryEngines = init.queryEngineTools.reduce<
|
||||
Record<string, BaseQueryEngine>
|
||||
>((acc, tool) => {
|
||||
acc[tool.metadata.name] = tool.queryEngine;
|
||||
return acc;
|
||||
}, {});
|
||||
this.queryEngines = init.queryEngineTools;
|
||||
this.metadatas = init.queryEngineTools.map((tool) => tool.metadata);
|
||||
}
|
||||
|
||||
static fromDefaults(init: {
|
||||
queryEngineTools: QueryEngineTool[];
|
||||
queryEngineTools: BaseTool[];
|
||||
questionGen?: BaseQuestionGenerator;
|
||||
responseSynthesizer?: BaseSynthesizer;
|
||||
serviceContext?: ServiceContext;
|
||||
@@ -122,13 +117,24 @@ export class SubQuestionQueryEngine implements BaseQueryEngine {
|
||||
): Promise<NodeWithScore | null> {
|
||||
try {
|
||||
const question = subQ.subQuestion;
|
||||
const queryEngine = this.queryEngines[subQ.toolName];
|
||||
|
||||
const response = await queryEngine.query({
|
||||
const queryEngine = this.queryEngines.find(
|
||||
(tool) => tool.metadata.name === subQ.toolName,
|
||||
);
|
||||
|
||||
if (!queryEngine) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const responseText = await queryEngine?.call?.({
|
||||
query: question,
|
||||
parentEvent,
|
||||
});
|
||||
const responseText = response.response;
|
||||
|
||||
if (!responseText) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nodeText = `Sub question: ${question}\nResponse: ${responseText}`;
|
||||
const node = new TextNode({ text: nodeText });
|
||||
return { node, score: 0 };
|
||||
|
||||
@@ -9,6 +9,7 @@ export * from "./Response";
|
||||
export * from "./Retriever";
|
||||
export * from "./ServiceContext";
|
||||
export * from "./TextSplitter";
|
||||
export * from "./agent";
|
||||
export * from "./callbacks/CallbackManager";
|
||||
export * from "./constants";
|
||||
export * from "./embeddings";
|
||||
@@ -33,4 +34,4 @@ export * from "./readers/base";
|
||||
export * from "./selectors";
|
||||
export * from "./storage";
|
||||
export * from "./synthesizers";
|
||||
export type * from "./types";
|
||||
export * from "./tools";
|
||||
|
||||
@@ -17,6 +17,12 @@ import { VectorStoreIndex } from "./VectorStoreIndex";
|
||||
* VectorIndexRetriever retrieves nodes from a VectorIndex.
|
||||
*/
|
||||
|
||||
export type VectorIndexRetrieverOptions = {
|
||||
index: VectorStoreIndex;
|
||||
similarityTopK?: number;
|
||||
imageSimilarityTopK?: number;
|
||||
};
|
||||
|
||||
export class VectorIndexRetriever implements BaseRetriever {
|
||||
index: VectorStoreIndex;
|
||||
similarityTopK: number;
|
||||
@@ -27,11 +33,7 @@ export class VectorIndexRetriever implements BaseRetriever {
|
||||
index,
|
||||
similarityTopK,
|
||||
imageSimilarityTopK,
|
||||
}: {
|
||||
index: VectorStoreIndex;
|
||||
similarityTopK?: number;
|
||||
imageSimilarityTopK?: number;
|
||||
}) {
|
||||
}: VectorIndexRetrieverOptions) {
|
||||
this.index = index;
|
||||
this.serviceContext = this.index.serviceContext;
|
||||
this.similarityTopK = similarityTopK ?? DEFAULT_SIMILARITY_TOP_K;
|
||||
|
||||
@@ -34,7 +34,10 @@ import {
|
||||
IndexDict,
|
||||
IndexStructType,
|
||||
} from "../BaseIndex";
|
||||
import { VectorIndexRetriever } from "./VectorIndexRetriever";
|
||||
import {
|
||||
VectorIndexRetriever,
|
||||
VectorIndexRetrieverOptions,
|
||||
} from "./VectorIndexRetriever";
|
||||
|
||||
interface IndexStructOptions {
|
||||
indexStruct?: IndexDict;
|
||||
@@ -260,7 +263,9 @@ export class VectorStoreIndex extends BaseIndex<IndexDict> {
|
||||
return index;
|
||||
}
|
||||
|
||||
asRetriever(options?: any): VectorIndexRetriever {
|
||||
asRetriever(
|
||||
options?: Omit<VectorIndexRetrieverOptions, "index">,
|
||||
): VectorIndexRetriever {
|
||||
return new VectorIndexRetriever({ index: this, ...options });
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,14 @@ export class OpenAI extends BaseLLM {
|
||||
maxTokens?: number;
|
||||
additionalChatOptions?: Omit<
|
||||
Partial<OpenAILLM.Chat.ChatCompletionCreateParams>,
|
||||
"max_tokens" | "messages" | "model" | "temperature" | "top_p" | "stream"
|
||||
| "max_tokens"
|
||||
| "messages"
|
||||
| "model"
|
||||
| "temperature"
|
||||
| "top_p"
|
||||
| "stream"
|
||||
| "tools"
|
||||
| "toolChoice"
|
||||
>;
|
||||
|
||||
// OpenAI session params
|
||||
@@ -179,7 +186,7 @@ export class OpenAI extends BaseLLM {
|
||||
|
||||
mapMessageType(
|
||||
messageType: MessageType,
|
||||
): "user" | "assistant" | "system" | "function" {
|
||||
): "user" | "assistant" | "system" | "function" | "tool" {
|
||||
switch (messageType) {
|
||||
case "user":
|
||||
return "user";
|
||||
@@ -189,11 +196,30 @@ export class OpenAI extends BaseLLM {
|
||||
return "system";
|
||||
case "function":
|
||||
return "function";
|
||||
case "tool":
|
||||
return "tool";
|
||||
default:
|
||||
return "user";
|
||||
}
|
||||
}
|
||||
|
||||
toOpenAIMessage(messages: ChatMessage[]) {
|
||||
return messages.map((message) => {
|
||||
const additionalKwargs = message.additionalKwargs ?? {};
|
||||
|
||||
if (message.additionalKwargs?.toolCalls) {
|
||||
additionalKwargs.tool_calls = message.additionalKwargs.toolCalls;
|
||||
delete additionalKwargs.toolCalls;
|
||||
}
|
||||
|
||||
return {
|
||||
role: this.mapMessageType(message.role),
|
||||
content: message.content,
|
||||
...additionalKwargs,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
chat(
|
||||
params: LLMChatParamsStreaming,
|
||||
): Promise<AsyncIterable<ChatResponseChunk>>;
|
||||
@@ -201,18 +227,15 @@ export class OpenAI extends BaseLLM {
|
||||
async chat(
|
||||
params: LLMChatParamsNonStreaming | LLMChatParamsStreaming,
|
||||
): Promise<ChatResponse | AsyncIterable<ChatResponseChunk>> {
|
||||
const { messages, parentEvent, stream } = params;
|
||||
const baseRequestParams: OpenAILLM.Chat.ChatCompletionCreateParams = {
|
||||
const { messages, parentEvent, stream, tools, toolChoice } = params;
|
||||
|
||||
let baseRequestParams: OpenAILLM.Chat.ChatCompletionCreateParams = {
|
||||
model: this.model,
|
||||
temperature: this.temperature,
|
||||
max_tokens: this.maxTokens,
|
||||
messages: messages.map(
|
||||
(message) =>
|
||||
({
|
||||
role: this.mapMessageType(message.role),
|
||||
content: message.content,
|
||||
}) as ChatCompletionMessageParam,
|
||||
),
|
||||
tools: tools,
|
||||
tool_choice: toolChoice,
|
||||
messages: this.toOpenAIMessage(messages) as ChatCompletionMessageParam[],
|
||||
top_p: this.topP,
|
||||
...this.additionalChatOptions,
|
||||
};
|
||||
@@ -221,6 +244,7 @@ export class OpenAI extends BaseLLM {
|
||||
if (stream) {
|
||||
return this.streamChat(params);
|
||||
}
|
||||
|
||||
// Non-streaming
|
||||
const response = await this.session.openai.chat.completions.create({
|
||||
...baseRequestParams,
|
||||
@@ -228,8 +252,19 @@ export class OpenAI extends BaseLLM {
|
||||
});
|
||||
|
||||
const content = response.choices[0].message?.content ?? "";
|
||||
|
||||
const kwargsOutput: Record<string, any> = {};
|
||||
|
||||
if (response.choices[0].message?.tool_calls) {
|
||||
kwargsOutput.toolCalls = response.choices[0].message.tool_calls;
|
||||
}
|
||||
|
||||
return {
|
||||
message: { content, role: response.choices[0].message.role },
|
||||
message: {
|
||||
content,
|
||||
role: response.choices[0].message.role,
|
||||
additionalKwargs: kwargsOutput,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -39,17 +39,20 @@ export type MessageType =
|
||||
| "system"
|
||||
| "generic"
|
||||
| "function"
|
||||
| "memory";
|
||||
| "memory"
|
||||
| "tool";
|
||||
|
||||
export interface ChatMessage {
|
||||
// TODO: use MessageContent
|
||||
content: any;
|
||||
role: MessageType;
|
||||
additionalKwargs?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface ChatResponse {
|
||||
message: ChatMessage;
|
||||
raw?: Record<string, any>;
|
||||
additionalKwargs?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface ChatResponseChunk {
|
||||
@@ -74,6 +77,9 @@ export interface LLMChatParamsBase {
|
||||
messages: ChatMessage[];
|
||||
parentEvent?: Event;
|
||||
extraParams?: Record<string, any>;
|
||||
tools?: any;
|
||||
toolChoice?: any;
|
||||
additionalKwargs?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface LLMChatParamsStreaming extends LLMChatParamsBase {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { ChatMessage } from "../llm";
|
||||
import { SimpleChatStore } from "../storage/chatStore/SimpleChatStore";
|
||||
import { BaseChatStore } from "../storage/chatStore/types";
|
||||
import { BaseMemory } from "./types";
|
||||
|
||||
type ChatMemoryBufferParams = {
|
||||
tokenLimit?: number;
|
||||
chatStore?: BaseChatStore;
|
||||
chatStoreKey?: string;
|
||||
chatHistory?: ChatMessage[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Chat memory buffer.
|
||||
*/
|
||||
export class ChatMemoryBuffer implements BaseMemory {
|
||||
tokenLimit: number;
|
||||
|
||||
chatStore: BaseChatStore;
|
||||
chatStoreKey: string;
|
||||
|
||||
/**
|
||||
* Initialize.
|
||||
*/
|
||||
constructor(init?: Partial<ChatMemoryBufferParams>) {
|
||||
this.tokenLimit = init?.tokenLimit ?? 3000;
|
||||
this.chatStore = init?.chatStore ?? new SimpleChatStore();
|
||||
this.chatStoreKey = init?.chatStoreKey ?? "chat_history";
|
||||
|
||||
if (init?.chatHistory) {
|
||||
this.chatStore.setMessages(this.chatStoreKey, init.chatHistory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Get chat history.
|
||||
@param initialTokenCount: number of tokens to start with
|
||||
*/
|
||||
get(initialTokenCount: number = 0): ChatMessage[] {
|
||||
const chatHistory = this.getAll();
|
||||
|
||||
if (initialTokenCount > this.tokenLimit) {
|
||||
throw new Error("Initial token count exceeds token limit");
|
||||
}
|
||||
|
||||
let messageCount = chatHistory.length;
|
||||
let tokenCount =
|
||||
this._tokenCountForMessageCount(messageCount) + initialTokenCount;
|
||||
|
||||
while (tokenCount > this.tokenLimit && messageCount > 1) {
|
||||
messageCount -= 1;
|
||||
if (chatHistory[-messageCount].role === "assistant") {
|
||||
// we cannot have an assistant message at the start of the chat history
|
||||
// if after removal of the first, we have an assistant message,
|
||||
// we need to remove the assistant message too
|
||||
messageCount -= 1;
|
||||
}
|
||||
|
||||
tokenCount =
|
||||
this._tokenCountForMessageCount(messageCount) + initialTokenCount;
|
||||
}
|
||||
|
||||
// catch one message longer than token limit
|
||||
if (tokenCount > this.tokenLimit || messageCount <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return chatHistory.slice(-messageCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all chat history.
|
||||
* @returns {ChatMessage[]} chat history
|
||||
*/
|
||||
getAll(): ChatMessage[] {
|
||||
return this.chatStore.getMessages(this.chatStoreKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put chat history.
|
||||
* @param message
|
||||
*/
|
||||
put(message: ChatMessage): void {
|
||||
this.chatStore.addMessage(this.chatStoreKey, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set chat history.
|
||||
* @param messages
|
||||
*/
|
||||
set(messages: ChatMessage[]): void {
|
||||
this.chatStore.setMessages(this.chatStoreKey, messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset chat history.
|
||||
*/
|
||||
reset(): void {
|
||||
this.chatStore.deleteMessages(this.chatStoreKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token count for message count.
|
||||
* @param messageCount
|
||||
* @returns {number} token count
|
||||
*/
|
||||
private _tokenCountForMessageCount(messageCount: number): number {
|
||||
if (messageCount <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const chatHistory = this.getAll();
|
||||
const msgStr = chatHistory
|
||||
.slice(-messageCount)
|
||||
.map((m) => m.content)
|
||||
.join(" ");
|
||||
return msgStr.split(" ").length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ChatMessage } from "../llm";
|
||||
|
||||
export interface BaseMemory {
|
||||
/*
|
||||
Get chat history.
|
||||
*/
|
||||
get(...args: any): ChatMessage[];
|
||||
/*
|
||||
Get all chat history.
|
||||
*/
|
||||
getAll(): ChatMessage[];
|
||||
/*
|
||||
Put chat history.
|
||||
*/
|
||||
put(message: ChatMessage): void;
|
||||
/*
|
||||
Set chat history.
|
||||
*/
|
||||
set(messages: ChatMessage[]): void;
|
||||
/*
|
||||
Reset chat history.
|
||||
*/
|
||||
reset(): void;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { BaseNode, TextNode } from "../Node";
|
||||
import { BaseRetriever } from "../Retriever";
|
||||
|
||||
// Assuming that necessary interfaces and classes (like OT, TextNode, BaseNode, etc.) are defined elsewhere
|
||||
// Import statements (e.g., for TextNode, BaseNode) should be added based on your project's structure
|
||||
|
||||
export abstract class BaseObjectNodeMapping<OT> {
|
||||
// TypeScript doesn't support Python's classmethod directly, but we can use static methods as an alternative
|
||||
abstract fromObjects<OT>(
|
||||
objs: OT[],
|
||||
...args: any[]
|
||||
): BaseObjectNodeMapping<OT>;
|
||||
|
||||
// Abstract methods in TypeScript
|
||||
abstract objNodeMapping(): Record<any, any>;
|
||||
abstract toNode(obj: OT): TextNode;
|
||||
|
||||
// Concrete methods can be defined as usual
|
||||
validateObject(obj: OT): void {}
|
||||
|
||||
// Implementing the add object logic
|
||||
addObj(obj: OT): void {
|
||||
this.validateObject(obj);
|
||||
this._addObj(obj);
|
||||
}
|
||||
|
||||
// Abstract method for internal add object logic
|
||||
protected abstract _addObj(obj: OT): void;
|
||||
|
||||
// Implementing toNodes method
|
||||
toNodes(objs: OT[]): TextNode[] {
|
||||
return objs.map((obj) => this.toNode(obj));
|
||||
}
|
||||
|
||||
// Abstract method for internal from node logic
|
||||
protected abstract _fromNode(node: BaseNode): OT;
|
||||
|
||||
// Implementing fromNode method
|
||||
fromNode(node: BaseNode): OT {
|
||||
const obj = this._fromNode(node);
|
||||
this.validateObject(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Abstract methods for persistence
|
||||
abstract persist(persistDir: string, objNodeMappingFilename: string): void;
|
||||
}
|
||||
|
||||
// You will need to implement specific subclasses of BaseObjectNodeMapping as per your project requirements.
|
||||
|
||||
type QueryType = string;
|
||||
|
||||
export class ObjectRetriever<OT> {
|
||||
private _retriever: BaseRetriever;
|
||||
private _objectNodeMapping: BaseObjectNodeMapping<OT>;
|
||||
|
||||
constructor(
|
||||
retriever: BaseRetriever,
|
||||
objectNodeMapping: BaseObjectNodeMapping<OT>,
|
||||
) {
|
||||
this._retriever = retriever;
|
||||
this._objectNodeMapping = objectNodeMapping;
|
||||
}
|
||||
|
||||
// In TypeScript, getters are defined like this.
|
||||
get retriever(): BaseRetriever {
|
||||
return this._retriever;
|
||||
}
|
||||
|
||||
// Translating the retrieve method
|
||||
async retrieve(strOrQueryBundle: QueryType): Promise<OT[]> {
|
||||
const nodes = await this._retriever.retrieve(strOrQueryBundle);
|
||||
return nodes.map((node) => this._objectNodeMapping.fromNode(node.node));
|
||||
}
|
||||
|
||||
// // Translating the _asQueryComponent method
|
||||
// public asQueryComponent(kwargs: any): any {
|
||||
// return new ObjectRetrieverComponent(this);
|
||||
// }
|
||||
}
|
||||
@@ -42,7 +42,11 @@ export class NotionReader implements BaseReader {
|
||||
toDocuments(pages: Pages): Document[] {
|
||||
return Object.values(pages).map((page) => {
|
||||
const text = pageToString(page);
|
||||
return new Document({ text, metadata: page.metadata });
|
||||
return new Document({
|
||||
id_: page.metadata.id, // Use the Notion-provided UUID for the document
|
||||
text,
|
||||
metadata: page.metadata,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { ChatMessage } from "../../llm";
|
||||
import { BaseChatStore } from "./types";
|
||||
|
||||
/**
|
||||
* Simple chat store.
|
||||
*/
|
||||
export class SimpleChatStore implements BaseChatStore {
|
||||
store: { [key: string]: ChatMessage[] } = {};
|
||||
|
||||
/**
|
||||
* Set messages.
|
||||
* @param key: key
|
||||
* @param messages: messages
|
||||
* @returns: void
|
||||
*/
|
||||
public setMessages(key: string, messages: ChatMessage[]): void {
|
||||
this.store[key] = messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get messages.
|
||||
* @param key: key
|
||||
* @returns: messages
|
||||
*/
|
||||
public getMessages(key: string): ChatMessage[] {
|
||||
return this.store[key] || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add message.
|
||||
* @param key: key
|
||||
* @param message: message
|
||||
* @returns: void
|
||||
*/
|
||||
public addMessage(key: string, message: ChatMessage): void {
|
||||
this.store[key] = this.store[key] || [];
|
||||
this.store[key].push(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete messages.
|
||||
* @param key: key
|
||||
* @returns: messages
|
||||
*/
|
||||
public deleteMessages(key: string): ChatMessage[] | null {
|
||||
if (!(key in this.store)) {
|
||||
return null;
|
||||
}
|
||||
const messages = this.store[key];
|
||||
delete this.store[key];
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete message.
|
||||
* @param key: key
|
||||
* @param idx: idx
|
||||
* @returns: message
|
||||
*/
|
||||
public deleteMessage(key: string, idx: number): ChatMessage | null {
|
||||
if (!(key in this.store)) {
|
||||
return null;
|
||||
}
|
||||
if (idx >= this.store[key].length) {
|
||||
return null;
|
||||
}
|
||||
return this.store[key].splice(idx, 1)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete last message.
|
||||
* @param key: key
|
||||
* @returns: message
|
||||
*/
|
||||
public deleteLastMessage(key: string): ChatMessage | null {
|
||||
if (!(key in this.store)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lastMessage = this.store[key].pop();
|
||||
|
||||
return lastMessage || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get keys.
|
||||
* @returns: keys
|
||||
*/
|
||||
public getKeys(): string[] {
|
||||
return Object.keys(this.store);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ChatMessage } from "../../llm";
|
||||
|
||||
export interface BaseChatStore {
|
||||
setMessages(key: string, messages: ChatMessage[]): void;
|
||||
getMessages(key: string): ChatMessage[];
|
||||
addMessage(key: string, message: ChatMessage): void;
|
||||
deleteMessages(key: string): ChatMessage[] | null;
|
||||
deleteMessage(key: string, idx: number): ChatMessage | null;
|
||||
deleteLastMessage(key: string): ChatMessage | null;
|
||||
getKeys(): string[];
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
export * from "./FileSystem";
|
||||
export * from "./StorageContext";
|
||||
export { SimpleChatStore } from "./chatStore/SimpleChatStore";
|
||||
export * from "./chatStore/types";
|
||||
export * from "./constants";
|
||||
export { SimpleDocumentStore } from "./docStore/SimpleDocumentStore";
|
||||
export * from "./docStore/types";
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { AstraDB } from "@datastax/astra-db-ts";
|
||||
import { Collection } from "@datastax/astra-db-ts/dist/collections";
|
||||
import { CreateCollectionOptions } from "@datastax/astra-db-ts/dist/collections/options";
|
||||
import { BaseNode, Document, MetadataMode } from "../../Node";
|
||||
import { BaseNode, MetadataMode } from "../../Node";
|
||||
import { VectorStore, VectorStoreQuery, VectorStoreQueryResult } from "./types";
|
||||
import { metadataDictToNode, nodeToMetadata } from "./utils";
|
||||
|
||||
const MAX_INSERT_BATCH_SIZE = 20;
|
||||
|
||||
@@ -12,7 +13,7 @@ export class AstraDBVectorStore implements VectorStore {
|
||||
|
||||
astraDBClient: AstraDB;
|
||||
idKey: string;
|
||||
contentKey: string | undefined; // if undefined the entirety of the node aside from the id and embedding will be stored as content
|
||||
contentKey: string;
|
||||
metadataKey: string;
|
||||
|
||||
private collection: Collection | undefined;
|
||||
@@ -22,6 +23,7 @@ export class AstraDBVectorStore implements VectorStore {
|
||||
params?: {
|
||||
token: string;
|
||||
endpoint: string;
|
||||
namespace: string;
|
||||
};
|
||||
},
|
||||
) {
|
||||
@@ -40,11 +42,15 @@ export class AstraDBVectorStore implements VectorStore {
|
||||
if (!endpoint) {
|
||||
throw new Error("Must specify ASTRA_DB_ENDPOINT via env variable.");
|
||||
}
|
||||
this.astraDBClient = new AstraDB(token, endpoint);
|
||||
const namespace =
|
||||
init?.params?.namespace ??
|
||||
process.env.ASTRA_DB_NAMESPACE ??
|
||||
"default_keyspace";
|
||||
this.astraDBClient = new AstraDB(token, endpoint, namespace);
|
||||
}
|
||||
|
||||
this.idKey = init?.idKey ?? "_id";
|
||||
this.contentKey = init?.contentKey;
|
||||
this.contentKey = init?.contentKey ?? "content";
|
||||
this.metadataKey = init?.metadataKey ?? "metadata";
|
||||
}
|
||||
|
||||
@@ -102,12 +108,20 @@ export class AstraDBVectorStore implements VectorStore {
|
||||
if (!nodes || nodes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dataToInsert = nodes.map((node) => {
|
||||
const metadata = nodeToMetadata(
|
||||
node,
|
||||
true,
|
||||
this.contentKey,
|
||||
this.flatMetadata,
|
||||
);
|
||||
|
||||
return {
|
||||
_id: node.id_,
|
||||
$vector: node.getEmbedding(),
|
||||
content: node.getContent(MetadataMode.ALL),
|
||||
metadata: node.metadata,
|
||||
[this.idKey]: node.id_,
|
||||
[this.contentKey]: node.getContent(MetadataMode.NONE),
|
||||
[this.metadataKey]: metadata,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -122,11 +136,10 @@ export class AstraDBVectorStore implements VectorStore {
|
||||
|
||||
for (const batch of batchData) {
|
||||
console.debug(`Inserting batch of size ${batch.length}`);
|
||||
|
||||
const result = await collection.insertMany(batch);
|
||||
await collection.insertMany(batch);
|
||||
}
|
||||
|
||||
return dataToInsert.map((node) => node._id);
|
||||
return dataToInsert.map((node) => node?.[this.idKey] as string);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,27 +198,24 @@ export class AstraDBVectorStore implements VectorStore {
|
||||
const similarities: number[] = [];
|
||||
|
||||
await cursor.forEach(async (row: Record<string, any>) => {
|
||||
const id = row[this.idKey];
|
||||
const embedding = row.$vector;
|
||||
const similarity = row.$similarity;
|
||||
const metadata = row[this.metadataKey];
|
||||
const {
|
||||
$vector: embedding,
|
||||
$similarity: similarity,
|
||||
[this.idKey]: id,
|
||||
[this.contentKey]: content,
|
||||
[this.metadataKey]: metadata = {},
|
||||
...rest
|
||||
} = row;
|
||||
|
||||
// Remove fields from content
|
||||
delete row[this.idKey];
|
||||
delete row.$similarity;
|
||||
delete row.$vector;
|
||||
delete row[this.metadataKey];
|
||||
|
||||
const content = this.contentKey
|
||||
? row[this.contentKey]
|
||||
: JSON.stringify(row);
|
||||
|
||||
const node = new Document({
|
||||
id_: id,
|
||||
text: content,
|
||||
metadata: metadata ?? {},
|
||||
embedding: embedding,
|
||||
const node = metadataDictToNode(metadata, {
|
||||
fallback: {
|
||||
id,
|
||||
text: content,
|
||||
metadata,
|
||||
...rest,
|
||||
},
|
||||
});
|
||||
node.setContent(content);
|
||||
|
||||
ids.push(id);
|
||||
similarities.push(similarity);
|
||||
|
||||
@@ -142,8 +142,8 @@ export class PineconeVectorStore implements VectorStore {
|
||||
var options: any = {
|
||||
vector: query.queryEmbedding,
|
||||
topK: query.similarityTopK,
|
||||
include_values: true,
|
||||
include_metadara: true,
|
||||
includeValues: true,
|
||||
includeMetadata: true,
|
||||
filter: filter,
|
||||
};
|
||||
|
||||
|
||||
@@ -36,7 +36,16 @@ export function nodeToMetadata(
|
||||
return metadata;
|
||||
}
|
||||
|
||||
export function metadataDictToNode(metadata: Metadata): BaseNode {
|
||||
type MetadataDictToNodeOptions = {
|
||||
// If the metadata doesn't contain node content, use this object as a fallback, for usage see
|
||||
// AstraDBVectorStore.ts
|
||||
fallback: Record<string, any>;
|
||||
};
|
||||
|
||||
export function metadataDictToNode(
|
||||
metadata: Metadata,
|
||||
options?: MetadataDictToNodeOptions,
|
||||
): BaseNode {
|
||||
const {
|
||||
_node_content: nodeContent,
|
||||
_node_type: nodeType,
|
||||
@@ -45,11 +54,17 @@ export function metadataDictToNode(metadata: Metadata): BaseNode {
|
||||
ref_doc_id,
|
||||
...rest
|
||||
} = metadata;
|
||||
let nodeObj;
|
||||
if (!nodeContent) {
|
||||
throw new Error("Node content not found in metadata.");
|
||||
if (options?.fallback) {
|
||||
nodeObj = options?.fallback;
|
||||
} else {
|
||||
throw new Error("Node content not found in metadata.");
|
||||
}
|
||||
} else {
|
||||
nodeObj = JSON.parse(nodeContent);
|
||||
nodeObj.metadata = rest;
|
||||
}
|
||||
const nodeObj = JSON.parse(nodeContent);
|
||||
nodeObj.metadata = rest;
|
||||
|
||||
// Note: we're using the name of the class stored in `_node_type`
|
||||
// and not the type attribute to reconstruct
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { similarity, SimilarityType } from "../embeddings";
|
||||
import { OpenAIEmbedding, similarity, SimilarityType } from "../embeddings";
|
||||
import { mockEmbeddingModel } from "./utility/mockOpenAI";
|
||||
|
||||
// Mock the OpenAI getOpenAISession function during testing
|
||||
jest.mock("../llm/open_ai", () => {
|
||||
return {
|
||||
getOpenAISession: jest.fn().mockImplementation(() => null),
|
||||
};
|
||||
});
|
||||
|
||||
describe("similarity", () => {
|
||||
test("throws error on mismatched lengths", () => {
|
||||
@@ -42,3 +50,32 @@ describe("similarity", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("[OpenAIEmbedding]", () => {
|
||||
let embedModel: OpenAIEmbedding;
|
||||
|
||||
beforeAll(() => {
|
||||
let openAIEmbedding = new OpenAIEmbedding();
|
||||
|
||||
mockEmbeddingModel(openAIEmbedding);
|
||||
|
||||
embedModel = openAIEmbedding;
|
||||
});
|
||||
|
||||
test("getTextEmbedding", async () => {
|
||||
const embedding = await embedModel.getTextEmbedding("hello");
|
||||
expect(embedding.length).toEqual(6);
|
||||
});
|
||||
|
||||
test("getTextEmbeddings", async () => {
|
||||
const texts = ["hello", "world"];
|
||||
const embeddings = await embedModel.getTextEmbeddings(texts);
|
||||
expect(embeddings.length).toEqual(1);
|
||||
});
|
||||
|
||||
test("getTextEmbeddingsBatch", async () => {
|
||||
const texts = ["hello", "world"];
|
||||
const embeddings = await embedModel.getTextEmbeddingsBatch(texts);
|
||||
expect(embeddings.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -50,6 +50,7 @@ describe("[MetadataExtractor]: Extractors should populate the metadata", () => {
|
||||
mockLlmGeneration({ languageModel, callbackManager });
|
||||
|
||||
const embedModel = new OpenAIEmbedding();
|
||||
|
||||
mockEmbeddingModel(embedModel);
|
||||
|
||||
serviceContext = serviceContextFromDefaults({
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { OpenAIAgent } from "../../agent";
|
||||
import { CallbackManager } from "../../callbacks/CallbackManager";
|
||||
import { OpenAI } from "../../llm";
|
||||
import { FunctionTool } from "../../tools";
|
||||
import { mockLlmToolCallGeneration } from "../utility/mockOpenAI";
|
||||
|
||||
// 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"],
|
||||
};
|
||||
|
||||
jest.mock("../../llm/open_ai", () => {
|
||||
return {
|
||||
getOpenAISession: jest.fn().mockImplementation(() => null),
|
||||
};
|
||||
});
|
||||
|
||||
describe("OpenAIAgent", () => {
|
||||
let openaiAgent: OpenAIAgent;
|
||||
|
||||
beforeEach(() => {
|
||||
const callbackManager = new CallbackManager({});
|
||||
|
||||
const languageModel = new OpenAI({
|
||||
model: "gpt-3.5-turbo",
|
||||
callbackManager,
|
||||
});
|
||||
|
||||
mockLlmToolCallGeneration({
|
||||
languageModel,
|
||||
callbackManager,
|
||||
});
|
||||
|
||||
const sumFunctionTool = new FunctionTool(sumNumbers, {
|
||||
name: "sumNumbers",
|
||||
description: "Use this function to sum two numbers",
|
||||
parameters: sumJSON,
|
||||
});
|
||||
|
||||
openaiAgent = new OpenAIAgent({
|
||||
tools: [sumFunctionTool],
|
||||
llm: languageModel,
|
||||
verbose: false,
|
||||
});
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import { OpenAIAgentWorker } from "../../../agent";
|
||||
import { AgentRunner } from "../../../agent/runner/base";
|
||||
import { CallbackManager } from "../../../callbacks/CallbackManager";
|
||||
import { OpenAI } from "../../../llm/LLM";
|
||||
|
||||
import {
|
||||
DEFAULT_LLM_TEXT_OUTPUT,
|
||||
mockLlmGeneration,
|
||||
} from "../../utility/mockOpenAI";
|
||||
|
||||
jest.mock("../../../llm/open_ai", () => {
|
||||
return {
|
||||
getOpenAISession: jest.fn().mockImplementation(() => null),
|
||||
};
|
||||
});
|
||||
|
||||
describe("Agent Runner", () => {
|
||||
let agentRunner: AgentRunner;
|
||||
|
||||
beforeEach(() => {
|
||||
const callbackManager = new CallbackManager({});
|
||||
|
||||
const languageModel = new OpenAI({
|
||||
model: "gpt-3.5-turbo",
|
||||
callbackManager,
|
||||
});
|
||||
|
||||
mockLlmGeneration({
|
||||
languageModel,
|
||||
callbackManager,
|
||||
});
|
||||
|
||||
agentRunner = new AgentRunner({
|
||||
llm: languageModel,
|
||||
agentWorker: new OpenAIAgentWorker({
|
||||
llm: languageModel,
|
||||
tools: [],
|
||||
verbose: false,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to initialize a task", () => {
|
||||
const task = agentRunner.createTask("hello world");
|
||||
|
||||
expect(task.input).toEqual("hello world");
|
||||
expect(task.taskId in agentRunner.state.taskDict).toEqual(true);
|
||||
|
||||
expect(agentRunner.listTasks().length).toEqual(1);
|
||||
});
|
||||
|
||||
it("should be able to run a step", async () => {
|
||||
const task = agentRunner.createTask("hello world");
|
||||
|
||||
expect(agentRunner.getCompletedSteps(task.taskId)).toBeUndefined();
|
||||
|
||||
const stepOutput = await agentRunner.runStep(task.taskId, task.input);
|
||||
|
||||
const completedSteps = agentRunner.getCompletedSteps(task.taskId);
|
||||
|
||||
expect(completedSteps.length).toEqual(1);
|
||||
|
||||
expect(stepOutput.isLast).toEqual(true);
|
||||
});
|
||||
|
||||
it("should be able to finalize a task", async () => {
|
||||
const task = agentRunner.createTask("hello world");
|
||||
|
||||
expect(agentRunner.getCompletedSteps(task.taskId)).toBeUndefined();
|
||||
|
||||
const stepOutput1 = await agentRunner.runStep(task.taskId, task.input);
|
||||
|
||||
expect(stepOutput1.isLast).toEqual(true);
|
||||
});
|
||||
|
||||
it("should be able to delete a task", () => {
|
||||
const task = agentRunner.createTask("hello world");
|
||||
|
||||
expect(agentRunner.listTasks().length).toEqual(1);
|
||||
|
||||
agentRunner.deleteTask(task.taskId);
|
||||
|
||||
expect(agentRunner.listTasks().length).toEqual(0);
|
||||
});
|
||||
|
||||
it("should be able to run a chat", async () => {
|
||||
const response = await agentRunner.chat({
|
||||
message: "hello world",
|
||||
});
|
||||
|
||||
expect(agentRunner.listTasks().length).toEqual(1);
|
||||
|
||||
expect(response).toEqual({
|
||||
response: DEFAULT_LLM_TEXT_OUTPUT,
|
||||
sourceNodes: [],
|
||||
sources: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { FunctionTool, ToolOutput } from "../../tools";
|
||||
import { callToolWithErrorHandling } from "../../tools/utils";
|
||||
|
||||
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,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -64,12 +64,37 @@ export function mockLlmGeneration({
|
||||
);
|
||||
}
|
||||
|
||||
export function mockLlmToolCallGeneration({
|
||||
languageModel,
|
||||
callbackManager,
|
||||
}: {
|
||||
languageModel: OpenAI;
|
||||
callbackManager: CallbackManager;
|
||||
}) {
|
||||
jest.spyOn(languageModel, "chat").mockImplementation(
|
||||
() =>
|
||||
new Promise((resolve) =>
|
||||
resolve({
|
||||
message: {
|
||||
content: "The sum is 2",
|
||||
role: "assistant",
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function mockEmbeddingModel(embedModel: OpenAIEmbedding) {
|
||||
jest.spyOn(embedModel, "getTextEmbedding").mockImplementation(async (x) => {
|
||||
return new Promise((resolve) => {
|
||||
resolve([1, 0, 0, 0, 0, 0]);
|
||||
});
|
||||
});
|
||||
jest.spyOn(embedModel, "getTextEmbeddings").mockImplementation(async (x) => {
|
||||
return new Promise((resolve) => {
|
||||
resolve([[1, 0, 0, 0, 0, 0]]);
|
||||
});
|
||||
});
|
||||
jest.spyOn(embedModel, "getQueryEmbedding").mockImplementation(async (x) => {
|
||||
return new Promise((resolve) => {
|
||||
resolve([0, 1, 0, 0, 0, 0]);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { BaseQueryEngine, BaseTool, ToolMetadata } from "../types";
|
||||
|
||||
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 = {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "The query to search for",
|
||||
},
|
||||
},
|
||||
required: ["query"],
|
||||
};
|
||||
|
||||
export class QueryEngineTool implements BaseTool {
|
||||
private queryEngine: BaseQueryEngine;
|
||||
metadata: ToolMetadata;
|
||||
|
||||
constructor({ queryEngine, metadata }: QueryEngineToolParams) {
|
||||
this.queryEngine = queryEngine;
|
||||
this.metadata = {
|
||||
name: metadata?.name ?? DEFAULT_NAME,
|
||||
description: metadata?.description ?? DEFAULT_DESCRIPTION,
|
||||
parameters: metadata?.parameters ?? DEFAULT_PARAMETERS,
|
||||
};
|
||||
}
|
||||
|
||||
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 });
|
||||
|
||||
return response.response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { BaseTool, ToolMetadata } from "../types";
|
||||
|
||||
type Metadata = {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: ToolMetadata["parameters"];
|
||||
};
|
||||
|
||||
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 fromDefaults<T = any>(
|
||||
fn: (...args: any[]) => any,
|
||||
metadata?: Metadata,
|
||||
): FunctionTool<T> {
|
||||
return new FunctionTool(fn, metadata!);
|
||||
}
|
||||
|
||||
get metadata(): ToolMetadata {
|
||||
return this._metadata;
|
||||
}
|
||||
|
||||
async call(...args: any[]): Promise<any> {
|
||||
return this._fn(...args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./QueryEngineTool";
|
||||
export * from "./functionTool";
|
||||
export * from "./types";
|
||||
@@ -0,0 +1,22 @@
|
||||
export class ToolOutput {
|
||||
content: string;
|
||||
toolName: string;
|
||||
rawInput: any;
|
||||
rawOutput: any;
|
||||
|
||||
constructor(
|
||||
content: string,
|
||||
toolName: string,
|
||||
rawInput: any,
|
||||
rawOutput: any,
|
||||
) {
|
||||
this.content = content;
|
||||
this.toolName = toolName;
|
||||
this.rawInput = rawInput;
|
||||
this.rawOutput = rawOutput;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { BaseTool } from "../types";
|
||||
import { ToolOutput } from "./types";
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
try {
|
||||
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,
|
||||
tool.metadata.name,
|
||||
{ kwargs: inputDict },
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* Top level types to avoid circular dependencies
|
||||
*/
|
||||
|
||||
import { Event } from "./callbacks/CallbackManager";
|
||||
import { Response } from "./Response";
|
||||
|
||||
@@ -37,16 +36,10 @@ export interface BaseQueryEngine {
|
||||
* Simple Tool interface. Likely to change.
|
||||
*/
|
||||
export interface BaseTool {
|
||||
call?: (...args: any[]) => any;
|
||||
metadata: ToolMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Tool that uses a QueryEngine.
|
||||
*/
|
||||
export interface QueryEngineTool extends BaseTool {
|
||||
queryEngine: BaseQueryEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* An OutputParser is used to extract structured data from the raw output of the LLM.
|
||||
*/
|
||||
@@ -64,9 +57,17 @@ 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">;
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
"strict": true,
|
||||
"lib": ["es2015", "dom"],
|
||||
"target": "ES2015",
|
||||
"resolveJsonModule": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["./src"],
|
||||
"exclude": ["node_modules"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# create-llama
|
||||
|
||||
## 0.0.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d06a85b: Add option to create an agent by selecting tools (Google, Wikipedia)
|
||||
- 7b7329b: Added latest turbo models for GPT-3.5 and GPT 4
|
||||
|
||||
## 0.0.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ba95ca3: Use condense plus context chat engine for FastAPI as default
|
||||
|
||||
## 0.0.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import path from "path";
|
||||
import { green } from "picocolors";
|
||||
import { green, yellow } from "picocolors";
|
||||
import { tryGitInit } from "./helpers/git";
|
||||
import { isFolderEmpty } from "./helpers/is-folder-empty";
|
||||
import { getOnline } from "./helpers/is-online";
|
||||
@@ -12,6 +12,7 @@ import terminalLink from "terminal-link";
|
||||
import type { InstallTemplateArgs } from "./helpers";
|
||||
import { installTemplate } from "./helpers";
|
||||
import { templatesDir } from "./helpers/dir";
|
||||
import { toolsRequireConfig } from "./helpers/tools";
|
||||
|
||||
export type InstallAppArgs = Omit<
|
||||
InstallTemplateArgs,
|
||||
@@ -38,6 +39,7 @@ export async function createApp({
|
||||
externalPort,
|
||||
postInstallAction,
|
||||
dataSource,
|
||||
tools,
|
||||
}: InstallAppArgs): Promise<void> {
|
||||
const root = path.resolve(appPath);
|
||||
|
||||
@@ -82,6 +84,7 @@ export async function createApp({
|
||||
externalPort,
|
||||
postInstallAction,
|
||||
dataSource,
|
||||
tools,
|
||||
};
|
||||
|
||||
if (frontend) {
|
||||
@@ -114,6 +117,17 @@ export async function createApp({
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (toolsRequireConfig(tools)) {
|
||||
console.log(
|
||||
yellow(
|
||||
`You have selected tools that require configuration. Please configure them in the ${terminalLink(
|
||||
"tools_config.json",
|
||||
`file://${root}/tools_config.json`,
|
||||
)} file.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
console.log("");
|
||||
console.log(`${green("Success!")} Created ${appName} at ${appPath}`);
|
||||
|
||||
console.log(
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": "./lib/.tsbuildinfo",
|
||||
"tsBuildInfoFile": "./lib/.tsbuildinfo"
|
||||
},
|
||||
"include": ["./**/*.ts"],
|
||||
"include": ["./**/*.ts"]
|
||||
}
|
||||
|
||||
@@ -117,6 +117,8 @@ export async function runCreateLlama(
|
||||
externalPort,
|
||||
"--post-install-action",
|
||||
postInstallAction,
|
||||
"--tools",
|
||||
"none",
|
||||
].join(" ");
|
||||
console.log(`running command '${command}' in ${cwd}`);
|
||||
let appProcess = exec(command, {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { cyan, red, yellow } from "picocolors";
|
||||
import { cyan, red } from "picocolors";
|
||||
import { parse, stringify } from "smol-toml";
|
||||
import terminalLink from "terminal-link";
|
||||
import { copy } from "./copy";
|
||||
import { templatesDir } from "./dir";
|
||||
import { isPoetryAvailable, tryPoetryInstall } from "./poetry";
|
||||
import { getToolConfig } from "./tools";
|
||||
import { InstallTemplateArgs, TemplateVectorDB } from "./types";
|
||||
|
||||
interface Dependency {
|
||||
@@ -103,18 +104,19 @@ export const installPythonDependencies = (
|
||||
const installSuccessful = tryPoetryInstall(noRoot);
|
||||
if (!installSuccessful) {
|
||||
console.error(
|
||||
red("Install failed. Please install dependencies manually."),
|
||||
red(
|
||||
"Installing dependencies using poetry failed. Please check error log above and try running create-llama again.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
yellow(
|
||||
`Poetry is not available in the current environment. The Python dependencies will not be installed automatically.
|
||||
Please check ${terminalLink(
|
||||
console.error(
|
||||
red(
|
||||
`Poetry is not available in the current environment. Please check ${terminalLink(
|
||||
"Poetry Installation",
|
||||
`https://python-poetry.org/docs/#installation`,
|
||||
)} to install poetry first, then install the dependencies manually.`,
|
||||
)} to install poetry first, then run create-llama again.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
@@ -128,6 +130,7 @@ export const installPythonTemplate = async ({
|
||||
engine,
|
||||
vectorDb,
|
||||
dataSource,
|
||||
tools,
|
||||
postInstallAction,
|
||||
}: Pick<
|
||||
InstallTemplateArgs,
|
||||
@@ -137,6 +140,7 @@ export const installPythonTemplate = async ({
|
||||
| "engine"
|
||||
| "vectorDb"
|
||||
| "dataSource"
|
||||
| "tools"
|
||||
| "postInstallAction"
|
||||
>) => {
|
||||
console.log("\nInitializing Python project with template:", template, "\n");
|
||||
@@ -162,20 +166,44 @@ export const installPythonTemplate = async ({
|
||||
});
|
||||
|
||||
if (engine === "context") {
|
||||
const enginePath = path.join(root, "app", "engine");
|
||||
const compPath = path.join(templatesDir, "components");
|
||||
let vectorDbDirName = vectorDb ?? "none";
|
||||
|
||||
const vectorDbDirName = vectorDb ?? "none";
|
||||
const VectorDBPath = path.join(
|
||||
compPath,
|
||||
"vectordbs",
|
||||
"python",
|
||||
vectorDbDirName,
|
||||
);
|
||||
const enginePath = path.join(root, "app", "engine");
|
||||
await copy("**", path.join(root, "app", "engine"), {
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: VectorDBPath,
|
||||
});
|
||||
|
||||
// Copy engine code
|
||||
if (tools !== undefined && tools.length > 0) {
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "engines", "python", "agent"),
|
||||
});
|
||||
// Write tools_config.json
|
||||
const configContent: Record<string, any> = {};
|
||||
tools.forEach((tool) => {
|
||||
configContent[tool] = getToolConfig(tool) ?? {};
|
||||
});
|
||||
const configFilePath = path.join(root, "tools_config.json");
|
||||
await fs.writeFile(
|
||||
configFilePath,
|
||||
JSON.stringify(configContent, null, 2),
|
||||
);
|
||||
} else {
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "engines", "python", "chat"),
|
||||
});
|
||||
}
|
||||
|
||||
const dataSourceType = dataSource?.type;
|
||||
if (dataSourceType !== undefined && dataSourceType !== "none") {
|
||||
let loaderPath =
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
export type Tool = {
|
||||
display: string;
|
||||
name: string;
|
||||
config?: Record<string, any>;
|
||||
};
|
||||
|
||||
export const supportedTools: Tool[] = [
|
||||
{
|
||||
display: "Google Search (configuration required after installation)",
|
||||
name: "google_search",
|
||||
config: {
|
||||
engine:
|
||||
"Your search engine id, see https://developers.google.com/custom-search/v1/overview#prerequisites",
|
||||
key: "Your search api key",
|
||||
num: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
display: "Wikipedia",
|
||||
name: "wikipedia",
|
||||
},
|
||||
];
|
||||
|
||||
export const getToolConfig = (name: string) => {
|
||||
return supportedTools.find((tool) => tool.name === name)?.config;
|
||||
};
|
||||
|
||||
export const toolsRequireConfig = (tools?: string[]): boolean => {
|
||||
if (tools) {
|
||||
return tools.some((tool) => getToolConfig(tool));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -41,4 +41,5 @@ export interface InstallTemplateArgs {
|
||||
vectorDb?: TemplateVectorDB;
|
||||
externalPort?: number;
|
||||
postInstallAction?: TemplatePostInstallAction;
|
||||
tools?: string[];
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { createApp } from "./create-app";
|
||||
import { getPkgManager } from "./helpers/get-pkg-manager";
|
||||
import { isFolderEmpty } from "./helpers/is-folder-empty";
|
||||
import { runApp } from "./helpers/run-app";
|
||||
import { supportedTools } from "./helpers/tools";
|
||||
import { validateNpmName } from "./helpers/validate-pkg";
|
||||
import packageJson from "./package.json";
|
||||
import { QuestionArgs, askQuestions, onPromptState } from "./questions";
|
||||
@@ -146,6 +147,13 @@ const program = new Commander.Command(packageJson.name)
|
||||
`
|
||||
|
||||
Select which vector database you would like to use, such as 'none', 'pg' or 'mongo'. The default option is not to use a vector database and use the local filesystem instead ('none').
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--tools <tools>",
|
||||
`
|
||||
|
||||
Specify the tools you want to use by providing a comma-separated list. For example, 'google_search,wikipedia'. Use 'none' to not using any tools.
|
||||
`,
|
||||
)
|
||||
.allowUnknownOption()
|
||||
@@ -156,6 +164,25 @@ if (process.argv.includes("--no-frontend")) {
|
||||
if (process.argv.includes("--no-eslint")) {
|
||||
program.eslint = false;
|
||||
}
|
||||
if (process.argv.includes("--tools")) {
|
||||
if (program.tools === "none") {
|
||||
program.tools = [];
|
||||
} else {
|
||||
program.tools = program.tools.split(",");
|
||||
// Check if tools are available
|
||||
const toolsName = supportedTools.map((tool) => tool.name);
|
||||
program.tools.forEach((tool: string) => {
|
||||
if (!toolsName.includes(tool)) {
|
||||
console.error(
|
||||
`Error: Tool '${tool}' is not supported. Supported tools are: ${toolsName.join(
|
||||
", ",
|
||||
)}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const packageManager = !!program.useNpm
|
||||
? "npm"
|
||||
@@ -256,6 +283,7 @@ async function run(): Promise<void> {
|
||||
externalPort: program.externalPort,
|
||||
postInstallAction: program.postInstallAction,
|
||||
dataSource: program.dataSource,
|
||||
tools: program.tools,
|
||||
});
|
||||
conf.set("preferences", preferences);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-llama",
|
||||
"version": "0.0.23",
|
||||
"version": "0.0.25",
|
||||
"keywords": [
|
||||
"rag",
|
||||
"llamaindex",
|
||||
@@ -36,7 +36,7 @@
|
||||
"@types/prompts": "2.0.1",
|
||||
"@types/tar": "6.1.5",
|
||||
"@types/validate-npm-package-name": "3.0.0",
|
||||
"@vercel/ncc": "0.34.0",
|
||||
"@vercel/ncc": "0.38.1",
|
||||
"async-retry": "1.3.1",
|
||||
"async-sema": "3.0.1",
|
||||
"ci-info": "github:watson/ci-info#f43f6a1cefff47fb361c88cf4b943fdbcaafe540",
|
||||
|
||||
@@ -7,8 +7,10 @@ import prompts from "prompts";
|
||||
import { InstallAppArgs } from "./create-app";
|
||||
import { TemplateDataSourceType, TemplateFramework } from "./helpers";
|
||||
import { COMMUNITY_OWNER, COMMUNITY_REPO } from "./helpers/constant";
|
||||
import { templatesDir } from "./helpers/dir";
|
||||
import { getAvailableLlamapackOptions } from "./helpers/llama-pack";
|
||||
import { getRepoRootFolders } from "./helpers/repo";
|
||||
import { supportedTools, toolsRequireConfig } from "./helpers/tools";
|
||||
|
||||
export type QuestionArgs = Omit<
|
||||
InstallAppArgs,
|
||||
@@ -69,6 +71,7 @@ const defaults: QuestionArgs = {
|
||||
type: "none",
|
||||
config: {},
|
||||
},
|
||||
tools: [],
|
||||
};
|
||||
|
||||
const handlers = {
|
||||
@@ -89,7 +92,7 @@ const getVectorDbChoices = (framework: TemplateFramework) => {
|
||||
];
|
||||
|
||||
const vectordbLang = framework === "fastapi" ? "python" : "typescript";
|
||||
const compPath = path.join(__dirname, "..", "templates", "components");
|
||||
const compPath = path.join(templatesDir, "components");
|
||||
const vectordbPath = path.join(compPath, "vectordbs", vectordbLang);
|
||||
|
||||
const availableChoices = fs
|
||||
@@ -213,7 +216,12 @@ export const askQuestions = async (
|
||||
|
||||
const hasOpenAiKey = program.openAiKey || process.env["OPENAI_API_KEY"];
|
||||
const hasVectorDb = program.vectorDb && program.vectorDb !== "none";
|
||||
if (!hasVectorDb && hasOpenAiKey) {
|
||||
// Can run the app if all tools do not require configuration
|
||||
if (
|
||||
!hasVectorDb &&
|
||||
hasOpenAiKey &&
|
||||
!toolsRequireConfig(program.tools)
|
||||
) {
|
||||
actionChoices.push({
|
||||
title:
|
||||
"Generate code, install dependencies, and run the app (~2 min)",
|
||||
@@ -411,9 +419,9 @@ export const askQuestions = async (
|
||||
name: "model",
|
||||
message: "Which model would you like to use?",
|
||||
choices: [
|
||||
{ title: "gpt-3.5-turbo", value: "gpt-3.5-turbo" },
|
||||
{ title: "gpt-3.5-turbo", value: "gpt-3.5-turbo-0125" },
|
||||
{ title: "gpt-4-turbo-preview", value: "gpt-4-turbo-preview" },
|
||||
{ title: "gpt-4", value: "gpt-4" },
|
||||
{ title: "gpt-4-1106-preview", value: "gpt-4-1106-preview" },
|
||||
{
|
||||
title: "gpt-4-vision-preview",
|
||||
value: "gpt-4-vision-preview",
|
||||
@@ -562,6 +570,30 @@ export const askQuestions = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!program.tools &&
|
||||
program.framework === "fastapi" &&
|
||||
program.engine === "context"
|
||||
) {
|
||||
if (ciInfo.isCI) {
|
||||
program.tools = getPrefOrDefault("tools");
|
||||
} else {
|
||||
const toolChoices = supportedTools.map((tool) => ({
|
||||
title: tool.display,
|
||||
value: tool.name,
|
||||
}));
|
||||
const { tools } = await prompts({
|
||||
type: "multiselect",
|
||||
name: "tools",
|
||||
message:
|
||||
"Would you like to build an agent using tools? If so, select the tools here, otherwise just press enter",
|
||||
choices: toolChoices,
|
||||
});
|
||||
program.tools = tools;
|
||||
preferences.tools = tools;
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.openAiKey) {
|
||||
const { key } = await prompts(
|
||||
{
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import os
|
||||
|
||||
from typing import Any, Optional
|
||||
from llama_index.llms import LLM
|
||||
from llama_index.agent import AgentRunner
|
||||
|
||||
from app.engine.tools import ToolFactory
|
||||
from app.engine.index import get_index
|
||||
from llama_index.agent import ReActAgent
|
||||
from llama_index.tools.query_engine import QueryEngineTool
|
||||
|
||||
|
||||
def create_agent_from_llm(
|
||||
llm: Optional[LLM] = None,
|
||||
**kwargs: Any,
|
||||
) -> AgentRunner:
|
||||
from llama_index.agent import OpenAIAgent, ReActAgent
|
||||
from llama_index.llms.openai import OpenAI
|
||||
from llama_index.llms.openai_utils import is_function_calling_model
|
||||
|
||||
if isinstance(llm, OpenAI) and is_function_calling_model(llm.model):
|
||||
return OpenAIAgent.from_tools(
|
||||
llm=llm,
|
||||
**kwargs,
|
||||
)
|
||||
else:
|
||||
return ReActAgent.from_tools(
|
||||
llm=llm,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def get_chat_engine():
|
||||
tools = []
|
||||
|
||||
# Add query tool
|
||||
index = get_index()
|
||||
llm = index.service_context.llm
|
||||
query_engine = index.as_query_engine(similarity_top_k=3)
|
||||
query_engine_tool = QueryEngineTool.from_defaults(query_engine=query_engine)
|
||||
tools.append(query_engine_tool)
|
||||
|
||||
# Add additional tools
|
||||
tools += ToolFactory.from_env()
|
||||
|
||||
return create_agent_from_llm(
|
||||
llm=llm,
|
||||
tools=tools,
|
||||
verbose=True,
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
import json
|
||||
import importlib
|
||||
|
||||
from llama_index.tools.tool_spec.base import BaseToolSpec
|
||||
from llama_index.tools.function_tool import FunctionTool
|
||||
|
||||
|
||||
class ToolFactory:
|
||||
|
||||
@staticmethod
|
||||
def create_tool(tool_name: str, **kwargs) -> list[FunctionTool]:
|
||||
try:
|
||||
module_name = f"llama_hub.tools.{tool_name}.base"
|
||||
module = importlib.import_module(module_name)
|
||||
tool_cls_name = tool_name.title().replace("_", "") + "ToolSpec"
|
||||
tool_class = getattr(module, tool_cls_name)
|
||||
tool_spec: BaseToolSpec = tool_class(**kwargs)
|
||||
return tool_spec.to_tool_list()
|
||||
except (ImportError, AttributeError) as e:
|
||||
raise ValueError(f"Unsupported tool: {tool_name}") from e
|
||||
except TypeError as e:
|
||||
raise ValueError(
|
||||
f"Could not create tool: {tool_name}. With config: {kwargs}"
|
||||
) from e
|
||||
|
||||
@staticmethod
|
||||
def from_env() -> list[FunctionTool]:
|
||||
tools = []
|
||||
with open("tools_config.json", "r") as f:
|
||||
tool_configs = json.load(f)
|
||||
for name, config in tool_configs.items():
|
||||
tools += ToolFactory.create_tool(name, **config)
|
||||
return tools
|
||||
@@ -0,0 +1,7 @@
|
||||
from app.engine.index import get_index
|
||||
|
||||
|
||||
def get_chat_engine():
|
||||
return get_index().as_chat_engine(
|
||||
similarity_top_k=3, chat_mode="condense_plus_context"
|
||||
)
|
||||
@@ -9,7 +9,7 @@ from llama_index.vector_stores import MongoDBAtlasVectorSearch
|
||||
from app.engine.context import create_service_context
|
||||
|
||||
|
||||
def get_chat_engine():
|
||||
def get_index():
|
||||
service_context = create_service_context()
|
||||
logger = logging.getLogger("uvicorn")
|
||||
logger.info("Connecting to index from MongoDB...")
|
||||
@@ -20,4 +20,4 @@ def get_chat_engine():
|
||||
)
|
||||
index = VectorStoreIndex.from_vector_store(store, service_context)
|
||||
logger.info("Finished connecting to index from MongoDB.")
|
||||
return index.as_chat_engine(similarity_top_k=5)
|
||||
return index
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from app.engine.constants import STORAGE_DIR
|
||||
from app.engine.context import create_service_context
|
||||
from llama_index import (
|
||||
StorageContext,
|
||||
load_index_from_storage,
|
||||
)
|
||||
|
||||
from app.engine.constants import STORAGE_DIR
|
||||
from app.engine.context import create_service_context
|
||||
|
||||
|
||||
def get_chat_engine():
|
||||
def get_index():
|
||||
service_context = create_service_context()
|
||||
# check if storage already exists
|
||||
if not os.path.exists(STORAGE_DIR):
|
||||
@@ -22,4 +22,4 @@ def get_chat_engine():
|
||||
storage_context = StorageContext.from_defaults(persist_dir=STORAGE_DIR)
|
||||
index = load_index_from_storage(storage_context, service_context=service_context)
|
||||
logger.info(f"Finished loading index from {STORAGE_DIR}")
|
||||
return index.as_chat_engine()
|
||||
return index
|
||||
|
||||
@@ -6,11 +6,11 @@ from app.engine.context import create_service_context
|
||||
from app.engine.utils import init_pg_vector_store_from_env
|
||||
|
||||
|
||||
def get_chat_engine():
|
||||
def get_index():
|
||||
service_context = create_service_context()
|
||||
logger = logging.getLogger("uvicorn")
|
||||
logger.info("Connecting to index from PGVector...")
|
||||
store = init_pg_vector_store_from_env()
|
||||
index = VectorStoreIndex.from_vector_store(store, service_context)
|
||||
logger.info("Finished connecting to index from PGVector.")
|
||||
return index.as_chat_engine(similarity_top_k=5)
|
||||
return index
|
||||
|
||||
@@ -29,7 +29,7 @@ async function getDataSource(llm: LLM) {
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
const retriever = index.asRetriever({ similarityTopK: 5 });
|
||||
const retriever = index.asRetriever({ similarityTopK: 3 });
|
||||
return new ContextChatEngine({
|
||||
chatModel: llm,
|
||||
retriever,
|
||||
|
||||
@@ -35,7 +35,7 @@ async function getDataSource(llm: LLM) {
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
const retriever = index.asRetriever();
|
||||
retriever.similarityTopK = 5;
|
||||
retriever.similarityTopK = 3;
|
||||
|
||||
return new ContextChatEngine({
|
||||
chatModel: llm,
|
||||
|
||||
@@ -31,7 +31,7 @@ async function getDataSource(llm: LLM) {
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
const retriever = index.asRetriever({ similarityTopK: 5 });
|
||||
const retriever = index.asRetriever({ similarityTopK: 3 });
|
||||
return new ContextChatEngine({
|
||||
chatModel: llm,
|
||||
retriever,
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node",
|
||||
},
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ from llama_index.chat_engine.types import BaseChatEngine
|
||||
from llama_index.llms.base import ChatMessage
|
||||
from llama_index.llms.types import MessageRole
|
||||
from pydantic import BaseModel
|
||||
from app.engine.index import get_chat_engine
|
||||
from app.engine import get_chat_engine
|
||||
|
||||
chat_router = r = APIRouter()
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
from llama_index.chat_engine import SimpleChatEngine
|
||||
|
||||
from app.context import create_base_context
|
||||
|
||||
|
||||
def get_chat_engine():
|
||||
return SimpleChatEngine.from_defaults(service_context=create_base_context())
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
from llama_index.chat_engine import SimpleChatEngine
|
||||
|
||||
from app.context import create_base_context
|
||||
|
||||
|
||||
def get_chat_engine():
|
||||
return SimpleChatEngine.from_defaults(service_context=create_base_context())
|
||||
@@ -13,6 +13,8 @@ llama-index = "^0.9.19"
|
||||
pypdf = "^3.17.0"
|
||||
python-dotenv = "^1.0.0"
|
||||
docx2txt = "^0.8"
|
||||
llama-hub = "^0.0.77"
|
||||
wikipedia = "^1.4.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node",
|
||||
},
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user