mirror of
https://github.com/run-llama/LlamaIndexTS.git
synced 2026-07-15 06:52:45 -04:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b2e1df94db | |||
| b4963cabc8 | |||
| 2851024340 | |||
| 7f25a25729 | |||
| acfe23265a | |||
| 2c6fbbd7dd | |||
| f84507f513 | |||
| be6a9e4a48 | |||
| 69e7634619 | |||
| d18748aba4 | |||
| 27c4ef3410 | |||
| a7ee392d3e | |||
| 4415a6fdef | |||
| 1e1e6e96a1 | |||
| 461d1dfbcc | |||
| 5975fafefb | |||
| 71169fd545 | |||
| be895d564d | |||
| f36a27c218 | |||
| 8cdb07f151 | |||
| ea403a0ffe | |||
| 7f0b4e66ae | |||
| 3b226965ba | |||
| 63daf77412 | |||
| df5cbe30a6 | |||
| 9e1a536778 | |||
| a1db8833ef | |||
| 95dd0e0158 | |||
| 079a1d5cc3 | |||
| 2377d1a466 | |||
| 9f9f29391e | |||
| b64716d3f7 | |||
| d7a47abe38 | |||
| 58b314a61e | |||
| 4431ec7a5e | |||
| 9542026d70 | |||
| cc4c5b64c0 | |||
| 82c2aac4a0 | |||
| a143e0f0f1 | |||
| db9775dc32 | |||
| 538c0b0740 | |||
| 21cd88caf6 | |||
| 0660d9e2a5 | |||
| 25257f49d7 | |||
| dd615f106d | |||
| 5db64d61e0 | |||
| ee5e1f94e4 | |||
| 031e926414 | |||
| 88b4b3143d | |||
| c1ce84ecec | |||
| d670011363 | |||
| c88332366b | |||
| cfee282c28 | |||
| a5ae1eea30 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"create-llama": patch
|
||||
---
|
||||
|
||||
change version (thanks Marcus)
|
||||
@@ -2,3 +2,4 @@
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
pnpm lint
|
||||
npx lint-staged
|
||||
|
||||
@@ -89,7 +89,7 @@ Check out our NextJS playground at https://llama-playground.vercel.app/. The sou
|
||||
If you're using NextJS App Router, you'll need to use the NodeJS runtime (default) and add the follow config to your next.config.js to have it use imports/exports in the same way Node does.
|
||||
|
||||
```js
|
||||
export const runtime = "nodejs" // default
|
||||
export const runtime = "nodejs"; // default
|
||||
```
|
||||
|
||||
```js
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_position: 4
|
||||
|
||||
We include several end-to-end examples using LlamaIndex.TS in the repository
|
||||
|
||||
Check out the examples below or try them out and complete them in minutes with interactive Github Codespace tutorials provided by Dev-Docs [here](https://codespaces.new/team-dev-docs/lits-dev-docs-playground?devcontainer_path=.devcontainer%2Fjavascript_ltsquickstart%2Fdevcontainer.json):
|
||||
|
||||
## [Chat Engine](https://github.com/run-llama/LlamaIndexTS/blob/main/apps/simple/chatEngine.ts)
|
||||
|
||||
Read a file and chat about it with the LLM.
|
||||
|
||||
@@ -11,7 +11,7 @@ LlamaIndex currently officially supports NodeJS 18 and NodeJS 20.
|
||||
If you're using NextJS App Router route handlers/serverless functions, you'll need to use the NodeJS mode:
|
||||
|
||||
```js
|
||||
export const runtime = "nodejs" // default
|
||||
export const runtime = "nodejs"; // default
|
||||
```
|
||||
|
||||
and you'll need to add an exception for pdf-parse in your next.config.js
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import * as dotenv from "dotenv";
|
||||
import * as fs from "fs";
|
||||
import { MongoClient } from "mongodb";
|
||||
|
||||
// Load environment variables from local .env file
|
||||
dotenv.config();
|
||||
|
||||
const jsonFile = "tinytweets.json";
|
||||
const mongoUri = process.env.MONGODB_URI!;
|
||||
const databaseName = process.env.MONGODB_DATABASE!;
|
||||
const collectionName = process.env.MONGODB_COLLECTION!;
|
||||
|
||||
async function importJsonToMongo() {
|
||||
// Load the tweets from a local file
|
||||
const tweets = JSON.parse(fs.readFileSync(jsonFile, "utf-8"));
|
||||
|
||||
// Create a new client and connect to the server
|
||||
const client = new MongoClient(mongoUri);
|
||||
|
||||
const db = client.db(databaseName);
|
||||
const collection = db.collection(collectionName);
|
||||
|
||||
// Insert the tweets into mongo
|
||||
await collection.insertMany(tweets);
|
||||
|
||||
console.log(
|
||||
`Data imported successfully to the MongoDB collection ${collectionName}.`,
|
||||
);
|
||||
await client.close();
|
||||
}
|
||||
|
||||
// Run the import function
|
||||
importJsonToMongo();
|
||||
@@ -0,0 +1,50 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import * as dotenv from "dotenv";
|
||||
import {
|
||||
MongoDBAtlasVectorSearch,
|
||||
SimpleMongoReader,
|
||||
VectorStoreIndex,
|
||||
storageContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import { MongoClient } from "mongodb";
|
||||
|
||||
// Load environment variables from local .env file
|
||||
dotenv.config();
|
||||
|
||||
const mongoUri = process.env.MONGODB_URI!;
|
||||
const databaseName = process.env.MONGODB_DATABASE!;
|
||||
const collectionName = process.env.MONGODB_COLLECTION!;
|
||||
const vectorCollectionName = process.env.MONGODB_VECTORS!;
|
||||
const indexName = process.env.MONGODB_VECTOR_INDEX!;
|
||||
|
||||
async function loadAndIndex() {
|
||||
// Create a new client and connect to the server
|
||||
const client = new MongoClient(mongoUri);
|
||||
// load objects from mongo and convert them into LlamaIndex Document objects
|
||||
// llamaindex has a special class that does this for you
|
||||
// it pulls every object in a given collection
|
||||
const reader = new SimpleMongoReader(client);
|
||||
const documents = await reader.loadData(databaseName, collectionName, [
|
||||
"full_text",
|
||||
]);
|
||||
|
||||
// create Atlas as a vector store
|
||||
const vectorStore = new MongoDBAtlasVectorSearch({
|
||||
mongodbClient: client,
|
||||
dbName: databaseName,
|
||||
collectionName: vectorCollectionName, // this is where your embeddings will be stored
|
||||
indexName: indexName, // this is the name of the index you will need to create
|
||||
});
|
||||
|
||||
// now create an index from all the Documents and store them in Atlas
|
||||
const storageContext = await storageContextFromDefaults({ vectorStore });
|
||||
await VectorStoreIndex.fromDocuments(documents, { storageContext });
|
||||
console.log(
|
||||
`Successfully created embeddings in the MongoDB collection ${vectorCollectionName}.`,
|
||||
);
|
||||
await client.close();
|
||||
}
|
||||
|
||||
loadAndIndex();
|
||||
|
||||
// you can't query your index yet because you need to create a vector search index in mongodb's UI now
|
||||
@@ -0,0 +1,34 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import * as dotenv from "dotenv";
|
||||
import {
|
||||
MongoDBAtlasVectorSearch,
|
||||
VectorStoreIndex,
|
||||
serviceContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import { MongoClient } from "mongodb";
|
||||
|
||||
// Load environment variables from local .env file
|
||||
dotenv.config();
|
||||
|
||||
async function query() {
|
||||
const client = new MongoClient(process.env.MONGODB_URI!);
|
||||
const serviceContext = serviceContextFromDefaults();
|
||||
const store = new MongoDBAtlasVectorSearch({
|
||||
mongodbClient: client,
|
||||
dbName: process.env.MONGODB_DATABASE!,
|
||||
collectionName: process.env.MONGODB_VECTORS!,
|
||||
indexName: process.env.MONGODB_VECTOR_INDEX!,
|
||||
});
|
||||
|
||||
const index = await VectorStoreIndex.fromVectorStore(store, serviceContext);
|
||||
|
||||
const retriever = index.asRetriever({ similarityTopK: 20 });
|
||||
const queryEngine = index.asQueryEngine({ retriever });
|
||||
const result = await queryEngine.query(
|
||||
"What does the author think of web frameworks?",
|
||||
);
|
||||
console.log(result.response);
|
||||
await client.close();
|
||||
}
|
||||
|
||||
query();
|
||||
@@ -0,0 +1,127 @@
|
||||
# LlamaIndexTS retrieval augmented generation with MongoDB
|
||||
|
||||
### Prepare Environment
|
||||
|
||||
Make sure to run `pnpm install` and set your OpenAI environment variable before running these examples.
|
||||
|
||||
```
|
||||
pnpm install
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
```
|
||||
|
||||
### Sign up for MongoDB Atlas
|
||||
|
||||
We'll be using MongoDB's hosted database service, [MongoDB Atlas](https://www.mongodb.com/cloud/atlas/register). You can sign up for free and get a small hosted cluster for free:
|
||||
|
||||

|
||||
|
||||
The signup process will walk you through the process of creating your cluster and ensuring it's configured for you to access. Once the cluster is created, choose "Connect" and then "Connect to your application". Choose Python, and you'll be presented with a connection string that looks like this:
|
||||
|
||||

|
||||
|
||||
### Set up environment variables
|
||||
|
||||
Copy the connection string (make sure you include your password) and put it into a file called `.env` in the root of this repo. It should look like this:
|
||||
|
||||
```
|
||||
MONGODB_URI=mongodb+srv://seldo:xxxxxxxxxxx@llamaindexdemocluster.xfrdhpz.mongodb.net/?retryWrites=true&w=majority
|
||||
```
|
||||
|
||||
You will also need to choose a name for your database, and the collection where we will store the tweets, and also include them in .env. They can be any string, but this is what we used:
|
||||
|
||||
```
|
||||
MONGODB_DATABASE=tiny_tweets_db
|
||||
MONGODB_COLLECTION=tiny_tweets_collection
|
||||
```
|
||||
|
||||
### Import tweets into MongoDB
|
||||
|
||||
You are now ready to import our ready-made data set into Mongo. This is the file `tinytweets.json`, a selection of approximately 1000 tweets from @seldo on Twitter in mid-2019. With your environment set up you can do this by running
|
||||
|
||||
```
|
||||
pnpm ts-node 1_import.ts
|
||||
```
|
||||
|
||||
If you don't want to use tweets, you can replace `json_file` with any other array of JSON objects, but you will need to modify some code later to make sure the correct field gets indexed. There is no LlamaIndex-specific code here; you can load your data into Mongo any way you want to.
|
||||
|
||||
### Load and index your data
|
||||
|
||||
Now we're ready to index our data. To do this, LlamaIndex will pull your text out of Mongo, split it into chunks, and then send those chunks to OpenAI to be turned into [vector embeddings](https://docs.llamaindex.ai/en/stable/understanding/indexing/indexing.html#what-is-an-embedding). The embeddings will then be stored in a new collection in Mongo. This will take a while depending how much text you have, but the good news is that once it's done you will be able to query quickly without needing to re-index.
|
||||
|
||||
We'll be using OpenAI to do the embedding, so now is when you need to [generate an OpenAI API key](https://platform.openai.com/account/api-keys) if you haven't already and add it to your `.env` file like this:
|
||||
|
||||
```
|
||||
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
You'll also need to pick a name for the new collection where the embeddings will be stored, and add it to `.env`, along with the name of a vector search index (we'll be creating this in the next step, after you've indexed your data):
|
||||
|
||||
```
|
||||
MONGODB_VECTORS=tiny_tweets_vectors
|
||||
MONGODB_VECTOR_INDEX=tiny_tweets_vector_index
|
||||
```
|
||||
|
||||
If the data you're indexing is the tweets we gave you, you're ready to go:
|
||||
|
||||
```bash
|
||||
pnpm ts-node 2_load_and_index.ts
|
||||
```
|
||||
|
||||
> Note: this script is running a couple of minutes and currently doesn't show any progress.
|
||||
|
||||
What you're doing here is creating a Reader which loads the data out of Mongo in the collection and database specified. It looks for text in a set of specific keys in each object. In this case we've given it just one key, "full_text".
|
||||
|
||||
Now you're creating a vector search client for Mongo. In addition to a MongoDB client object, you again tell it what database everything is in. This time you give it the name of the collection where you'll store the vector embeddings, and the name of the vector search index you'll create in the next step.
|
||||
|
||||
### Create a vector search index
|
||||
|
||||
Now if all has gone well you should be able to log in to the Mongo Atlas UI and see two collections in your database: the original data in `tiny_tweets_collection`, and the vector embeddings in `tiny_tweets_vectors`.
|
||||
|
||||

|
||||
|
||||
Now it's time to create the vector search index so that you can query the data.
|
||||
It's not yet possible to programmatically create a vector search index using the [`createIndex`](https://www.mongodb.com/docs/manual/reference/method/db.collection.createIndex/) function, therefore we have to create one manually in the UI.
|
||||
To do so, first, click the Search tab, and then click "Create Search Index":
|
||||
|
||||

|
||||
|
||||
We have to use the JSON editor, as the Visual Editor does not yet support to create a vector search index:
|
||||
|
||||

|
||||
|
||||
Now under "database and collection" select `tiny_tweets_db` and within that select `tiny_tweets_vectors`. Then under "Index name" enter `tiny_tweets_vector_index` (or whatever value you put for MONGODB_VECTOR_INDEX in `.env`). Under that, you'll want to enter this JSON object:
|
||||
|
||||
```json
|
||||
{
|
||||
"mappings": {
|
||||
"dynamic": true,
|
||||
"fields": {
|
||||
"embedding": {
|
||||
"dimensions": 1536,
|
||||
"similarity": "cosine",
|
||||
"type": "knnVector"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This tells Mongo that the `embedding` field in each document (in the `tiny_tweets_vectors` collection) is a vector of 1536 dimensions (this is the size of embeddings used by OpenAI), and that we want to use cosine similarity to compare vectors. You don't need to worry too much about these values unless you want to use a different LLM to OpenAI entirely.
|
||||
|
||||
The UI will ask you to review and confirm your choices, then you need to wait a minute or two while it generates the index. If all goes well, you should see something like this screen:
|
||||
|
||||

|
||||
|
||||
Now you're ready to query your data!
|
||||
|
||||
### Run a test query
|
||||
|
||||
You can do this by running
|
||||
|
||||
```bash
|
||||
pnpm ts-node 3_query.ts
|
||||
```
|
||||
|
||||
This sets up a connection to Atlas just like `2_load_and_index.ts` did, then it creates a [query engine](https://docs.llamaindex.ai/en/stable/understanding/querying/querying.html#getting-started) and runs a query against it.
|
||||
|
||||
If all is well, you should get a nuanced opinion about web frameworks.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 360 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 230 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 278 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 211 KiB |
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"name": "mongodb-llamaindexts",
|
||||
"dependencies": {
|
||||
"llamaindex": "workspace:*",
|
||||
"dotenv": "^16.3.1",
|
||||
"mongodb": "^6.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.18.6",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint ."
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ import readline from "node:readline/promises";
|
||||
import { ChatMessage, LlamaDeuce, OpenAI } from "llamaindex";
|
||||
|
||||
(async () => {
|
||||
const gpt4 = new OpenAI({ model: "gpt-4-vision-preview", temperature: 0.9 });
|
||||
const gpt4 = new OpenAI({ model: "gpt-4", temperature: 0.9 });
|
||||
const l2 = new LlamaDeuce({
|
||||
model: "Llama-2-70b-chat-4bit",
|
||||
temperature: 0.9,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ChatMessage, OpenAI, SimpleChatEngine } from "llamaindex";
|
||||
import {Anthropic} from "../../packages/core/src/llm/LLM";
|
||||
import { ChatMessage, SimpleChatEngine } from "llamaindex";
|
||||
import { stdin as input, stdout as output } from "node:process";
|
||||
import readline from "node:readline/promises";
|
||||
import { Anthropic } from "../../packages/core/src/llm/LLM";
|
||||
|
||||
async function main() {
|
||||
const query: string = `
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MongoClient } from "mongodb";
|
||||
import { VectorStoreIndex } from "../../packages/core/src/indices";
|
||||
import { Document } from "../../packages/core/src/Node";
|
||||
import { VectorStoreIndex } from "../../packages/core/src/indices";
|
||||
import { SimpleMongoReader } from "../../packages/core/src/readers/SimpleMongoReader";
|
||||
|
||||
import { stdin as input, stdout as output } from "node:process";
|
||||
|
||||
+11
-11
@@ -1,23 +1,23 @@
|
||||
import { Portkey } from "llamaindex";
|
||||
|
||||
(async () => {
|
||||
const llms = [{
|
||||
|
||||
}]
|
||||
const llms = [{}];
|
||||
const portkey = new Portkey({
|
||||
mode: "single",
|
||||
llms: [{
|
||||
provider:"anyscale",
|
||||
virtual_key:"anyscale-3b3c04",
|
||||
model: "meta-llama/Llama-2-13b-chat-hf",
|
||||
max_tokens: 2000
|
||||
}]
|
||||
llms: [
|
||||
{
|
||||
provider: "anyscale",
|
||||
virtual_key: "anyscale-3b3c04",
|
||||
model: "meta-llama/Llama-2-13b-chat-hf",
|
||||
max_tokens: 2000,
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = portkey.stream_chat([
|
||||
{ role: "system", content: "You are a helpful assistant." },
|
||||
{ role: "user", content: "Tell me a joke." }
|
||||
{ role: "user", content: "Tell me a joke." },
|
||||
]);
|
||||
for await (const res of result) {
|
||||
process.stdout.write(res)
|
||||
process.stdout.write(res);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { SimpleDirectoryReader } from "llamaindex";
|
||||
|
||||
function callback(
|
||||
category: string,
|
||||
name: string,
|
||||
status: any,
|
||||
message?: string,
|
||||
): boolean {
|
||||
console.log(category, name, status, message);
|
||||
if (name.endsWith(".pdf")) {
|
||||
console.log("I DON'T WANT PDF FILES!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Load page
|
||||
const reader = new SimpleDirectoryReader(callback);
|
||||
const params = { directoryPath: "./data" };
|
||||
await reader.loadData(params);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,21 @@
|
||||
import { HTMLReader, VectorStoreIndex } from "llamaindex";
|
||||
|
||||
async function main() {
|
||||
// Load page
|
||||
const reader = new HTMLReader();
|
||||
const documents = await reader.loadData("data/18-1_Changelog.html");
|
||||
|
||||
// Split text and create embeddings. Store them in a VectorStoreIndex
|
||||
const index = await VectorStoreIndex.fromDocuments(documents);
|
||||
|
||||
// Query the index
|
||||
const queryEngine = index.asQueryEngine();
|
||||
const response = await queryEngine.query(
|
||||
"What were the notable changes in 18.1?",
|
||||
);
|
||||
|
||||
// Output response
|
||||
console.log(response.toString());
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ChatMessage, SimpleChatEngine } from "llamaindex";
|
||||
import { stdin as input, stdout as output } from "node:process";
|
||||
import readline from "node:readline/promises";
|
||||
import { Anthropic } from "../../packages/core/src/llm/LLM";
|
||||
|
||||
async function main() {
|
||||
const query: string = `
|
||||
Where is Istanbul?
|
||||
`;
|
||||
|
||||
// const llm = new OpenAI({ model: "gpt-3.5-turbo", temperature: 0.1 });
|
||||
const llm = new Anthropic();
|
||||
const message: ChatMessage = { content: query, role: "user" };
|
||||
|
||||
//TODO: Add callbacks later
|
||||
|
||||
//Stream Complete
|
||||
//Note: Setting streaming flag to true or false will auto-set your return type to
|
||||
//either an AsyncGenerator or a Response.
|
||||
// Omitting the streaming flag automatically sets streaming to false
|
||||
|
||||
const chatEngine: SimpleChatEngine = new SimpleChatEngine({
|
||||
chatHistory: undefined,
|
||||
llm: llm,
|
||||
});
|
||||
|
||||
const rl = readline.createInterface({ input, output });
|
||||
while (true) {
|
||||
const query = await rl.question("Query: ");
|
||||
|
||||
if (!query) {
|
||||
break;
|
||||
}
|
||||
|
||||
//Case 1: .chat(query, undefined, true) => Stream
|
||||
//Case 2: .chat(query, undefined, false) => Response object
|
||||
//Case 3: .chat(query, undefined) => Response object
|
||||
const chatStream = await chatEngine.chat(query, undefined, true);
|
||||
var accumulated_result = "";
|
||||
for await (const part of chatStream) {
|
||||
accumulated_result += part;
|
||||
process.stdout.write(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,68 @@
|
||||
import { MongoClient } from "mongodb";
|
||||
import { Document } from "../../packages/core/src/Node";
|
||||
import { VectorStoreIndex } from "../../packages/core/src/indices";
|
||||
import { SimpleMongoReader } from "../../packages/core/src/readers/SimpleMongoReader";
|
||||
|
||||
import { stdin as input, stdout as output } from "node:process";
|
||||
import readline from "node:readline/promises";
|
||||
|
||||
async function main() {
|
||||
//Dummy test code
|
||||
const query: object = { _id: "waldo" };
|
||||
const options: object = {};
|
||||
const projections: object = { embedding: 0 };
|
||||
const limit: number = Infinity;
|
||||
const uri: string = process.env.MONGODB_URI ?? "fake_uri";
|
||||
const client: MongoClient = new MongoClient(uri);
|
||||
|
||||
//Where the real code starts
|
||||
const MR = new SimpleMongoReader(client);
|
||||
const documents: Document[] = await MR.loadData(
|
||||
"data",
|
||||
"posts",
|
||||
1,
|
||||
{},
|
||||
options,
|
||||
projections,
|
||||
);
|
||||
|
||||
//
|
||||
//If you need to look at low-level details of
|
||||
// a queryEngine (for example, needing to check each individual node)
|
||||
//
|
||||
|
||||
// Split text and create embeddings. Store them in a VectorStoreIndex
|
||||
// var storageContext = await storageContextFromDefaults({});
|
||||
// var serviceContext = serviceContextFromDefaults({});
|
||||
// const docStore = storageContext.docStore;
|
||||
|
||||
// for (const doc of documents) {
|
||||
// docStore.setDocumentHash(doc.id_, doc.hash);
|
||||
// }
|
||||
// const nodes = serviceContext.nodeParser.getNodesFromDocuments(documents);
|
||||
// console.log(nodes);
|
||||
|
||||
//
|
||||
//Making Vector Store from documents
|
||||
//
|
||||
|
||||
const index = await VectorStoreIndex.fromDocuments(documents);
|
||||
// Create query engine
|
||||
const queryEngine = index.asQueryEngine();
|
||||
|
||||
const rl = readline.createInterface({ input, output });
|
||||
while (true) {
|
||||
const query = await rl.question("Query: ");
|
||||
|
||||
if (!query) {
|
||||
break;
|
||||
}
|
||||
|
||||
const response = await queryEngine.query(query);
|
||||
|
||||
// Output response
|
||||
console.log(response.toString());
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
+11
-11
@@ -1,23 +1,23 @@
|
||||
import { Portkey } from "llamaindex";
|
||||
|
||||
(async () => {
|
||||
const llms = [{
|
||||
|
||||
}]
|
||||
const llms = [{}];
|
||||
const portkey = new Portkey({
|
||||
mode: "single",
|
||||
llms: [{
|
||||
provider:"anyscale",
|
||||
virtual_key:"anyscale-3b3c04",
|
||||
model: "meta-llama/Llama-2-13b-chat-hf",
|
||||
max_tokens: 2000
|
||||
}]
|
||||
llms: [
|
||||
{
|
||||
provider: "anyscale",
|
||||
virtual_key: "anyscale-3b3c04",
|
||||
model: "meta-llama/Llama-2-13b-chat-hf",
|
||||
max_tokens: 2000,
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = portkey.stream_chat([
|
||||
{ role: "system", content: "You are a helpful assistant." },
|
||||
{ role: "user", content: "Tell me a joke." }
|
||||
{ role: "user", content: "Tell me a joke." },
|
||||
]);
|
||||
for await (const res of result) {
|
||||
process.stdout.write(res)
|
||||
process.stdout.write(res);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
OpenAI,
|
||||
RetrieverQueryEngine,
|
||||
serviceContextFromDefaults,
|
||||
SimilarityPostprocessor,
|
||||
VectorStoreIndex,
|
||||
} from "llamaindex";
|
||||
import essay from "./essay";
|
||||
@@ -21,8 +22,16 @@ async function main() {
|
||||
|
||||
const retriever = index.asRetriever();
|
||||
retriever.similarityTopK = 5;
|
||||
const nodePostprocessor = new SimilarityPostprocessor({
|
||||
similarityCutoff: 0.7,
|
||||
});
|
||||
// TODO: cannot pass responseSynthesizer into retriever query engine
|
||||
const queryEngine = new RetrieverQueryEngine(retriever);
|
||||
const queryEngine = new RetrieverQueryEngine(
|
||||
retriever,
|
||||
undefined,
|
||||
undefined,
|
||||
[nodePostprocessor],
|
||||
);
|
||||
|
||||
const response = await queryEngine.query(
|
||||
"What did the author do growing up?",
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import {
|
||||
OpenAI,
|
||||
ResponseSynthesizer,
|
||||
RetrieverQueryEngine,
|
||||
serviceContextFromDefaults,
|
||||
TextNode,
|
||||
TreeSummarize,
|
||||
VectorIndexRetriever,
|
||||
VectorStore,
|
||||
VectorStoreIndex,
|
||||
VectorStoreQuery,
|
||||
VectorStoreQueryResult,
|
||||
} from "llamaindex";
|
||||
|
||||
import { Index, Pinecone, RecordMetadata } from "@pinecone-database/pinecone";
|
||||
|
||||
/**
|
||||
* Please do not use this class in production; it's only for demonstration purposes.
|
||||
*/
|
||||
class PineconeVectorStore<T extends RecordMetadata = RecordMetadata>
|
||||
implements VectorStore
|
||||
{
|
||||
storesText = true;
|
||||
isEmbeddingQuery = false;
|
||||
|
||||
indexName!: string;
|
||||
pineconeClient!: Pinecone;
|
||||
index!: Index<T>;
|
||||
|
||||
constructor({ indexName, client }: { indexName: string; client: Pinecone }) {
|
||||
this.indexName = indexName;
|
||||
this.pineconeClient = client;
|
||||
this.index = client.index<T>(indexName);
|
||||
}
|
||||
|
||||
client() {
|
||||
return this.pineconeClient;
|
||||
}
|
||||
|
||||
async query(
|
||||
query: VectorStoreQuery,
|
||||
kwargs?: any,
|
||||
): Promise<VectorStoreQueryResult> {
|
||||
let queryEmbedding: number[] = [];
|
||||
if (query.queryEmbedding) {
|
||||
if (typeof query.alpha === "number") {
|
||||
const alpha = query.alpha;
|
||||
queryEmbedding = query.queryEmbedding.map((v) => v * alpha);
|
||||
} else {
|
||||
queryEmbedding = query.queryEmbedding;
|
||||
}
|
||||
}
|
||||
|
||||
// Current LlamaIndexTS implementation only support exact match filter, so we use kwargs instead.
|
||||
const filter = kwargs?.filter || {};
|
||||
|
||||
const response = await this.index.query({
|
||||
filter,
|
||||
vector: queryEmbedding,
|
||||
topK: query.similarityTopK,
|
||||
includeValues: true,
|
||||
includeMetadata: true,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Numbers of vectors returned by Pinecone after preFilters are applied: ${
|
||||
response?.matches?.length || 0
|
||||
}.`,
|
||||
);
|
||||
|
||||
const topKIds: string[] = [];
|
||||
const topKNodes: TextNode[] = [];
|
||||
const topKScores: number[] = [];
|
||||
|
||||
const metadataToNode = (metadata?: T): Partial<TextNode> => {
|
||||
if (!metadata) {
|
||||
throw new Error("metadata is undefined.");
|
||||
}
|
||||
|
||||
const nodeContent = metadata["_node_content"];
|
||||
if (!nodeContent) {
|
||||
throw new Error("nodeContent is undefined.");
|
||||
}
|
||||
|
||||
if (typeof nodeContent !== "string") {
|
||||
throw new Error("nodeContent is not a string.");
|
||||
}
|
||||
|
||||
return JSON.parse(nodeContent);
|
||||
};
|
||||
|
||||
if (response.matches) {
|
||||
for (const match of response.matches) {
|
||||
const node = new TextNode({
|
||||
...metadataToNode(match.metadata),
|
||||
embedding: match.values,
|
||||
});
|
||||
|
||||
topKIds.push(match.id);
|
||||
topKNodes.push(node);
|
||||
topKScores.push(match.score ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
const result = {
|
||||
ids: topKIds,
|
||||
nodes: topKNodes,
|
||||
similarities: topKScores,
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
add(): Promise<string[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
delete(): Promise<void> {
|
||||
throw new Error("Method `delete` not implemented.");
|
||||
}
|
||||
|
||||
persist(): Promise<void> {
|
||||
throw new Error("Method `persist` not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The goal of this example is to show how to use Pinecone as a vector store
|
||||
* for LlamaIndexTS with(out) preFilters.
|
||||
*
|
||||
* It should not be used in production like that,
|
||||
* as you might want to find a proper PineconeVectorStore implementation.
|
||||
*/
|
||||
async function main() {
|
||||
process.env.PINECONE_API_KEY = "Your Pinecone API Key.";
|
||||
process.env.PINECONE_ENVIRONMENT = "Your Pinecone Environment.";
|
||||
process.env.PINECONE_PROJECT_ID = "Your Pinecone Project ID.";
|
||||
process.env.PINECONE_INDEX_NAME = "Your Pinecone Index Name.";
|
||||
process.env.OPENAI_API_KEY = "Your OpenAI API Key.";
|
||||
process.env.OPENAI_API_ORGANIZATION = "Your OpenAI API Organization.";
|
||||
|
||||
const getPineconeVectorStore = async () => {
|
||||
return new PineconeVectorStore({
|
||||
indexName: process.env.PINECONE_INDEX_NAME || "index-name",
|
||||
client: new Pinecone(),
|
||||
});
|
||||
};
|
||||
|
||||
const getServiceContext = () => {
|
||||
const openAI = new OpenAI({
|
||||
model: "gpt-4",
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
});
|
||||
|
||||
return serviceContextFromDefaults({
|
||||
llm: openAI,
|
||||
});
|
||||
};
|
||||
|
||||
const getQueryEngine = async (filter: unknown) => {
|
||||
const vectorStore = await getPineconeVectorStore();
|
||||
const serviceContext = getServiceContext();
|
||||
|
||||
const vectorStoreIndex = await VectorStoreIndex.fromVectorStore(
|
||||
vectorStore,
|
||||
serviceContext,
|
||||
);
|
||||
|
||||
const retriever = new VectorIndexRetriever({
|
||||
index: vectorStoreIndex,
|
||||
similarityTopK: 500,
|
||||
});
|
||||
|
||||
const responseSynthesizer = new ResponseSynthesizer({
|
||||
serviceContext,
|
||||
responseBuilder: new TreeSummarize(serviceContext),
|
||||
});
|
||||
|
||||
return new RetrieverQueryEngine(retriever, responseSynthesizer, {
|
||||
filter,
|
||||
});
|
||||
};
|
||||
|
||||
// whatever is a key from your metadata
|
||||
const queryEngine = await getQueryEngine({
|
||||
whatever: {
|
||||
$gte: 1,
|
||||
$lte: 100,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await queryEngine.query("How many results do you have?");
|
||||
|
||||
console.log(response.toString());
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,15 @@
|
||||
import { OpenAI } from "llamaindex";
|
||||
|
||||
(async () => {
|
||||
const llm = new OpenAI({ model: "gpt-4-vision-preview", temperature: 0.1 });
|
||||
|
||||
// complete api
|
||||
const response1 = await llm.complete("How are you?");
|
||||
console.log(response1.message.content);
|
||||
|
||||
// chat api
|
||||
const response2 = await llm.chat([
|
||||
{ content: "Tell me a joke!", role: "user" },
|
||||
]);
|
||||
console.log(response2.message.content);
|
||||
})();
|
||||
+11
-9
@@ -3,7 +3,7 @@
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev",
|
||||
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
||||
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,md}\"",
|
||||
"lint": "turbo run lint",
|
||||
"prepare": "husky install",
|
||||
"test": "turbo run test",
|
||||
@@ -11,25 +11,27 @@
|
||||
"publish-snapshot": "turbo run build lint test && changeset version --snapshot && changeset publish"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.26.2",
|
||||
"@turbo/gen": "^1.10.16",
|
||||
"@types/jest": "^29.5.6",
|
||||
"eslint": "^8.52.0",
|
||||
"@types/jest": "^29.5.8",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier-plugin-organize-imports": "^3.2.3",
|
||||
"lint-staged": "^15.1.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"ts-jest": "^29.1.1",
|
||||
"turbo": "^1.10.16"
|
||||
},
|
||||
"packageManager": "pnpm@8.10.4+sha256.df3202c6c8fd345be5ba6a4199297582d5bebf8963822aa3344f4cd2b8be8d43",
|
||||
"dependencies": {
|
||||
"@changesets/cli": "^2.26.2"
|
||||
},
|
||||
"packageManager": "pnpm@8.10.5+sha256.a4bd9bb7b48214bbfcd95f264bd75bb70d100e5d4b58808f5cd6ab40c6ac21c5",
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"trim": "1.0.1",
|
||||
"@babel/traverse": "7.23.2"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,md}": "prettier --write"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 63f2108: Add multimodal support (thanks Marcus)
|
||||
- 63f2108: Add multimodal support (thanks @marcusschiesser)
|
||||
|
||||
## 0.0.34
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.9.0",
|
||||
"@notionhq/client": "^2.2.13",
|
||||
"crypto-js": "^4.2.0",
|
||||
"js-tiktoken": "^1.0.7",
|
||||
"lodash": "^4.17.21",
|
||||
"mammoth": "^1.6.0",
|
||||
@@ -16,12 +17,13 @@
|
||||
"pdf-parse": "^1.1.1",
|
||||
"portkey-ai": "^0.1.16",
|
||||
"rake-modified": "^1.0.8",
|
||||
"replicate": "^0.20.1",
|
||||
"replicate": "^0.21.1",
|
||||
"string-strip-html": "^13.4.3",
|
||||
"uuid": "^9.0.1",
|
||||
"wink-nlp": "^1.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.2.1",
|
||||
"@types/lodash": "^4.14.200",
|
||||
"@types/node": "^18.18.8",
|
||||
"@types/papaparse": "^5.3.10",
|
||||
@@ -44,4 +46,4 @@
|
||||
"build": "tsup src/index.ts --format esm,cjs --dts",
|
||||
"dev": "tsup src/index.ts --format esm,cjs --dts --watch"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { encodingForModel, TiktokenModel } from "js-tiktoken";
|
||||
import { encodingForModel } from "js-tiktoken";
|
||||
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Event, EventTag, EventType } from "./callbacks/CallbackManager";
|
||||
@@ -27,7 +27,7 @@ class GlobalsHelper {
|
||||
const numberArray = Array.from(tokens);
|
||||
const text = encoding.decode(numberArray);
|
||||
const uint8Array = new TextEncoder().encode(text);
|
||||
return new TextDecoder().decode(uint8Array);
|
||||
return new TextDecoder().decode(uint8Array);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -39,10 +39,10 @@ class GlobalsHelper {
|
||||
if (!this.defaultTokenizer) {
|
||||
this.initDefaultTokenizer();
|
||||
}
|
||||
|
||||
|
||||
return this.defaultTokenizer!.encode.bind(this.defaultTokenizer);
|
||||
}
|
||||
|
||||
|
||||
tokenizerDecoder(encoding?: string) {
|
||||
if (encoding && encoding !== Tokenizers.CL100K_BASE) {
|
||||
throw new Error(`Tokenizer encoding ${encoding} not yet supported`);
|
||||
@@ -50,7 +50,7 @@ class GlobalsHelper {
|
||||
if (!this.defaultTokenizer) {
|
||||
this.initDefaultTokenizer();
|
||||
}
|
||||
|
||||
|
||||
return this.defaultTokenizer!.decode.bind(this.defaultTokenizer);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import crypto from "crypto"; // TODO Node dependency
|
||||
import CryptoJS from "crypto-js";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
export enum NodeRelationship {
|
||||
@@ -175,13 +175,13 @@ export class TextNode<T extends Metadata = Metadata> extends BaseNode<T> {
|
||||
* @returns
|
||||
*/
|
||||
generateHash() {
|
||||
const hashFunction = crypto.createHash("sha256");
|
||||
const hashFunction = CryptoJS.algo.SHA256.create();
|
||||
hashFunction.update(`type=${this.getType()}`);
|
||||
hashFunction.update(
|
||||
`startCharIdx=${this.startCharIdx} endCharIdx=${this.endCharIdx}`,
|
||||
);
|
||||
hashFunction.update(this.getContent(MetadataMode.ALL));
|
||||
return hashFunction.digest("base64");
|
||||
return hashFunction.finalize().toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
|
||||
getType(): ObjectType {
|
||||
@@ -272,12 +272,13 @@ export class Document<T extends Metadata = Metadata> extends TextNode<T> {
|
||||
}
|
||||
}
|
||||
|
||||
export function jsonToNode(json: any) {
|
||||
if (!json.type) {
|
||||
export function jsonToNode(json: any, type?: ObjectType) {
|
||||
if (!json.type && !type) {
|
||||
throw new Error("Node type not found");
|
||||
}
|
||||
const nodeType = type || json.type;
|
||||
|
||||
switch (json.type) {
|
||||
switch (nodeType) {
|
||||
case ObjectType.TEXT:
|
||||
return new TextNode(json);
|
||||
case ObjectType.INDEX:
|
||||
@@ -285,7 +286,7 @@ export function jsonToNode(json: any) {
|
||||
case ObjectType.DOCUMENT:
|
||||
return new Document(json);
|
||||
default:
|
||||
throw new Error(`Invalid node type: ${json.type}`);
|
||||
throw new Error(`Invalid node type: ${nodeType}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Event } from "./callbacks/CallbackManager";
|
||||
import { BaseNodePostprocessor } from "./indices/BaseNodePostprocessor";
|
||||
import { NodeWithScore, TextNode } from "./Node";
|
||||
import {
|
||||
BaseQuestionGenerator,
|
||||
@@ -12,6 +10,8 @@ import { CompactAndRefine, ResponseSynthesizer } from "./ResponseSynthesizer";
|
||||
import { BaseRetriever } from "./Retriever";
|
||||
import { ServiceContext, serviceContextFromDefaults } from "./ServiceContext";
|
||||
import { QueryEngineTool, ToolMetadata } from "./Tool";
|
||||
import { Event } from "./callbacks/CallbackManager";
|
||||
import { BaseNodePostprocessor } from "./indices/BaseNodePostprocessor";
|
||||
|
||||
/**
|
||||
* A query engine is a question answerer that can use one or more steps.
|
||||
|
||||
+13
-12
@@ -1,11 +1,7 @@
|
||||
export * from "./callbacks/CallbackManager";
|
||||
export * from "./ChatEngine";
|
||||
export * from "./ChatHistory";
|
||||
export * from "./constants";
|
||||
export * from "./Embedding";
|
||||
export * from "./GlobalsHelper";
|
||||
export * from "./indices";
|
||||
export * from "./llm/LLM";
|
||||
export * from "./Node";
|
||||
export * from "./NodeParser";
|
||||
export * from "./OutputParser";
|
||||
@@ -13,17 +9,22 @@ export * from "./Prompt";
|
||||
export * from "./PromptHelper";
|
||||
export * from "./QueryEngine";
|
||||
export * from "./QuestionGenerator";
|
||||
export * from "./readers/base";
|
||||
export * from "./readers/CSVReader";
|
||||
export * from "./readers/MarkdownReader";
|
||||
export * from "./readers/NotionReader";
|
||||
export * from "./readers/PDFReader";
|
||||
export * from "./readers/HTMLReader";
|
||||
export * from "./readers/SimpleDirectoryReader";
|
||||
export * from "./Response";
|
||||
export * from "./ResponseSynthesizer";
|
||||
export * from "./Retriever";
|
||||
export * from "./ServiceContext";
|
||||
export * from "./storage";
|
||||
export * from "./TextSplitter";
|
||||
export * from "./Tool";
|
||||
export * from "./callbacks/CallbackManager";
|
||||
export * from "./constants";
|
||||
export * from "./indices";
|
||||
export * from "./llm/LLM";
|
||||
export * from "./readers/CSVReader";
|
||||
export * from "./readers/HTMLReader";
|
||||
export * from "./readers/MarkdownReader";
|
||||
export * from "./readers/NotionReader";
|
||||
export * from "./readers/PDFReader";
|
||||
export * from "./readers/SimpleDirectoryReader";
|
||||
export * from "./readers/SimpleMongoReader";
|
||||
export * from "./readers/base";
|
||||
export * from "./storage";
|
||||
|
||||
@@ -10,11 +10,11 @@ import {
|
||||
ServiceContext,
|
||||
serviceContextFromDefaults,
|
||||
} from "../../ServiceContext";
|
||||
import { BaseDocumentStore, RefDocInfo } from "../../storage/docStore/types";
|
||||
import {
|
||||
StorageContext,
|
||||
storageContextFromDefaults,
|
||||
} from "../../storage/StorageContext";
|
||||
import { BaseDocumentStore, RefDocInfo } from "../../storage/docStore/types";
|
||||
import {
|
||||
BaseIndex,
|
||||
BaseIndexInit,
|
||||
|
||||
@@ -32,7 +32,11 @@ export class VectorIndexRetriever implements BaseRetriever {
|
||||
this.similarityTopK = similarityTopK ?? DEFAULT_SIMILARITY_TOP_K;
|
||||
}
|
||||
|
||||
async retrieve(query: string, parentEvent?: Event, preFilters?: unknown): Promise<NodeWithScore[]> {
|
||||
async retrieve(
|
||||
query: string,
|
||||
parentEvent?: Event,
|
||||
preFilters?: unknown,
|
||||
): Promise<NodeWithScore[]> {
|
||||
const queryEmbedding =
|
||||
await this.serviceContext.embedModel.getQueryEmbedding(query);
|
||||
|
||||
|
||||
@@ -377,10 +377,10 @@ export const ALL_AVAILABLE_LLAMADEUCE_MODELS = {
|
||||
"Llama-2-70b-chat-4bit": {
|
||||
contextWindow: 4096,
|
||||
replicateApi:
|
||||
"replicate/llama70b-v2-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1",
|
||||
"meta/llama-2-70b-chat:02e509c789964a7ea8736978a43525956ef40397be9033abf9fd2badfe68c9e3",
|
||||
//^ Model is based off of exllama 4bit.
|
||||
},
|
||||
"Llama-2-13b-chat": {
|
||||
"Llama-2-13b-chat-old": {
|
||||
contextWindow: 4096,
|
||||
replicateApi:
|
||||
"a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5",
|
||||
@@ -389,9 +389,9 @@ export const ALL_AVAILABLE_LLAMADEUCE_MODELS = {
|
||||
"Llama-2-13b-chat-4bit": {
|
||||
contextWindow: 4096,
|
||||
replicateApi:
|
||||
"a16z-infra/llama13b-v2-chat:2a7f981751ec7fdf87b5b91ad4db53683a98082e9ff7bfd12c8cd5ea85980a52",
|
||||
"meta/llama-2-13b-chat:f4e2de70d66816a838a89eeeb621910adffb0dd0baba3976c96980970978018d",
|
||||
},
|
||||
"Llama-2-7b-chat": {
|
||||
"Llama-2-7b-chat-old": {
|
||||
contextWindow: 4096,
|
||||
replicateApi:
|
||||
"a16z-infra/llama7b-v2-chat:4f0a4744c7295c024a1de15e1a63c880d3da035fa1f49bfd344fe076074c8eea",
|
||||
@@ -403,7 +403,7 @@ export const ALL_AVAILABLE_LLAMADEUCE_MODELS = {
|
||||
"Llama-2-7b-chat-4bit": {
|
||||
contextWindow: 4096,
|
||||
replicateApi:
|
||||
"a16z-infra/llama7b-v2-chat:4f0b260b6a13eb53a6b1891f089d57c08f41003ae79458be5011303d81a394dc",
|
||||
"meta/llama-2-7b-chat:13c3cdee13ee059ab779f0291d29054dab00a47dad8261375654de5540165fb0",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -415,6 +415,8 @@ export enum DeuceChatStrategy {
|
||||
// Unfortunately any string only API won't support these properly.
|
||||
REPLICATE4BIT = "replicate4bit",
|
||||
//^ To satisfy Replicate's 4 bit models' requirements where they also insert some INST tags
|
||||
REPLICATE4BITWNEWLINES = "replicate4bitwnewlines",
|
||||
//^ Replicate's documentation recommends using newlines: https://replicate.com/blog/how-to-prompt-llama
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -434,7 +436,7 @@ export class LlamaDeuce implements LLM {
|
||||
this.chatStrategy =
|
||||
init?.chatStrategy ??
|
||||
(this.model.endsWith("4bit")
|
||||
? DeuceChatStrategy.REPLICATE4BIT // With the newer A16Z/Replicate models they do the system message themselves.
|
||||
? DeuceChatStrategy.REPLICATE4BITWNEWLINES // With the newer Replicate models they do the system message themselves.
|
||||
: DeuceChatStrategy.METAWBOS); // With BOS and EOS seems to work best, although they all have problems past a certain point
|
||||
this.temperature = init?.temperature ?? 0.1; // minimum temperature is 0.01 for Replicate endpoint
|
||||
this.topP = init?.topP ?? 1;
|
||||
@@ -468,7 +470,15 @@ export class LlamaDeuce implements LLM {
|
||||
} else if (this.chatStrategy === DeuceChatStrategy.METAWBOS) {
|
||||
return this.mapMessagesToPromptMeta(messages, { withBos: true });
|
||||
} else if (this.chatStrategy === DeuceChatStrategy.REPLICATE4BIT) {
|
||||
return this.mapMessagesToPromptMeta(messages, { replicate4Bit: true });
|
||||
return this.mapMessagesToPromptMeta(messages, {
|
||||
replicate4Bit: true,
|
||||
withNewlines: true,
|
||||
});
|
||||
} else if (this.chatStrategy === DeuceChatStrategy.REPLICATE4BITWNEWLINES) {
|
||||
return this.mapMessagesToPromptMeta(messages, {
|
||||
replicate4Bit: true,
|
||||
withNewlines: true,
|
||||
});
|
||||
} else {
|
||||
return this.mapMessagesToPromptMeta(messages);
|
||||
}
|
||||
@@ -503,9 +513,17 @@ export class LlamaDeuce implements LLM {
|
||||
|
||||
mapMessagesToPromptMeta(
|
||||
messages: ChatMessage[],
|
||||
opts?: { withBos?: boolean; replicate4Bit?: boolean },
|
||||
opts?: {
|
||||
withBos?: boolean;
|
||||
replicate4Bit?: boolean;
|
||||
withNewlines?: boolean;
|
||||
},
|
||||
) {
|
||||
const { withBos = false, replicate4Bit = false } = opts ?? {};
|
||||
const {
|
||||
withBos = false,
|
||||
replicate4Bit = false,
|
||||
withNewlines = false,
|
||||
} = opts ?? {};
|
||||
const DEFAULT_SYSTEM_PROMPT = `You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.
|
||||
|
||||
If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.`;
|
||||
@@ -553,11 +571,18 @@ If a question does not make any sense, or is not factually coherent, explain why
|
||||
return {
|
||||
prompt: messages.reduce((acc, message, index) => {
|
||||
if (index % 2 === 0) {
|
||||
return `${acc}${
|
||||
withBos ? BOS : ""
|
||||
}${B_INST} ${message.content.trim()} ${E_INST}`;
|
||||
return (
|
||||
`${acc}${
|
||||
withBos ? BOS : ""
|
||||
}${B_INST} ${message.content.trim()} ${E_INST}` +
|
||||
(withNewlines ? "\n" : "")
|
||||
);
|
||||
} else {
|
||||
return `${acc} ${message.content.trim()} ` + (withBos ? EOS : ""); // Yes, the EOS comes after the space. This is not a mistake.
|
||||
return (
|
||||
`${acc} ${message.content.trim()}` +
|
||||
(withNewlines ? "\n" : " ") +
|
||||
(withBos ? EOS : "")
|
||||
); // Yes, the EOS comes after the space. This is not a mistake.
|
||||
}
|
||||
}, ""),
|
||||
systemPrompt,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import _ from "lodash";
|
||||
import { LLMOptions, Portkey } from "portkey-ai";
|
||||
|
||||
export const readEnv = (env: string, default_val?: string): string | undefined => {
|
||||
if (typeof process !== 'undefined') {
|
||||
return process.env?.[env] ?? default_val;
|
||||
export const readEnv = (
|
||||
env: string,
|
||||
default_val?: string,
|
||||
): string | undefined => {
|
||||
if (typeof process !== "undefined") {
|
||||
return process.env?.[env] ?? default_val;
|
||||
}
|
||||
return default_val;
|
||||
};
|
||||
@@ -12,23 +15,23 @@ interface PortkeyOptions {
|
||||
apiKey?: string;
|
||||
baseURL?: string;
|
||||
mode?: string;
|
||||
llms?: [LLMOptions] | null
|
||||
llms?: [LLMOptions] | null;
|
||||
}
|
||||
|
||||
export class PortkeySession {
|
||||
portkey: Portkey;
|
||||
|
||||
constructor(options:PortkeyOptions = {}) {
|
||||
constructor(options: PortkeyOptions = {}) {
|
||||
if (!options.apiKey) {
|
||||
options.apiKey = readEnv('PORTKEY_API_KEY')
|
||||
options.apiKey = readEnv("PORTKEY_API_KEY");
|
||||
}
|
||||
|
||||
if (!options.baseURL) {
|
||||
options.baseURL = readEnv('PORTKEY_BASE_URL', "https://api.portkey.ai")
|
||||
options.baseURL = readEnv("PORTKEY_BASE_URL", "https://api.portkey.ai");
|
||||
}
|
||||
|
||||
this.portkey = new Portkey({});
|
||||
this.portkey.llms = [{}]
|
||||
this.portkey.llms = [{}];
|
||||
if (!options.apiKey) {
|
||||
throw new Error("Set Portkey ApiKey in PORTKEY_API_KEY env variable");
|
||||
}
|
||||
@@ -59,4 +62,3 @@ export function getPortkeySession(options: PortkeyOptions = {}) {
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import mammoth from "mammoth";
|
||||
import { Document } from "../Node";
|
||||
import { DEFAULT_FS } from "../storage/constants";
|
||||
import { GenericFileSystem } from "../storage/FileSystem";
|
||||
import { DEFAULT_FS } from "../storage/constants";
|
||||
import { BaseReader } from "./base";
|
||||
|
||||
export class DocxReader implements BaseReader {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MongoClient } from "mongodb";
|
||||
import { Document } from "../Node";
|
||||
import { Document, Metadata } from "../Node";
|
||||
import { BaseReader } from "./base";
|
||||
|
||||
/**
|
||||
@@ -13,39 +13,70 @@ export class SimpleMongoReader implements BaseReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data from MongoDB collection
|
||||
* @param {string} db_name - The name of the database to load.
|
||||
* @param {string} collection_name - The name of the collection to load.
|
||||
* @param {Number} [max_docs = 0] - Maximum number of documents to return. 0 means no limit.
|
||||
* @param {Record<string, any>} [query_dict={}] - Specific query, as specified by MongoDB NodeJS documentation.
|
||||
* @param {Record<string, any>} [query_options={}] - Specific query options, as specified by MongoDB NodeJS documentation.
|
||||
* @param {Record<string, any>} [projection = {}] - Projection options, as specified by MongoDB NodeJS documentation.
|
||||
* @returns {Promise<Document[]>}
|
||||
* Flattens an array of strings or string arrays into a single-dimensional array of strings.
|
||||
* @param texts - The array of strings or string arrays to flatten.
|
||||
* @returns The flattened array of strings.
|
||||
*/
|
||||
async loadData(
|
||||
db_name: string,
|
||||
collection_name: string,
|
||||
max_docs = 0,
|
||||
//For later: Think about whether we want to pass generic objects in...
|
||||
query_dict: Record<string, any> = {},
|
||||
query_options: Record<string, any> = {},
|
||||
projection: Record<string, any> = {},
|
||||
): Promise<Document[]> {
|
||||
//Get items from collection using built-in functions
|
||||
const cursor: Partial<Document>[] = await this.client
|
||||
.db(db_name)
|
||||
.collection(collection_name)
|
||||
.find(query_dict, query_options)
|
||||
.limit(max_docs)
|
||||
.project(projection)
|
||||
.toArray();
|
||||
private flatten(texts: Array<string | string[]>): string[] {
|
||||
return texts.reduce<string[]>(
|
||||
(result, text) => result.concat(text instanceof Array ? text : [text]),
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data from MongoDB collection
|
||||
* @param {string} dbName - The name of the database to load.
|
||||
* @param {string} collectionName - The name of the collection to load.
|
||||
* @param {string[]} fieldNames - An array of field names to retrieve from each document. Defaults to ["text"].
|
||||
* @param {string} separator - The separator to join multiple field values. Defaults to an empty string.
|
||||
* @param {Record<string, any>} filterQuery - Specific query, as specified by MongoDB NodeJS documentation.
|
||||
* @param {Number} maxDocs - The maximum number of documents to retrieve. Defaults to 0 (retrieve all documents).
|
||||
* @param {string[]} metadataNames - An optional array of metadata field names. If specified extracts this information as metadata.
|
||||
* @returns {Promise<Document[]>}
|
||||
* @throws If a field specified in fieldNames or metadataNames is not found in a MongoDB document.
|
||||
*/
|
||||
public async loadData(
|
||||
dbName: string,
|
||||
collectionName: string,
|
||||
fieldNames: string[] = ["text"],
|
||||
separator: string = "",
|
||||
filterQuery: Record<string, any> = {},
|
||||
maxDocs: number = 0,
|
||||
metadataNames?: string[],
|
||||
): Promise<Document[]> {
|
||||
const db = this.client.db(dbName);
|
||||
// Get items from collection
|
||||
const cursor = db
|
||||
.collection(collectionName)
|
||||
.find(filterQuery)
|
||||
.limit(maxDocs);
|
||||
|
||||
//Aggregate results and return
|
||||
const documents: Document[] = [];
|
||||
cursor.forEach((element: Partial<Document>) => {
|
||||
//For later: Metadata filtering
|
||||
documents.push(new Document({ text: JSON.stringify(element) }));
|
||||
});
|
||||
|
||||
for await (const item of cursor) {
|
||||
try {
|
||||
const texts: Array<string | string[]> = fieldNames.map(
|
||||
(name) => item[name],
|
||||
);
|
||||
const flattenedTexts = this.flatten(texts);
|
||||
const text = flattenedTexts.join(separator);
|
||||
|
||||
let metadata: Metadata = {};
|
||||
if (metadataNames) {
|
||||
// extract metadata if fields are specified
|
||||
metadata = Object.fromEntries(
|
||||
metadataNames.map((name) => [name, item[name]]),
|
||||
);
|
||||
}
|
||||
|
||||
documents.push(new Document({ text, metadata }));
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Field not found in Mongo document: ${(err as Error).message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return documents;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,6 @@ export { SimpleIndexStore } from "./indexStore/SimpleIndexStore";
|
||||
export * from "./indexStore/types";
|
||||
export { SimpleKVStore } from "./kvStore/SimpleKVStore";
|
||||
export * from "./kvStore/types";
|
||||
export { MongoDBAtlasVectorSearch } from "./vectorStore/MongoDBAtlasVectorStore";
|
||||
export { SimpleVectorStore } from "./vectorStore/SimpleVectorStore";
|
||||
export * from "./vectorStore/types";
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
import { BulkWriteOptions, Collection, MongoClient } from "mongodb";
|
||||
import { BaseNode, MetadataMode } from "../../Node";
|
||||
import {
|
||||
MetadataFilters,
|
||||
VectorStore,
|
||||
VectorStoreQuery,
|
||||
VectorStoreQueryResult,
|
||||
} from "./types";
|
||||
import { metadataDictToNode, nodeToMetadata } from "./utils";
|
||||
|
||||
// Utility function to convert metadata filters to MongoDB filter
|
||||
function toMongoDBFilter(
|
||||
standardFilters: MetadataFilters,
|
||||
): Record<string, any> {
|
||||
const filters: Record<string, any> = {};
|
||||
for (const filter of standardFilters.filters) {
|
||||
filters[filter.key] = filter.value;
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
// MongoDB Atlas Vector Store class implementing VectorStore
|
||||
export class MongoDBAtlasVectorSearch implements VectorStore {
|
||||
storesText: boolean = true;
|
||||
flatMetadata: boolean = true;
|
||||
|
||||
mongodbClient: MongoClient;
|
||||
indexName: string;
|
||||
embeddingKey: string;
|
||||
idKey: string;
|
||||
textKey: string;
|
||||
metadataKey: string;
|
||||
insertOptions?: BulkWriteOptions;
|
||||
private collection: Collection;
|
||||
|
||||
constructor(
|
||||
init: Partial<MongoDBAtlasVectorSearch> & {
|
||||
dbName: string;
|
||||
collectionName: string;
|
||||
},
|
||||
) {
|
||||
if (init.mongodbClient) {
|
||||
this.mongodbClient = init.mongodbClient;
|
||||
} else {
|
||||
const mongoUri = process.env.MONGODB_URI;
|
||||
if (!mongoUri) {
|
||||
throw new Error(
|
||||
"Must specify MONGODB_URI via env variable if not directly passing in client.",
|
||||
);
|
||||
}
|
||||
this.mongodbClient = new MongoClient(mongoUri);
|
||||
}
|
||||
|
||||
this.collection = this.mongodbClient
|
||||
.db(init.dbName ?? "default_db")
|
||||
.collection(init.collectionName ?? "default_collection");
|
||||
this.indexName = init.indexName ?? "default";
|
||||
this.embeddingKey = init.embeddingKey ?? "embedding";
|
||||
this.idKey = init.idKey ?? "id";
|
||||
this.textKey = init.textKey ?? "text";
|
||||
this.metadataKey = init.metadataKey ?? "metadata";
|
||||
this.insertOptions = init.insertOptions;
|
||||
}
|
||||
|
||||
async add(nodes: BaseNode[]): Promise<string[]> {
|
||||
if (!nodes || nodes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const dataToInsert = nodes.map((node) => {
|
||||
const metadata = nodeToMetadata(
|
||||
node,
|
||||
true,
|
||||
this.textKey,
|
||||
this.flatMetadata,
|
||||
);
|
||||
|
||||
return {
|
||||
[this.idKey]: node.id_,
|
||||
[this.embeddingKey]: node.getEmbedding(),
|
||||
[this.textKey]: node.getContent(MetadataMode.NONE) || "",
|
||||
[this.metadataKey]: metadata,
|
||||
};
|
||||
});
|
||||
|
||||
console.debug("Inserting data into MongoDB: ", dataToInsert);
|
||||
const insertResult = await this.collection.insertMany(
|
||||
dataToInsert,
|
||||
this.insertOptions,
|
||||
);
|
||||
console.debug("Result of insert: ", insertResult);
|
||||
return nodes.map((node) => node.id_);
|
||||
}
|
||||
|
||||
async delete(refDocId: string, deleteOptions?: any): Promise<void> {
|
||||
await this.collection.deleteOne(
|
||||
{
|
||||
[`${this.metadataKey}.ref_doc_id`]: refDocId,
|
||||
},
|
||||
deleteOptions,
|
||||
);
|
||||
}
|
||||
|
||||
get client(): any {
|
||||
return this.mongodbClient;
|
||||
}
|
||||
|
||||
async query(
|
||||
query: VectorStoreQuery,
|
||||
options?: any,
|
||||
): Promise<VectorStoreQueryResult> {
|
||||
const params: any = {
|
||||
queryVector: query.queryEmbedding,
|
||||
path: this.embeddingKey,
|
||||
numCandidates: query.similarityTopK * 10,
|
||||
limit: query.similarityTopK,
|
||||
index: this.indexName,
|
||||
};
|
||||
|
||||
if (query.filters) {
|
||||
params.filter = toMongoDBFilter(query.filters);
|
||||
}
|
||||
|
||||
const queryField = { $vectorSearch: params };
|
||||
const pipeline = [
|
||||
queryField,
|
||||
{
|
||||
$project: {
|
||||
score: { $meta: "vectorSearchScore" },
|
||||
[this.embeddingKey]: 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
console.debug("Running query pipeline: ", pipeline);
|
||||
const cursor = await this.collection.aggregate(pipeline);
|
||||
|
||||
const nodes: BaseNode[] = [];
|
||||
const ids: string[] = [];
|
||||
const similarities: number[] = [];
|
||||
|
||||
for await (const res of await cursor) {
|
||||
const text = res[this.textKey];
|
||||
const score = res.score;
|
||||
const id = res[this.idKey];
|
||||
const metadata = res[this.metadataKey];
|
||||
|
||||
const node = metadataDictToNode(metadata);
|
||||
node.setContent(text);
|
||||
|
||||
ids.push(id);
|
||||
nodes.push(node);
|
||||
similarities.push(score);
|
||||
}
|
||||
|
||||
const result = {
|
||||
nodes,
|
||||
similarities,
|
||||
ids,
|
||||
};
|
||||
|
||||
console.debug("Result of query (ids):", ids);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { BaseNode } from "../../Node";
|
||||
import { GenericFileSystem } from "../FileSystem";
|
||||
|
||||
export interface VectorStoreQueryResult {
|
||||
nodes?: BaseNode[];
|
||||
@@ -62,10 +61,9 @@ export interface VectorStore {
|
||||
isEmbeddingQuery?: boolean;
|
||||
client(): any;
|
||||
add(embeddingResults: BaseNode[]): Promise<string[]>;
|
||||
delete(refDocId: string, deleteKwargs?: any): Promise<void>;
|
||||
delete(refDocId: string, deleteOptions?: any): Promise<void>;
|
||||
query(
|
||||
query: VectorStoreQuery,
|
||||
options?: any,
|
||||
): Promise<VectorStoreQueryResult>;
|
||||
persist(persistPath: string, fs?: GenericFileSystem): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { BaseNode, Metadata, ObjectType, jsonToNode } from "../../Node";
|
||||
|
||||
const DEFAULT_TEXT_KEY = "text";
|
||||
|
||||
export function validateIsFlat(obj: { [key: string]: any }): void {
|
||||
for (let key in obj) {
|
||||
if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||
throw new Error(`Value for metadata ${key} must not be another object`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function nodeToMetadata(
|
||||
node: BaseNode,
|
||||
removeText: boolean = false,
|
||||
textField: string = DEFAULT_TEXT_KEY,
|
||||
flatMetadata: boolean = false,
|
||||
): Metadata {
|
||||
const nodeObj = node.toJSON();
|
||||
const metadata = node.metadata;
|
||||
|
||||
if (flatMetadata) {
|
||||
validateIsFlat(node.metadata);
|
||||
}
|
||||
|
||||
if (removeText) {
|
||||
nodeObj[textField] = "";
|
||||
}
|
||||
|
||||
nodeObj["embedding"] = null;
|
||||
|
||||
metadata["_node_content"] = JSON.stringify(nodeObj);
|
||||
metadata["_node_type"] = node.constructor.name.replace("_", ""); // remove leading underscore to be compatible with Python
|
||||
|
||||
metadata["document_id"] = node.sourceNode?.nodeId || "None";
|
||||
metadata["doc_id"] = node.sourceNode?.nodeId || "None";
|
||||
metadata["ref_doc_id"] = node.sourceNode?.nodeId || "None";
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
export function metadataDictToNode(metadata: Metadata): BaseNode {
|
||||
const nodeContent = metadata["_node_content"];
|
||||
if (!nodeContent) {
|
||||
throw new Error("Node content not found in metadata.");
|
||||
}
|
||||
const nodeObj = JSON.parse(nodeContent);
|
||||
|
||||
// Note: we're using the name of the class stored in `_node_type`
|
||||
// and not the type attribute to reconstruct
|
||||
// the node. This way we're compatible with LlamaIndex Python
|
||||
const node_type = metadata["_node_type"];
|
||||
switch (node_type) {
|
||||
case "IndexNode":
|
||||
return jsonToNode(nodeObj, ObjectType.INDEX);
|
||||
default:
|
||||
return jsonToNode(nodeObj, ObjectType.TEXT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { TextNode } from "../Node";
|
||||
|
||||
describe("TextNode", () => {
|
||||
let node: TextNode;
|
||||
|
||||
beforeEach(() => {
|
||||
node = new TextNode({ text: "Hello World" });
|
||||
});
|
||||
|
||||
describe("generateHash", () => {
|
||||
it("should generate a hash", () => {
|
||||
expect(node.hash).toBe("nTSKdUTYqR52MPv/brvb4RTGeqedTEqG9QN8KSAj2Do=");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,49 @@
|
||||
# create-llama
|
||||
|
||||
## 0.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- acfe232: Deployment fixes (thanks @seldo)
|
||||
|
||||
## 0.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8cdb07f: Fix Next deployment (thanks @seldo and @marcusschiesser)
|
||||
|
||||
## 0.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9f9f293: Added more to README and made it easier to switch models (thanks @seldo)
|
||||
|
||||
## 0.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4431ec7: Label bug fix (thanks @marcusschiesser)
|
||||
|
||||
## 0.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 25257f4: Fix issue where it doesn't find OpenAI Key when running npm run generate (#182) (thanks @RayFernando1337)
|
||||
|
||||
## 0.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 031e926: Update create-llama readme (thanks @logan-markewich)
|
||||
|
||||
## 0.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 91b42a3: change version (thanks @marcusschiesser)
|
||||
|
||||
## 0.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e2a6805: Hello Create Llama (thanks Marcus!)
|
||||
- e2a6805: Hello Create Llama (thanks @marcusschiesser)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2023 LlamaIndex, Vercel, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,32 +1,99 @@
|
||||
# Create LlamaIndex App
|
||||
|
||||
The easiest way to get started with LlamaIndex is by using `create-llama`. This CLI tool enables you to quickly start building a new LlamaIndex application, with everything set up for you.
|
||||
To get started, use the following command:
|
||||
The easiest way to get started with [LlamaIndex](https://www.llamaindex.ai/) is by using `create-llama`. This CLI tool enables you to quickly start building a new LlamaIndex application, with everything set up for you.
|
||||
|
||||
### Interactive
|
||||
Just run
|
||||
|
||||
You can create a new project interactively by running:
|
||||
```bash
|
||||
npx create-llama@latest
|
||||
```
|
||||
|
||||
to get started, or see below for more options. Once your app is generated, run
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
to start the development server. You can then visit [http://localhost:3000](http://localhost:3000) to see your app.
|
||||
|
||||
## What you'll get
|
||||
|
||||
- A Next.js-powered front-end. The app is set up as a chat interface that can answer questions about your data (see below)
|
||||
- You can style it with HTML and CSS, or you can optionally use components from [shadcn/ui](https://ui.shadcn.com/)
|
||||
- Your choice of 3 back-ends:
|
||||
- **Next.js**: if you select this option, you’ll have a full stack Next.js application that you can deploy to a host like [Vercel](https://vercel.com/) in just a few clicks. This uses [LlamaIndex.TS](https://www.npmjs.com/package/llamaindex), our TypeScript library.
|
||||
- **Express**: if you want a more traditional Node.js application you can generate an Express backend. This also uses LlamaIndex.TS.
|
||||
- **Python FastAPI**: if you select this option you’ll get a backend powered by the [llama-index python package](https://pypi.org/project/llama-index/), which you can deploy to a service like Render or fly.io.
|
||||
- The back-end has a single endpoint that allows you to send the state of your chat and receive additional responses
|
||||
- You can choose whether you want a streaming or non-streaming back-end (if you're not sure, we recommend streaming)
|
||||
- You can choose whether you want to use `ContextChatEngine` or `SimpleChatEngine`
|
||||
- `SimpleChatEngine` will just talk to the LLM directly without using your data
|
||||
- `ContextChatEngine` will use your data to answer questions (see below).
|
||||
- The app uses OpenAI by default, so you'll need an OpenAI API key, or you can customize it to use any of the dozens of LLMs we support.
|
||||
|
||||
## Using your data
|
||||
|
||||
If you've enabled `ContextChatEngine`, you can supply your own data and the app will index it and answer questions. Your generated app will have a folder called `data`:
|
||||
|
||||
- With the Next.js backend this is `./data`
|
||||
- With the Express or Python backend this is in `./backend/data`
|
||||
|
||||
The app will ingest any supported files you put in this directory. Your Next.js and Express apps use LlamaIndex.TS so they will be able to ingest any PDF, text, CSV, Markdown, Word and HTML files. The Python backend can read even more types, including video and audio files.
|
||||
|
||||
Before you can use your data, you need to index it. If you're using the Next.js or Express apps, run:
|
||||
|
||||
```bash
|
||||
npm run generate
|
||||
```
|
||||
|
||||
Then re-start your app. Remember you'll need to re-run `generate` if you add new files to your `data` folder. If you're using the Python backend, you can trigger indexing of your data by deleting the `./storage` folder and re-starting the app.
|
||||
|
||||
## Don't want a front-end?
|
||||
|
||||
It's optional! If you've selected the Python or Express back-ends, just delete the `frontend` folder and you'll get an API without any front-end code.
|
||||
|
||||
## Customizing the LLM
|
||||
|
||||
By default the app will use OpenAI's gpt-3.5-turbo model. If you want to use GPT-4, you can modify this by editing a file:
|
||||
|
||||
- In the Next.js backend, edit `./app/api/chat/route.ts` and replace `gpt-3.5-turbo` with `gpt-4`
|
||||
- In the Express backend, edit `./backend/src/controllers/chat.controller.ts` and likewise replace `gpt-3.5-turbo` with `gpt-4`
|
||||
- In the Python backend, edit `./backend/app/utils/index.py` and once again replace `gpt-3.5-turbo` with `gpt-4`
|
||||
|
||||
You can also replace OpenAI with one of our [dozens of other supported LLMs](https://docs.llamaindex.ai/en/stable/module_guides/models/llms/modules.html).
|
||||
|
||||
## Example
|
||||
|
||||
The simplest thing to do is run `create-llama` in interactive mode:
|
||||
|
||||
```bash
|
||||
npx create-llama@latest
|
||||
# or
|
||||
npm create llama
|
||||
npm create llama@latest
|
||||
# or
|
||||
yarn create llama
|
||||
# or
|
||||
pnpm create llama
|
||||
pnpm create llama@latest
|
||||
```
|
||||
|
||||
You will be asked for the name of your project, and then which framework you want to use
|
||||
create a TypeScript project:
|
||||
You will be asked for the name of your project, along with other configuration options, something like this:
|
||||
|
||||
```bash
|
||||
>> npm create llama@latest
|
||||
Need to install the following packages:
|
||||
create-llama@latest
|
||||
Ok to proceed? (y) y
|
||||
✔ What is your project named? … my-app
|
||||
✔ Which template would you like to use? › Chat with streaming
|
||||
✔ Which framework would you like to use? › NextJS
|
||||
✔ Which UI would you like to use? › Just HTML
|
||||
✔ Which chat engine would you like to use? › ContextChatEngine
|
||||
✔ Please provide your OpenAI API key (leave blank to skip): …
|
||||
✔ Would you like to use ESLint? … No / Yes
|
||||
Creating a new LlamaIndex app in /home/my-app.
|
||||
```
|
||||
|
||||
You can choose between NextJS and Express.
|
||||
|
||||
### Non-interactive
|
||||
### Running non-interactively
|
||||
|
||||
You can also pass command line arguments to set up a new project
|
||||
non-interactively. See `create-llama --help`:
|
||||
@@ -36,7 +103,6 @@ create-llama <project-directory> [options]
|
||||
|
||||
Options:
|
||||
-V, --version output the version number
|
||||
|
||||
|
||||
--use-npm
|
||||
|
||||
@@ -52,3 +118,9 @@ Options:
|
||||
|
||||
```
|
||||
|
||||
## LlamaIndex Documentation
|
||||
|
||||
- [TS/JS docs](https://ts.llamaindex.ai/)
|
||||
- [Python docs](https://docs.llamaindex.ai/en/stable/)
|
||||
|
||||
Inspired by and adapted from [create-next-app](https://github.com/vercel/next.js/tree/canary/packages/create-next-app)
|
||||
|
||||
@@ -88,7 +88,7 @@ export async function createApp({
|
||||
path.join(root, "README.md"),
|
||||
);
|
||||
} else {
|
||||
await installTemplate({ ...args, backend: true });
|
||||
await installTemplate({ ...args, backend: true, forBackend: framework });
|
||||
}
|
||||
|
||||
process.chdir(root);
|
||||
|
||||
@@ -43,7 +43,7 @@ export function tryGitInit(root: string): boolean {
|
||||
}
|
||||
|
||||
execSync("git add -A", { stdio: "ignore" });
|
||||
execSync('git commit -m "Initial commit from Create Next App"', {
|
||||
execSync('git commit -m "Initial commit from Create Llama"', {
|
||||
stdio: "ignore",
|
||||
});
|
||||
return true;
|
||||
|
||||
@@ -79,10 +79,10 @@ const program = new Commander.Command(packageJson.name)
|
||||
const packageManager = !!program.useNpm
|
||||
? "npm"
|
||||
: !!program.usePnpm
|
||||
? "pnpm"
|
||||
: !!program.useYarn
|
||||
? "yarn"
|
||||
: getPkgManager();
|
||||
? "pnpm"
|
||||
: !!program.useYarn
|
||||
? "yarn"
|
||||
: getPkgManager();
|
||||
|
||||
async function run(): Promise<void> {
|
||||
const conf = new Conf({ projectName: "create-llama" });
|
||||
@@ -235,8 +235,8 @@ async function run(): Promise<void> {
|
||||
program.framework === "express"
|
||||
? "Express "
|
||||
: program.framework === "fastapi"
|
||||
? "FastAPI (Python) "
|
||||
: "",
|
||||
? "FastAPI (Python) "
|
||||
: "",
|
||||
);
|
||||
const { frontend } = await prompts({
|
||||
onState: onPromptState,
|
||||
@@ -288,8 +288,11 @@ async function run(): Promise<void> {
|
||||
name: "engine",
|
||||
message: "Which chat engine would you like to use?",
|
||||
choices: [
|
||||
{ title: "SimpleChatEngine", value: "simple" },
|
||||
{ title: "ContextChatEngine", value: "context" },
|
||||
{
|
||||
title: "SimpleChatEngine (no data, just chat)",
|
||||
value: "simple",
|
||||
},
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
@@ -359,10 +362,10 @@ async function notifyUpdate(): Promise<void> {
|
||||
if (res?.latest) {
|
||||
const updateMessage =
|
||||
packageManager === "yarn"
|
||||
? "yarn global add create-llama"
|
||||
? "yarn global add create-llama@latest"
|
||||
: packageManager === "pnpm"
|
||||
? "pnpm add -g create-llama"
|
||||
: "npm i -g create-llama";
|
||||
? "pnpm add -g create-llama@latest"
|
||||
: "npm i -g create-llama@latest";
|
||||
|
||||
console.log(
|
||||
yellow(bold("A new version of `create-llama` is available!")) +
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-llama",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.9",
|
||||
"keywords": [
|
||||
"rag",
|
||||
"llamaindex",
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import React, { FC, memo } from "react"
|
||||
import { Check, Copy, Download } from "lucide-react"
|
||||
import { Prism, SyntaxHighlighterProps } from "react-syntax-highlighter"
|
||||
import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism"
|
||||
import { Check, Copy, Download } from "lucide-react";
|
||||
import { FC, memo } from "react";
|
||||
import { Prism, SyntaxHighlighterProps } from "react-syntax-highlighter";
|
||||
import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
|
||||
import { Button } from "../button"
|
||||
import { useCopyToClipboard } from "./use-copy-to-clipboard"
|
||||
import { Button } from "../button";
|
||||
import { useCopyToClipboard } from "./use-copy-to-clipboard";
|
||||
|
||||
// TODO: Remove this when @type/react-syntax-highlighter is updated
|
||||
const SyntaxHighlighter = Prism as unknown as FC<SyntaxHighlighterProps>
|
||||
const SyntaxHighlighter = Prism as unknown as FC<SyntaxHighlighterProps>;
|
||||
|
||||
interface Props {
|
||||
language: string
|
||||
value: string
|
||||
language: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface languageMap {
|
||||
[key: string]: string | undefined
|
||||
[key: string]: string | undefined;
|
||||
}
|
||||
|
||||
export const programmingLanguages: languageMap = {
|
||||
@@ -45,52 +45,52 @@ export const programmingLanguages: languageMap = {
|
||||
html: ".html",
|
||||
css: ".css",
|
||||
// add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
|
||||
}
|
||||
};
|
||||
|
||||
export const generateRandomString = (length: number, lowercase = false) => {
|
||||
const chars = "ABCDEFGHJKLMNPQRSTUVWXY3456789" // excluding similar looking characters like Z, 2, I, 1, O, 0
|
||||
let result = ""
|
||||
const chars = "ABCDEFGHJKLMNPQRSTUVWXY3456789"; // excluding similar looking characters like Z, 2, I, 1, O, 0
|
||||
let result = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return lowercase ? result.toLowerCase() : result
|
||||
}
|
||||
return lowercase ? result.toLowerCase() : result;
|
||||
};
|
||||
|
||||
const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
||||
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })
|
||||
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 });
|
||||
|
||||
const downloadAsFile = () => {
|
||||
if (typeof window === "undefined") {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const fileExtension = programmingLanguages[language] || ".file"
|
||||
const fileExtension = programmingLanguages[language] || ".file";
|
||||
const suggestedFileName = `file-${generateRandomString(
|
||||
3,
|
||||
true
|
||||
)}${fileExtension}`
|
||||
const fileName = window.prompt("Enter file name" || "", suggestedFileName)
|
||||
true,
|
||||
)}${fileExtension}`;
|
||||
const fileName = window.prompt("Enter file name" || "", suggestedFileName);
|
||||
|
||||
if (!fileName) {
|
||||
// User pressed cancel on prompt.
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = new Blob([value], { type: "text/plain" })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement("a")
|
||||
link.download = fileName
|
||||
link.href = url
|
||||
link.style.display = "none"
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
const blob = new Blob([value], { type: "text/plain" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.download = fileName;
|
||||
link.href = url;
|
||||
link.style.display = "none";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const onCopy = () => {
|
||||
if (isCopied) return
|
||||
copyToClipboard(value)
|
||||
}
|
||||
if (isCopied) return;
|
||||
copyToClipboard(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="codeblock relative w-full bg-zinc-950 font-sans">
|
||||
@@ -132,8 +132,8 @@ const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
||||
{value}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
CodeBlock.displayName = "CodeBlock"
|
||||
);
|
||||
});
|
||||
CodeBlock.displayName = "CodeBlock";
|
||||
|
||||
export { CodeBlock }
|
||||
export { CodeBlock };
|
||||
|
||||
@@ -2,4 +2,4 @@ import ChatInput from "./chat-input";
|
||||
import ChatMessages from "./chat-messages";
|
||||
|
||||
export { type ChatHandler, type Message } from "./chat.interface";
|
||||
export { ChatMessages, ChatInput };
|
||||
export { ChatInput, ChatMessages };
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { FC, memo } from "react"
|
||||
import ReactMarkdown, { Options } from "react-markdown"
|
||||
import remarkGfm from "remark-gfm"
|
||||
import remarkMath from "remark-math"
|
||||
import { FC, memo } from "react";
|
||||
import ReactMarkdown, { Options } from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkMath from "remark-math";
|
||||
|
||||
import { CodeBlock } from "./codeblock"
|
||||
import { CodeBlock } from "./codeblock";
|
||||
|
||||
const MemoizedReactMarkdown: FC<Options> = memo(
|
||||
ReactMarkdown,
|
||||
(prevProps, nextProps) =>
|
||||
prevProps.children === nextProps.children &&
|
||||
prevProps.className === nextProps.className
|
||||
)
|
||||
prevProps.className === nextProps.className,
|
||||
);
|
||||
|
||||
export default function Markdown({ content }: { content: string }) {
|
||||
return (
|
||||
@@ -19,27 +19,27 @@ export default function Markdown({ content }: { content: string }) {
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
components={{
|
||||
p({ children }) {
|
||||
return <p className="mb-2 last:mb-0">{children}</p>
|
||||
return <p className="mb-2 last:mb-0">{children}</p>;
|
||||
},
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
if (children.length) {
|
||||
if (children[0] == "▍") {
|
||||
return (
|
||||
<span className="mt-1 animate-pulse cursor-default">▍</span>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
children[0] = (children[0] as string).replace("`▍`", "▍")
|
||||
children[0] = (children[0] as string).replace("`▍`", "▍");
|
||||
}
|
||||
|
||||
const match = /language-(\w+)/.exec(className || "")
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
|
||||
if (inline) {
|
||||
return (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -49,11 +49,11 @@ export default function Markdown({ content }: { content: string }) {
|
||||
value={String(children).replace(/\n$/, "")}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</MemoizedReactMarkdown>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
+14
-14
@@ -1,33 +1,33 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import * as React from 'react'
|
||||
import * as React from "react";
|
||||
|
||||
export interface useCopyToClipboardProps {
|
||||
timeout?: number
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export function useCopyToClipboard({
|
||||
timeout = 2000
|
||||
timeout = 2000,
|
||||
}: useCopyToClipboardProps) {
|
||||
const [isCopied, setIsCopied] = React.useState<Boolean>(false)
|
||||
const [isCopied, setIsCopied] = React.useState<Boolean>(false);
|
||||
|
||||
const copyToClipboard = (value: string) => {
|
||||
if (typeof window === 'undefined' || !navigator.clipboard?.writeText) {
|
||||
return
|
||||
if (typeof window === "undefined" || !navigator.clipboard?.writeText) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(value).then(() => {
|
||||
setIsCopied(true)
|
||||
setIsCopied(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsCopied(false)
|
||||
}, timeout)
|
||||
})
|
||||
}
|
||||
setIsCopied(false);
|
||||
}, timeout);
|
||||
});
|
||||
};
|
||||
|
||||
return { isCopied, copyToClipboard }
|
||||
return { isCopied, copyToClipboard };
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ const createEnvLocalFile = async (
|
||||
`OPENAI_API_KEY=${openAIKey}\n`,
|
||||
);
|
||||
console.log(`Created '${envFileName}' file containing OPENAI_API_KEY`);
|
||||
process.env["OPENAI_API_KEY"] = openAIKey;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -53,11 +54,21 @@ const copyTestData = async (
|
||||
}
|
||||
|
||||
if (packageManager && engine === "context") {
|
||||
console.log(
|
||||
`\nRunning ${cyan("npm run generate")} to generate the context data.\n`,
|
||||
);
|
||||
await callPackageManager(packageManager, true, ["run", "generate"]);
|
||||
console.log();
|
||||
if (process.env["OPENAI_API_KEY"]) {
|
||||
console.log(
|
||||
`\nRunning ${cyan(
|
||||
`${packageManager} run generate`,
|
||||
)} to generate the context data.\n`,
|
||||
);
|
||||
await callPackageManager(packageManager, true, ["run", "generate"]);
|
||||
console.log();
|
||||
} else {
|
||||
console.log(
|
||||
`\nAfter setting your OpenAI key, run ${cyan(
|
||||
`${packageManager} run generate`,
|
||||
)} to generate the context data.\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -92,6 +103,7 @@ const installTSTemplate = async ({
|
||||
ui,
|
||||
eslint,
|
||||
customApiPath,
|
||||
forBackend,
|
||||
}: InstallTemplateArgs) => {
|
||||
console.log(bold(`Using ${packageManager}.`));
|
||||
|
||||
@@ -109,6 +121,26 @@ const installTSTemplate = async ({
|
||||
rename,
|
||||
});
|
||||
|
||||
/**
|
||||
* If the backend is next.js, rename next.config.app.js to next.config.js
|
||||
* If not, rename next.config.static.js to next.config.js
|
||||
*/
|
||||
if (framework == "nextjs" && forBackend === "nextjs") {
|
||||
const nextConfigAppPath = path.join(root, "next.config.app.js");
|
||||
const nextConfigPath = path.join(root, "next.config.js");
|
||||
await fs.rename(nextConfigAppPath, nextConfigPath);
|
||||
// delete next.config.static.js
|
||||
const nextConfigStaticPath = path.join(root, "next.config.static.js");
|
||||
await fs.rm(nextConfigStaticPath);
|
||||
} else if (framework == "nextjs" && typeof forBackend === "undefined") {
|
||||
const nextConfigStaticPath = path.join(root, "next.config.static.js");
|
||||
const nextConfigPath = path.join(root, "next.config.js");
|
||||
await fs.rename(nextConfigStaticPath, nextConfigPath);
|
||||
// delete next.config.app.js
|
||||
const nextConfigAppPath = path.join(root, "next.config.app.js");
|
||||
await fs.rm(nextConfigAppPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the selected chat engine files to the target directory and reference it.
|
||||
*/
|
||||
|
||||
@@ -17,4 +17,5 @@ export interface InstallTemplateArgs {
|
||||
eslint: boolean;
|
||||
customApiPath?: string;
|
||||
openAIKey?: string;
|
||||
forBackend?: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# local env files
|
||||
.env
|
||||
@@ -8,12 +8,24 @@ const port = 8000;
|
||||
|
||||
const env = process.env["NODE_ENV"];
|
||||
const isDevelopment = !env || env === "development";
|
||||
const prodCorsOrigin = process.env["PROD_CORS_ORIGIN"];
|
||||
|
||||
if (isDevelopment) {
|
||||
console.warn("Running in development mode - allowing CORS for all origins");
|
||||
app.use(cors());
|
||||
} else if (prodCorsOrigin) {
|
||||
console.log(
|
||||
`Running in production mode - allowing CORS for domain: ${prodCorsOrigin}`,
|
||||
);
|
||||
const corsOptions = {
|
||||
origin: prodCorsOrigin, // Restrict to production domain
|
||||
};
|
||||
app.use(cors(corsOptions));
|
||||
} else {
|
||||
console.warn("Production CORS origin not set, defaulting to no CORS.");
|
||||
}
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.text());
|
||||
|
||||
app.get("/", (req: Request, res: Response) => {
|
||||
res.send("LlamaIndex Express Server");
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { createChatEngine } from "./engine";
|
||||
|
||||
export const chat = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { messages }: { messages: ChatMessage[] } = req.body;
|
||||
const { messages }: { messages: ChatMessage[] } = JSON.parse(req.body);
|
||||
const lastMessage = messages.pop();
|
||||
if (!messages || !lastMessage || lastMessage.role !== "user") {
|
||||
return res.status(400).json({
|
||||
|
||||
@@ -6,12 +6,18 @@ from llama_index import (
|
||||
StorageContext,
|
||||
VectorStoreIndex,
|
||||
load_index_from_storage,
|
||||
ServiceContext,
|
||||
)
|
||||
from llama_index.llms import OpenAI
|
||||
|
||||
|
||||
STORAGE_DIR = "./storage" # directory to cache the generated index
|
||||
DATA_DIR = "./data" # directory containing the documents to index
|
||||
|
||||
service_context = ServiceContext.from_defaults(
|
||||
llm=OpenAI(model="gpt-3.5-turbo")
|
||||
)
|
||||
|
||||
|
||||
def get_index():
|
||||
logger = logging.getLogger("uvicorn")
|
||||
@@ -20,7 +26,7 @@ def get_index():
|
||||
logger.info("Creating new index")
|
||||
# load the documents and create the index
|
||||
documents = SimpleDirectoryReader(DATA_DIR).load_data()
|
||||
index = VectorStoreIndex.from_documents(documents)
|
||||
index = VectorStoreIndex.from_documents(documents,service_context=service_context)
|
||||
# store it for later
|
||||
index.storage_context.persist(STORAGE_DIR)
|
||||
logger.info(f"Finished creating new index. Stored in {STORAGE_DIR}")
|
||||
@@ -28,6 +34,6 @@ def get_index():
|
||||
# load the existing index
|
||||
logger.info(f"Loading index from {STORAGE_DIR}...")
|
||||
storage_context = StorageContext.from_defaults(persist_dir=STORAGE_DIR)
|
||||
index = load_index_from_storage(storage_context)
|
||||
index = load_index_from_storage(storage_context,service_context=service_context)
|
||||
logger.info(f"Finished loading index from {STORAGE_DIR}")
|
||||
return index
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
__pycache__
|
||||
storage
|
||||
.env
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
import logging
|
||||
import os
|
||||
import uvicorn
|
||||
from app.api.routers.chat import chat_router
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@ import ChatMessages from "./chat-messages";
|
||||
|
||||
export type { ChatInputProps } from "./chat-input";
|
||||
export type { Message } from "./chat-messages";
|
||||
export { ChatMessages, ChatInput };
|
||||
export { ChatInput, ChatMessages };
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: ["llamaindex"],
|
||||
outputFileTracingIncludes: {
|
||||
"/*": ["./cache/**/*"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
@@ -1,8 +0,0 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: ["llamaindex"],
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
@@ -0,0 +1,13 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "export",
|
||||
images: { unoptimized: true },
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: ["llamaindex"],
|
||||
outputFileTracingIncludes: {
|
||||
"/*": ["./cache/**/*"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
@@ -3,4 +3,4 @@ module.exports = {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# local env files
|
||||
.env
|
||||
@@ -8,12 +8,24 @@ const port = 8000;
|
||||
|
||||
const env = process.env["NODE_ENV"];
|
||||
const isDevelopment = !env || env === "development";
|
||||
const prodCorsOrigin = process.env["PROD_CORS_ORIGIN"];
|
||||
|
||||
if (isDevelopment) {
|
||||
console.warn("Running in development mode - allowing CORS for all origins");
|
||||
app.use(cors());
|
||||
} else if (prodCorsOrigin) {
|
||||
console.log(
|
||||
`Running in production mode - allowing CORS for domain: ${prodCorsOrigin}`,
|
||||
);
|
||||
const corsOptions = {
|
||||
origin: prodCorsOrigin, // Restrict to production domain
|
||||
};
|
||||
app.use(cors(corsOptions));
|
||||
} else {
|
||||
console.warn("Production CORS origin not set, defaulting to no CORS.");
|
||||
}
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.text());
|
||||
|
||||
app.get("/", (req: Request, res: Response) => {
|
||||
res.send("LlamaIndex Express Server");
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import { LlamaIndexStream } from "./llamaindex-stream";
|
||||
|
||||
export const chat = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { messages }: { messages: ChatMessage[] } = req.body;
|
||||
const { messages }: { messages: ChatMessage[] } = JSON.parse(req.body);
|
||||
const lastMessage = messages.pop();
|
||||
if (!messages || !lastMessage || lastMessage.role !== "user") {
|
||||
return res.status(400).json({
|
||||
|
||||
@@ -6,12 +6,17 @@ from llama_index import (
|
||||
StorageContext,
|
||||
VectorStoreIndex,
|
||||
load_index_from_storage,
|
||||
ServiceContext,
|
||||
)
|
||||
from llama_index.llms import OpenAI
|
||||
|
||||
|
||||
STORAGE_DIR = "./storage" # directory to cache the generated index
|
||||
DATA_DIR = "./data" # directory containing the documents to index
|
||||
|
||||
service_context = ServiceContext.from_defaults(
|
||||
llm=OpenAI(model="gpt-3.5-turbo")
|
||||
)
|
||||
|
||||
def get_index():
|
||||
logger = logging.getLogger("uvicorn")
|
||||
@@ -20,7 +25,7 @@ def get_index():
|
||||
logger.info("Creating new index")
|
||||
# load the documents and create the index
|
||||
documents = SimpleDirectoryReader(DATA_DIR).load_data()
|
||||
index = VectorStoreIndex.from_documents(documents)
|
||||
index = VectorStoreIndex.from_documents(documents,service_context=service_context)
|
||||
# store it for later
|
||||
index.storage_context.persist(STORAGE_DIR)
|
||||
logger.info(f"Finished creating new index. Stored in {STORAGE_DIR}")
|
||||
@@ -28,6 +33,6 @@ def get_index():
|
||||
# load the existing index
|
||||
logger.info(f"Loading index from {STORAGE_DIR}...")
|
||||
storage_context = StorageContext.from_defaults(persist_dir=STORAGE_DIR)
|
||||
index = load_index_from_storage(storage_context)
|
||||
index = load_index_from_storage(storage_context,service_context=service_context)
|
||||
logger.info(f"Finished loading index from {STORAGE_DIR}")
|
||||
return index
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
__pycache__
|
||||
storage
|
||||
.env
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
import logging
|
||||
import os
|
||||
import uvicorn
|
||||
from app.api.routers.chat import chat_router
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
+1
-1
@@ -3,4 +3,4 @@ import ChatMessages from "./chat-messages";
|
||||
|
||||
export type { ChatInputProps } from "./chat-input";
|
||||
export type { Message } from "./chat-messages";
|
||||
export { ChatMessages, ChatInput };
|
||||
export { ChatInput, ChatMessages };
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: ["llamaindex"],
|
||||
outputFileTracingIncludes: {
|
||||
"/*": ["./cache/**/*"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
@@ -1,8 +0,0 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: ["llamaindex"],
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
@@ -0,0 +1,13 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "export",
|
||||
images: { unoptimized: true },
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: ["llamaindex"],
|
||||
outputFileTracingIncludes: {
|
||||
"/*": ["./cache/**/*"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
@@ -3,4 +3,4 @@ module.exports = {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
Generated
+786
-559
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user