Compare commits

...

97 Commits

Author SHA1 Message Date
Yi Ding eb0e9947f2 changesets 2023-10-07 15:56:42 -07:00
Yi Ding 23a09cff1b export PromptHelper 2023-10-07 15:54:35 -07:00
Yi Ding ebe9041fdc esm module 2023-10-07 14:07:16 -07:00
Yi Ding f93ef09b58 upgrade packages 2023-10-07 13:48:44 -07:00
Yi Ding e74cfb93b5 package upgrades 2023-10-07 13:32:09 -07:00
yisding 4a44621f87 Merge pull request #138 from run-llama/feat/improve-chat-history-summarizer
feat: improved chat history summarizer
2023-10-05 18:37:35 -07:00
Yi Ding c7acaa2f5e fix test 2023-10-05 15:50:11 -07:00
Yi Ding 139abad1f4 changeset 2023-10-05 15:02:35 -07:00
Marcus Schiesser a3a5306f11 feat: improved chat history summarizer 2023-10-05 17:14:19 +07:00
yisding fb1c3bc446 Merge pull request #130 from Einsenhorn/einsenhorn/from_vector_store
VectorStore - Add Method "VectorStoreIndex.fromVectorStore" + Prefilters + Pinecone Demo
2023-10-03 14:48:39 -07:00
yisding aaf344a4dd Merge pull request #133 from noble-varghese/noble-varghese/portkey-integration
feat: Portkey integration with LLamaIndexTS
2023-10-03 14:48:16 -07:00
Yi Ding 62ca9c0ed2 fix lint errors and change spelling of organization 2023-10-03 11:57:07 -07:00
Louis de Courcel dc8be8740d impr: add a simple example to show pinecone query with prefilters 2023-10-03 11:23:44 -07:00
Louis de Courcel d9bcf4df92 impr: add fromVectorStore method 2023-10-03 11:22:17 -07:00
yisding 7ceb94f9c2 Merge pull request #131 from kkang2097/chat-queryengine-streaming
ChatEngine streaming [needs merge]
2023-10-03 11:12:12 -07:00
Elliot Kang 2e5becb4fb Update LLM.ts - anthropic comment 2023-09-30 15:17:03 -07:00
Elliot Kang 5e12f568bd formatting 2023-09-30 14:10:55 -07:00
Elliot Kang 80382c0bf9 fix example + bugfixes 2023-09-30 13:50:11 -07:00
Elliot Kang 91150d4150 Updated Anthropic Stream Token 2023-09-30 13:49:54 -07:00
Elliot Kang 6bfc38db53 pnpm run format 2023-09-30 12:20:11 -07:00
Elliot Kang 95b99db199 example fix 2023-09-30 12:18:31 -07:00
Elliot Kang 1b13395e65 Anthropic steaming support 2023-09-30 12:18:17 -07:00
Elliot Kang fe21904b53 added AnthropicStreamToken type 2023-09-30 12:18:02 -07:00
Elliot Kang ab0d666f03 fixed imports, moved llmStrem example 2023-09-30 11:46:54 -07:00
Elliot Kang 30add7a765 add chatEngine example 2023-09-29 12:00:39 -07:00
Elliot Kang 83971a1913 revert interface change 2023-09-28 16:27:28 -07:00
Elliot Kang 2f62081683 pnpm run format 2023-09-28 16:26:07 -07:00
Elliot Kang c7eb81dfa4 camelcase 2023-09-28 16:23:20 -07:00
Elliot Kang 9f35f526e0 Updated ChatEngine interface
- makes chatEngine auto-set return type like LLM.ts
- added streaming support for some chatEngines
2023-09-28 16:21:06 -07:00
Elliot Kang e755a63250 fixed example based on new interface 2023-09-28 16:11:30 -07:00
Elliot Kang 29c6b62ba1 Updated LLM interface
- auto-sets return types based on streaming flag
2023-09-28 16:11:13 -07:00
noble-varghese 9d69903c36 fix: fixing the baseURL param 2023-09-28 18:44:55 +05:30
noble-varghese 51475a9290 docs: Added more examples 2023-09-28 17:45:10 +05:30
noble-varghese a9e794bde9 feat: Portkey integration with LLamaIndexTs 2023-09-28 17:27:39 +05:30
Elliot Kang 5114a7aa27 type fix + stream_chat for ChatEngines
- fixed chatModel type for ContextChatEngine
- added stream_chat for severl ChatEngines
2023-09-26 17:10:28 -07:00
Elliot Kang d14042e536 added optional streaming for QueryEngine 2023-09-26 17:09:02 -07:00
Elliot Kang 7819fca349 make stream_chat optional, +streaming to basicChatEngine 2023-09-26 16:43:53 -07:00
Yi Ding 68d9cfb550 0.0.29 2023-09-26 15:34:36 -07:00
Yi Ding 1b7fd95214 changeset and fixed test case bug 2023-09-26 15:30:38 -07:00
yisding 0a1e6ccf9a Merge pull request #129 from kkang2097/streaming-support
Fixed Streaming Support for OpenAI LLM [Needs Review]
2023-09-26 15:25:33 -07:00
Yi Ding 0db3f415a8 changeset 2023-09-26 13:41:40 -07:00
Yi Ding 8a1385b9d0 migrated to tiktoken lite
Hopefully fixes the Windows issue
2023-09-26 13:40:37 -07:00
Yi Ding a52143b0ef changeset and package update 2023-09-26 12:42:13 -07:00
yisding 75ec41c85a Merge pull request #128 from jayantasamaddar/jayanta/docx-reader
Added DocxReader, adding support for reading .docx files.
2023-09-26 12:38:44 -07:00
Elliot Kang 827c8b3c48 remove spaghetti 2023-09-25 01:21:23 -07:00
Elliot Kang 194b35d889 move event creation out of loop 2023-09-24 22:24:26 -07:00
Elliot Kang 1b33523537 made events optional in stream_chat 2023-09-24 22:16:20 -07:00
Elliot Kang 807b95597a pnpm run format (prettier) 2023-09-24 21:02:49 -07:00
Elliot Kang 14b1ffa413 OpenAI LLM streaming + callbacks demo
- this makes it easy for people to add logging/token tracking
- "for await" logic becomes even more elegant
2023-09-24 21:00:11 -07:00
Elliot Kang d1db4d5534 Final fixes, sanity checks on types
- expanded LLM interface
- cleaned up OpenAI LLM stream
- simplified types in CallbackManager
-> CallbackResponse should require Token I think, we already have the StreamToken inside of the LLM's stream_chat anyways
2023-09-24 20:57:30 -07:00
Elliot Kang a45c0e537f Update LLM interface
standardizing streaming behavior for LLMs
2023-09-23 22:07:49 -07:00
Elliot Kang 4dab9b8fa3 Update LLM.ts 2023-09-23 21:56:32 -07:00
Elliot Kang a84f8ba5d6 Remove in re-factor 2023-09-23 21:56:24 -07:00
Elliot Kang f6f5cab661 Update llm_stream.ts 2023-09-23 21:56:06 -07:00
Elliot Kang 618f563ce9 LLM Stream example, need to flesh out more 2023-09-23 21:51:06 -07:00
Elliot Kang 5b49c90538 Fixed streaming for OpenAI
- stream support was actually broken
2023-09-23 21:49:26 -07:00
Elliot Kang 41be0003f1 Not every StreamResponse fits into StreamToken
- adds flexibility to our CallbackResponse interface
2023-09-23 21:47:58 -07:00
Jayanta Samaddar 8f8ee28ba0 Added DocxReader, adding support for reading .docx files. Made changes to relevant docs as well. 2023-09-23 06:23:17 +05:30
Yi Ding b3ae7fbb49 0.0.28 2023-09-14 10:47:13 -07:00
Yi Ding 837854de1e rolled back notion package and changeset 2023-09-14 10:41:00 -07:00
yisding 8cc1f0726f Merge pull request #112 from kkang2097/fix-output-parser
Create OutputParser.test.ts [Needs Merge]
2023-09-14 09:32:35 -07:00
yisding e1d617ef70 Merge pull request #122 from kevinlu1248/patch-1
Update sweep.yaml with newest sandbox format
2023-09-13 20:49:22 -07:00
Kevin Lu 5f199d68f9 Update sweep.yaml 2023-09-13 18:49:11 -07:00
Elliot Kang b8cca2db97 make parseJsonMarkdown exportable 2023-09-13 15:30:55 -07:00
Elliot Kang 35e959219d prettify OutputParser 2023-09-12 17:14:39 -07:00
Elliot Kang 08d466faee Ported Python _marshall_llm_to_json
From the Python side:
- output_parsers/utils

We'll still call this parseJsonMarkdown on the TS side
2023-09-12 17:13:02 -07:00
Yi Ding 0b5823f451 updated packages 2023-09-11 21:53:15 -07:00
Elliot Kang c77b150c28 hardcoding single JSON object case 2023-09-11 15:24:07 -07:00
Elliot Kang 3cf27bb838 Okay, should be final version. 2023-09-11 14:55:50 -07:00
Elliot Kang 26a90435c7 Revert "Simplified OutputParser"
This reverts commit ff0e831da9.
2023-09-11 14:55:07 -07:00
Elliot Kang f6efaba906 Update OutputParser.test.ts
- test cases are much simpler now.
2023-09-11 14:46:21 -07:00
Elliot Kang ff0e831da9 Simplified OutputParser 2023-09-11 14:46:01 -07:00
Yi Ding 96bb65723a changesets 2023-09-11 11:14:17 -07:00
Yi Ding 33ac4bc424 changelog typo 2023-09-11 10:45:27 -07:00
yisding 698503b467 Merge pull request #108 from run-llama/sweep/fix-broken-link-summary-index
Fix broken link to Summary Index in end_to_end.md: Typo correction
2023-09-11 10:33:52 -07:00
yisding 0657525d40 Merge pull request #99 from kkang2097/main
Add MongoReader [Needs merge]
2023-09-11 10:33:01 -07:00
Yi Ding 064d0de531 fix lint 2023-09-11 10:31:37 -07:00
Elliot Kang 471bf36a7b Delete MongoReader.ts 2023-09-11 10:19:40 -07:00
Elliot Kang cb7d2b4040 Revert "commit cleanup"
This reverts commit 3dd334c6db5b211080e7a0b269e58e160914acc2.
2023-09-11 10:19:38 -07:00
Elliot Kang 6032cbcf45 Fixed typing to be more restrictive
- Should have done this in the beginning, {Key:Value} objects should always be defined by Record<string, any>
2023-09-11 10:18:13 -07:00
Elliot Kang 73785d7552 Run Prettier, minor fixes
-changed .limit(Infinity) to .limit(0) in readers/SimpleMongoReader.ts
2023-09-11 10:18:13 -07:00
Elliot Kang 431b5ffa59 rename to simpleMongoReader 2023-09-11 10:18:13 -07:00
Elliot Kang c0500a0d4d SimpleMongoReader demo 2023-09-11 10:18:13 -07:00
Yi Ding 5300534188 commit cleanup 2023-09-11 10:17:49 -07:00
Elliot Kang 02192a5f53 Create MongoReader.ts 2023-09-11 10:15:55 -07:00
Elliot Kang 2a98d5b8ee Add MongoReader 2023-09-11 10:15:55 -07:00
yisding 4f495b5fc6 Merge pull request #110 from TomPenguin/extend-document-type
Enhancing Type Safety for metadata
2023-09-11 10:12:39 -07:00
Elliot Kang b75e2d23a2 re-ordering logic for parser
- previous iteration ran the computation twice if we had an unexpected output format
- added comment for future use
2023-09-11 01:16:31 -07:00
Elliot Kang 5261cdc794 Update OutputParser.test.ts
removing comment
2023-09-10 21:52:57 -07:00
Elliot Kang b179f61c6f Update OutputParser.ts
Essentially, we're giving OutputParser an option to parse List[JSON object] in case our LLM doesn't give us the exact output we want.
2023-09-10 21:39:41 -07:00
Elliot Kang 71b245ad6f Update OutputParser.test.ts
added new test cases, our LLM is not guaranteed to give us the exact formatted output.
2023-09-10 21:36:16 -07:00
Elliot Kang 5b070cf87a Add new test, this one fails
- fix this after adding all tests
2023-09-10 20:19:38 -07:00
Elliot Kang b8afe0b364 Update OutputParser.test.ts
- initial version of test script
2023-09-10 19:57:07 -07:00
Elliot Kang 92b4ec48f7 Create OutputParser.test.ts
- SubQuestionOutputParser is not working as expected, writing some tests to check it out.

QuestionGenerator outputs a list of objects in string format, which is unexpected.

In particular:
"[{prompt: "smth", response: "nothing"}]"
2023-09-10 05:00:09 -07:00
TomPenguin 6a69ac997d Type-safe Metadata 2023-09-09 12:02:33 +09:00
Yi Ding 8c542c30a9 0.0.27 2023-09-08 08:49:27 -07:00
sweep-ai[bot] a8388c841f Updated apps/docs/docs/end_to_end.md 2023-09-06 21:22:28 +00:00
47 changed files with 3656 additions and 2202 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"llamaindex": patch
---
Streaming improvements including Anthropic (thanks @kkang2097)
+5
View File
@@ -0,0 +1,5 @@
---
"llamaindex": patch
---
Portkey integration (Thank you @noble-varghese)
+5
View File
@@ -0,0 +1,5 @@
---
"llamaindex": patch
---
Add export for PromptHelper (thanks @zigamall)
-5
View File
@@ -1,5 +0,0 @@
---
"llamaindex": patch
---
Chat History summarization (thanks @marcusschlesser)
-5
View File
@@ -1,5 +0,0 @@
---
"llamaindex": patch
---
Notion database support (thanks @TomPenguin)
+5
View File
@@ -0,0 +1,5 @@
---
"llamaindex": patch
---
Publish ESM module again
-5
View File
@@ -1,5 +0,0 @@
---
"llamaindex": patch
---
KeywordIndex (thanks @swk777)
+5
View File
@@ -0,0 +1,5 @@
---
"llamaindex": patch
---
Pinecone demo (thanks @Einsenhorn)
+1 -1
View File
@@ -14,7 +14,7 @@ Read a file and chat about it with the LLM.
Create a vector index and query it. The vector index will use embeddings to fetch the top k most relevant nodes. By default, the top k is 2.
## [Summary Index](https://github.com/run-llama/LlamaIndexTS/blob/main/apps/simple/summarIndex.ts)
## [Summary Index](https://github.com/run-llama/LlamaIndexTS/blob/main/apps/simple/summaryIndex.ts)
Create a list index and query it. This example also use the `LLMRetriever`, which will use the LLM to select the best nodes to use when generating answer.
+1 -1
View File
@@ -19,7 +19,7 @@ That's where **LlamaIndex.TS** comes in.
LlamaIndex.TS provides the following tools:
- **Data loading** ingest your existing `txt` and `pdf` data directly
- **Data loading** ingest your existing `.txt`, `.pdf`, `.csv`, `.md` and `.docx` data directly
- **Data indexes** structure your data in intermediate representations that are easy and performant for LLMs to consume.
- **Engines** provide natural language access to your data. For example:
- Query engines are powerful retrieval interfaces for knowledge-augmented output.
@@ -4,7 +4,7 @@ sidebar_position: 1
# Reader / Loader
LlamaIndex.TS supports easy loading of files from folders using the `SimpleDirectoryReader` class. Currently, `.txt` and `.pdf` files are supported, with more planned in the future!
LlamaIndex.TS supports easy loading of files from folders using the `SimpleDirectoryReader` class. Currently, `.txt`, `.pdf`, `.csv`, `.md` and `.docx` files are supported, with more planned in the future!
```typescript
import { SimpleDirectoryReader } from "llamaindex";
+8 -8
View File
@@ -15,24 +15,24 @@
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "2.4.1",
"@docusaurus/preset-classic": "2.4.1",
"@docusaurus/remark-plugin-npm2yarn": "^2.4.1",
"@docusaurus/core": "2.4.3",
"@docusaurus/preset-classic": "2.4.3",
"@docusaurus/remark-plugin-npm2yarn": "^2.4.3",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"postcss": "^8.4.28",
"postcss": "^8.4.31",
"prism-react-renderer": "^1.3.5",
"raw-loader": "^4.0.2",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.4.1",
"@docusaurus/types": "^2.4.1",
"@tsconfig/docusaurus": "^1.0.7",
"@docusaurus/module-type-aliases": "2.4.3",
"@docusaurus/types": "^2.4.3",
"@tsconfig/docusaurus": "^2.0.1",
"docusaurus-plugin-typedoc": "^0.19.2",
"typedoc": "^0.24.8",
"typedoc-plugin-markdown": "^3.15.4",
"typedoc-plugin-markdown": "^3.16.0",
"typescript": "^4.9.5"
},
"browserslist": {
+27
View File
@@ -1,5 +1,32 @@
# simple
## 0.0.27
### Patch Changes
- Updated dependencies [a52143b]
- Updated dependencies [1b7fd95]
- Updated dependencies [0db3f41]
- llamaindex@0.0.29
## 0.0.26
### Patch Changes
- Updated dependencies [96bb657]
- Updated dependencies [96bb657]
- Updated dependencies [837854d]
- llamaindex@0.0.28
## 0.0.25
### Patch Changes
- Updated dependencies [4a5591b]
- Updated dependencies [4a5591b]
- Updated dependencies [4a5591b]
- llamaindex@0.0.27
## 0.0.24
### Patch Changes
+47
View File
@@ -0,0 +1,47 @@
import { ChatMessage, OpenAI, SimpleChatEngine } from "llamaindex";
import {Anthropic} from "../../packages/core/src/llm/LLM";
import { stdin as input, stdout as output } from "node:process";
import readline from "node:readline/promises";
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();
+68
View File
@@ -0,0 +1,68 @@
import { MongoClient } from "mongodb";
import { VectorStoreIndex } from "../../packages/core/src/indices";
import { Document } from "../../packages/core/src/Node";
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();
+2 -1
View File
@@ -1,9 +1,10 @@
{
"version": "0.0.24",
"version": "0.0.27",
"private": true,
"name": "simple",
"dependencies": {
"@notionhq/client": "^2.2.12",
"@pinecone-database/pinecone": "^1.0.1",
"commander": "^11.0.0",
"llamaindex": "workspace:*"
},
+23
View File
@@ -0,0 +1,23 @@
import { Portkey } from "llamaindex";
(async () => {
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
}]
});
const result = portkey.stream_chat([
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Tell me a joke." }
]);
for await (const res of result) {
process.stdout.write(res)
}
})();
+197
View File
@@ -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);
+23
View File
@@ -0,0 +1,23 @@
import { Portkey } from "llamaindex";
(async () => {
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
}]
});
const result = portkey.stream_chat([
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Tell me a joke." }
]);
for await (const res of result) {
process.stdout.write(res)
}
})();
+4 -4
View File
@@ -11,16 +11,16 @@
"publish-snapshot": "turbo run build lint test && changeset version --snapshot && changeset publish"
},
"devDependencies": {
"@turbo/gen": "^1.10.13",
"@types/jest": "^29.5.4",
"@turbo/gen": "^1.10.15",
"@types/jest": "^29.5.5",
"eslint": "^7.32.0",
"eslint-config-custom": "workspace:*",
"husky": "^8.0.3",
"jest": "^29.6.4",
"jest": "^29.7.0",
"prettier": "^3.0.3",
"prettier-plugin-organize-imports": "^3.2.3",
"ts-jest": "^29.1.1",
"turbo": "^1.10.13"
"turbo": "^1.10.15"
},
"packageManager": "pnpm@7.15.0",
"dependencies": {
+24
View File
@@ -1,5 +1,29 @@
# llamaindex
## 0.0.29
### Patch Changes
- a52143b: Added DocxReader for Word documents (thanks @jayantasamaddar)
- 1b7fd95: Updated OpenAI streaming (thanks @kkang2097)
- 0db3f41: Migrated to Tiktoken lite, which hopefully fixes the Windows issue
## 0.0.28
### Patch Changes
- 96bb657: Typesafe metadata (thanks @TomPenguin)
- 96bb657: MongoReader (thanks @kkang2097)
- 837854d: Make OutputParser less strict and add tests (Thanks @kkang2097)
## 0.0.27
### Patch Changes
- 4a5591b: Chat History summarization (thanks @marcusschiesser)
- 4a5591b: Notion database support (thanks @TomPenguin)
- 4a5591b: KeywordIndex (thanks @swk777)
## 0.0.26
### Patch Changes
+18 -12
View File
@@ -1,34 +1,40 @@
{
"name": "llamaindex",
"version": "0.0.26",
"version": "0.0.29",
"dependencies": {
"@anthropic-ai/sdk": "^0.6.2",
"@notionhq/client": "^2.2.12",
"@notionhq/client": "^2.2.13",
"lodash": "^4.17.21",
"mammoth": "^1.6.0",
"md-utils-ts": "^2.0.0",
"mongodb": "^6.1.0",
"notion-md-crawler": "^0.0.2",
"openai": "^4.3.1",
"openai": "^4.11.1",
"papaparse": "^5.4.1",
"pdf-parse": "^1.1.1",
"portkey-ai": "^0.1.11",
"rake-modified": "^1.0.8",
"replicate": "^0.16.1",
"tiktoken-node": "^0.0.6",
"uuid": "^9.0.0",
"replicate": "^0.20.0",
"tiktoken": "^1.0.10",
"uuid": "^9.0.1",
"wink-nlp": "^1.14.3"
},
"devDependencies": {
"@types/lodash": "^4.14.197",
"@types/node": "^18.17.12",
"@types/papaparse": "^5.3.8",
"@types/pdf-parse": "^1.1.1",
"@types/uuid": "^9.0.3",
"@types/lodash": "^4.14.199",
"@types/node": "^18.18.4",
"@types/papaparse": "^5.3.9",
"@types/pdf-parse": "^1.1.2",
"@types/uuid": "^9.0.5",
"node-stdlib-browser": "^1.2.0",
"tsup": "^7.2.0"
"tsup": "^7.2.0",
"typescript": "^4.9.5"
},
"engines": {
"node": ">=18.0.0"
},
"types": "./dist/index.d.ts",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"scripts": {
"lint": "eslint .",
"test": "jest",
+145 -13
View File
@@ -23,8 +23,16 @@ export interface ChatEngine {
* Send message along with the class's current chat history to the LLM.
* @param message
* @param chatHistory optional chat history if you want to customize the chat history
* @param streaming optional streaming flag, which auto-sets the return value if True.
*/
chat(message: string, chatHistory?: ChatMessage[]): Promise<Response>;
chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : Response,
>(
message: string,
chatHistory?: ChatMessage[],
streaming?: T,
): Promise<R>;
/**
* Resets the chat history so that it's empty.
@@ -44,13 +52,45 @@ export class SimpleChatEngine implements ChatEngine {
this.llm = init?.llm ?? new OpenAI();
}
async chat(message: string, chatHistory?: ChatMessage[]): Promise<Response> {
async chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : Response,
>(message: string, chatHistory?: ChatMessage[], streaming?: T): Promise<R> {
//Streaming option
if (streaming) {
return this.streamChat(message, chatHistory) as R;
}
//Non-streaming option
chatHistory = chatHistory ?? this.chatHistory;
chatHistory.push({ content: message, role: "user" });
const response = await this.llm.chat(chatHistory);
const response = await this.llm.chat(chatHistory, undefined);
chatHistory.push(response.message);
this.chatHistory = chatHistory;
return new Response(response.message.content);
return new Response(response.message.content) as R;
}
protected async *streamChat(
message: string,
chatHistory?: ChatMessage[],
): AsyncGenerator<string, void, unknown> {
chatHistory = chatHistory ?? this.chatHistory;
chatHistory.push({ content: message, role: "user" });
const response_generator = await this.llm.chat(
chatHistory,
undefined,
true,
);
var accumulator: string = "";
for await (const part of response_generator) {
accumulator += part;
yield part;
}
chatHistory.push({ content: accumulator, role: "assistant" });
this.chatHistory = chatHistory;
return;
}
reset() {
@@ -99,10 +139,14 @@ export class CondenseQuestionChatEngine implements ChatEngine {
);
}
async chat(
async chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : Response,
>(
message: string,
chatHistory?: ChatMessage[] | undefined,
): Promise<Response> {
streaming?: T,
): Promise<R> {
chatHistory = chatHistory ?? this.chatHistory;
const condensedQuestion = (
@@ -114,7 +158,7 @@ export class CondenseQuestionChatEngine implements ChatEngine {
chatHistory.push({ content: message, role: "user" });
chatHistory.push({ content: response.response, role: "assistant" });
return response;
return response as R;
}
reset() {
@@ -129,13 +173,13 @@ export class CondenseQuestionChatEngine implements ChatEngine {
*/
export class ContextChatEngine implements ChatEngine {
retriever: BaseRetriever;
chatModel: OpenAI;
chatModel: LLM;
chatHistory: ChatMessage[];
contextSystemPrompt: ContextSystemPrompt;
constructor(init: {
retriever: BaseRetriever;
chatModel?: OpenAI;
chatModel?: LLM;
chatHistory?: ChatMessage[];
contextSystemPrompt?: ContextSystemPrompt;
}) {
@@ -147,9 +191,21 @@ export class ContextChatEngine implements ChatEngine {
init?.contextSystemPrompt ?? defaultContextSystemPrompt;
}
async chat(message: string, chatHistory?: ChatMessage[] | undefined) {
async chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : Response,
>(
message: string,
chatHistory?: ChatMessage[] | undefined,
streaming?: T,
): Promise<R> {
chatHistory = chatHistory ?? this.chatHistory;
//Streaming option
if (streaming) {
return this.streamChat(message, chatHistory) as R;
}
const parentEvent: Event = {
id: uuidv4(),
type: "wrapper",
@@ -182,7 +238,52 @@ export class ContextChatEngine implements ChatEngine {
return new Response(
response.message.content,
sourceNodesWithScore.map((r) => r.node),
) as R;
}
protected async *streamChat(
message: string,
chatHistory?: ChatMessage[] | undefined,
): AsyncGenerator<string, void, unknown> {
chatHistory = chatHistory ?? this.chatHistory;
const parentEvent: Event = {
id: uuidv4(),
type: "wrapper",
tags: ["final"],
};
const sourceNodesWithScore = await this.retriever.retrieve(
message,
parentEvent,
);
const systemMessage: ChatMessage = {
content: this.contextSystemPrompt({
context: sourceNodesWithScore
.map((r) => (r.node as TextNode).text)
.join("\n\n"),
}),
role: "system",
};
chatHistory.push({ content: message, role: "user" });
const response_stream = await this.chatModel.chat(
[systemMessage, ...chatHistory],
parentEvent,
true,
);
var accumulator: string = "";
for await (const part of response_stream) {
accumulator += part;
yield part;
}
chatHistory.push({ content: accumulator, role: "system" });
this.chatHistory = chatHistory;
return;
}
reset() {
@@ -203,11 +304,42 @@ export class HistoryChatEngine implements ChatEngine {
this.llm = init?.llm ?? new OpenAI();
}
async chat(message: string): Promise<Response> {
async chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : Response,
>(
message: string,
chatHistory?: ChatMessage[] | undefined,
streaming?: T,
): Promise<R> {
//Streaming option
if (streaming) {
return this.streamChat(message, chatHistory) as R;
}
this.chatHistory.addMessage({ content: message, role: "user" });
const response = await this.llm.chat(this.chatHistory.messages);
const response = await this.llm.chat(this.chatHistory.requestMessages);
this.chatHistory.addMessage(response.message);
return new Response(response.message.content);
return new Response(response.message.content) as R;
}
protected async *streamChat(
message: string,
chatHistory?: ChatMessage[] | undefined,
): AsyncGenerator<string, void, unknown> {
this.chatHistory.addMessage({ content: message, role: "user" });
const response_stream = await this.llm.chat(
this.chatHistory.requestMessages,
undefined,
true,
);
var accumulator = "";
for await (const part of response_stream) {
accumulator += part;
yield part;
}
this.chatHistory.addMessage({ content: accumulator, role: "user" });
return;
}
reset() {
+53 -7
View File
@@ -16,6 +16,11 @@ export interface ChatHistory {
*/
addMessage(message: ChatMessage): Promise<void>;
/**
* Returns the messages that should be used as input to the LLM.
*/
requestMessages: ChatMessage[];
/**
* Resets the chat history so that it's empty.
*/
@@ -28,45 +33,86 @@ export class SimpleChatHistory implements ChatHistory {
constructor(init?: Partial<SimpleChatHistory>) {
this.messages = init?.messages ?? [];
}
async addMessage(message: ChatMessage) {
this.messages.push(message);
}
get requestMessages() {
return this.messages;
}
reset() {
this.messages = [];
}
}
export class SummaryChatHistory implements ChatHistory {
messagesToSummarize: number;
messages: ChatMessage[];
summaryPrompt: SummaryPrompt;
llm: LLM;
constructor(init?: Partial<SummaryChatHistory>) {
this.messagesToSummarize = init?.messagesToSummarize ?? 5;
this.messages = init?.messages ?? [];
this.summaryPrompt = init?.summaryPrompt ?? defaultSummaryPrompt;
this.llm = init?.llm ?? new OpenAI();
}
private async summarize() {
const chatHistoryStr = messagesToHistoryStr(this.messages);
// get all messages after the last summary message (including)
const chatHistoryStr = messagesToHistoryStr(
this.messages.slice(this.getLastSummaryIndex()),
);
const response = await this.llm.complete(
this.summaryPrompt({ context: chatHistoryStr }),
);
this.messages = [{ content: response.message.content, role: "system" }];
this.messages.push({ content: response.message.content, role: "memory" });
}
async addMessage(message: ChatMessage) {
// TODO: check if summarization is necessary
// TBD what are good conditions, e.g. depending on the context length of the LLM?
// for now we just have a dummy implementation at always summarizes the messages
await this.summarize();
const lastSummaryIndex = this.getLastSummaryIndex();
// if there are more than or equal `messagesToSummarize` messages since the last summary, call summarize
if (
lastSummaryIndex !== -1 &&
this.messages.length - lastSummaryIndex - 1 >= this.messagesToSummarize
) {
// TODO: define what are better conditions, e.g. depending on the context length of the LLM?
// for now we just summarize each `messagesToSummarize` messages
await this.summarize();
}
this.messages.push(message);
}
// Find last summary message
private getLastSummaryIndex() {
return this.messages
.slice()
.reverse()
.findIndex((message) => message.role === "memory");
}
get requestMessages() {
const lastSummaryIndex = this.getLastSummaryIndex();
// get array of all system messages
const systemMessages = this.messages.filter(
(message) => message.role === "system",
);
// convert summary message so it can be send to the LLM
const summaryMessage: ChatMessage = {
content: `This is a summary of conversation so far: ${this.messages[lastSummaryIndex].content}`,
role: "system",
};
// return system messages, last summary and all messages after the last summary message
return [
...systemMessages,
summaryMessage,
...this.messages.slice(lastSummaryIndex + 1),
];
}
reset() {
this.messages = [];
}
+24 -6
View File
@@ -1,3 +1,6 @@
import cl100k_base from "tiktoken/encoders/cl100k_base.json";
import { Tiktoken } from "tiktoken/lite";
import { v4 as uuidv4 } from "uuid";
import { Event, EventTag, EventType } from "./callbacks/CallbackManager";
@@ -6,14 +9,30 @@ import { Event, EventTag, EventType } from "./callbacks/CallbackManager";
*/
class GlobalsHelper {
defaultTokenizer: {
encode: (text: string) => number[];
decode: (tokens: number[]) => string;
encode: (text: string) => Uint32Array;
decode: (tokens: Uint32Array) => string;
} | null = null;
private initDefaultTokenizer() {
const encoding = new Tiktoken(
cl100k_base.bpe_ranks,
cl100k_base.special_tokens,
cl100k_base.pat_str,
);
this.defaultTokenizer = {
encode: (text: string) => {
return encoding.encode(text);
},
decode: (tokens: Uint32Array) => {
return new TextDecoder().decode(encoding.decode(tokens));
},
};
}
tokenizer() {
if (!this.defaultTokenizer) {
const tiktoken = require("tiktoken-node");
this.defaultTokenizer = tiktoken.getEncoding("gpt2");
this.initDefaultTokenizer();
}
return this.defaultTokenizer!.encode.bind(this.defaultTokenizer);
@@ -21,8 +40,7 @@ class GlobalsHelper {
tokenizerDecoder() {
if (!this.defaultTokenizer) {
const tiktoken = require("tiktoken-node");
this.defaultTokenizer = tiktoken.getEncoding("gpt2");
this.initDefaultTokenizer();
}
return this.defaultTokenizer!.decode.bind(this.defaultTokenizer);
+25 -21
View File
@@ -23,19 +23,23 @@ export enum MetadataMode {
NONE = "NONE",
}
export interface RelatedNodeInfo {
export type Metadata = Record<string, any>;
export interface RelatedNodeInfo<T extends Metadata = Metadata> {
nodeId: string;
nodeType?: ObjectType;
metadata: Record<string, any>;
metadata: T;
hash?: string;
}
export type RelatedNodeType = RelatedNodeInfo | RelatedNodeInfo[];
export type RelatedNodeType<T extends Metadata = Metadata> =
| RelatedNodeInfo<T>
| RelatedNodeInfo<T>[];
/**
* Generic abstract class for retrievable nodes
*/
export abstract class BaseNode {
export abstract class BaseNode<T extends Metadata = Metadata> {
/**
* The unique ID of the Node/Document. The trailing underscore is here
* to avoid collisions with the id keyword in Python.
@@ -46,13 +50,13 @@ export abstract class BaseNode {
embedding?: number[];
// Metadata fields
metadata: Record<string, any> = {};
metadata: T = {} as T;
excludedEmbedMetadataKeys: string[] = [];
excludedLlmMetadataKeys: string[] = [];
relationships: Partial<Record<NodeRelationship, RelatedNodeType>> = {};
relationships: Partial<Record<NodeRelationship, RelatedNodeType<T>>> = {};
hash: string = "";
constructor(init?: Partial<BaseNode>) {
constructor(init?: Partial<BaseNode<T>>) {
Object.assign(this, init);
}
@@ -62,7 +66,7 @@ export abstract class BaseNode {
abstract getMetadataStr(metadataMode: MetadataMode): string;
abstract setContent(value: any): void;
get sourceNode(): RelatedNodeInfo | undefined {
get sourceNode(): RelatedNodeInfo<T> | undefined {
const relationship = this.relationships[NodeRelationship.SOURCE];
if (Array.isArray(relationship)) {
@@ -72,7 +76,7 @@ export abstract class BaseNode {
return relationship;
}
get prevNode(): RelatedNodeInfo | undefined {
get prevNode(): RelatedNodeInfo<T> | undefined {
const relationship = this.relationships[NodeRelationship.PREVIOUS];
if (Array.isArray(relationship)) {
@@ -84,7 +88,7 @@ export abstract class BaseNode {
return relationship;
}
get nextNode(): RelatedNodeInfo | undefined {
get nextNode(): RelatedNodeInfo<T> | undefined {
const relationship = this.relationships[NodeRelationship.NEXT];
if (Array.isArray(relationship)) {
@@ -94,7 +98,7 @@ export abstract class BaseNode {
return relationship;
}
get parentNode(): RelatedNodeInfo | undefined {
get parentNode(): RelatedNodeInfo<T> | undefined {
const relationship = this.relationships[NodeRelationship.PARENT];
if (Array.isArray(relationship)) {
@@ -104,7 +108,7 @@ export abstract class BaseNode {
return relationship;
}
get childNodes(): RelatedNodeInfo[] | undefined {
get childNodes(): RelatedNodeInfo<T>[] | undefined {
const relationship = this.relationships[NodeRelationship.CHILD];
if (!Array.isArray(relationship)) {
@@ -126,7 +130,7 @@ export abstract class BaseNode {
return this.embedding;
}
asRelatedNodeInfo(): RelatedNodeInfo {
asRelatedNodeInfo(): RelatedNodeInfo<T> {
return {
nodeId: this.id_,
metadata: this.metadata,
@@ -146,7 +150,7 @@ export abstract class BaseNode {
/**
* TextNode is the default node type for text. Most common node type in LlamaIndex.TS
*/
export class TextNode extends BaseNode {
export class TextNode<T extends Metadata = Metadata> extends BaseNode<T> {
text: string = "";
startCharIdx?: number;
endCharIdx?: number;
@@ -154,7 +158,7 @@ export class TextNode extends BaseNode {
// metadataTemplate: NOTE write your own formatter if needed
metadataSeparator: string = "\n";
constructor(init?: Partial<TextNode>) {
constructor(init?: Partial<TextNode<T>>) {
super(init);
Object.assign(this, init);
@@ -233,10 +237,10 @@ export class TextNode extends BaseNode {
// }
// }
export class IndexNode extends TextNode {
export class IndexNode<T extends Metadata = Metadata> extends TextNode<T> {
indexId: string = "";
constructor(init?: Partial<IndexNode>) {
constructor(init?: Partial<IndexNode<T>>) {
super(init);
Object.assign(this, init);
@@ -253,8 +257,8 @@ export class IndexNode extends TextNode {
/**
* A document is just a special text node with a docId.
*/
export class Document extends TextNode {
constructor(init?: Partial<Document>) {
export class Document<T extends Metadata = Metadata> extends TextNode<T> {
constructor(init?: Partial<Document<T>>) {
super(init);
Object.assign(this, init);
@@ -292,7 +296,7 @@ export function jsonToNode(json: any) {
/**
* A node with a similarity score
*/
export interface NodeWithScore {
node: BaseNode;
export interface NodeWithScore<T extends Metadata = Metadata> {
node: BaseNode<T>;
score?: number;
}
+18 -17
View File
@@ -53,30 +53,31 @@ class OutputParserError extends Error {
* @param text A markdown block with JSON
* @returns parsed JSON object
*/
function parseJsonMarkdown(text: string) {
export function parseJsonMarkdown(text: string) {
text = text.trim();
const beginDelimiter = "```json";
const endDelimiter = "```";
const left_square = text.indexOf("[");
const left_brace = text.indexOf("{");
const beginIndex = text.indexOf(beginDelimiter);
const endIndex = text.indexOf(
endDelimiter,
beginIndex + beginDelimiter.length,
);
if (beginIndex === -1 || endIndex === -1) {
throw new OutputParserError("Not a json markdown", { output: text });
var left: number;
var right: number;
if (left_square < left_brace && left_square != -1) {
left = left_square;
right = text.lastIndexOf("]");
} else {
left = left_brace;
right = text.lastIndexOf("}");
}
const jsonText = text.substring(beginIndex + beginDelimiter.length, endIndex);
const jsonText = text.substring(left, right + 1);
try {
//Single JSON object case
if (left_square === -1) {
return [JSON.parse(jsonText)];
}
//Multiple JSON object case.
return JSON.parse(jsonText);
} catch (e) {
throw new OutputParserError("Not a valid json", {
cause: e as Error,
output: text,
});
throw new OutputParserError("Not a json markdown", { output: text });
}
}
+2 -2
View File
@@ -34,7 +34,7 @@ export class PromptHelper {
numOutput = DEFAULT_NUM_OUTPUTS;
chunkOverlapRatio = DEFAULT_CHUNK_OVERLAP_RATIO;
chunkSizeLimit?: number;
tokenizer: (text: string) => number[];
tokenizer: (text: string) => Uint32Array;
separator = " ";
constructor(
@@ -42,7 +42,7 @@ export class PromptHelper {
numOutput = DEFAULT_NUM_OUTPUTS,
chunkOverlapRatio = DEFAULT_CHUNK_OVERLAP_RATIO,
chunkSizeLimit?: number,
tokenizer?: (text: string) => number[],
tokenizer?: (text: string) => Uint32Array,
separator = " ",
) {
this.contextWindow = contextWindow;
+9 -2
View File
@@ -1,4 +1,5 @@
import { v4 as uuidv4 } from "uuid";
import { Event } from "./callbacks/CallbackManager";
import { NodeWithScore, TextNode } from "./Node";
import {
BaseQuestionGenerator,
@@ -10,7 +11,6 @@ import { CompactAndRefine, ResponseSynthesizer } from "./ResponseSynthesizer";
import { BaseRetriever } from "./Retriever";
import { ServiceContext, serviceContextFromDefaults } from "./ServiceContext";
import { QueryEngineTool, ToolMetadata } from "./Tool";
import { Event } from "./callbacks/CallbackManager";
/**
* A query engine is a question answerer that can use one or more steps.
@@ -30,16 +30,19 @@ export interface BaseQueryEngine {
export class RetrieverQueryEngine implements BaseQueryEngine {
retriever: BaseRetriever;
responseSynthesizer: ResponseSynthesizer;
preFilters?: unknown;
constructor(
retriever: BaseRetriever,
responseSynthesizer?: ResponseSynthesizer,
preFilters?: unknown,
) {
this.retriever = retriever;
const serviceContext: ServiceContext | undefined =
this.retriever.getServiceContext();
this.responseSynthesizer =
responseSynthesizer || new ResponseSynthesizer({ serviceContext });
this.preFilters = preFilters;
}
async query(query: string, parentEvent?: Event) {
@@ -48,7 +51,11 @@ export class RetrieverQueryEngine implements BaseQueryEngine {
type: "wrapper",
tags: ["final"],
};
const nodes = await this.retriever.retrieve(query, _parentEvent);
const nodes = await this.retriever.retrieve(
query,
_parentEvent,
this.preFilters,
);
return this.responseSynthesizer.synthesize(query, nodes, _parentEvent);
}
}
+8 -5
View File
@@ -1,18 +1,18 @@
import { Event } from "./callbacks/CallbackManager";
import { LLM } from "./llm/LLM";
import { MetadataMode, NodeWithScore } from "./Node";
import {
defaultRefinePrompt,
defaultTextQaPrompt,
defaultTreeSummarizePrompt,
RefinePrompt,
SimplePrompt,
TextQaPrompt,
TreeSummarizePrompt,
defaultRefinePrompt,
defaultTextQaPrompt,
defaultTreeSummarizePrompt,
} from "./Prompt";
import { getBiggestPrompt } from "./PromptHelper";
import { Response } from "./Response";
import { ServiceContext, serviceContextFromDefaults } from "./ServiceContext";
import { Event } from "./callbacks/CallbackManager";
import { LLM } from "./llm/LLM";
/**
* Response modes of the response synthesizer
@@ -231,6 +231,7 @@ export class TreeSummarize implements BaseResponseBuilder {
throw new Error("Must have at least one text chunk");
}
// Should we send the query here too?
const packedTextChunks = this.serviceContext.promptHelper.repack(
this.summaryTemplate,
textChunks,
@@ -241,6 +242,7 @@ export class TreeSummarize implements BaseResponseBuilder {
await this.serviceContext.llm.complete(
this.summaryTemplate({
context: packedTextChunks[0],
query,
}),
parentEvent,
)
@@ -251,6 +253,7 @@ export class TreeSummarize implements BaseResponseBuilder {
this.serviceContext.llm.complete(
this.summaryTemplate({
context: chunk,
query,
}),
parentEvent,
),
+6 -2
View File
@@ -1,11 +1,15 @@
import { Event } from "./callbacks/CallbackManager";
import { NodeWithScore } from "./Node";
import { ServiceContext } from "./ServiceContext";
import { Event } from "./callbacks/CallbackManager";
/**
* Retrievers retrieve the nodes that most closely match our query in similarity.
*/
export interface BaseRetriever {
retrieve(query: string, parentEvent?: Event): Promise<NodeWithScore[]>;
retrieve(
query: string,
parentEvent?: Event,
preFilters?: unknown,
): Promise<NodeWithScore[]>;
getServiceContext(): ServiceContext;
}
+21 -2
View File
@@ -20,7 +20,8 @@ interface BaseCallbackResponse {
event: Event;
}
export interface StreamToken {
//Specify StreamToken per mainstream LLM
export interface DefaultStreamToken {
id: string;
object: string;
created: number;
@@ -35,10 +36,28 @@ export interface StreamToken {
}[];
}
//OpenAI stream token schema is the default.
//Note: Anthropic and Replicate also use similar token schemas.
export type OpenAIStreamToken = DefaultStreamToken;
export type AnthropicStreamToken = {
completion: string;
model: string;
stop_reason: string | undefined;
stop?: boolean | undefined;
log_id?: string;
};
//
//Callback Responses
//
//TODO: Write Embedding Callbacks
//StreamCallbackResponse should let practitioners implement callbacks out of the box...
//When custom streaming LLMs are involved, people are expected to write their own StreamCallbackResponses
export interface StreamCallbackResponse extends BaseCallbackResponse {
index: number;
isDone?: boolean;
token?: StreamToken;
token?: DefaultStreamToken;
}
export interface RetrievalCallbackResponse extends BaseCallbackResponse {
@@ -1,45 +0,0 @@
import { ChatCompletionChunk } from "openai/resources/chat";
import { Stream } from "openai/streaming";
import { globalsHelper } from "../../GlobalsHelper";
import { MessageType } from "../../llm/LLM";
import { Event, StreamCallbackResponse } from "../CallbackManager";
/**
* Handles the OpenAI streaming interface and pipes it to the callback function
* @param response - The response from the OpenAI API.
* @param onLLMStream - A callback function to handle the LLM stream.
* @param parentEvent - An optional parent event.
* @returns A promise that resolves to an object with a message and a role.
*/
export async function handleOpenAIStream({
response,
onLLMStream,
parentEvent,
}: {
response: Stream<ChatCompletionChunk>;
onLLMStream: (data: StreamCallbackResponse) => void;
parentEvent?: Event;
}): Promise<{ message: string; role: MessageType }> {
const event = globalsHelper.createEvent({
parentEvent,
type: "llmPredict",
});
let index = 0;
let cumulativeText = "";
let messageRole: MessageType = "assistant";
for await (const part of response) {
const { content = "", role = "assistant" } = part.choices[0].delta;
// ignore the first token
if (!content && role === "assistant" && index === 0) {
continue;
}
cumulativeText += content;
messageRole = role;
onLLMStream?.({ event, index, token: part });
index++;
}
onLLMStream?.({ event, index, isDone: true });
return { message: cumulativeText, role: messageRole };
}
+12 -15
View File
@@ -1,30 +1,27 @@
export * from "./callbacks/CallbackManager";
export * from "./ChatEngine";
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";
export * from "./Prompt";
export * from "./PromptHelper";
export * from "./QueryEngine";
export * from "./QuestionGenerator";
export * from "./Response";
export * from "./ResponseSynthesizer";
export * from "./Retriever";
export * from "./ServiceContext";
export * from "./TextSplitter";
export * from "./Tool";
export * from "./constants";
export * from "./llm/LLM";
export * from "./indices";
export * from "./callbacks/CallbackManager";
export * from "./readers/base";
export * from "./readers/CSVReader";
export * from "./readers/MarkdownReader";
export * from "./readers/NotionReader";
export * from "./readers/PDFReader";
export * from "./readers/SimpleDirectoryReader";
export * from "./readers/base";
export * from "./Response";
export * from "./ResponseSynthesizer";
export * from "./Retriever";
export * from "./ServiceContext";
export * from "./storage";
export * from "./TextSplitter";
export * from "./Tool";
@@ -1,9 +1,9 @@
import { Event } from "../../callbacks/CallbackManager";
import { DEFAULT_SIMILARITY_TOP_K } from "../../constants";
import { globalsHelper } from "../../GlobalsHelper";
import { NodeWithScore } from "../../Node";
import { BaseRetriever } from "../../Retriever";
import { ServiceContext } from "../../ServiceContext";
import { Event } from "../../callbacks/CallbackManager";
import { DEFAULT_SIMILARITY_TOP_K } from "../../constants";
import {
VectorStoreQuery,
VectorStoreQueryMode,
@@ -32,7 +32,7 @@ export class VectorIndexRetriever implements BaseRetriever {
this.similarityTopK = similarityTopK ?? DEFAULT_SIMILARITY_TOP_K;
}
async retrieve(query: string, parentEvent?: Event): Promise<NodeWithScore[]> {
async retrieve(query: string, parentEvent?: Event, preFilters?: unknown): Promise<NodeWithScore[]> {
const queryEmbedding =
await this.serviceContext.embedModel.getQueryEmbedding(query);
@@ -41,10 +41,15 @@ export class VectorIndexRetriever implements BaseRetriever {
mode: VectorStoreQueryMode.DEFAULT,
similarityTopK: this.similarityTopK,
};
const result = await this.index.vectorStore.query(q);
const result = await this.index.vectorStore.query(q, preFilters);
let nodesWithScores: NodeWithScore[] = [];
for (let i = 0; i < result.ids.length; i++) {
const nodeFromResult = result.nodes?.[i];
if (!this.index.indexStruct.nodesDict[result.ids[i]] && nodeFromResult) {
this.index.indexStruct.nodesDict[result.ids[i]] = nodeFromResult;
}
const node = this.index.indexStruct.nodesDict[result.ids[i]];
nodesWithScores.push({
node: node,
@@ -219,6 +219,27 @@ export class VectorStoreIndex extends BaseIndex<IndexDict> {
return index;
}
static async fromVectorStore(
vectorStore: VectorStore,
serviceContext: ServiceContext,
) {
if (!vectorStore.storesText) {
throw new Error(
"Cannot initialize from a vector store that does not store text",
);
}
const storageContext = await storageContextFromDefaults({ vectorStore });
const index = await VectorStoreIndex.init({
nodes: [],
storageContext,
serviceContext,
});
return index;
}
asRetriever(options?: any): VectorIndexRetriever {
return new VectorIndexRetriever({ index: this, ...options });
}
+324 -47
View File
@@ -1,10 +1,18 @@
import OpenAILLM, { ClientOptions as OpenAIClientOptions } from "openai";
import { CallbackManager, Event } from "../callbacks/CallbackManager";
import { handleOpenAIStream } from "../callbacks/utility/handleOpenAIStream";
import {
AnthropicStreamToken,
CallbackManager,
Event,
EventType,
OpenAIStreamToken,
StreamCallbackResponse,
} from "../callbacks/CallbackManager";
import { LLMOptions } from "portkey-ai";
import {
AnthropicSession,
ANTHROPIC_AI_PROMPT,
ANTHROPIC_HUMAN_PROMPT,
AnthropicSession,
getAnthropicSession,
} from "./anthropic";
import {
@@ -14,7 +22,8 @@ import {
getAzureModel,
shouldUseAzure,
} from "./azure";
import { OpenAISession, getOpenAISession } from "./openai";
import { getOpenAISession, OpenAISession } from "./openai";
import { getPortkeySession, PortkeySession } from "./portkey";
import { ReplicateSession } from "./replicate";
export type MessageType =
@@ -22,7 +31,8 @@ export type MessageType =
| "assistant"
| "system"
| "generic"
| "function";
| "function"
| "memory";
export interface ChatMessage {
content: string;
@@ -42,17 +52,35 @@ export type CompletionResponse = ChatResponse;
* Unified language model interface
*/
export interface LLM {
// Whether a LLM has streaming support
hasStreaming: boolean;
/**
* Get a chat response from the LLM
* @param messages
*
* The return type of chat() and complete() are set by the "streaming" parameter being set to True.
*/
chat(messages: ChatMessage[], parentEvent?: Event): Promise<ChatResponse>;
chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : ChatResponse,
>(
messages: ChatMessage[],
parentEvent?: Event,
streaming?: T,
): Promise<R>;
/**
* Get a prompt completion from the LLM
* @param prompt the prompt to complete
*/
complete(prompt: string, parentEvent?: Event): Promise<CompletionResponse>;
complete<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : ChatResponse,
>(
prompt: string,
parentEvent?: Event,
streaming?: T,
): Promise<R>;
}
export const GPT4_MODELS = {
@@ -77,13 +105,15 @@ export const ALL_AVAILABLE_OPENAI_MODELS = {
* OpenAI LLM implementation
*/
export class OpenAI implements LLM {
hasStreaming: boolean = true;
// Per completion OpenAI params
model: keyof typeof ALL_AVAILABLE_OPENAI_MODELS;
temperature: number;
topP: number;
maxTokens?: number;
additionalChatOptions?: Omit<
Partial<OpenAILLM.Chat.CompletionCreateParams>,
Partial<OpenAILLM.Chat.ChatCompletionCreateParams>,
"max_tokens" | "messages" | "model" | "temperature" | "top_p" | "streaming"
>;
@@ -170,11 +200,59 @@ export class OpenAI implements LLM {
}
}
async chat(
async chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : ChatResponse,
>(messages: ChatMessage[], parentEvent?: Event, streaming?: T): Promise<R> {
const baseRequestParams: OpenAILLM.Chat.ChatCompletionCreateParams = {
model: this.model,
temperature: this.temperature,
max_tokens: this.maxTokens,
messages: messages.map((message) => ({
role: this.mapMessageType(message.role),
content: message.content,
})),
top_p: this.topP,
...this.additionalChatOptions,
};
// Streaming
if (streaming) {
if (!this.hasStreaming) {
throw Error("No streaming support for this LLM.");
}
return this.streamChat(messages, parentEvent) as R;
}
// Non-streaming
const response = await this.session.openai.chat.completions.create({
...baseRequestParams,
stream: false,
});
const content = response.choices[0].message?.content ?? "";
return {
message: { content, role: response.choices[0].message.role },
} as R;
}
async complete<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : ChatResponse,
>(prompt: string, parentEvent?: Event, streaming?: T): Promise<R> {
return this.chat(
[{ content: prompt, role: "user" }],
parentEvent,
streaming,
);
}
//We can wrap a stream in a generator to add some additional logging behavior
//For future edits: syntax for generator type is <typeof Yield, typeof Return, typeof Accept>
//"typeof Accept" refers to what types you'll accept when you manually call generator.next(<AcceptType>)
protected async *streamChat(
messages: ChatMessage[],
parentEvent?: Event,
): Promise<ChatResponse> {
const baseRequestParams: OpenAILLM.Chat.CompletionCreateParams = {
): AsyncGenerator<string, void, unknown> {
const baseRequestParams: OpenAILLM.Chat.ChatCompletionCreateParams = {
model: this.model,
temperature: this.temperature,
max_tokens: this.maxTokens,
@@ -186,36 +264,54 @@ export class OpenAI implements LLM {
...this.additionalChatOptions,
};
if (this.callbackManager?.onLLMStream) {
// Streaming
const response = await this.session.openai.chat.completions.create({
//Now let's wrap our stream in a callback
const onLLMStream = this.callbackManager?.onLLMStream
? this.callbackManager.onLLMStream
: () => {};
const chunk_stream: AsyncIterable<OpenAIStreamToken> =
await this.session.openai.chat.completions.create({
...baseRequestParams,
stream: true,
});
const { message, role } = await handleOpenAIStream({
response,
onLLMStream: this.callbackManager.onLLMStream,
parentEvent,
});
return { message: { content: message, role } };
} else {
// Non-streaming
const response = await this.session.openai.chat.completions.create({
...baseRequestParams,
stream: false,
});
const event: Event = parentEvent
? parentEvent
: {
id: "unspecified",
type: "llmPredict" as EventType,
};
const content = response.choices[0].message?.content ?? "";
return { message: { content, role: response.choices[0].message.role } };
//Indices
var idx_counter: number = 0;
for await (const part of chunk_stream) {
//Increment
part.choices[0].index = idx_counter;
const is_done: boolean =
part.choices[0].finish_reason === "stop" ? true : false;
//onLLMStream Callback
const stream_callback: StreamCallbackResponse = {
event: event,
index: idx_counter,
isDone: is_done,
token: part,
};
onLLMStream(stream_callback);
idx_counter++;
yield part.choices[0].delta.content ? part.choices[0].delta.content : "";
}
return;
}
async complete(
prompt: string,
//streamComplete doesn't need to be async because it's child function is already async
protected streamComplete(
query: string,
parentEvent?: Event,
): Promise<CompletionResponse> {
return this.chat([{ content: prompt, role: "user" }], parentEvent);
): AsyncGenerator<string, void, unknown> {
return this.streamChat([{ content: query, role: "user" }], parentEvent);
}
}
@@ -279,6 +375,7 @@ export class LlamaDeuce implements LLM {
topP: number;
maxTokens?: number;
replicateSession: ReplicateSession;
hasStreaming: boolean;
constructor(init?: Partial<LlamaDeuce>) {
this.model = init?.model ?? "Llama-2-70b-chat-4bit";
@@ -293,6 +390,7 @@ export class LlamaDeuce implements LLM {
init?.maxTokens ??
ALL_AVAILABLE_LLAMADEUCE_MODELS[this.model].contextWindow; // For Replicate, the default is 500 tokens which is too low.
this.replicateSession = init?.replicateSession ?? new ReplicateSession();
this.hasStreaming = init?.hasStreaming ?? false;
}
mapMessagesToPrompt(messages: ChatMessage[]) {
@@ -399,10 +497,10 @@ If a question does not make any sense, or is not factually coherent, explain why
};
}
async chat(
messages: ChatMessage[],
_parentEvent?: Event,
): Promise<ChatResponse> {
async chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : ChatResponse,
>(messages: ChatMessage[], _parentEvent?: Event, streaming?: T): Promise<R> {
const api = ALL_AVAILABLE_LLAMADEUCE_MODELS[this.model]
.replicateApi as `${string}/${string}:${string}`;
@@ -423,6 +521,9 @@ If a question does not make any sense, or is not factually coherent, explain why
replicateOptions.input.max_length = this.maxTokens;
}
//TODO: Add streaming for this
//Non-streaming
const response = await this.replicateSession.replicate.run(
api,
replicateOptions,
@@ -433,13 +534,13 @@ If a question does not make any sense, or is not factually coherent, explain why
//^ We need to do this because Replicate returns a list of strings (for streaming functionality which is not exposed by the run function)
role: "assistant",
},
};
} as R;
}
async complete(
prompt: string,
parentEvent?: Event,
): Promise<CompletionResponse> {
async complete<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : ChatResponse,
>(prompt: string, parentEvent?: Event, streaming?: T): Promise<R> {
return this.chat([{ content: prompt, role: "user" }], parentEvent);
}
}
@@ -449,6 +550,8 @@ If a question does not make any sense, or is not factually coherent, explain why
*/
export class Anthropic implements LLM {
hasStreaming: boolean = true;
// Per completion Anthropic params
model: string;
temperature: number;
@@ -498,10 +601,22 @@ export class Anthropic implements LLM {
);
}
async chat(
async chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : ChatResponse,
>(
messages: ChatMessage[],
parentEvent?: Event | undefined,
): Promise<ChatResponse> {
streaming?: T,
): Promise<R> {
//Streaming
if (streaming) {
if (!this.hasStreaming) {
throw Error("No streaming support for this LLM.");
}
return this.streamChat(messages, parentEvent) as R;
}
//Non-streaming
const response = await this.session.anthropic.completions.create({
model: this.model,
prompt: this.mapMessagesToPrompt(messages),
@@ -514,12 +629,174 @@ export class Anthropic implements LLM {
message: { content: response.completion.trimStart(), role: "assistant" },
//^ We're trimming the start because Anthropic often starts with a space in the response
// That space will be re-added when we generate the next prompt.
};
} as R;
}
async complete(
protected async *streamChat(
messages: ChatMessage[],
parentEvent?: Event | undefined,
): AsyncGenerator<string, void, unknown> {
// AsyncIterable<AnthropicStreamToken>
const stream: AsyncIterable<AnthropicStreamToken> =
await this.session.anthropic.completions.create({
model: this.model,
prompt: this.mapMessagesToPrompt(messages),
max_tokens_to_sample: this.maxTokens ?? 100000,
temperature: this.temperature,
top_p: this.topP,
stream: true,
});
var idx_counter: number = 0;
for await (const part of stream) {
//TODO: LLM Stream Callback, pending re-work.
idx_counter++;
yield part.completion;
}
return;
}
async complete<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : ChatResponse,
>(
prompt: string,
parentEvent?: Event | undefined,
): Promise<CompletionResponse> {
return this.chat([{ content: prompt, role: "user" }], parentEvent);
streaming?: T,
): Promise<R> {
if (streaming) {
return this.streamComplete(prompt, parentEvent) as R;
}
return this.chat(
[{ content: prompt, role: "user" }],
parentEvent,
streaming,
) as R;
}
protected streamComplete(
prompt: string,
parentEvent?: Event | undefined,
): AsyncGenerator<string, void, unknown> {
return this.streamChat([{ content: prompt, role: "user" }], parentEvent);
}
}
export class Portkey implements LLM {
hasStreaming: boolean = true;
apiKey?: string = undefined;
baseURL?: string = undefined;
mode?: string = undefined;
llms?: [LLMOptions] | null = undefined;
session: PortkeySession;
callbackManager?: CallbackManager;
constructor(init?: Partial<Portkey>) {
this.apiKey = init?.apiKey;
this.baseURL = init?.baseURL;
this.mode = init?.mode;
this.llms = init?.llms;
this.session = getPortkeySession({
apiKey: this.apiKey,
baseURL: this.baseURL,
llms: this.llms,
mode: this.mode,
});
this.callbackManager = init?.callbackManager;
}
async chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : ChatResponse,
>(
messages: ChatMessage[],
parentEvent?: Event | undefined,
streaming?: T,
params?: Record<string, any>,
): Promise<R> {
if (streaming) {
return this.streamChat(messages, parentEvent, params) as R;
} else {
const resolvedParams = params || {};
const response = await this.session.portkey.chatCompletions.create({
messages,
...resolvedParams,
});
const content = response.choices[0].message?.content ?? "";
const role = response.choices[0].message?.role || "assistant";
return { message: { content, role: role as MessageType } } as R;
}
}
async complete<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : ChatResponse,
>(
prompt: string,
parentEvent?: Event | undefined,
streaming?: T,
): Promise<R> {
return this.chat(
[{ content: prompt, role: "user" }],
parentEvent,
streaming,
);
}
async *streamChat(
messages: ChatMessage[],
parentEvent?: Event,
params?: Record<string, any>,
): AsyncGenerator<string, void, unknown> {
// Wrapping the stream in a callback.
const onLLMStream = this.callbackManager?.onLLMStream
? this.callbackManager.onLLMStream
: () => {};
const chunkStream = await this.session.portkey.chatCompletions.create({
messages,
...params,
stream: true,
});
const event: Event = parentEvent
? parentEvent
: {
id: "unspecified",
type: "llmPredict" as EventType,
};
//Indices
var idx_counter: number = 0;
for await (const part of chunkStream) {
//Increment
part.choices[0].index = idx_counter;
const is_done: boolean =
part.choices[0].finish_reason === "stop" ? true : false;
//onLLMStream Callback
const stream_callback: StreamCallbackResponse = {
event: event,
index: idx_counter,
isDone: is_done,
// token: part,
};
onLLMStream(stream_callback);
idx_counter++;
yield part.choices[0].delta?.content ?? "";
}
return;
}
streamComplete(
query: string,
parentEvent?: Event,
): AsyncGenerator<string, void, unknown> {
return this.streamChat([{ content: query, role: "user" }], parentEvent);
}
}
+62
View File
@@ -0,0 +1,62 @@
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;
}
return default_val;
};
interface PortkeyOptions {
apiKey?: string;
baseURL?: string;
mode?: string;
llms?: [LLMOptions] | null
}
export class PortkeySession {
portkey: Portkey;
constructor(options:PortkeyOptions = {}) {
if (!options.apiKey) {
options.apiKey = readEnv('PORTKEY_API_KEY')
}
if (!options.baseURL) {
options.baseURL = readEnv('PORTKEY_BASE_URL', "https://api.portkey.ai")
}
this.portkey = new Portkey({});
this.portkey.llms = [{}]
if (!options.apiKey) {
throw new Error("Set Portkey ApiKey in PORTKEY_API_KEY env variable");
}
this.portkey = new Portkey(options);
}
}
let defaultPortkeySession: {
session: PortkeySession;
options: PortkeyOptions;
}[] = [];
/**
* Get a session for the Portkey API. If one already exists with the same options,
* it will be returned. Otherwise, a new session will be created.
* @param options
* @returns
*/
export function getPortkeySession(options: PortkeyOptions = {}) {
let session = defaultPortkeySession.find((session) => {
return _.isEqual(session.options, options);
})?.session;
if (!session) {
session = new PortkeySession(options);
defaultPortkeySession.push({ session, options });
}
return session;
}
+17
View File
@@ -0,0 +1,17 @@
import mammoth from "mammoth";
import { Document } from "../Node";
import { DEFAULT_FS } from "../storage/constants";
import { GenericFileSystem } from "../storage/FileSystem";
import { BaseReader } from "./base";
export class DocxReader implements BaseReader {
/** DocxParser */
async loadData(
file: string,
fs: GenericFileSystem = DEFAULT_FS,
): Promise<Document[]> {
const dataBuffer = (await fs.readFile(file)) as any;
const { value } = await mammoth.extractRawText({ buffer: dataBuffer });
return [new Document({ text: value, id_: file })];
}
}
@@ -1,11 +1,12 @@
import _ from "lodash";
import { Document } from "../Node";
import { CompleteFileSystem, walk } from "../storage/FileSystem";
import { DEFAULT_FS } from "../storage/constants";
import { CompleteFileSystem, walk } from "../storage/FileSystem";
import { BaseReader } from "./base";
import { PapaCSVReader } from "./CSVReader";
import { DocxReader } from "./DocxReader";
import { MarkdownReader } from "./MarkdownReader";
import { PDFReader } from "./PDFReader";
import { BaseReader } from "./base";
/**
* Read a .txt file
@@ -25,6 +26,7 @@ const FILE_EXT_TO_READER: Record<string, BaseReader> = {
pdf: new PDFReader(),
csv: new PapaCSVReader(),
md: new MarkdownReader(),
docx: new DocxReader(),
};
export type SimpleDirectoryReaderLoadDataProps = {
@@ -0,0 +1,51 @@
import { MongoClient } from "mongodb";
import { Document } from "../Node";
import { BaseReader } from "./base";
/**
* Read in from MongoDB
*/
export class SimpleMongoReader implements BaseReader {
private client: MongoClient;
constructor(client: MongoClient) {
this.client = client;
}
/**
* 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[]>}
*/
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();
//Aggregate results and return
const documents: Document[] = [];
cursor.forEach((element: Partial<Document>) => {
//For later: Metadata filtering
documents.push(new Document({ text: JSON.stringify(element) }));
});
return documents;
}
}
@@ -63,6 +63,9 @@ export interface VectorStore {
client(): any;
add(embeddingResults: BaseNode[]): Promise<string[]>;
delete(refDocId: string, deleteKwargs?: any): Promise<void>;
query(query: VectorStoreQuery, kwargs?: any): Promise<VectorStoreQueryResult>;
query(
query: VectorStoreQuery,
options?: any,
): Promise<VectorStoreQueryResult>;
persist(persistPath: string, fs?: GenericFileSystem): Promise<void>;
}
@@ -0,0 +1,90 @@
import { SubQuestionOutputParser } from "../OutputParser";
//This parser is really important, so make sure to add tests
// as the parser sees through more iterations.
describe("SubQuestionOutputParser", () => {
test("parses expected", () => {
const parser = new SubQuestionOutputParser();
const data = [
{
name: "uber_10k",
description: "Provides information about Uber financials for year 2021",
},
{
name: "lyft_10k",
description: "Provides information about Lyft financials for year 2021",
},
];
const data_str: string = JSON.stringify(data);
const full_string = `\`\`\`json
${data_str}
\`\`\``;
const real_answer = { parsedOutput: data, rawOutput: full_string };
expect(parser.parse(full_string)).toEqual(real_answer);
});
//This is in case our LLM outputs a list response, but without ```json.
test("parses without ```json", () => {
const parser = new SubQuestionOutputParser();
const data = [
{
name: "uber_10k",
description: "Provides information about Uber financials for year 2021",
},
{
name: "lyft_10k",
description: "Provides information about Lyft financials for year 2021",
},
];
const data_str: string = JSON.stringify(data);
const full_string = `${data_str}`;
const real_answer = { parsedOutput: data, rawOutput: full_string };
expect(parser.parse(JSON.stringify(data))).toEqual(real_answer);
});
test("parses null single response", () => {
const parser = new SubQuestionOutputParser();
const data_str =
"[\n" +
" {\n" +
` "subQuestion": "Sorry, I don't have any relevant information to answer your question",\n` +
' "toolName": ""\n' +
" }\n" +
"]";
const data = [
{
subQuestion:
"Sorry, I don't have any relevant information to answer your question",
toolName: "",
},
];
const real_answer = { parsedOutput: data, rawOutput: data_str };
expect(parser.parse(data_str)).toEqual(real_answer);
});
test("Single JSON object case", () => {
const parser = new SubQuestionOutputParser();
const data_str =
" {\n" +
` "subQuestion": "Sorry, I don't have any relevant information to answer your question",\n` +
' "toolName": ""\n' +
" }\n";
const data = [
{
subQuestion:
"Sorry, I don't have any relevant information to answer your question",
toolName: "",
},
];
const real_answer = { parsedOutput: data, rawOutput: data_str };
expect(parser.parse(data_str)).toEqual(real_answer);
});
});
+2 -2
View File
@@ -6,10 +6,10 @@
"moduleResolution": "node",
"preserveWatchOutput": true,
"skipLibCheck": true,
"noEmit": true,
"strict": true,
"lib": ["es2015", "dom"],
"target": "ES2015"
"target": "ES2015",
"resolveJsonModule": true
},
"exclude": ["node_modules"]
}
+7
View File
@@ -18,6 +18,12 @@ module.exports = {
"OPENAI_API_BASE",
"OPENAI_API_VERSION",
"OPENAI_API_TYPE",
"OPENAI_API_ORGANIZATION",
"PINECONE_API_KEY",
"PINECONE_ENVIRONMENT",
"PINECONE_PROJECT_ID",
"PINECONE_INDEX_NAME",
"AZURE_OPENAI_API_KEY",
"AZURE_OPENAI_API_INSTANCE_NAME",
@@ -28,6 +34,7 @@ module.exports = {
"NO_PROXY",
"NOTION_TOKEN",
"MONGODB_URI",
],
},
],
+2260 -1965
View File
File diff suppressed because it is too large Load Diff
+13 -1
View File
@@ -7,5 +7,17 @@ branch: "main"
gha_enabled: False
# This is the description of your project. It will be used by sweep when creating PRs. You can tell Sweep what's unique about your project, what frameworks you use, or anything else you want.
# Here's an example: sweepai/sweep is a python project. The main api endpoints are in sweepai/api.py. Write code that adheres to PEP8.
description: ""
description: "LlamaIndexTS is a data framework in TypeScript for your LLM applications"
sandbox:
install:
- npm install -g pnpm
- pnpm i
- pnpm add --save-dev prettier -w
check:
- pnpx prettier --write {file_path}
- pnpm eslint --fix {file_path}
- pnpx ts-node --type-check {file_path}
- pnpm test
# Default Values: https://github.com/sweepai/sweep/blob/main/sweep.yaml