Compare commits

..

54 Commits

Author SHA1 Message Date
Alex Yang d038938272 chore: manual export modules 2024-01-24 15:18:07 -06:00
Alex Yang 76bb6c5f7f fix: set default fs 2024-01-24 14:53:09 -06:00
Alex Yang 38a5a3a04f fix: test 2024-01-24 14:43:02 -06:00
Alex Yang 86d780d580 fix: test 2024-01-24 14:42:48 -06:00
Alex Yang cb5141c410 feat: abstract file system api 2024-01-24 14:41:10 -06:00
Emanuel Ferreira eee39221c4 feat(qdrant): Add Qdrant Vector DB (#408) 2024-01-24 17:49:49 +07:00
Marcus Schiesser 4303961948 fix changeset format 2024-01-24 17:37:28 +07:00
Marcus Schiesser e2790dabc8 Add ingestion pipeline with doc store strategies (#418) 2024-01-24 17:26:24 +07:00
Thuc Pham ba42aa592c fix: spawn run fail on window (#419) 2024-01-24 17:07:59 +07:00
Alex Yang e1deba1222 fix: abstract createHash (#427) 2024-01-23 20:59:55 -06:00
Alex Yang f9c2dd1b3a fix: abstract some node API (#426) 2024-01-23 20:03:22 -06:00
Alex Yang 8bf0a41926 fix: error when running examples (#425) 2024-01-23 11:27:32 -06:00
Alex Yang 5c89aa54c4 feat: abstract node:os (#422) 2024-01-23 15:34:28 +07:00
Nikolai Lehbrink 69484526e6 fix: typo in customized vector index section (#423) 2024-01-23 15:33:29 +07:00
Marcus Schiesser bfc84384ea fix: don't create new hash deserialization a node (#307) 2024-01-23 15:32:11 +07:00
Alex Yang cce3b792db revert: missing files (#421) 2024-01-22 22:36:40 -06:00
Alex Yang bff40f27c5 feat: use conditional exports (#401) 2024-01-22 15:52:20 -06:00
Emanuel Ferreira c3e3b598bb fix(metadataFiltering): prefilters not being passed to vector query (#412) 2024-01-22 17:30:29 +07:00
Huu Le (Lee) fa17f7e352 add run app option (#399) 2024-01-22 14:03:57 +07:00
Motoki saito 3aed922a3b readme sample code chnaged (#414) 2024-01-22 10:39:56 +07:00
Emanuel Ferreira 7c4e37c5cd fix(getCollection): getOrCreateCollection (#413) 2024-01-22 10:36:11 +07:00
Emanuel Ferreira 2d8845b084 feat(extractors): add keyword extractor and base extractor (#404)
Co-authored-by: Alex Yang <himself65@outlook.com>
2024-01-21 16:28:02 -06:00
Owen Craston 47f21796e0 fix: add root package name (#403) 2024-01-20 13:53:38 -06:00
yisding eb6de99fcb Merge pull request #406 from run-llama/new-prettier
New prettier version
2024-01-19 15:31:31 -08:00
yisding 2bfc8f3161 try adding a version to pnpm
I don't think it should make a difference...
2024-01-19 15:26:07 -08:00
yisding bcacf88e55 run prettier format:fix 2024-01-19 15:14:40 -08:00
yisding 13766a82b2 new prettier version 2024-01-19 15:11:59 -08:00
Marcus Schiesser 34d7ca66f5 improved publish scripts 2024-01-19 15:41:54 +07:00
Marcus Schiesser 17a803bb17 RELEASING: Releasing 2 package(s)
Releases:
  llamaindex@0.0.48
  create-llama@0.0.16

[skip ci]
2024-01-19 14:53:34 +07:00
Marcus Schiesser 1bd47969b3 fix changesets 2024-01-19 14:21:09 +07:00
Marcus Schiesser 5fec0f1135 Feat: add example for SummaryChatHistory 2024-01-19 14:18:33 +07:00
Thuc Pham a73942ddea fix: should bundle mongo dependency (#402) 2024-01-19 14:17:03 +07:00
Marcus Schiesser 34a26e5e4d Use ChatHistory in all ChatEngines (#400)
* refactor: merge HistoryChatEngine and ContextChatEngine and use ChatHistory for all chat engines

* fix: add safeguard for tokensToSummarize

* refactor: unfold chat engines to own folder

* refactor: extract LLM types

* refactor: move multi-modal types to llm

* docs(changeset): Remove HistoryChatEngine and use ChatHistory for all chat engines

* dev: add debug launcher and don't lint generated code
2024-01-18 17:18:10 +07:00
Thuc Pham f74dea5fae feat(express): support showing image on chat message express backend (#380) 2024-01-18 14:24:48 +07:00
Marcus Schiesser ee3eb7d8e2 fix: update create-llama examples for new chat engine (#396)
---------

Co-authored-by: thucpn <thucsh2@gmail.com>
2024-01-18 12:10:37 +07:00
Marcus Schiesser 75f94eea1b fix: lint errors 2024-01-18 11:31:25 +07:00
Marcus Schiesser b737bda40d refactor: encourage using parameter objects for functions with more than 4 parameters (#398) 2024-01-18 08:46:08 +07:00
Marcus Schiesser d99b1d61d7 llamaindex@0.0.47 2024-01-17 15:39:17 +07:00
Marcus Schiesser 844029d8e5 feat: Add streaming support for QueryEngine (and unify streaming interface with ChatEngine) (#393) 2024-01-17 14:29:27 +07:00
Huu Le (Lee) 9492cc64b5 Added option to automatically install dependencies (for Python and TS) (#381) 2024-01-16 16:12:37 +07:00
Alex Yang 5773f97e88 feat: add together AI vector index example (#390) 2024-01-15 21:52:15 -06:00
Marcus Schiesser 0784dc3a0a fix: replace missing import * as (#392) 2024-01-15 21:37:01 -06:00
Alex Yang 7993be7d0d RELEASING: Releasing 2 package(s)
Releases:
  llamaindex@0.0.46
  create-llama@0.0.15

[skip ci]
2024-01-15 14:21:18 -06:00
Alex Yang f22ce6e757 Revert "RELEASING: Releasing 2 package(s)"
This reverts commit 3c4347b247.
2024-01-15 14:21:18 -06:00
Alex Yang 3c4347b247 RELEASING: Releasing 2 package(s)
Releases:
  llamaindex@0.1.0
  create-llama@0.0.15

[skip ci]
2024-01-15 14:08:59 -06:00
Aziz Khoury 977f2840b9 fix: wrong import for path in SimpleKVStore.ts (#383)
Co-authored-by: Alex Yang <himself65@outlook.com>
2024-01-15 14:03:30 -06:00
Alex Yang 5d3bb6642e fix: default import (#386) 2024-01-15 13:59:31 -06:00
Marcus Schiesser 2001eb7ffb fix: format 2024-01-15 18:15:44 +07:00
Marcus Schiesser f18c9f69d4 refactor: update low-level streaming interface (#325) 2024-01-15 18:06:53 +07:00
Thuc Pham 8e124e5b63 feat: support showing image for chat message in NextJS (#368) 2024-01-15 17:57:20 +07:00
Nir Gazit 4ed5e544b0 docs: added openllmetry observability (#369) 2024-01-15 10:15:02 +07:00
Alex Yang b185bda5b1 RELEASING: Releasing 2 package(s)
Releases:
  create-llama@0.0.14
  llamaindex@0.0.45

[skip ci]
2024-01-14 18:14:55 -06:00
Alex Yang d79804e271 docs: update README.md (#376) 2024-01-14 18:10:55 -06:00
Alex Yang 2b356c8613 fix(create-llama): component choice (#377) 2024-01-14 18:10:33 -06:00
192 changed files with 5117 additions and 1592 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"llamaindex": patch
---
feat(qdrant): Add Qdrant Vector DB
+5
View File
@@ -0,0 +1,5 @@
---
"llamaindex": patch
---
Preview: Add ingestion pipeline (incl. different strategies to handle doc store duplicates)
-5
View File
@@ -1,5 +0,0 @@
---
"create-llama": patch
---
fix: re-organize file structure
+5
View File
@@ -0,0 +1,5 @@
---
"create-llama": patch
---
Add an option that allows the user to run the generated app
+16
View File
@@ -0,0 +1,16 @@
---
"llamaindex": patch
---
feat: use conditional exports
The benefit of conditional exports is we split the llamaindex into different files. This will improve the tree shake if you are building web apps.
This also requires node16 (see https://nodejs.org/api/packages.html#conditional-exports).
If you are seeing typescript issue `TS2724`('llamaindex' has no exported member named XXX):
1. update `moduleResolution` to `bundler` in `tsconfig.json`, more for the web applications like Next.js, and vite, but still works for ts-node or tsx.
2. consider the ES module in your project, add `"type": "module"` into `package.json` and update `moduleResolution` to `node16` or `nodenext` in `tsconfig.json`.
We still support both cjs and esm, but you should update `tsconfig.json` to make the typescript happy.
-5
View File
@@ -1,5 +0,0 @@
---
"llamaindex": patch
---
feat: support together AI
+5
View File
@@ -0,0 +1,5 @@
---
"llamaindex": patch
---
feat(extractors): add keyword extractor and base extractor
+2 -2
View File
@@ -4,6 +4,6 @@
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/devcontainers-contrib/features/turborepo-npm:1": {},
"ghcr.io/devcontainers-contrib/features/typescript:2": {},
"ghcr.io/devcontainers-contrib/features/pnpm:2": {}
}
"ghcr.io/devcontainers-contrib/features/pnpm:2": {},
},
}
+4
View File
@@ -7,4 +7,8 @@ module.exports = {
rootDir: ["apps/*/"],
},
},
rules: {
"max-params": ["error", 4],
},
ignorePatterns: ["dist/"],
};
+15
View File
@@ -8,6 +8,9 @@ on:
- ".github/workflows/e2e.yml"
branches: [main]
env:
POETRY_VERSION: "1.6.1"
jobs:
e2e:
name: create-llama
@@ -16,10 +19,22 @@ jobs:
fail-fast: true
matrix:
node-version: [18, 20]
python-version: ["3.11"]
os: [macos-latest, windows-latest]
defaults:
run:
shell: bash
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Set up python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: ${{ env.POETRY_VERSION }}
- uses: pnpm/action-setup@v2
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
@@ -14,6 +14,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: latest
- name: Setup Node.js
uses: actions/setup-node@v4
with:
-3
View File
@@ -39,9 +39,6 @@ yarn-error.log*
dist/
lib/
# vs code
.vscode/launch.json
.cache
test-results/
playwright-report/
+1
View File
@@ -2,3 +2,4 @@ apps/docs/i18n
pnpm-lock.yaml
lib/
dist/
.docusaurus/
+17
View File
@@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Example",
"skipFiles": ["<node_internals>/**"],
"runtimeExecutable": "pnpm",
"cwd": "${workspaceFolder}/examples",
"runtimeArgs": ["ts-node", "${fileBasename}"]
}
]
}
+3 -4
View File
@@ -52,9 +52,9 @@ async function main() {
// Query the index
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query(
"What did the author do in college?",
);
const response = await queryEngine.query({
query: "What did the author do in college?",
});
// Output response
console.log(response.toString());
@@ -106,7 +106,6 @@ const nextConfig = {
...config.resolve.alias,
sharp$: false,
"onnxruntime-node$": false,
mongodb$: false,
};
return config;
},
+1 -1
View File
@@ -26,7 +26,7 @@ Create and load a vector index. Persistance to disk in LlamaIndex.TS happens aut
## [Customized Vector Index](https://github.com/run-llama/LlamaIndexTS/blob/main/examples/vectorIndexCustomize.ts)
Create a vector index and query it, while also configuring the the `LLM`, the `ServiceContext`, and the `similarity_top_k`.
Create a vector index and query it, while also configuring the `LLM`, the `ServiceContext`, and the `similarity_top_k`.
## [OpenAI LLM](https://github.com/run-llama/LlamaIndexTS/blob/main/examples/openai.ts)
@@ -11,7 +11,16 @@ const retriever = index.asRetriever();
const chatEngine = new ContextChatEngine({ retriever });
// start chatting
const response = await chatEngine.chat(query);
const response = await chatEngine.chat({ message: query });
```
The `chat` function also supports streaming, just add `stream: true` as an option:
```typescript
const stream = await chatEngine.chat({ message: query, stream: true });
for await (const chunk of stream) {
process.stdout.write(chunk.response);
}
```
## Api References
@@ -8,7 +8,16 @@ A query engine wraps a `Retriever` and a `ResponseSynthesizer` into a pipeline,
```typescript
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query("query string");
const response = await queryEngine.query({ query: "query string" });
```
The `query` function also supports streaming, just add `stream: true` as an option:
```typescript
const stream = await queryEngine.query({ query: "query string", stream: true });
for await (const chunk of stream) {
process.stdout.write(chunk.response);
}
```
## Sub Question Query Engine
@@ -35,13 +35,26 @@ const nodesWithScore: NodeWithScore[] = [
},
];
const response = await responseSynthesizer.synthesize(
"What age am I?",
const response = await responseSynthesizer.synthesize({
query: "What age am I?",
nodesWithScore,
);
});
console.log(response.response);
```
The `synthesize` function also supports streaming, just add `stream: true` as an option:
```typescript
const stream = await responseSynthesizer.synthesize({
query: "What age am I?",
nodesWithScore,
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.response);
}
```
## API Reference
- [ResponseSynthesizer](../../api/classes/ResponseSynthesizer.md)
@@ -0,0 +1 @@
label: Observability
@@ -0,0 +1,35 @@
# Observability
LlamaIndex provides **one-click observability** 🔭 to allow you to build principled LLM applications in a production setting.
A key requirement for principled development of LLM applications over your data (RAG systems, agents) is being able to observe, debug, and evaluate
your system - both as a whole and for each component.
This feature allows you to seamlessly integrate the LlamaIndex library with powerful observability/evaluation tools offered by our partners.
Configure a variable once, and you'll be able to do things like the following:
- View LLM/prompt inputs/outputs
- Ensure that the outputs of any component (LLMs, embeddings) are performing as expected
- View call traces for both indexing and querying
Each provider has similarities and differences. Take a look below for the full set of guides for each one!
## OpenLLMetry
[OpenLLMetry](https://github.com/traceloop/openllmetry-js) is an open-source project based on OpenTelemetry for tracing and monitoring
LLM applications. It connects to [all major observability platforms](https://www.traceloop.com/docs/openllmetry/integrations/introduction) and installs in minutes.
### Usage Pattern
```bash
npm install @traceloop/node-server-sdk
```
```js
import * as traceloop from "@traceloop/node-server-sdk";
traceloop.initialize({
apiKey: process.env.TRACELOOP_API_KEY,
disableBatch: true,
});
```
+2 -2
View File
@@ -6,6 +6,6 @@
"composite": true,
"incremental": true,
"outDir": "./lib",
"tsBuildInfoFile": "./lib/.tsbuildinfo"
}
"tsBuildInfoFile": "./lib/.tsbuildinfo",
},
}
+10 -8
View File
@@ -2,13 +2,15 @@ import { Anthropic } from "llamaindex";
(async () => {
const anthropic = new Anthropic();
const result = await anthropic.chat([
{ content: "You want to talk in rhymes.", role: "system" },
{
content:
"How much wood would a woodchuck chuck if a woodchuck could chuck wood?",
role: "user",
},
]);
const result = await anthropic.chat({
messages: [
{ content: "You want to talk in rhymes.", role: "system" },
{
content:
"How much wood would a woodchuck chuck if a woodchuck could chuck wood?",
role: "user",
},
],
});
console.log(result);
})();
+3 -1
View File
@@ -18,7 +18,9 @@ async function main() {
const queryEngine = await index.asQueryEngine({ retriever });
const results = await queryEngine.query("What is the best reviewed movie?");
const results = await queryEngine.query({
query: "What is the best reviewed movie?",
});
console.log(results.response);
} catch (e) {
+6 -3
View File
@@ -10,7 +10,7 @@ import {
VectorStoreIndex,
} from "llamaindex";
import essay from "./essay";
import essay from "./essay.js";
async function main() {
const document = new Document({ text: essay });
@@ -25,8 +25,11 @@ async function main() {
while (true) {
const query = await rl.question("Query: ");
const response = await chatEngine.chat(query);
console.log(response.toString());
const stream = await chatEngine.chat({ message: query, stream: true });
console.log();
for await (const chunk of stream) {
process.stdout.write(chunk.response);
}
}
}
+32
View File
@@ -0,0 +1,32 @@
import { stdin as input, stdout as output } from "node:process";
import readline from "node:readline/promises";
import { OpenAI, SimpleChatEngine, SummaryChatHistory } from "llamaindex";
async function main() {
// Set maxTokens to 75% of the context window size of 4096
// This will trigger the summarizer once the chat history reaches 25% of the context window size (1024 tokens)
const llm = new OpenAI({ model: "gpt-3.5-turbo", maxTokens: 4096 * 0.75 });
const chatHistory = new SummaryChatHistory({ llm });
const chatEngine = new SimpleChatEngine({ llm });
const rl = readline.createInterface({ input, output });
while (true) {
const query = await rl.question("Query: ");
const stream = await chatEngine.chat({
message: query,
chatHistory,
stream: true,
});
if (chatHistory.getLastSummary()) {
// Print the summary of the conversation so far that is produced by the SummaryChatHistory
console.log(`Summary: ${chatHistory.getLastSummary()?.content}`);
}
for await (const chunk of stream) {
process.stdout.write(chunk.response);
}
console.log();
}
}
main().catch(console.error);
+57
View File
@@ -0,0 +1,57 @@
import {
ChromaVectorStore,
Document,
VectorStoreIndex,
storageContextFromDefaults,
} from "llamaindex";
const collectionName = "dog_colors";
async function main() {
try {
const docs = [
new Document({
text: "The dog is brown",
metadata: {
dogId: "1",
},
}),
new Document({
text: "The dog is red",
metadata: {
dogId: "2",
},
}),
];
console.log("Creating ChromaDB vector store");
const chromaVS = new ChromaVectorStore({ collectionName });
const ctx = await storageContextFromDefaults({ vectorStore: chromaVS });
console.log("Embedding documents and adding to index");
const index = await VectorStoreIndex.fromDocuments(docs, {
storageContext: ctx,
});
console.log("Querying index");
const queryEngine = index.asQueryEngine({
preFilters: {
filters: [
{
key: "dogId",
value: "2",
filterType: "ExactMatch",
},
],
},
});
const response = await queryEngine.query({
query: "What is the color of the dog?",
});
console.log(response.toString());
} catch (e) {
console.error(e);
}
}
main();
+3 -3
View File
@@ -28,9 +28,9 @@ async function main() {
console.log("Querying index");
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query(
"Tell me about Godfrey Cheshire's rating of La Sapienza.",
);
const response = await queryEngine.query({
query: "Tell me about Godfrey Cheshire's rating of La Sapienza.",
});
console.log(response.toString());
} catch (e) {
console.error(e);
+24
View File
@@ -0,0 +1,24 @@
import {
Document,
KeywordExtractor,
OpenAI,
SimpleNodeParser,
} from "llamaindex";
(async () => {
const openaiLLM = new OpenAI({ model: "gpt-3.5-turbo", temperature: 0 });
const nodeParser = new SimpleNodeParser();
const nodes = nodeParser.getNodesFromDocuments([
new Document({ text: "banana apple orange pear peach watermelon" }),
]);
console.log(nodes);
const keywordExtractor = new KeywordExtractor(openaiLLM, 5);
const nodesWithKeywordMetadata = await keywordExtractor.processNodes(nodes);
process.stdout.write(JSON.stringify(nodesWithKeywordMetadata, null, 2));
})();
@@ -0,0 +1,31 @@
import {
Document,
OpenAI,
QuestionsAnsweredExtractor,
SimpleNodeParser,
} from "llamaindex";
(async () => {
const openaiLLM = new OpenAI({ model: "gpt-3.5-turbo", temperature: 0 });
const nodeParser = new SimpleNodeParser();
const nodes = nodeParser.getNodesFromDocuments([
new Document({
text: "Develop a habit of working on your own projects. Don't let work mean something other people tell you to do. If you do manage to do great work one day, it will probably be on a project of your own. It may be within some bigger project, but you'll be driving your part of it.",
}),
new Document({
text: "The best way to get a good idea is to get a lot of ideas. The best way to get a lot of ideas is to get a lot of bad ideas. The best way to get a lot of bad ideas is to get a lot of ideas.",
}),
]);
const questionsAnsweredExtractor = new QuestionsAnsweredExtractor(
openaiLLM,
5,
);
const nodesWithQuestionsMetadata =
await questionsAnsweredExtractor.processNodes(nodes);
process.stdout.write(JSON.stringify(nodesWithQuestionsMetadata, null, 2));
})();
+24
View File
@@ -0,0 +1,24 @@
import {
Document,
OpenAI,
SimpleNodeParser,
SummaryExtractor,
} from "llamaindex";
(async () => {
const openaiLLM = new OpenAI({ model: "gpt-3.5-turbo", temperature: 0 });
const nodeParser = new SimpleNodeParser();
const nodes = nodeParser.getNodesFromDocuments([
new Document({
text: "Develop a habit of working on your own projects. Don't let work mean something other people tell you to do. If you do manage to do great work one day, it will probably be on a project of your own. It may be within some bigger project, but you'll be driving your part of it.",
}),
]);
const summaryExtractor = new SummaryExtractor(openaiLLM);
const nodesWithSummaryMetadata = await summaryExtractor.processNodes(nodes);
process.stdout.write(JSON.stringify(nodesWithSummaryMetadata, null, 2));
})();
+19
View File
@@ -0,0 +1,19 @@
import { Document, OpenAI, SimpleNodeParser, TitleExtractor } from "llamaindex";
(async () => {
const openaiLLM = new OpenAI({ model: "gpt-3.5-turbo", temperature: 0 });
const nodeParser = new SimpleNodeParser();
const nodes = nodeParser.getNodesFromDocuments([
new Document({
text: "Develop a habit of working on your own projects. Don't let work mean something other people tell you to do. If you do manage to do great work one day, it will probably be on a project of your own. It may be within some bigger project, but you'll be driving your part of it.",
}),
]);
const titleExtractor = new TitleExtractor(openaiLLM, 1);
const nodesWithTitledMetadata = await titleExtractor.processNodes(nodes);
process.stdout.write(JSON.stringify(nodesWithTitledMetadata, null, 2));
})();
+3 -3
View File
@@ -25,12 +25,12 @@ import { ChatMessage, LlamaDeuce, OpenAI } from "llamaindex";
while (true) {
const next = history.length % 2 === 1 ? gpt4 : l2;
const r = await next.chat(
history.map(({ content, role }) => ({
const r = await next.chat({
messages: history.map(({ content, role }) => ({
content,
role: next === l2 ? role : role === "user" ? "assistant" : "user",
})),
);
});
history.push({
content: r.message.content,
role: next === l2 ? "assistant" : "user",
+7 -4
View File
@@ -32,12 +32,15 @@ async function main() {
// Query the index
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query(
"What did the author do in college?",
);
const stream = await queryEngine.query({
query: "What did the author do in college?",
stream: true,
});
// Output response
console.log(response.toString());
for await (const chunk of stream) {
process.stdout.write(chunk.response);
}
}
main().catch(console.error);
+14 -12
View File
@@ -21,18 +21,20 @@ async function main() {
action_items: ["action item 1", "action item 2"],
};
const response = await llm.chat([
{
role: "system",
content: `You are an expert assistant for summarizing and extracting insights from sales call transcripts.\n\nGenerate a valid JSON in the following format:\n\n${JSON.stringify(
example,
)}`,
},
{
role: "user",
content: `Here is the transcript: \n------\n${transcript}\n------`,
},
]);
const response = await llm.chat({
messages: [
{
role: "system",
content: `You are an expert assistant for summarizing and extracting insights from sales call transcripts.\n\nGenerate a valid JSON in the following format:\n\n${JSON.stringify(
example,
)}`,
},
{
role: "user",
content: `Here is the transcript: \n------\n${transcript}\n------`,
},
],
});
const json = JSON.parse(response.message.content);
+4 -4
View File
@@ -3,7 +3,7 @@ import {
KeywordTableIndex,
KeywordTableRetrieverMode,
} from "llamaindex";
import essay from "./essay";
import essay from "./essay.js";
async function main() {
const document = new Document({ text: essay, id_: "essay" });
@@ -20,9 +20,9 @@ async function main() {
mode,
}),
});
const response = await queryEngine.query(
"What did the author do growing up?",
);
const response = await queryEngine.query({
query: "What did the author do growing up?",
});
console.log(response.toString());
});
}
+3 -1
View File
@@ -2,6 +2,8 @@ import { DeuceChatStrategy, LlamaDeuce } from "llamaindex";
(async () => {
const deuce = new LlamaDeuce({ chatStrategy: DeuceChatStrategy.META });
const result = await deuce.chat([{ content: "Hello, world!", role: "user" }]);
const result = await deuce.chat({
messages: [{ content: "Hello, world!", role: "user" }],
});
console.log(result);
})();
+7 -4
View File
@@ -27,9 +27,12 @@ import {
},
];
const response = await responseSynthesizer.synthesize(
"What age am I?",
const stream = await responseSynthesizer.synthesize({
query: "What age am I?",
nodesWithScore,
);
console.log(response.response);
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.response);
}
})();
+3 -1
View File
@@ -11,7 +11,9 @@ async function main() {
// Query the index
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query("What does the example code do?");
const response = await queryEngine.query({
query: "What does the example code do?",
});
// Output response
console.log(response.toString());
+11 -10
View File
@@ -27,7 +27,7 @@ async function rag(llm: LLM, embedModel: BaseEmbedding, query: string) {
// Query the index
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query(query);
const response = await queryEngine.query({ query });
return response.response;
}
@@ -43,19 +43,20 @@ async function rag(llm: LLM, embedModel: BaseEmbedding, query: string) {
// chat api (non-streaming)
const llm = new MistralAI({ model: "mistral-tiny" });
const response = await llm.chat([
{ content: "What is the best French cheese?", role: "user" },
]);
const response = await llm.chat({
messages: [{ content: "What is the best French cheese?", role: "user" }],
});
console.log(response.message.content);
// chat api (streaming)
const stream = await llm.chat(
[{ content: "Who is the most renowned French painter?", role: "user" }],
undefined,
true,
);
const stream = await llm.chat({
messages: [
{ content: "Who is the most renowned French painter?", role: "user" },
],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk);
process.stdout.write(chunk.delta);
}
// rag
+1 -1
View File
@@ -54,7 +54,7 @@ async function main() {
break;
}
const response = await queryEngine.query(query);
const response = await queryEngine.query({ query });
// Output response
console.log(response.toString());
+3 -3
View File
@@ -24,9 +24,9 @@ async function query() {
const retriever = index.asRetriever({ similarityTopK: 20 });
const queryEngine = index.asQueryEngine({ retriever });
const result = await queryEngine.query(
"What does the author think of web frameworks?",
);
const result = await queryEngine.query({
query: "What does the author think of web frameworks?",
});
console.log(result.response);
await client.close();
}
+3 -3
View File
@@ -46,9 +46,9 @@ async function main() {
responseSynthesizer: new MultiModalResponseSynthesizer({ serviceContext }),
retriever: index.asRetriever({ similarityTopK: 3, imageSimilarityTopK: 1 }),
});
const result = await queryEngine.query(
"Tell me more about Vincent van Gogh's famous paintings",
);
const result = await queryEngine.query({
query: "Tell me more about Vincent van Gogh's famous paintings",
});
console.log(result.response, "\n");
images.forEach((image) =>
console.log(`Image retrieved and used in inference: ${image.toString()}`),
+15 -13
View File
@@ -3,32 +3,34 @@ import { Ollama } from "llamaindex";
(async () => {
const llm = new Ollama({ model: "llama2", temperature: 0.75 });
{
const response = await llm.chat([
{ content: "Tell me a joke.", role: "user" },
]);
const response = await llm.chat({
messages: [{ content: "Tell me a joke.", role: "user" }],
});
console.log("Response 1:", response.message.content);
}
{
const response = await llm.complete("How are you?");
console.log("Response 2:", response.message.content);
const response = await llm.complete({ prompt: "How are you?" });
console.log("Response 2:", response.text);
}
{
const response = await llm.chat(
[{ content: "Tell me a joke.", role: "user" }],
undefined,
true,
);
const response = await llm.chat({
messages: [{ content: "Tell me a joke.", role: "user" }],
stream: true,
});
console.log("Response 3:");
for await (const message of response) {
process.stdout.write(message); // no newline
process.stdout.write(message.delta); // no newline
}
console.log(); // newline
}
{
const response = await llm.complete("How are you?", undefined, true);
const response = await llm.complete({
prompt: "How are you?",
stream: true,
});
console.log("Response 4:");
for await (const message of response) {
process.stdout.write(message); // no newline
process.stdout.write(message.text); // no newline
}
console.log(); // newline
}
+5 -5
View File
@@ -4,12 +4,12 @@ import { OpenAI } from "llamaindex";
const llm = new OpenAI({ model: "gpt-4-1106-preview", temperature: 0.1 });
// complete api
const response1 = await llm.complete("How are you?");
console.log(response1.message.content);
const response1 = await llm.complete({ prompt: "How are you?" });
console.log(response1.text);
// chat api
const response2 = await llm.chat([
{ content: "Tell me a joke.", role: "user" },
]);
const response2 = await llm.chat({
messages: [{ content: "Tell me a joke.", role: "user" }],
});
console.log(response2.message.content);
})();
+1 -1
View File
@@ -31,7 +31,7 @@ async function main() {
}
try {
const answer = await queryEngine.query(question);
const answer = await queryEngine.query({ query: question });
console.log(answer.response);
} catch (error) {
console.error("Error:", error);
+1 -1
View File
@@ -29,7 +29,7 @@ async function main() {
}
try {
const answer = await queryEngine.query(question);
const answer = await queryEngine.query({ query: question });
console.log(answer.response);
} catch (error) {
console.error("Error:", error);
+36
View File
@@ -0,0 +1,36 @@
import fs from "node:fs/promises";
import {
Document,
IngestionPipeline,
MetadataMode,
OpenAIEmbedding,
SimpleNodeParser,
} from "llamaindex";
async function main() {
// Load essay from abramov.txt in Node
const path = "node_modules/llamaindex/examples/abramov.txt";
const essay = await fs.readFile(path, "utf-8");
// Create Document object with essay
const document = new Document({ text: essay, id_: path });
const pipeline = new IngestionPipeline({
transformations: [
new SimpleNodeParser({ chunkSize: 1024, chunkOverlap: 20 }),
// new TitleExtractor(llm),
new OpenAIEmbedding(),
],
});
// run the pipeline
const nodes = await pipeline.run({ documents: [document] });
// print out the result of the pipeline run
for (const node of nodes) {
console.log(node.getContent(MetadataMode.NONE));
}
}
main().catch(console.error);
+8 -5
View File
@@ -12,11 +12,14 @@ import { Portkey } from "llamaindex";
},
],
});
const result = portkey.streamChat([
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Tell me a joke." },
]);
const result = await portkey.chat({
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Tell me a joke." },
],
stream: true,
});
for await (const res of result) {
process.stdout.write(res);
process.stdout.write(res.delta);
}
})();
+1 -1
View File
@@ -48,7 +48,7 @@ program
break;
}
const response = await queryEngine.query(query);
const response = await queryEngine.query({ query });
console.log(response.toString());
}
+3 -3
View File
@@ -38,9 +38,9 @@ Given the CSV file, generate me Typescript code to answer the question: ${query}
const queryEngine = index.asQueryEngine({ responseSynthesizer });
// Query the index
const response = await queryEngine.query(
"What is the correlation between survival and age?",
);
const response = await queryEngine.query({
query: "What is the correlation between survival and age?",
});
// Output response
console.log(response.toString());
+1 -1
View File
@@ -15,7 +15,7 @@ async function main() {
// Test query
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query(SAMPLE_QUERY);
const response = await queryEngine.query({ query: SAMPLE_QUERY });
console.log(`Test query > ${SAMPLE_QUERY}:\n`, response.toString());
}
+3 -3
View File
@@ -10,9 +10,9 @@ async function main() {
// Query the index
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query(
"What were the notable changes in 18.1?",
);
const response = await queryEngine.query({
query: "What were the notable changes in 18.1?",
});
// Output response
console.log(response.toString());
+1 -1
View File
@@ -15,7 +15,7 @@ async function main() {
// Test query
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query(SAMPLE_QUERY);
const response = await queryEngine.query({ query: SAMPLE_QUERY });
console.log(`Test query > ${SAMPLE_QUERY}:\n`, response.toString());
}
+1 -1
View File
@@ -79,7 +79,7 @@ program
break;
}
const response = await queryEngine.query(query);
const response = await queryEngine.query({ query });
// Output response
console.log(response.toString());
+3 -1
View File
@@ -10,7 +10,9 @@ async function main() {
// Query the index
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query("What mistakes did they make?");
const response = await queryEngine.query({
query: "What mistakes did they make?",
});
// Output response
console.log(response.toString());
+4 -4
View File
@@ -6,7 +6,7 @@ import {
VectorStoreIndex,
serviceContextFromDefaults,
} from "llamaindex";
import essay from "./essay";
import essay from "./essay.js";
async function main() {
const document = new Document({ text: essay, id_: "essay" });
@@ -31,9 +31,9 @@ async function main() {
const queryEngine = index.asQueryEngine({
nodePostprocessors: [new MetadataReplacementPostProcessor("window")],
});
const response = await queryEngine.query(
"What did the author do in college?",
);
const response = await queryEngine.query({
query: "What did the author do in college?",
});
// Output response
console.log(response.toString());
+7 -7
View File
@@ -3,7 +3,7 @@ import {
storageContextFromDefaults,
VectorStoreIndex,
} from "llamaindex";
import essay from "./essay";
import essay from "./essay.js";
async function main() {
// Create Document object with essay
@@ -20,9 +20,9 @@ async function main() {
// Query the index
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query(
"What did the author do in college?",
);
const response = await queryEngine.query({
query: "What did the author do in college?",
});
// Output response
console.log(response.toString());
@@ -35,9 +35,9 @@ async function main() {
storageContext: secondStorageContext,
});
const loadedQueryEngine = loadedIndex.asQueryEngine();
const loadedResponse = await loadedQueryEngine.query(
"What did the author do growing up?",
);
const loadedResponse = await loadedQueryEngine.query({
query: "What did the author do growing up?",
});
console.log(loadedResponse.toString());
}
+4 -4
View File
@@ -1,6 +1,6 @@
import { Document, SubQuestionQueryEngine, VectorStoreIndex } from "llamaindex";
import essay from "./essay";
import essay from "./essay.js";
(async () => {
const document = new Document({ text: essay, id_: essay });
@@ -18,9 +18,9 @@ import essay from "./essay";
],
});
const response = await queryEngine.query(
"How was Paul Grahams life different before and after YC?",
);
const response = await queryEngine.query({
query: "How was Paul Grahams life different before and after YC?",
});
console.log(response.toString());
})();
+4 -4
View File
@@ -5,7 +5,7 @@ import {
SummaryRetrieverMode,
serviceContextFromDefaults,
} from "llamaindex";
import essay from "./essay";
import essay from "./essay.js";
async function main() {
const serviceContext = serviceContextFromDefaults({
@@ -20,9 +20,9 @@ async function main() {
const queryEngine = index.asQueryEngine({
retriever: index.asRetriever({ mode: SummaryRetrieverMode.LLM }),
});
const response = await queryEngine.query(
"What did the author do growing up?",
);
const response = await queryEngine.query({
query: "What did the author do growing up?",
});
console.log(response.toString());
}
+5 -6
View File
@@ -6,8 +6,8 @@ const together = new TogetherLLM({
});
(async () => {
const generator = await together.chat(
[
const generator = await together.chat({
messages: [
{
role: "system",
content: "You are an AI assistant",
@@ -17,12 +17,11 @@ const together = new TogetherLLM({
content: "Tell me about San Francisco",
},
],
undefined,
true,
);
stream: true,
});
console.log("Chatting with Together AI...");
for await (const message of generator) {
process.stdout.write(message);
process.stdout.write(message.delta);
}
const embedding = new TogetherEmbedding();
const vector = await embedding.getTextEmbedding("Hello world!");
+39
View File
@@ -0,0 +1,39 @@
import fs from "node:fs/promises";
import {
Document,
TogetherEmbedding,
TogetherLLM,
VectorStoreIndex,
serviceContextFromDefaults,
} from "llamaindex";
async function main() {
const apiKey = process.env.TOGETHER_API_KEY;
if (!apiKey) {
throw new Error("Missing TOGETHER_API_KEY");
}
const path = require.resolve("llamaindex/examples/abramov.txt");
const essay = await fs.readFile(path, "utf-8");
const document = new Document({ text: essay, id_: path });
const serviceContext = serviceContextFromDefaults({
llm: new TogetherLLM({ model: "mistralai/Mixtral-8x7B-Instruct-v0.1" }),
embedModel: new TogetherEmbedding(),
});
const index = await VectorStoreIndex.fromDocuments([document], {
serviceContext,
});
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query({
query: "What did the author do in college?",
});
console.log(response.toString());
}
main().catch(console.error);
+6 -8
View File
@@ -1,12 +1,10 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
"ts-node": {
"files": true,
"compilerOptions": {
"module": "commonjs",
},
},
"include": ["./**/*.ts"]
"include": ["./**/*.ts"],
}
+3 -3
View File
@@ -16,9 +16,9 @@ async function main() {
// Query the index
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query(
"What did the author do in college?",
);
const response = await queryEngine.query({
query: "What did the author do in college?",
});
// Output response
console.log(response.toString());
+3 -3
View File
@@ -35,9 +35,9 @@ async function main() {
// Query the index
const queryEngine = index.asQueryEngine({ responseSynthesizer });
const response = await queryEngine.query(
"What did the author do in college?",
);
const response = await queryEngine.query({
query: "What did the author do in college?",
});
// Output response
console.log(response.toString());
+4 -4
View File
@@ -6,7 +6,7 @@ import {
SimilarityPostprocessor,
VectorStoreIndex,
} from "llamaindex";
import essay from "./essay";
import essay from "./essay.js";
// Customize retrieval and query args
async function main() {
@@ -33,9 +33,9 @@ async function main() {
[nodePostprocessor],
);
const response = await queryEngine.query(
"What did the author do growing up?",
);
const response = await queryEngine.query({
query: "What did the author do growing up?",
});
console.log(response.response);
}
+3 -1
View File
@@ -189,7 +189,9 @@ async function main() {
},
});
const response = await queryEngine.query("How many results do you have?");
const response = await queryEngine.query({
query: "How many results do you have?",
});
console.log(response.toString());
}
+3 -3
View File
@@ -25,9 +25,9 @@ async function main() {
// Query the index
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query(
"What did the author do in college?",
);
const response = await queryEngine.query({
query: "What did the author do in college?",
});
// Output response
console.log(response.toString());
+5 -5
View File
@@ -4,12 +4,12 @@ import { OpenAI } from "llamaindex";
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);
const response1 = await llm.complete({ prompt: "How are you?" });
console.log(response1.text);
// chat api
const response2 = await llm.chat([
{ content: "Tell me a joke!", role: "user" },
]);
const response2 = await llm.chat({
messages: [{ content: "Tell me a joke!", role: "user" }],
});
console.log(response2.message.content);
})();
+6 -3
View File
@@ -1,7 +1,9 @@
{
"name": "@llamaindex/monorepo",
"private": true,
"scripts": {
"build": "turbo run build",
"build:release": "turbo run build lint test --filter=\"!docs\"",
"dev": "turbo run dev",
"format": "prettier --ignore-unknown --cache --check .",
"format:write": "prettier --ignore-unknown --write .",
@@ -9,8 +11,9 @@
"prepare": "husky install",
"test": "turbo run test",
"type-check": "tsc -b --diagnostics",
"new-version": "turbo run build lint test --filter=\"!docs\" && changeset version",
"new-snapshot": "turbo run build lint test --filter=\"!docs\" && changeset version --snapshot"
"release": "pnpm run build:release && changeset publish",
"new-version": "pnpm run build:release && changeset version",
"new-snapshot": "pnpm run build:release && changeset version --snapshot"
},
"devDependencies": {
"@changesets/cli": "^2.27.1",
@@ -21,7 +24,7 @@
"husky": "^8.0.3",
"jest": "^29.7.0",
"lint-staged": "^15.2.0",
"prettier": "^3.1.1",
"prettier": "^3.2.4",
"prettier-plugin-organize-imports": "^3.2.4",
"ts-jest": "^29.1.1",
"turbo": "^1.11.2",
+27
View File
@@ -1,5 +1,32 @@
# llamaindex
## 0.0.48
### Patch Changes
- 34a26e5: Remove HistoryChatEngine and use ChatHistory for all chat engines
## 0.0.47
### Patch Changes
- 844029d: Add streaming support for QueryEngine (and unify streaming interface with ChatEngine)
- 844029d: Breaking: Use parameter object for query and chat methods of ChatEngine and QueryEngine
## 0.0.46
### Patch Changes
- 977f284: fixing import statement
- 5d3bb66: fix: class SimpleKVStore might throw error in ES module
- f18c9f6: refactor: Updated low-level streaming interface
## 0.0.45
### Patch Changes
- 2e6b36e: feat: support together AI
## 0.0.44
### Patch Changes
-1
View File
@@ -106,7 +106,6 @@ const nextConfig = {
...config.resolve.alias,
sharp$: false,
"onnxruntime-node$": false,
mongodb$: false,
};
return config;
},
+1
View File
@@ -2,4 +2,5 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
testPathIgnorePatterns: ["/lib/"],
};
+151 -4
View File
@@ -1,6 +1,6 @@
{
"name": "llamaindex",
"version": "0.0.44",
"version": "0.0.48",
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": "^0.9.1",
@@ -8,6 +8,7 @@
"@mistralai/mistralai": "^0.0.7",
"@notionhq/client": "^2.2.14",
"@pinecone-database/pinecone": "^1.1.2",
"@qdrant/js-client-rest": "^1.7.0",
"@xenova/transformers": "^2.10.0",
"assemblyai": "^4.0.0",
"chromadb": "^1.7.3",
@@ -30,12 +31,14 @@
"wink-nlp": "^1.14.3"
},
"devDependencies": {
"@aws-crypto/sha256-js": "^5.2.0",
"@types/jest": "^29.5.11",
"@types/lodash": "^4.14.202",
"@types/node": "^18.19.6",
"@types/papaparse": "^5.3.14",
"@types/pg": "^8.10.9",
"bunchee": "^4.3.3",
"bunchee": "^4.4.1",
"madge": "^6.1.0",
"node-stdlib-browser": "^1.2.0",
"typescript": "^5.3.3"
},
@@ -47,8 +50,151 @@
"exports": {
".": {
"types": "./dist/index.d.mts",
"edge-light": "./dist/index.edge-light.mjs",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./env": {
"types": "./dist/env.d.mts",
"edge-light": "./dist/env.edge-light.mjs",
"import": "./dist/env.mjs",
"require": "./dist/env.js"
},
"./storage/FileSystem": {
"types": "./dist/storage/FileSystem.d.mts",
"edge-light": "./dist/storage/FileSystem.edge-light.mjs",
"import": "./dist/storage/FileSystem.mjs",
"require": "./dist/storage/FileSystem.js"
},
"./ChatHistory": {
"types": "./dist/ChatHistory.d.mts",
"import": "./dist/ChatHistory.mjs",
"require": "./dist/ChatHistory.js"
},
"./constants": {
"types": "./dist/constants.d.mts",
"import": "./dist/constants.mjs",
"require": "./dist/constants.js"
},
"./GlobalsHelper": {
"types": "./dist/GlobalsHelper.d.mts",
"import": "./dist/GlobalsHelper.mjs",
"require": "./dist/GlobalsHelper.js"
},
"./Node": {
"types": "./dist/Node.d.mts",
"import": "./dist/Node.mjs",
"require": "./dist/Node.js"
},
"./OutputParser": {
"types": "./dist/OutputParser.d.mts",
"import": "./dist/OutputParser.mjs",
"require": "./dist/OutputParser.js"
},
"./Prompt": {
"types": "./dist/Prompt.d.mts",
"import": "./dist/Prompt.mjs",
"require": "./dist/Prompt.js"
},
"./PromptHelper": {
"types": "./dist/PromptHelper.d.mts",
"import": "./dist/PromptHelper.mjs",
"require": "./dist/PromptHelper.js"
},
"./QueryEngine": {
"types": "./dist/QueryEngine.d.mts",
"import": "./dist/QueryEngine.mjs",
"require": "./dist/QueryEngine.js"
},
"./QuestionGenerator": {
"types": "./dist/QuestionGenerator.d.mts",
"import": "./dist/QuestionGenerator.mjs",
"require": "./dist/QuestionGenerator.js"
},
"./Response": {
"types": "./dist/Response.d.mts",
"import": "./dist/Response.mjs",
"require": "./dist/Response.js"
},
"./Retriever": {
"types": "./dist/Retriever.d.mts",
"import": "./dist/Retriever.mjs",
"require": "./dist/Retriever.js"
},
"./ServiceContext": {
"types": "./dist/ServiceContext.d.mts",
"import": "./dist/ServiceContext.mjs",
"require": "./dist/ServiceContext.js"
},
"./TextSplitter": {
"types": "./dist/TextSplitter.d.mts",
"import": "./dist/TextSplitter.mjs",
"require": "./dist/TextSplitter.js"
},
"./Tool": {
"types": "./dist/Tool.d.mts",
"import": "./dist/Tool.mjs",
"require": "./dist/Tool.js"
},
"./readers": {
"types": "./dist/readers.d.mts",
"import": "./dist/readers.mjs",
"require": "./dist/readers.js"
},
"./readers/AssemblyAI": {
"types": "./dist/readers/AssemblyAI.d.mts",
"import": "./dist/readers/AssemblyAI.mjs",
"require": "./dist/readers/AssemblyAI.js"
},
"./readers/base": {
"types": "./dist/readers/base.d.mts",
"import": "./dist/readers/base.mjs",
"require": "./dist/readers/base.js"
},
"./readers/PapaCSVReader": {
"types": "./dist/readers/PapaCSVReader.d.mts",
"import": "./dist/readers/PapaCSVReader.mjs",
"require": "./dist/readers/PapaCSVReader.js"
},
"./readers/DocxReader": {
"types": "./dist/readers/DocxReader.d.mts",
"import": "./dist/readers/DocxReader.mjs",
"require": "./dist/readers/DocxReader.js"
},
"./readers/HTMLReader": {
"types": "./dist/readers/HTMLReader.d.mts",
"import": "./dist/readers/HTMLReader.mjs",
"require": "./dist/readers/HTMLReader.js"
},
"./readers/ImageReader": {
"types": "./dist/readers/ImageReader.d.mts",
"import": "./dist/readers/ImageReader.mjs",
"require": "./dist/readers/ImageReader.js"
},
"./readers/MarkdownReader": {
"types": "./dist/readers/MarkdownReader.d.mts",
"import": "./dist/readers/MarkdownReader.mjs",
"require": "./dist/readers/MarkdownReader.js"
},
"./readers/NotionReader": {
"types": "./dist/readers/NotionReader.d.mts",
"import": "./dist/readers/NotionReader.mjs",
"require": "./dist/readers/NotionReader.js"
},
"./readers/PDFReader": {
"types": "./dist/readers/PDFReader.d.mts",
"import": "./dist/readers/PDFReader.mjs",
"require": "./dist/readers/PDFReader.js"
},
"./readers/SimpleDirectoryReader": {
"types": "./dist/readers/SimpleDirectoryReader.d.mts",
"import": "./dist/readers/SimpleDirectoryReader.mjs",
"require": "./dist/readers/SimpleDirectoryReader.js"
},
"./readers/SimpleMongoReader": {
"types": "./dist/readers/SimpleMongoReader.d.mts",
"import": "./dist/readers/SimpleMongoReader.mjs",
"require": "./dist/readers/SimpleMongoReader.js"
}
},
"files": [
@@ -66,7 +212,8 @@
"scripts": {
"lint": "eslint .",
"test": "jest",
"build": "bunchee",
"dev": "bunchee -w"
"build": "NODE_OPTIONS=--max_old_space_size=8192 bunchee",
"dev": "NODE_OPTIONS=--max_old_space_size=8192 bunchee -w",
"circular-check": "madge --circular ./src/*.ts"
}
}
-452
View File
@@ -1,452 +0,0 @@
import { randomUUID } from "node:crypto";
import { ChatHistory } from "./ChatHistory";
import { NodeWithScore, TextNode } from "./Node";
import {
CondenseQuestionPrompt,
ContextSystemPrompt,
defaultCondenseQuestionPrompt,
defaultContextSystemPrompt,
messagesToHistoryStr,
} from "./Prompt";
import { BaseQueryEngine } from "./QueryEngine";
import { Response } from "./Response";
import { BaseRetriever } from "./Retriever";
import { ServiceContext, serviceContextFromDefaults } from "./ServiceContext";
import { Event } from "./callbacks/CallbackManager";
import { ChatMessage, LLM, OpenAI } from "./llm";
import { BaseNodePostprocessor } from "./postprocessors";
/**
* A ChatEngine is used to handle back and forth chats between the application and the LLM.
*/
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<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : Response,
>(
message: MessageContent,
chatHistory?: ChatMessage[],
streaming?: T,
): Promise<R>;
/**
* Resets the chat history so that it's empty.
*/
reset(): void;
}
/**
* SimpleChatEngine is the simplest possible chat engine. Useful for using your own custom prompts.
*/
export class SimpleChatEngine implements ChatEngine {
chatHistory: ChatMessage[];
llm: LLM;
constructor(init?: Partial<SimpleChatEngine>) {
this.chatHistory = init?.chatHistory ?? [];
this.llm = init?.llm ?? new OpenAI();
}
async chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : Response,
>(
message: MessageContent,
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, undefined);
chatHistory.push(response.message);
this.chatHistory = chatHistory;
return new Response(response.message.content) as R;
}
protected async *streamChat(
message: MessageContent,
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() {
this.chatHistory = [];
}
}
/**
* CondenseQuestionChatEngine is used in conjunction with a Index (for example VectorStoreIndex).
* It does two steps on taking a user's chat message: first, it condenses the chat message
* with the previous chat history into a question with more context.
* Then, it queries the underlying Index using the new question with context and returns
* the response.
* CondenseQuestionChatEngine performs well when the input is primarily questions about the
* underlying data. It performs less well when the chat messages are not questions about the
* data, or are very referential to previous context.
*/
export class CondenseQuestionChatEngine implements ChatEngine {
queryEngine: BaseQueryEngine;
chatHistory: ChatMessage[];
serviceContext: ServiceContext;
condenseMessagePrompt: CondenseQuestionPrompt;
constructor(init: {
queryEngine: BaseQueryEngine;
chatHistory: ChatMessage[];
serviceContext?: ServiceContext;
condenseMessagePrompt?: CondenseQuestionPrompt;
}) {
this.queryEngine = init.queryEngine;
this.chatHistory = init?.chatHistory ?? [];
this.serviceContext =
init?.serviceContext ?? serviceContextFromDefaults({});
this.condenseMessagePrompt =
init?.condenseMessagePrompt ?? defaultCondenseQuestionPrompt;
}
private async condenseQuestion(chatHistory: ChatMessage[], question: string) {
const chatHistoryStr = messagesToHistoryStr(chatHistory);
return this.serviceContext.llm.complete(
defaultCondenseQuestionPrompt({
question: question,
chatHistory: chatHistoryStr,
}),
);
}
async chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : Response,
>(
message: MessageContent,
chatHistory?: ChatMessage[] | undefined,
streaming?: T,
): Promise<R> {
chatHistory = chatHistory ?? this.chatHistory;
const condensedQuestion = (
await this.condenseQuestion(chatHistory, extractText(message))
).message.content;
const response = await this.queryEngine.query(condensedQuestion);
chatHistory.push({ content: message, role: "user" });
chatHistory.push({ content: response.response, role: "assistant" });
return response as R;
}
reset() {
this.chatHistory = [];
}
}
export interface Context {
message: ChatMessage;
nodes: NodeWithScore[];
}
export interface ContextGenerator {
generate(message: string, parentEvent?: Event): Promise<Context>;
}
export class DefaultContextGenerator implements ContextGenerator {
retriever: BaseRetriever;
contextSystemPrompt: ContextSystemPrompt;
nodePostprocessors: BaseNodePostprocessor[];
constructor(init: {
retriever: BaseRetriever;
contextSystemPrompt?: ContextSystemPrompt;
nodePostprocessors?: BaseNodePostprocessor[];
}) {
this.retriever = init.retriever;
this.contextSystemPrompt =
init?.contextSystemPrompt ?? defaultContextSystemPrompt;
this.nodePostprocessors = init.nodePostprocessors || [];
}
private applyNodePostprocessors(nodes: NodeWithScore[]) {
return this.nodePostprocessors.reduce(
(nodes, nodePostprocessor) => nodePostprocessor.postprocessNodes(nodes),
nodes,
);
}
async generate(message: string, parentEvent?: Event): Promise<Context> {
if (!parentEvent) {
parentEvent = {
id: randomUUID(),
type: "wrapper",
tags: ["final"],
};
}
const sourceNodesWithScore = await this.retriever.retrieve(
message,
parentEvent,
);
const nodes = this.applyNodePostprocessors(sourceNodesWithScore);
return {
message: {
content: this.contextSystemPrompt({
context: nodes.map((r) => (r.node as TextNode).text).join("\n\n"),
}),
role: "system",
},
nodes,
};
}
}
/**
* ContextChatEngine uses the Index to get the appropriate context for each query.
* The context is stored in the system prompt, and the chat history is preserved,
* ideally allowing the appropriate context to be surfaced for each query.
*/
export class ContextChatEngine implements ChatEngine {
chatModel: LLM;
chatHistory: ChatMessage[];
contextGenerator: ContextGenerator;
constructor(init: {
retriever: BaseRetriever;
chatModel?: LLM;
chatHistory?: ChatMessage[];
contextSystemPrompt?: ContextSystemPrompt;
nodePostprocessors?: BaseNodePostprocessor[];
}) {
this.chatModel =
init.chatModel ?? new OpenAI({ model: "gpt-3.5-turbo-16k" });
this.chatHistory = init?.chatHistory ?? [];
this.contextGenerator = new DefaultContextGenerator({
retriever: init.retriever,
contextSystemPrompt: init?.contextSystemPrompt,
});
}
async chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : Response,
>(
message: MessageContent,
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: randomUUID(),
type: "wrapper",
tags: ["final"],
};
const context = await this.contextGenerator.generate(
extractText(message),
parentEvent,
);
chatHistory.push({ content: message, role: "user" });
const response = await this.chatModel.chat(
[context.message, ...chatHistory],
parentEvent,
);
chatHistory.push(response.message);
this.chatHistory = chatHistory;
return new Response(
response.message.content,
context.nodes.map((r) => r.node),
) as R;
}
protected async *streamChat(
message: MessageContent,
chatHistory?: ChatMessage[] | undefined,
): AsyncGenerator<string, void, unknown> {
chatHistory = chatHistory ?? this.chatHistory;
const parentEvent: Event = {
id: randomUUID(),
type: "wrapper",
tags: ["final"],
};
const context = await this.contextGenerator.generate(
extractText(message),
parentEvent,
);
chatHistory.push({ content: message, role: "user" });
const response_stream = await this.chatModel.chat(
[context.message, ...chatHistory],
parentEvent,
true,
);
var accumulator: string = "";
for await (const part of response_stream) {
accumulator += part;
yield part;
}
chatHistory.push({ content: accumulator, role: "assistant" });
this.chatHistory = chatHistory;
return;
}
reset() {
this.chatHistory = [];
}
}
export interface MessageContentDetail {
type: "text" | "image_url";
text?: string;
image_url?: { url: string };
}
/**
* Extended type for the content of a message that allows for multi-modal messages.
*/
export type MessageContent = string | MessageContentDetail[];
/**
* Extracts just the text from a multi-modal message or the message itself if it's just text.
*
* @param message The message to extract text from.
* @returns The extracted text
*/
function extractText(message: MessageContent): string {
if (Array.isArray(message)) {
// message is of type MessageContentDetail[] - retrieve just the text parts and concatenate them
// so we can pass them to the context generator
return (message as MessageContentDetail[])
.filter((c) => c.type === "text")
.map((c) => c.text)
.join("\n\n");
}
return message;
}
/**
* HistoryChatEngine is a ChatEngine that uses a `ChatHistory` object
* to keeps track of chat's message history.
* A `ChatHistory` object is passed as a parameter for each call to the `chat` method,
* so the state of the chat engine is preserved between calls.
* Optionally, a `ContextGenerator` can be used to generate an additional context for each call to `chat`.
*/
export class HistoryChatEngine {
llm: LLM;
contextGenerator?: ContextGenerator;
constructor(init?: Partial<HistoryChatEngine>) {
this.llm = init?.llm ?? new OpenAI();
this.contextGenerator = init?.contextGenerator;
}
async chat<
T extends boolean | undefined = undefined,
R = T extends true ? AsyncGenerator<string, void, unknown> : Response,
>(
message: MessageContent,
chatHistory: ChatHistory,
streaming?: T,
): Promise<R> {
//Streaming option
if (streaming) {
return this.streamChat(message, chatHistory) as R;
}
const requestMessages = await this.prepareRequestMessages(
message,
chatHistory,
);
const response = await this.llm.chat(requestMessages);
chatHistory.addMessage(response.message);
return new Response(response.message.content) as R;
}
protected async *streamChat(
message: MessageContent,
chatHistory: ChatHistory,
): AsyncGenerator<string, void, unknown> {
const requestMessages = await this.prepareRequestMessages(
message,
chatHistory,
);
const response_stream = await this.llm.chat(
requestMessages,
undefined,
true,
);
var accumulator = "";
for await (const part of response_stream) {
accumulator += part;
yield part;
}
chatHistory.addMessage({
content: accumulator,
role: "assistant",
});
return;
}
private async prepareRequestMessages(
message: MessageContent,
chatHistory: ChatHistory,
) {
chatHistory.addMessage({
content: message,
role: "user",
});
let requestMessages;
let context;
if (this.contextGenerator) {
const textOnly = extractText(message);
context = await this.contextGenerator.generate(textOnly);
}
requestMessages = await chatHistory.requestMessages(
context ? [context.message] : undefined,
);
return requestMessages;
}
}
+34 -10
View File
@@ -1,4 +1,5 @@
import { ChatMessage, LLM, MessageType, OpenAI } from "./llm/LLM";
import { OpenAI } from "./llm/LLM";
import { ChatMessage, LLM, MessageType } from "./llm/types";
import {
defaultSummaryPrompt,
messagesToHistoryStr,
@@ -8,35 +9,38 @@ import {
/**
* A ChatHistory is used to keep the state of back and forth chat messages
*/
export interface ChatHistory {
messages: ChatMessage[];
export abstract class ChatHistory {
abstract get messages(): ChatMessage[];
/**
* Adds a message to the chat history.
* @param message
*/
addMessage(message: ChatMessage): void;
abstract addMessage(message: ChatMessage): void;
/**
* Returns the messages that should be used as input to the LLM.
*/
requestMessages(transientMessages?: ChatMessage[]): Promise<ChatMessage[]>;
abstract requestMessages(
transientMessages?: ChatMessage[],
): Promise<ChatMessage[]>;
/**
* Resets the chat history so that it's empty.
*/
reset(): void;
abstract reset(): void;
/**
* Returns the new messages since the last call to this function (or since calling the constructor)
*/
newMessages(): ChatMessage[];
abstract newMessages(): ChatMessage[];
}
export class SimpleChatHistory implements ChatHistory {
export class SimpleChatHistory extends ChatHistory {
messages: ChatMessage[];
private messagesBefore: number;
constructor(init?: Partial<SimpleChatHistory>) {
super();
this.messages = init?.messages ?? [];
this.messagesBefore = this.messages.length;
}
@@ -60,7 +64,7 @@ export class SimpleChatHistory implements ChatHistory {
}
}
export class SummaryChatHistory implements ChatHistory {
export class SummaryChatHistory extends ChatHistory {
tokensToSummarize: number;
messages: ChatMessage[];
summaryPrompt: SummaryPrompt;
@@ -68,6 +72,7 @@ export class SummaryChatHistory implements ChatHistory {
private messagesBefore: number;
constructor(init?: Partial<SummaryChatHistory>) {
super();
this.messages = init?.messages ?? [];
this.messagesBefore = this.messages.length;
this.summaryPrompt = init?.summaryPrompt ?? defaultSummaryPrompt;
@@ -79,6 +84,11 @@ export class SummaryChatHistory implements ChatHistory {
}
this.tokensToSummarize =
this.llm.metadata.contextWindow - this.llm.metadata.maxTokens;
if (this.tokensToSummarize < this.llm.metadata.contextWindow * 0.25) {
throw new Error(
"The number of tokens that trigger the summarize process are less than 25% of the context window. Try lowering maxTokens or use a model with a larger context window.",
);
}
}
private async summarize(): Promise<ChatMessage> {
@@ -99,7 +109,7 @@ export class SummaryChatHistory implements ChatHistory {
messagesToSummarize.shift();
} while (this.llm.tokens(promptMessages) > this.tokensToSummarize);
const response = await this.llm.chat(promptMessages);
const response = await this.llm.chat({ messages: promptMessages });
return { content: response.message.content, role: "memory" };
}
@@ -119,6 +129,11 @@ export class SummaryChatHistory implements ChatHistory {
return this.messages.length - 1 - index;
}
public getLastSummary(): ChatMessage | null {
const lastSummaryIndex = this.getLastSummaryIndex();
return lastSummaryIndex ? this.messages[lastSummaryIndex] : null;
}
private get systemMessages() {
// get array of all system messages
return this.messages.filter((message) => message.role === "system");
@@ -198,3 +213,12 @@ export class SummaryChatHistory implements ChatHistory {
return newMessages;
}
}
export function getHistory(
chatHistory?: ChatMessage[] | ChatHistory,
): ChatHistory {
if (chatHistory instanceof ChatHistory) {
return chatHistory;
}
return new SimpleChatHistory({ messages: chatHistory });
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { encodingForModel } from "js-tiktoken";
import { randomUUID } from "node:crypto";
import { Event, EventTag, EventType } from "./callbacks/CallbackManager";
import { randomUUID } from "./env";
export enum Tokenizers {
CL100K_BASE = "cl100k_base",
+9 -8
View File
@@ -1,6 +1,6 @@
import _ from "lodash";
import { createHash, randomUUID } from "node:crypto";
import path from "node:path";
import { createSHA256, randomUUID } from "./env";
export enum NodeRelationship {
SOURCE = "SOURCE",
@@ -168,6 +168,8 @@ export abstract class BaseNode<T extends Metadata = Metadata> {
*/
export class TextNode<T extends Metadata = Metadata> extends BaseNode<T> {
text: string = "";
textTemplate: string = "";
startCharIdx?: number;
endCharIdx?: number;
// textTemplate: NOTE write your own formatter if needed
@@ -181,7 +183,7 @@ export class TextNode<T extends Metadata = Metadata> extends BaseNode<T> {
if (new.target === TextNode) {
// Don't generate the hash repeatedly so only do it if this is
// constructing the derived class
this.hash = this.generateHash();
this.hash = init?.hash ?? this.generateHash();
}
}
@@ -191,13 +193,13 @@ export class TextNode<T extends Metadata = Metadata> extends BaseNode<T> {
* @returns
*/
generateHash() {
const hashFunction = createHash("sha256");
const hashFunction = createSHA256();
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.digest();
}
getType(): ObjectType {
@@ -232,7 +234,6 @@ export class TextNode<T extends Metadata = Metadata> extends BaseNode<T> {
setContent(value: string) {
this.text = value;
this.hash = this.generateHash();
}
@@ -253,7 +254,7 @@ export class IndexNode<T extends Metadata = Metadata> extends TextNode<T> {
Object.assign(this, init);
if (new.target === IndexNode) {
this.hash = this.generateHash();
this.hash = init?.hash ?? this.generateHash();
}
}
@@ -271,7 +272,7 @@ export class Document<T extends Metadata = Metadata> extends TextNode<T> {
Object.assign(this, init);
if (new.target === Document) {
this.hash = this.generateHash();
this.hash = init?.hash ?? this.generateHash();
}
}
@@ -332,7 +333,7 @@ export class ImageDocument<T extends Metadata = Metadata> extends ImageNode<T> {
super(init);
if (new.target === ImageDocument) {
this.hash = this.generateHash();
this.hash = init?.hash ?? this.generateHash();
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { ChatMessage } from "./llm/LLM";
import { ChatMessage } from "./llm/types";
import { SubQuestion } from "./QuestionGenerator";
import { ToolMetadata } from "./Tool";
+1
View File
@@ -37,6 +37,7 @@ export class PromptHelper {
tokenizer: (text: string) => Uint32Array;
separator = " ";
// eslint-disable-next-line max-params
constructor(
contextWindow = DEFAULT_CONTEXT_WINDOW,
numOutput = DEFAULT_NUM_OUTPUTS,
+66 -13
View File
@@ -1,4 +1,3 @@
import { randomUUID } from "node:crypto";
import { NodeWithScore, TextNode } from "./Node";
import {
BaseQuestionGenerator,
@@ -10,6 +9,7 @@ import { BaseRetriever } from "./Retriever";
import { ServiceContext, serviceContextFromDefaults } from "./ServiceContext";
import { QueryEngineTool, ToolMetadata } from "./Tool";
import { Event } from "./callbacks/CallbackManager";
import { randomUUID } from "./env";
import { BaseNodePostprocessor } from "./postprocessors";
import {
BaseSynthesizer,
@@ -17,16 +17,32 @@ import {
ResponseSynthesizer,
} from "./synthesizers";
/**
* Parameters for sending a query.
*/
export interface QueryEngineParamsBase {
query: string;
parentEvent?: Event;
}
export interface QueryEngineParamsStreaming extends QueryEngineParamsBase {
stream: true;
}
export interface QueryEngineParamsNonStreaming extends QueryEngineParamsBase {
stream?: false | null;
}
/**
* A query engine is a question answerer that can use one or more steps.
*/
export interface BaseQueryEngine {
/**
* Query the query engine and get a response.
* @param query
* @param parentEvent
* @param params
*/
query(query: string, parentEvent?: Event): Promise<Response>;
query(params: QueryEngineParamsStreaming): Promise<AsyncIterable<Response>>;
query(params: QueryEngineParamsNonStreaming): Promise<Response>;
}
/**
@@ -70,14 +86,31 @@ export class RetrieverQueryEngine implements BaseQueryEngine {
return this.applyNodePostprocessors(nodes);
}
async query(query: string, parentEvent?: Event) {
const _parentEvent: Event = parentEvent || {
query(params: QueryEngineParamsStreaming): Promise<AsyncIterable<Response>>;
query(params: QueryEngineParamsNonStreaming): Promise<Response>;
async query(
params: QueryEngineParamsStreaming | QueryEngineParamsNonStreaming,
): Promise<Response | AsyncIterable<Response>> {
const { query, stream } = params;
const parentEvent: Event = params.parentEvent || {
id: randomUUID(),
type: "wrapper",
tags: ["final"],
};
const nodes = await this.retrieve(query, _parentEvent);
return this.responseSynthesizer.synthesize(query, nodes, _parentEvent);
const nodesWithScore = await this.retrieve(query, parentEvent);
if (stream) {
return this.responseSynthesizer.synthesize({
query,
nodesWithScore,
parentEvent,
stream: true,
});
}
return this.responseSynthesizer.synthesize({
query,
nodesWithScore,
parentEvent,
});
}
}
@@ -131,11 +164,16 @@ export class SubQuestionQueryEngine implements BaseQueryEngine {
});
}
async query(query: string): Promise<Response> {
query(params: QueryEngineParamsStreaming): Promise<AsyncIterable<Response>>;
query(params: QueryEngineParamsNonStreaming): Promise<Response>;
async query(
params: QueryEngineParamsStreaming | QueryEngineParamsNonStreaming,
): Promise<Response | AsyncIterable<Response>> {
const { query, stream } = params;
const subQuestions = await this.questionGen.generate(this.metadatas, query);
// groups final retrieval+synthesis operation
const parentEvent: Event = {
const parentEvent: Event = params.parentEvent || {
id: randomUUID(),
type: "wrapper",
tags: ["final"],
@@ -153,10 +191,22 @@ export class SubQuestionQueryEngine implements BaseQueryEngine {
subQuestions.map((subQ) => this.querySubQ(subQ, subQueryParentEvent)),
);
const nodes = subQNodes
const nodesWithScore = subQNodes
.filter((node) => node !== null)
.map((node) => node as NodeWithScore);
return this.responseSynthesizer.synthesize(query, nodes, parentEvent);
if (stream) {
return this.responseSynthesizer.synthesize({
query,
nodesWithScore,
parentEvent,
stream: true,
});
}
return this.responseSynthesizer.synthesize({
query,
nodesWithScore,
parentEvent,
});
}
private async querySubQ(
@@ -167,7 +217,10 @@ export class SubQuestionQueryEngine implements BaseQueryEngine {
const question = subQ.subQuestion;
const queryEngine = this.queryEngines[subQ.toolName];
const response = await queryEngine.query(question, parentEvent);
const response = await queryEngine.query({
query: question,
parentEvent,
});
const responseText = response.response;
const nodeText = `Sub question: ${question}\nResponse: ${responseText}`;
const node = new TextNode({ text: nodeText });
+6 -5
View File
@@ -9,7 +9,8 @@ import {
defaultSubQuestionPrompt,
} from "./Prompt";
import { ToolMetadata } from "./Tool";
import { LLM, OpenAI } from "./llm/LLM";
import { OpenAI } from "./llm/LLM";
import { LLM } from "./llm/types";
export interface SubQuestion {
subQuestion: string;
@@ -41,13 +42,13 @@ export class LLMQuestionGenerator implements BaseQuestionGenerator {
const toolsStr = buildToolsText(tools);
const queryStr = query;
const prediction = (
await this.llm.complete(
this.prompt({
await this.llm.complete({
prompt: this.prompt({
toolsStr,
queryStr,
}),
)
).message.content;
})
).text;
const structuredOutput = this.outputParser.parse(prediction);
+1 -1
View File
@@ -1,7 +1,7 @@
import { BaseNode } from "./Node";
/**
* Respone is the output of a LLM
* Response is the output of a LLM
*/
export class Response {
response: string;
+1 -1
View File
@@ -1,4 +1,4 @@
import { EOL } from "node:os";
import { EOL } from "./env";
// GitHub translated
import { globalsHelper } from "./GlobalsHelper";
import { DEFAULT_CHUNK_OVERLAP, DEFAULT_CHUNK_SIZE } from "./constants";
+12 -1
View File
@@ -1,3 +1,5 @@
import { BaseNode, MetadataMode } from "../Node";
import { TransformComponent } from "../ingestion";
import { similarity } from "./utils";
/**
@@ -10,7 +12,7 @@ export enum SimilarityType {
EUCLIDEAN = "euclidean",
}
export abstract class BaseEmbedding {
export abstract class BaseEmbedding implements TransformComponent {
similarity(
embedding1: number[],
embedding2: number[],
@@ -21,4 +23,13 @@ export abstract class BaseEmbedding {
abstract getTextEmbedding(text: string): Promise<number[]>;
abstract getQueryEmbedding(query: string): Promise<number[]>;
async transform(nodes: BaseNode[], _options?: any): Promise<BaseNode[]> {
for (const node of nodes) {
node.embedding = await this.getTextEmbedding(
node.getContent(MetadataMode.EMBED),
);
}
return nodes;
}
}
+6 -3
View File
@@ -1,7 +1,8 @@
import _ from "lodash";
import { ImageType } from "../Node";
import { DEFAULT_SIMILARITY_TOP_K } from "../constants";
import { DEFAULT_FS, VectorStoreQueryMode } from "../storage";
import { defaultFS } from "../env";
import { VectorStoreQueryMode } from "../storage";
import { SimilarityType } from "./types";
/**
@@ -66,6 +67,7 @@ export function similarity(
* @param similarityCutoff minimum similarity score
* @returns
*/
// eslint-disable-next-line max-params
export function getTopKEmbeddings(
queryEmbedding: number[],
embeddings: number[][],
@@ -108,6 +110,7 @@ export function getTopKEmbeddings(
return [resultSimilarities, resultIds];
}
// eslint-disable-next-line max-params
export function getTopKEmbeddingsLearner(
queryEmbedding: number[],
embeddings: number[][],
@@ -120,6 +123,7 @@ export function getTopKEmbeddingsLearner(
// https://github.com/mljs/libsvm which itself hasn't been updated in a while
}
// eslint-disable-next-line max-params
export function getTopKMMREmbeddings(
queryEmbedding: number[],
embeddings: number[][],
@@ -241,8 +245,7 @@ export async function imageToDataUrl(input: ImageType): Promise<string> {
_.isString(input)
) {
// string or file URL
const fs = DEFAULT_FS;
const dataBuffer = await fs.readFile(
const dataBuffer = await defaultFS.readFile(
input instanceof URL ? input.pathname : input,
);
input = new Blob([dataBuffer]);
@@ -0,0 +1,104 @@
import { ChatHistory, getHistory } from "../../ChatHistory";
import {
CondenseQuestionPrompt,
defaultCondenseQuestionPrompt,
messagesToHistoryStr,
} from "../../Prompt";
import { BaseQueryEngine } from "../../QueryEngine";
import { Response } from "../../Response";
import {
ServiceContext,
serviceContextFromDefaults,
} from "../../ServiceContext";
import { ChatMessage, LLM } from "../../llm";
import { extractText, streamReducer } from "../../llm/utils";
import {
ChatEngine,
ChatEngineParamsNonStreaming,
ChatEngineParamsStreaming,
} from "./types";
/**
* CondenseQuestionChatEngine is used in conjunction with a Index (for example VectorStoreIndex).
* It does two steps on taking a user's chat message: first, it condenses the chat message
* with the previous chat history into a question with more context.
* Then, it queries the underlying Index using the new question with context and returns
* the response.
* CondenseQuestionChatEngine performs well when the input is primarily questions about the
* underlying data. It performs less well when the chat messages are not questions about the
* data, or are very referential to previous context.
*/
export class CondenseQuestionChatEngine implements ChatEngine {
queryEngine: BaseQueryEngine;
chatHistory: ChatHistory;
llm: LLM;
condenseMessagePrompt: CondenseQuestionPrompt;
constructor(init: {
queryEngine: BaseQueryEngine;
chatHistory: ChatMessage[];
serviceContext?: ServiceContext;
condenseMessagePrompt?: CondenseQuestionPrompt;
}) {
this.queryEngine = init.queryEngine;
this.chatHistory = getHistory(init?.chatHistory);
this.llm = init?.serviceContext?.llm ?? serviceContextFromDefaults().llm;
this.condenseMessagePrompt =
init?.condenseMessagePrompt ?? defaultCondenseQuestionPrompt;
}
private async condenseQuestion(chatHistory: ChatHistory, question: string) {
const chatHistoryStr = messagesToHistoryStr(
await chatHistory.requestMessages(),
);
return this.llm.complete({
prompt: defaultCondenseQuestionPrompt({
question: question,
chatHistory: chatHistoryStr,
}),
});
}
chat(params: ChatEngineParamsStreaming): Promise<AsyncIterable<Response>>;
chat(params: ChatEngineParamsNonStreaming): Promise<Response>;
async chat(
params: ChatEngineParamsStreaming | ChatEngineParamsNonStreaming,
): Promise<Response | AsyncIterable<Response>> {
const { message, stream } = params;
const chatHistory = params.chatHistory
? getHistory(params.chatHistory)
: this.chatHistory;
const condensedQuestion = (
await this.condenseQuestion(chatHistory, extractText(message))
).text;
chatHistory.addMessage({ content: message, role: "user" });
if (stream) {
const stream = await this.queryEngine.query({
query: condensedQuestion,
stream: true,
});
return streamReducer({
stream,
initialValue: "",
reducer: (accumulator, part) => (accumulator += part.response),
finished: (accumulator) => {
chatHistory.addMessage({ content: accumulator, role: "assistant" });
},
});
}
const response = await this.queryEngine.query({
query: condensedQuestion,
});
chatHistory.addMessage({ content: response.response, role: "assistant" });
return response;
}
reset() {
this.chatHistory.reset();
}
}
@@ -0,0 +1,113 @@
import { ChatHistory, getHistory } from "../../ChatHistory";
import { ContextSystemPrompt } from "../../Prompt";
import { Response } from "../../Response";
import { BaseRetriever } from "../../Retriever";
import { Event } from "../../callbacks/CallbackManager";
import { randomUUID } from "../../env";
import { ChatMessage, ChatResponseChunk, LLM, OpenAI } from "../../llm";
import { MessageContent } from "../../llm/types";
import { extractText, streamConverter, streamReducer } from "../../llm/utils";
import { BaseNodePostprocessor } from "../../postprocessors";
import { DefaultContextGenerator } from "./DefaultContextGenerator";
import {
ChatEngine,
ChatEngineParamsNonStreaming,
ChatEngineParamsStreaming,
ContextGenerator,
} from "./types";
/**
* ContextChatEngine uses the Index to get the appropriate context for each query.
* The context is stored in the system prompt, and the chat history is preserved,
* ideally allowing the appropriate context to be surfaced for each query.
*/
export class ContextChatEngine implements ChatEngine {
chatModel: LLM;
chatHistory: ChatHistory;
contextGenerator: ContextGenerator;
constructor(init: {
retriever: BaseRetriever;
chatModel?: LLM;
chatHistory?: ChatMessage[];
contextSystemPrompt?: ContextSystemPrompt;
nodePostprocessors?: BaseNodePostprocessor[];
}) {
this.chatModel =
init.chatModel ?? new OpenAI({ model: "gpt-3.5-turbo-16k" });
this.chatHistory = getHistory(init?.chatHistory);
this.contextGenerator = new DefaultContextGenerator({
retriever: init.retriever,
contextSystemPrompt: init?.contextSystemPrompt,
nodePostprocessors: init?.nodePostprocessors,
});
}
chat(params: ChatEngineParamsStreaming): Promise<AsyncIterable<Response>>;
chat(params: ChatEngineParamsNonStreaming): Promise<Response>;
async chat(
params: ChatEngineParamsStreaming | ChatEngineParamsNonStreaming,
): Promise<Response | AsyncIterable<Response>> {
const { message, stream } = params;
const chatHistory = params.chatHistory
? getHistory(params.chatHistory)
: this.chatHistory;
const parentEvent: Event = {
id: randomUUID(),
type: "wrapper",
tags: ["final"],
};
const requestMessages = await this.prepareRequestMessages(
message,
chatHistory,
parentEvent,
);
if (stream) {
const stream = await this.chatModel.chat({
messages: requestMessages.messages,
parentEvent,
stream: true,
});
return streamConverter(
streamReducer({
stream,
initialValue: "",
reducer: (accumulator, part) => (accumulator += part.delta),
finished: (accumulator) => {
chatHistory.addMessage({ content: accumulator, role: "assistant" });
},
}),
(r: ChatResponseChunk) => new Response(r.delta, requestMessages.nodes),
);
}
const response = await this.chatModel.chat({
messages: requestMessages.messages,
parentEvent,
});
chatHistory.addMessage(response.message);
return new Response(response.message.content, requestMessages.nodes);
}
reset() {
this.chatHistory.reset();
}
private async prepareRequestMessages(
message: MessageContent,
chatHistory: ChatHistory,
parentEvent?: Event,
) {
chatHistory.addMessage({
content: message,
role: "user",
});
const textOnly = extractText(message);
const context = await this.contextGenerator.generate(textOnly, parentEvent);
const nodes = context.nodes.map((r) => r.node);
const messages = await chatHistory.requestMessages(
context ? [context.message] : undefined,
);
return { nodes, messages };
}
}
@@ -0,0 +1,57 @@
import { NodeWithScore, TextNode } from "../../Node";
import { ContextSystemPrompt, defaultContextSystemPrompt } from "../../Prompt";
import { BaseRetriever } from "../../Retriever";
import { Event } from "../../callbacks/CallbackManager";
import { randomUUID } from "../../env";
import { BaseNodePostprocessor } from "../../postprocessors";
import { Context, ContextGenerator } from "./types";
export class DefaultContextGenerator implements ContextGenerator {
retriever: BaseRetriever;
contextSystemPrompt: ContextSystemPrompt;
nodePostprocessors: BaseNodePostprocessor[];
constructor(init: {
retriever: BaseRetriever;
contextSystemPrompt?: ContextSystemPrompt;
nodePostprocessors?: BaseNodePostprocessor[];
}) {
this.retriever = init.retriever;
this.contextSystemPrompt =
init?.contextSystemPrompt ?? defaultContextSystemPrompt;
this.nodePostprocessors = init.nodePostprocessors || [];
}
private applyNodePostprocessors(nodes: NodeWithScore[]) {
return this.nodePostprocessors.reduce(
(nodes, nodePostprocessor) => nodePostprocessor.postprocessNodes(nodes),
nodes,
);
}
async generate(message: string, parentEvent?: Event): Promise<Context> {
if (!parentEvent) {
parentEvent = {
id: randomUUID(),
type: "wrapper",
tags: ["final"],
};
}
const sourceNodesWithScore = await this.retriever.retrieve(
message,
parentEvent,
);
const nodes = this.applyNodePostprocessors(sourceNodesWithScore);
return {
message: {
content: this.contextSystemPrompt({
context: nodes.map((r) => (r.node as TextNode).text).join("\n\n"),
}),
role: "system",
},
nodes,
};
}
}
@@ -0,0 +1,64 @@
import { ChatHistory, getHistory } from "../../ChatHistory";
import { Response } from "../../Response";
import { ChatResponseChunk, LLM, OpenAI } from "../../llm";
import { streamConverter, streamReducer } from "../../llm/utils";
import {
ChatEngine,
ChatEngineParamsNonStreaming,
ChatEngineParamsStreaming,
} from "./types";
/**
* SimpleChatEngine is the simplest possible chat engine. Useful for using your own custom prompts.
*/
export class SimpleChatEngine implements ChatEngine {
chatHistory: ChatHistory;
llm: LLM;
constructor(init?: Partial<SimpleChatEngine>) {
this.chatHistory = getHistory(init?.chatHistory);
this.llm = init?.llm ?? new OpenAI();
}
chat(params: ChatEngineParamsStreaming): Promise<AsyncIterable<Response>>;
chat(params: ChatEngineParamsNonStreaming): Promise<Response>;
async chat(
params: ChatEngineParamsStreaming | ChatEngineParamsNonStreaming,
): Promise<Response | AsyncIterable<Response>> {
const { message, stream } = params;
const chatHistory = params.chatHistory
? getHistory(params.chatHistory)
: this.chatHistory;
chatHistory.addMessage({ content: message, role: "user" });
if (stream) {
const stream = await this.llm.chat({
messages: await chatHistory.requestMessages(),
stream: true,
});
return streamConverter(
streamReducer({
stream,
initialValue: "",
reducer: (accumulator, part) => (accumulator += part.delta),
finished: (accumulator) => {
chatHistory.addMessage({ content: accumulator, role: "assistant" });
},
}),
(r: ChatResponseChunk) => new Response(r.delta),
);
}
const response = await this.llm.chat({
messages: await chatHistory.requestMessages(),
});
chatHistory.addMessage(response.message);
return new Response(response.message.content);
}
reset() {
this.chatHistory.reset();
}
}
+4
View File
@@ -0,0 +1,4 @@
export { CondenseQuestionChatEngine } from "./CondenseQuestionChatEngine";
export { ContextChatEngine } from "./ContextChatEngine";
export { SimpleChatEngine } from "./SimpleChatEngine";
export * from "./types";
+54
View File
@@ -0,0 +1,54 @@
import { ChatHistory } from "../../ChatHistory";
import { NodeWithScore } from "../../Node";
import { Response } from "../../Response";
import { Event } from "../../callbacks/CallbackManager";
import { ChatMessage } from "../../llm";
import { MessageContent } from "../../llm/types";
/**
* Represents the base parameters for ChatEngine.
*/
export interface ChatEngineParamsBase {
message: MessageContent;
/**
* Optional chat history if you want to customize the chat history.
*/
chatHistory?: ChatMessage[] | ChatHistory;
}
export interface ChatEngineParamsStreaming extends ChatEngineParamsBase {
stream: true;
}
export interface ChatEngineParamsNonStreaming extends ChatEngineParamsBase {
stream?: false | null;
}
/**
* A ChatEngine is used to handle back and forth chats between the application and the LLM.
*/
export interface ChatEngine {
/**
* Send message along with the class's current chat history to the LLM.
* @param params
*/
chat(params: ChatEngineParamsStreaming): Promise<AsyncIterable<Response>>;
chat(params: ChatEngineParamsNonStreaming): Promise<Response>;
/**
* Resets the chat history so that it's empty.
*/
reset(): void;
}
export interface Context {
message: ChatMessage;
nodes: NodeWithScore[];
}
/**
* A ContextGenerator is used to generate a context based on a message's text content
*/
export interface ContextGenerator {
generate(message: string, parentEvent?: Event): Promise<Context>;
}
+37
View File
@@ -0,0 +1,37 @@
import { Sha256 } from "@aws-crypto/sha256-js";
import { CompleteFileSystem, InMemoryFileSystem } from "../storage";
export interface SHA256 {
update(data: string | Uint8Array): void;
// to base64
digest(): string;
}
export const EOL = "\n";
export const defaultFS: CompleteFileSystem = new InMemoryFileSystem();
export function ok(value: unknown, message?: string): asserts value {
if (!value) {
const error = Error(message);
error.name = "AssertionError";
error.message = message ?? "The expression evaluated to a falsy value.";
throw error;
}
}
export function createSHA256(): SHA256 {
const sha256 = new Sha256();
return {
update(data: string | Uint8Array): void {
sha256.update(data);
},
digest() {
return globalThis.btoa(sha256.digestSync().toString());
},
};
}
export function randomUUID(): string {
return crypto.randomUUID();
}
+22
View File
@@ -0,0 +1,22 @@
import { ok } from "node:assert";
import { createHash, randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import { EOL } from "node:os";
import type { CompleteFileSystem } from "../storage";
import type { SHA256 } from "./index.edge-light";
export function createSHA256(): SHA256 {
const hash = createHash("sha256");
return {
update(data: string | Uint8Array): void {
hash.update(data);
},
digest() {
return hash.digest("base64");
},
};
}
export const defaultFS: CompleteFileSystem = fs;
export { EOL, ok, randomUUID };
@@ -0,0 +1,410 @@
import { BaseNode, MetadataMode, TextNode } from "../Node";
import { LLM } from "../llm";
import {
defaultKeywordExtractorPromptTemplate,
defaultQuestionAnswerPromptTemplate,
defaultSummaryExtractorPromptTemplate,
defaultTitleCombinePromptTemplate,
defaultTitleExtractorPromptTemplate,
} from "./prompts";
import { BaseExtractor } from "./types";
const STRIP_REGEX = /(\r\n|\n|\r)/gm;
type ExtractKeyword = {
excerptKeywords: string;
};
/**
* Extract keywords from a list of nodes.
*/
export class KeywordExtractor extends BaseExtractor {
/**
* LLM instance.
* @type {LLM}
*/
llm: LLM;
/**
* Number of keywords to extract.
* @type {number}
* @default 5
*/
keywords: number = 5;
/**
* Constructor for the KeywordExtractor class.
* @param {LLM} llm LLM instance.
* @param {number} keywords Number of keywords to extract.
* @throws {Error} If keywords is less than 1.
*/
constructor(llm: LLM, keywords: number = 5) {
if (keywords < 1) throw new Error("Keywords must be greater than 0");
super();
this.llm = llm;
this.keywords = keywords;
}
/**
*
* @param node Node to extract keywords from.
* @returns Keywords extracted from the node.
*/
async extractKeywordsFromNodes(node: BaseNode): Promise<ExtractKeyword | {}> {
if (this.isTextNodeOnly && !(node instanceof TextNode)) {
return {};
}
const completion = await this.llm.complete({
prompt: defaultKeywordExtractorPromptTemplate({
contextStr: node.getContent(MetadataMode.ALL),
keywords: this.keywords,
}),
});
return {
excerptKeywords: completion.text,
};
}
/**
*
* @param nodes Nodes to extract keywords from.
* @returns Keywords extracted from the nodes.
*/
async extract(nodes: BaseNode[]): Promise<Array<ExtractKeyword> | Array<{}>> {
const results = await Promise.all(
nodes.map((node) => this.extractKeywordsFromNodes(node)),
);
return results;
}
}
type ExtractTitle = {
documentTitle: string;
};
/**
* Extract title from a list of nodes.
*/
export class TitleExtractor extends BaseExtractor {
/**
* LLM instance.
* @type {LLM}
*/
llm: LLM;
/**
* Can work for mixture of text and non-text nodes
* @type {boolean}
* @default false
*/
isTextNodeOnly: boolean = false;
/**
* Number of nodes to extrct titles from.
* @type {number}
* @default 5
*/
nodes: number = 5;
/**
* The prompt template to use for the title extractor.
* @type {string}
*/
nodeTemplate: string;
/**
* The prompt template to merge title with..
* @type {string}
*/
combineTemplate: string;
/**
* Constructor for the TitleExtractor class.
* @param {LLM} llm LLM instance.
* @param {number} nodes Number of nodes to extract titles from.
* @param {string} node_template The prompt template to use for the title extractor.
* @param {string} combine_template The prompt template to merge title with..
*/
constructor(
llm: LLM,
nodes: number = 5,
node_template?: string,
combine_template?: string,
) {
super();
this.llm = llm;
this.nodes = nodes;
this.nodeTemplate = node_template ?? defaultTitleExtractorPromptTemplate();
this.combineTemplate =
combine_template ?? defaultTitleCombinePromptTemplate();
}
/**
* Extract titles from a list of nodes.
* @param {BaseNode[]} nodes Nodes to extract titles from.
* @returns {Promise<BaseNode<ExtractTitle>[]>} Titles extracted from the nodes.
*/
async extract(nodes: BaseNode[]): Promise<Array<ExtractTitle>> {
const nodesToExtractTitle: BaseNode[] = [];
for (let i = 0; i < this.nodes; i++) {
if (nodesToExtractTitle.length >= nodes.length) break;
if (this.isTextNodeOnly && !(nodes[i] instanceof TextNode)) continue;
nodesToExtractTitle.push(nodes[i]);
}
if (nodesToExtractTitle.length === 0) return [];
let titlesCandidates: string[] = [];
let title: string = "";
for (let i = 0; i < nodesToExtractTitle.length; i++) {
const completion = await this.llm.complete({
prompt: defaultTitleExtractorPromptTemplate({
contextStr: nodesToExtractTitle[i].getContent(MetadataMode.ALL),
}),
});
titlesCandidates.push(completion.text);
}
if (nodesToExtractTitle.length > 1) {
const combinedTitles = titlesCandidates.join(",");
const completion = await this.llm.complete({
prompt: defaultTitleCombinePromptTemplate({
contextStr: combinedTitles,
}),
});
title = completion.text;
}
if (nodesToExtractTitle.length === 1) {
title = titlesCandidates[0];
}
return nodes.map((_) => ({
documentTitle: title.trim().replace(STRIP_REGEX, ""),
}));
}
}
type ExtractQuestion = {
questionsThisExcerptCanAnswer: string;
};
/**
* Extract questions from a list of nodes.
*/
export class QuestionsAnsweredExtractor extends BaseExtractor {
/**
* LLM instance.
* @type {LLM}
*/
llm: LLM;
/**
* Number of questions to generate.
* @type {number}
* @default 5
*/
questions: number = 5;
/**
* The prompt template to use for the question extractor.
* @type {string}
*/
promptTemplate: string;
/**
* Wheter to use metadata for embeddings only
* @type {boolean}
* @default false
*/
embeddingOnly: boolean = false;
/**
* Constructor for the QuestionsAnsweredExtractor class.
* @param {LLM} llm LLM instance.
* @param {number} questions Number of questions to generate.
* @param {string} promptTemplate The prompt template to use for the question extractor.
* @param {boolean} embeddingOnly Wheter to use metadata for embeddings only.
*/
constructor(
llm: LLM,
questions: number = 5,
promptTemplate?: string,
embeddingOnly: boolean = false,
) {
if (questions < 1) throw new Error("Questions must be greater than 0");
super();
this.llm = llm;
this.questions = questions;
this.promptTemplate =
promptTemplate ??
defaultQuestionAnswerPromptTemplate({
numQuestions: questions,
contextStr: "",
});
this.embeddingOnly = embeddingOnly;
}
/**
* Extract answered questions from a node.
* @param {BaseNode} node Node to extract questions from.
* @returns {Promise<Array<ExtractQuestion> | Array<{}>>} Questions extracted from the node.
*/
async extractQuestionsFromNode(
node: BaseNode,
): Promise<ExtractQuestion | {}> {
if (this.isTextNodeOnly && !(node instanceof TextNode)) {
return {};
}
const contextStr = node.getContent(this.metadataMode);
const prompt = defaultQuestionAnswerPromptTemplate({
contextStr,
numQuestions: this.questions,
});
const questions = await this.llm.complete({
prompt,
});
return {
questionsThisExcerptCanAnswer: questions.text.replace(STRIP_REGEX, ""),
};
}
/**
* Extract answered questions from a list of nodes.
* @param {BaseNode[]} nodes Nodes to extract questions from.
* @returns {Promise<Array<ExtractQuestion> | Array<{}>>} Questions extracted from the nodes.
*/
async extract(
nodes: BaseNode[],
): Promise<Array<ExtractQuestion> | Array<{}>> {
const results = await Promise.all(
nodes.map((node) => this.extractQuestionsFromNode(node)),
);
return results;
}
}
type ExtractSummary = {
sectionSummary: string;
prevSectionSummary: string;
nextSectionSummary: string;
};
/**
* Extract summary from a list of nodes.
*/
export class SummaryExtractor extends BaseExtractor {
/**
* LLM instance.
* @type {LLM}
*/
llm: LLM;
/**
* List of summaries to extract: 'self', 'prev', 'next'
* @type {string[]}
*/
summaries: string[];
/**
* The prompt template to use for the summary extractor.
* @type {string}
*/
promptTemplate: string;
private _selfSummary: boolean;
private _prevSummary: boolean;
private _nextSummary: boolean;
constructor(
llm: LLM,
summaries: string[] = ["self"],
promptTemplate?: string,
) {
if (!summaries.some((s) => ["self", "prev", "next"].includes(s)))
throw new Error("Summaries must be one of 'self', 'prev', 'next'");
super();
this.llm = llm;
this.summaries = summaries;
this.promptTemplate =
promptTemplate ?? defaultSummaryExtractorPromptTemplate();
this._selfSummary = summaries.includes("self");
this._prevSummary = summaries.includes("prev");
this._nextSummary = summaries.includes("next");
}
/**
* Extract summary from a node.
* @param {BaseNode} node Node to extract summary from.
* @returns {Promise<string>} Summary extracted from the node.
*/
async generateNodeSummary(node: BaseNode): Promise<string> {
if (this.isTextNodeOnly && !(node instanceof TextNode)) {
return "";
}
const contextStr = node.getContent(this.metadataMode);
const prompt = defaultSummaryExtractorPromptTemplate({
contextStr,
});
const summary = await this.llm.complete({
prompt,
});
return summary.text.replace(STRIP_REGEX, "");
}
/**
* Extract summaries from a list of nodes.
* @param {BaseNode[]} nodes Nodes to extract summaries from.
* @returns {Promise<Array<ExtractSummary> | Arry<{}>>} Summaries extracted from the nodes.
*/
async extract(nodes: BaseNode[]): Promise<Array<ExtractSummary> | Array<{}>> {
if (!nodes.every((n) => n instanceof TextNode))
throw new Error("Only `TextNode` is allowed for `Summary` extractor");
const nodeSummaries = await Promise.all(
nodes.map((node) => this.generateNodeSummary(node)),
);
let metadataList: any[] = nodes.map(() => ({}));
for (let i = 0; i < nodes.length; i++) {
if (i > 0 && this._prevSummary && nodeSummaries[i - 1]) {
metadataList[i]["prevSectionSummary"] = nodeSummaries[i - 1];
}
if (i < nodes.length - 1 && this._nextSummary && nodeSummaries[i + 1]) {
metadataList[i]["nextSectionSummary"] = nodeSummaries[i + 1];
}
if (this._selfSummary && nodeSummaries[i]) {
metadataList[i]["sectionSummary"] = nodeSummaries[i];
}
}
return metadataList;
}
}
+6
View File
@@ -0,0 +1,6 @@
export {
KeywordExtractor,
QuestionsAnsweredExtractor,
SummaryExtractor,
TitleExtractor,
} from "./MetadataExtractors";
+81
View File
@@ -0,0 +1,81 @@
export interface DefaultPromptTemplate {
contextStr: string;
}
export interface DefaultKeywordExtractorPromptTemplate
extends DefaultPromptTemplate {
keywords: number;
}
export interface DefaultQuestionAnswerPromptTemplate
extends DefaultPromptTemplate {
numQuestions: number;
}
export interface DefaultNodeTextTemplate {
metadataStr: string;
content: string;
}
export const defaultKeywordExtractorPromptTemplate = ({
contextStr = "",
keywords = 5,
}: DefaultKeywordExtractorPromptTemplate) => `
${contextStr}. Give ${keywords} unique keywords for thiss
document. Format as comma separated. Keywords:
`;
export const defaultTitleExtractorPromptTemplate = (
{ contextStr = "" }: DefaultPromptTemplate = {
contextStr: "",
},
) => `
${contextStr}. Give a title that summarizes all of the unique entities, titles or themes found in the context. Title:
`;
export const defaultTitleCombinePromptTemplate = (
{ contextStr = "" }: DefaultPromptTemplate = {
contextStr: "",
},
) => `
${contextStr}. Based on the above candidate titles and content,s
what is the comprehensive title for this document? Title:
`;
export const defaultQuestionAnswerPromptTemplate = (
{ contextStr = "", numQuestions = 5 }: DefaultQuestionAnswerPromptTemplate = {
contextStr: "",
numQuestions: 5,
},
) => `
${contextStr}. Given the contextual information,s
generate ${numQuestions} questions this context can provides
specific answers to which are unlikely to be found elsewhere.
Higher-level summaries of surrounding context may be provideds
as well. Try using these summaries to generate better questionss
that this context can answer.
`;
export const defaultSummaryExtractorPromptTemplate = (
{ contextStr = "" }: DefaultPromptTemplate = {
contextStr: "",
},
) => `
${contextStr}. Summarize the key topics and entities of the section.s
Summary:
`;
export const defaultNodeTextTemplate = ({
metadataStr = "",
content = "",
}: {
metadataStr?: string;
content?: string;
} = {}) => `[Excerpt from document]
${metadataStr}
Excerpt:
-----
${content}
-----
`;
+73
View File
@@ -0,0 +1,73 @@
import { BaseNode, MetadataMode, TextNode } from "../Node";
import { TransformComponent } from "../ingestion";
import { defaultNodeTextTemplate } from "./prompts";
/*
* Abstract class for all extractors.
*/
export abstract class BaseExtractor implements TransformComponent {
isTextNodeOnly: boolean = true;
showProgress: boolean = true;
metadataMode: MetadataMode = MetadataMode.ALL;
disableTemplateRewrite: boolean = false;
inPlace: boolean = true;
numWorkers: number = 4;
abstract extract(nodes: BaseNode[]): Promise<Record<string, any>[]>;
async transform(nodes: BaseNode[], options?: any): Promise<BaseNode[]> {
return this.processNodes(
nodes,
options?.excludedEmbedMetadataKeys,
options?.excludedLlmMetadataKeys,
);
}
/**
*
* @param nodes Nodes to extract metadata from.
* @param excludedEmbedMetadataKeys Metadata keys to exclude from the embedding.
* @param excludedLlmMetadataKeys Metadata keys to exclude from the LLM.
* @returns Metadata extracted from the nodes.
*/
async processNodes(
nodes: BaseNode[],
excludedEmbedMetadataKeys: string[] | undefined = undefined,
excludedLlmMetadataKeys: string[] | undefined = undefined,
): Promise<BaseNode[]> {
let newNodes: BaseNode[];
if (this.inPlace) {
newNodes = nodes;
} else {
newNodes = nodes.slice();
}
let curMetadataList = await this.extract(newNodes);
for (let idx in newNodes) {
newNodes[idx].metadata = curMetadataList[idx];
}
for (let idx in newNodes) {
if (excludedEmbedMetadataKeys) {
newNodes[idx].excludedEmbedMetadataKeys.concat(
excludedEmbedMetadataKeys,
);
}
if (excludedLlmMetadataKeys) {
newNodes[idx].excludedLlmMetadataKeys.concat(excludedLlmMetadataKeys);
}
if (!this.disableTemplateRewrite) {
if (newNodes[idx] instanceof TextNode) {
newNodes[idx] = new TextNode({
...newNodes[idx],
textTemplate: defaultNodeTextTemplate(),
});
}
}
}
return newNodes;
}
}
+4 -11
View File
@@ -1,4 +1,3 @@
export * from "./ChatEngine";
export * from "./ChatHistory";
export * from "./GlobalsHelper";
export * from "./Node";
@@ -15,19 +14,13 @@ export * from "./Tool";
export * from "./callbacks/CallbackManager";
export * from "./constants";
export * from "./embeddings";
export * from "./engines/chat";
export * from "./extractors";
export * from "./indices";
export * from "./ingestion";
export * from "./llm";
export * from "./nodeParsers";
export * from "./postprocessors";
export * from "./readers/AssemblyAI";
export * from "./readers/CSVReader";
export * from "./readers/DocxReader";
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 "./readers";
export * from "./storage";
export * from "./synthesizers";
+1 -1
View File
@@ -1,8 +1,8 @@
import { randomUUID } from "node:crypto";
import { BaseNode, Document, jsonToNode } from "../Node";
import { BaseQueryEngine } from "../QueryEngine";
import { BaseRetriever } from "../Retriever";
import { ServiceContext } from "../ServiceContext";
import { randomUUID } from "../env";
import { StorageContext } from "../storage/StorageContext";
import { BaseDocumentStore } from "../storage/docStore/types";
import { BaseIndexStore } from "../storage/indexStore/types";

Some files were not shown because too many files have changed in this diff Show More