Compare commits

..

19 Commits

Author SHA1 Message Date
github-actions[bot] 5cb270d07f Release 0.2.13 (#773)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-26 13:26:12 -05:00
Alex Yang 62771058aa fix: empty tools (#772) 2024-04-26 13:10:57 -05:00
github-actions[bot] ca348a6570 Release 0.2.12 (#770)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-26 13:11:23 +07:00
Marcus Schiesser 44a7fd72e8 ci: publish github release on tag pushes (#771) 2024-04-26 13:09:25 +07:00
Thuc Pham d8d952d937 feat: init gemini llm (#769) 2024-04-26 11:04:33 +07:00
github-actions[bot] 216ba1f22b Release 0.2.11 (#765)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-25 17:53:17 -07:00
Marcus Schiesser 74686f5776 ci: add version to release PR (#766) 2024-04-25 10:55:02 +07:00
Marcus Schiesser 1ebf9e67a4 ci: add release action (#764) 2024-04-25 10:09:55 +07:00
Alex Yang aeefc77da0 test: load large amount of data won't cause error (#762) 2024-04-24 15:04:29 -05:00
ezirmusitua 13d8d7cbbe fix: use Array.prototype.flat (#760)
Co-authored-by: Alex Yang <himself65@outlook.com>
2024-04-24 14:36:12 -05:00
Alex Yang 9c34e44b85 ci: coverage node.js 22 (#761) 2024-04-24 14:19:12 -05:00
Thuc Pham cb2dc802d9 docs: update next config for external packages (#759) 2024-04-24 17:27:20 +08:00
Ziniu Yu 5a6cc0e32e feat: support jina ai embedding and reranker (#734) 2024-04-24 15:45:36 +07:00
Marcus Schiesser a63256eb84 feat: add default file metadata (#758) 2024-04-24 13:54:29 +07:00
Alex Yang 0a160b97a0 fix(docs): api generation (#756) 2024-04-23 14:24:17 -05:00
Thuc Pham 95602c7959 feat: overide generate hash function for image document (#751)
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-04-23 11:56:37 +07:00
Alex Yang 20bc466ca1 chore: bump notion reader (#753) 2024-04-22 15:14:06 -05:00
Thuc Pham efb1c56ba5 fix: return buffer when loading image data (#749) 2024-04-22 15:28:19 +07:00
Alex Yang 286499388d fix: agent class should implement ChatEngine interface (#746) 2024-04-22 02:13:29 -05:00
66 changed files with 2246 additions and 1285 deletions
-6
View File
@@ -1,6 +0,0 @@
---
"llamaindex": patch
"@llamaindex/edge": patch
---
refactor: use ollama official sdk
-6
View File
@@ -1,6 +0,0 @@
---
"llamaindex": patch
"@llamaindex/edge": patch
---
feat: support output to json format
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
+37
View File
@@ -0,0 +1,37 @@
name: Publish to GitHub Releases
on:
push:
tags:
- "llamaindex@*"
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Build tarball
run: |
pnpm pack
working-directory: packages/core
- name: Create release
uses: ncipollo/release-action@v1
with:
artifacts: "packages/core/llamaindex-*.tgz"
name: Release ${{ github.ref }}
bodyFile: "packages/core/CHANGELOG.md"
token: ${{ secrets.GITHUB_TOKEN }}
+57
View File
@@ -0,0 +1,57 @@
name: Release
on:
push:
branches:
- main
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Add auth token to .npmrc file
run: |
cat << EOF >> ".npmrc"
//registry.npmjs.org/:_authToken=$NPM_TOKEN
EOF
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Get changeset status
id: get-changeset-status
run: |
pnpm changeset status --output .changeset/status.json
new_version=$(jq -r '.releases[] | select(.name == "llamaindex") | .newVersion' < .changeset/status.json)
rm -v .changeset/status.json
echo "new-version=${new_version}" >> "$GITHUB_OUTPUT"
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@v1
with:
commit: Release ${{ steps.get-changeset-status.outputs.new-version }}
title: Release ${{ steps.get-changeset-status.outputs.new-version }}
# update version PR with the latest changesets
version: pnpm new-version
# build package and call changeset publish
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+10 -8
View File
@@ -17,16 +17,18 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [18.x, 20.x, 21.x]
node-version: [18.x, 20.x, 22.x]
name: E2E on Node.js ${{ matrix.node-version }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
node-version: ${{ matrix.node-version }}
cache: "pnpm"
- name: Install dependencies
run: pnpm install
@@ -37,13 +39,13 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [18.x, 20.x, 21.x]
node-version: [18.x, 20.x, 22.x]
name: Test on Node.js ${{ matrix.node-version }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
@@ -58,7 +60,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
@@ -87,7 +89,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
@@ -105,7 +107,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
+5 -11
View File
@@ -91,16 +91,10 @@ Please send a descriptive changeset for each PR.
## Publishing (maintainers only)
To publish a new version of the library, first create a new version:
The [Release Github Action](.github/workflows/release.yml) is automatically generating and updating a
PR called "Release {version}".
```shell
pnpm new-version
```
This PR will update the `package.json` and `CHANGELOG.md` files of each package according to
the current changesets in the [.changeset](.changeset/) folder.
If everything looks good, commit the generated files and release the new version:
```shell
pnpm release
git push # push to the main branch
git push --tags
```
If this PR is merged it will automatically add version tags to the repository and publish the updated packages to NPM.
+13 -6
View File
@@ -114,14 +114,21 @@ Add the following config to your `next.config.js` to ignore specific packages in
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverComponentsExternalPackages: ["pdf2json", "@zilliz/milvus2-sdk-node"],
serverComponentsExternalPackages: [
"pdf2json",
"@zilliz/milvus2-sdk-node",
"sharp",
"onnxruntime-node",
],
},
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
sharp$: false,
"onnxruntime-node$": false,
};
config.externals.push({
pdf2json: "commonjs pdf2json",
"@zilliz/milvus2-sdk-node": "commonjs @zilliz/milvus2-sdk-node",
sharp: "commonjs sharp",
"onnxruntime-node": "commonjs onnxruntime-node",
});
return config;
},
};
+23
View File
@@ -1,5 +1,28 @@
# docs
## 0.0.7
### Patch Changes
- Updated dependencies [6277105]
- llamaindex@0.2.13
## 0.0.6
### Patch Changes
- Updated dependencies [d8d952d]
- llamaindex@0.2.12
## 0.0.5
### Patch Changes
- Updated dependencies [87142b2]
- Updated dependencies [5a6cc0e]
- Updated dependencies [87142b2]
- llamaindex@0.2.11
## 0.0.4
### Patch Changes
@@ -0,0 +1,33 @@
# Gemini
To use Gemini embeddings, you need to import `GeminiEmbedding` from `llamaindex`.
```ts
import { GeminiEmbedding, Settings } from "llamaindex";
// Update Embed Model
Settings.embedModel = new GeminiEmbedding();
const document = new Document({ text: essay, id_: "essay" });
const index = await VectorStoreIndex.fromDocuments([document]);
const queryEngine = index.asQueryEngine();
const query = "What is the meaning of life?";
const results = await queryEngine.query({
query,
});
```
Per default, `GeminiEmbedding` is using the `gemini-pro` model. You can change the model by passing the `model` parameter to the constructor.
For example:
```ts
import { GEMINI_MODEL, GeminiEmbedding } from "llamaindex";
Settings.embedModel = new GeminiEmbedding({
model: GEMINI_MODEL.GEMINI_PRO_LATEST,
});
```
@@ -0,0 +1,21 @@
# Jina AI
To use Jina AI embeddings, you need to import `JinaAIEmbedding` from `llamaindex`.
```ts
import { JinaAIEmbedding, Settings } from "llamaindex";
Settings.embedModel = new JinaAIEmbedding();
const document = new Document({ text: essay, id_: "essay" });
const index = await VectorStoreIndex.fromDocuments([document]);
const queryEngine = index.asQueryEngine();
const query = "What is the meaning of life?";
const results = await queryEngine.query({
query,
});
```
@@ -0,0 +1,71 @@
# Gemini
## Usage
```ts
import { Gemini, Settings, GEMINI_MODEL } from "llamaindex";
Settings.llm = new Gemini({
model: GEMINI_MODEL.GEMINI_PRO,
});
```
## Load and index documents
For this example, we will use a single document. In a real-world scenario, you would have multiple documents to index.
```ts
const document = new Document({ text: essay, id_: "essay" });
const index = await VectorStoreIndex.fromDocuments([document]);
```
## Query
```ts
const queryEngine = index.asQueryEngine();
const query = "What is the meaning of life?";
const results = await queryEngine.query({
query,
});
```
## Full Example
```ts
import {
Gemini,
Document,
VectorStoreIndex,
Settings,
GEMINI_MODEL,
} from "llamaindex";
Settings.llm = new Gemini({
model: GEMINI_MODEL.GEMINI_PRO,
});
async function main() {
const document = new Document({ text: essay, id_: "essay" });
// Load and index documents
const index = await VectorStoreIndex.fromDocuments([document]);
// Create a query engine
const queryEngine = index.asQueryEngine({
retriever,
});
const query = "What is the meaning of life?";
// Query
const response = await queryEngine.query({
query,
});
// Log the response
console.log(response.response);
}
```
@@ -0,0 +1,71 @@
# Jina AI Reranker
The Jina AI Reranker is a postprocessor that uses the Jina AI Reranker API to rerank the results of a search query.
## Setup
Firstly, you will need to install the `llamaindex` package.
```bash
pnpm install llamaindex
```
Now, you will need to sign up for an API key at [Jina AI](https://jina.ai/reranker). Once you have your API key you can import the necessary modules and create a new instance of the `JinaAIReranker` class.
```ts
import {
JinaAIReranker,
Document,
OpenAI,
VectorStoreIndex,
Settings,
} from "llamaindex";
```
## Load and index documents
For this example, we will use a single document. In a real-world scenario, you would have multiple documents to index.
```ts
const document = new Document({ text: essay, id_: "essay" });
Settings.llm = new OpenAI({ model: "gpt-3.5-turbo", temperature: 0.1 });
const index = await VectorStoreIndex.fromDocuments([document]);
```
## Increase similarity topK to retrieve more results
The default value for `similarityTopK` is 2. This means that only the most similar document will be returned. To retrieve more results, you can increase the value of `similarityTopK`.
```ts
const retriever = index.asRetriever();
retriever.similarityTopK = 5;
```
## Create a new instance of the JinaAIReranker class
Then you can create a new instance of the `JinaAIReranker` class and pass in the number of results you want to return.
The Jina AI Reranker API key is set in the `JINAAI_API_KEY` environment variable.
```bash
export JINAAI_API_KEY=<YOUR API KEY>
```
```ts
const nodePostprocessor = new JinaAIReranker({
topN: 5,
});
```
## Create a query engine with the retriever and node postprocessor
```ts
const queryEngine = index.asQueryEngine({
retriever,
nodePostprocessors: [nodePostprocessor],
});
// log the response
const response = await queryEngine.query("Where did the author grown up?");
```
+1 -1
View File
@@ -163,7 +163,7 @@ const config = {
"docusaurus-plugin-typedoc",
{
entryPoints: ["../../packages/core/src/index.ts"],
tsconfig: "../../packages/core/tsconfig.json",
tsconfig: "../../tsconfig.json",
readme: "none",
sourceLinkTemplate:
"https://github.com/run-llama/LlamaIndexTS/blob/{gitRevision}/{path}#L{line}",
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "docs",
"version": "0.0.4",
"version": "0.0.7",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@@ -20,6 +20,7 @@
"@llamaindex/examples": "workspace:*",
"@mdx-js/react": "^3.0.1",
"clsx": "^2.1.0",
"llamaindex": "workspace:*",
"postcss": "^8.4.38",
"prism-react-renderer": "^2.3.1",
"raw-loader": "^4.0.2",
+15
View File
@@ -0,0 +1,15 @@
import { GEMINI_MODEL, GeminiEmbedding } from "llamaindex";
async function main() {
if (!process.env.GOOGLE_API_KEY) {
throw new Error("Please set the GOOGLE_API_KEY environment variable.");
}
const embedModel = new GeminiEmbedding({
model: GEMINI_MODEL.GEMINI_PRO,
});
const texts = ["hello", "world"];
const embeddings = await embedModel.getTextEmbeddingsBatch(texts);
console.log(`\nWe have ${embeddings.length} embeddings`);
}
main().catch(console.error);
+21
View File
@@ -0,0 +1,21 @@
import { Gemini, GEMINI_MODEL } from "llamaindex";
(async () => {
if (!process.env.GOOGLE_API_KEY) {
throw new Error("Please set the GOOGLE_API_KEY environment variable.");
}
const gemini = new Gemini({
model: GEMINI_MODEL.GEMINI_PRO,
});
const result = await gemini.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);
})();
+1 -13
View File
@@ -1,16 +1,5 @@
import * as fs from "fs";
import { ClipEmbedding, similarity, SimilarityType } from "llamaindex";
async function loadImageFromDisk(path: string) {
try {
const file = fs.readFileSync(path);
return new Blob([file]);
} catch (error) {
console.error(`Error loading image from disk: ${error}`);
return null;
}
}
async function main() {
const clip = new ClipEmbedding();
@@ -23,8 +12,7 @@ async function main() {
// Get image embedding
const image =
"https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/football-match.jpg";
const blobImage = await loadImageFromDisk("./data/football-match.jpg");
const imageEmbedding = await clip.getImageEmbedding(blobImage || image);
const imageEmbedding = await clip.getImageEmbedding(image);
// Calc similarity
const sim1 = similarity(
Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

-2
View File
@@ -4,7 +4,6 @@ import {
VectorStoreIndex,
storageContextFromDefaults,
} from "llamaindex";
import { DocStoreStrategy } from "llamaindex/ingestion/strategies/index";
import * as path from "path";
@@ -32,7 +31,6 @@ async function generateDatasource() {
});
await VectorStoreIndex.fromDocuments(documents, {
storageContext,
docStoreStrategy: DocStoreStrategy.NONE,
});
});
console.log(`Storage successfully generated in ${ms / 1000}s.`);
+10 -9
View File
@@ -3,20 +3,21 @@
"private": true,
"type": "module",
"scripts": {
"start": "node --loader ts-node/esm ./src/simple-directory-reader.ts",
"start:csv": "node --loader ts-node/esm ./src/csv.ts",
"start:docx": "node --loader ts-node/esm ./src/docx.ts",
"start:html": "node --loader ts-node/esm ./src/html.ts",
"start:markdown": "node --loader ts-node/esm ./src/markdown.ts",
"start:pdf": "node --loader ts-node/esm ./src/pdf.ts",
"start:llamaparse": "node --loader ts-node/esm ./src/llamaparse.ts"
"start": "node --import tsx ./src/simple-directory-reader.ts",
"start:csv": "node --import tsx ./src/csv.ts",
"start:docx": "node --import tsx ./src/docx.ts",
"start:html": "node --import tsx ./src/html.ts",
"start:markdown": "node --import tsx ./src/markdown.ts",
"start:pdf": "node --import tsx ./src/pdf.ts",
"start:llamaparse": "node --import tsx ./src/llamaparse.ts",
"start:notion": "node --import tsx ./src/notion.ts"
},
"dependencies": {
"llamaindex": "*"
},
"devDependencies": {
"@types/node": "^20.12.7",
"ts-node": "^10.9.2",
"typescript": "^5.4.3"
"tsx": "^4.7.2",
"typescript": "^5.4.5"
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ import { createInterface } from "node:readline/promises";
program
.argument("[page]", "Notion page id (must be provided)")
.action(async (page, _options, command) => {
.action(async (page, _options) => {
// Initializing a client
if (!process.env.NOTION_TOKEN) {
@@ -55,7 +55,7 @@ program
.filter((page) => page !== null);
console.log("Found pages:");
console.table(pages);
console.log(`To run, run ts-node ${command.name()} [page id]`);
console.log(`To run, run with [page id]`);
return;
}
}
+2 -1
View File
@@ -1,3 +1,4 @@
.turbo
/README.md
LICENSE
LICENSE
*.tgz
+20
View File
@@ -1,5 +1,25 @@
# llamaindex
## 0.2.13
### Patch Changes
- 6277105: fix: allow passing empty tools to llms
## 0.2.12
### Patch Changes
- d8d952d: feat: add gemini llm and embedding
## 0.2.11
### Patch Changes
- 87142b2: refactor: use ollama official sdk
- 5a6cc0e: feat: support jina ai embedding and reranker
- 87142b2: feat: support output to json format
## 0.2.10
### Patch Changes
+4 -2
View File
@@ -2,7 +2,7 @@ import { consola } from "consola";
import { Anthropic, FunctionTool, Settings, type LLM } from "llamaindex";
import { AnthropicAgent } from "llamaindex/agent/anthropic";
import { extractText } from "llamaindex/llm/utils";
import { ok } from "node:assert";
import { ok, strictEqual } from "node:assert";
import { beforeEach, test } from "node:test";
import { sumNumbersTool } from "./fixtures/tools.js";
import { mockLLMEvent } from "./utils.js";
@@ -71,10 +71,12 @@ await test("anthropic agent", async (t) => {
},
],
});
const { response } = await agent.chat({
const { response, sources } = await agent.chat({
message: "What is the weather in San Francisco?",
});
consola.debug("response:", response.message.content);
strictEqual(sources.length, 1);
ok(extractText(response.message.content).includes("35"));
});
+17 -7
View File
@@ -13,6 +13,7 @@ import {
SummaryIndex,
VectorStoreIndex,
type LLM,
type ToolOutput,
} from "llamaindex";
import { extractText } from "llamaindex/llm/utils";
import { ok, strictEqual } from "node:assert";
@@ -222,10 +223,12 @@ await test("agent with object function call", async (t) => {
),
],
});
const { response } = await agent.chat({
const { response, sources } = await agent.chat({
message: "What is the weather in San Francisco?",
});
consola.debug("response:", response.message.content);
strictEqual(sources.length, 1);
ok(extractText(response.message.content).includes("72"));
});
});
@@ -253,10 +256,12 @@ await test("agent", async (t) => {
},
],
});
const { response } = await agent.chat({
const { response, sources } = await agent.chat({
message: "What is the weather in San Francisco?",
});
consola.debug("response:", response.message.content);
strictEqual(sources.length, 1);
ok(extractText(response.message.content).includes("35"));
});
@@ -290,9 +295,10 @@ await test("agent", async (t) => {
const agent = new OpenAIAgent({
tools: [showUniqueId],
});
const { response } = await agent.chat({
const { response, sources } = await agent.chat({
message: "My name is Alex Yang. What is my unique id?",
});
strictEqual(sources.length, 1);
ok(extractText(response.message.content).includes(uniqueId));
});
@@ -301,10 +307,11 @@ await test("agent", async (t) => {
tools: [sumNumbersTool],
});
const { response } = await openaiAgent.chat({
const { response, sources } = await openaiAgent.chat({
message: "how much is 1 + 1?",
});
strictEqual(sources.length, 1);
ok(extractText(response.message.content).includes("2"));
});
});
@@ -319,18 +326,21 @@ await test("agent stream", async (t) => {
tools: [sumNumbersTool, divideNumbersTool],
});
const { response } = await agent.chat({
const stream = await agent.chat({
message: "Divide 16 by 2 then add 20",
stream: true,
});
let message = "";
let soruces: ToolOutput[] = [];
for await (const chunk of response) {
message += chunk.delta;
for await (const { response, sources: _sources } of stream) {
message += response.delta;
soruces = _sources;
}
strictEqual(fn.mock.callCount(), 2);
strictEqual(soruces.length, 2);
ok(message.includes("28"));
Settings.callbackManager.off("llm-tool-call", fn);
});
+4 -7
View File
@@ -4,14 +4,11 @@
"outDir": "./lib",
"module": "node16",
"moduleResolution": "node16",
"target": "ESNext"
"target": "ESNext",
"lib": ["ES2022"],
"types": ["node"]
},
"include": [
"./**/*.ts",
"./mock-module.js",
"./mock-register.js",
"./fixtures"
],
"include": ["./node", "./mock-module.js", "./mock-register.js", "./fixtures"],
"references": [
{
"path": "../../core/tsconfig.json"
+7 -3
View File
@@ -1,6 +1,6 @@
{
"name": "llamaindex",
"version": "0.2.10",
"version": "0.2.13",
"expectedMinorVersion": "2",
"license": "MIT",
"type": "module",
@@ -8,11 +8,11 @@
"@anthropic-ai/sdk": "^0.20.6",
"@aws-crypto/sha256-js": "^5.2.0",
"@datastax/astra-db-ts": "^1.0.1",
"@google/generative-ai": "^0.8.0",
"@grpc/grpc-js": "^1.10.6",
"@llamaindex/cloud": "0.0.5",
"@llamaindex/env": "workspace:*",
"@mistralai/mistralai": "^0.1.3",
"@notionhq/client": "^2.2.15",
"@pinecone-database/pinecone": "^2.2.0",
"@qdrant/js-client-rest": "^1.8.2",
"@types/lodash": "^4.17.0",
@@ -31,7 +31,7 @@
"mammoth": "^1.7.1",
"md-utils-ts": "^2.0.0",
"mongodb": "^6.5.0",
"notion-md-crawler": "^0.0.2",
"notion-md-crawler": "^1.0.0",
"ollama": "^0.5.0",
"openai": "^4.38.0",
"papaparse": "^5.4.1",
@@ -45,7 +45,11 @@
"wikipedia": "^2.1.2",
"wink-nlp": "^1.14.3"
},
"peerDependencies": {
"@notionhq/client": "^2.2.15"
},
"devDependencies": {
"@notionhq/client": "^2.2.15",
"@swc/cli": "^0.3.12",
"@swc/core": "^1.4.16",
"concurrently": "^8.2.2",
+31
View File
@@ -326,6 +326,37 @@ export class ImageNode<T extends Metadata = Metadata> extends TextNode<T> {
const absPath = path.resolve(this.id_);
return new URL(`file://${absPath}`);
}
// Calculates the image part of the hash
private generateImageHash() {
const hashFunction = createSHA256();
if (this.image instanceof Blob) {
// TODO: ideally we should use the blob's content to calculate the hash:
// hashFunction.update(new Uint8Array(await this.image.arrayBuffer()));
// as this is async, we're using the node's ID for the time being
hashFunction.update(this.id_);
} else if (this.image instanceof URL) {
hashFunction.update(this.image.toString());
} else if (typeof this.image === "string") {
hashFunction.update(this.image);
} else {
throw new Error(
`Unknown image type: ${typeof this.image}. Can't calculate hash`,
);
}
return hashFunction.digest();
}
generateHash() {
const hashFunction = createSHA256();
// calculates hash based on hash of both components (image and text)
hashFunction.update(super.generateHash());
hashFunction.update(this.generateImageHash());
return hashFunction.digest();
}
}
export class ImageDocument<T extends Metadata = Metadata> extends ImageNode<T> {
+29 -10
View File
@@ -1,5 +1,6 @@
import { pipeline, randomUUID } from "@llamaindex/env";
import {
type ChatEngine,
type ChatEngineParamsNonStreaming,
type ChatEngineParamsStreaming,
} from "../engines/chat/index.js";
@@ -171,8 +172,9 @@ export async function* createTaskImpl<
}
export type AgentStreamChatResponse<Options extends object> = {
response: ReadableStream<ChatResponseChunk<Options>>;
sources: ToolOutput[];
response: ChatResponseChunk<Options>;
// sources of the response, will emit when new tool outputs are available
sources?: ToolOutput[];
};
export type AgentChatResponse<Options extends object> = {
@@ -276,7 +278,12 @@ export abstract class AgentRunner<
>
? AdditionalMessageOptions
: never,
> {
> implements
ChatEngine<
AgentChatResponse<AdditionalMessageOptions>,
ReadableStream<AgentStreamChatResponse<AdditionalMessageOptions>>
>
{
readonly #llm: AI;
readonly #tools:
| BaseToolWithCall[]
@@ -370,13 +377,13 @@ export abstract class AgentRunner<
): Promise<AgentChatResponse<AdditionalMessageOptions>>;
async chat(
params: ChatEngineParamsStreaming,
): Promise<AgentStreamChatResponse<AdditionalMessageOptions>>;
): Promise<ReadableStream<AgentStreamChatResponse<AdditionalMessageOptions>>>;
@wrapEventCaller
async chat(
params: ChatEngineParamsNonStreaming | ChatEngineParamsStreaming,
): Promise<
| AgentChatResponse<AdditionalMessageOptions>
| AgentStreamChatResponse<AdditionalMessageOptions>
| ReadableStream<AgentStreamChatResponse<AdditionalMessageOptions>>
> {
const task = await this.createTask(params.message, !!params.stream);
const stepOutput = await pipeline(
@@ -397,14 +404,26 @@ export abstract class AgentRunner<
const { output, taskStep } = stepOutput;
this.#chatHistory = [...taskStep.context.store.messages];
if (isAsyncIterable(output)) {
return {
response: output,
sources: [...taskStep.context.store.toolOutputs],
} satisfies AgentStreamChatResponse<AdditionalMessageOptions>;
return output.pipeThrough<
AgentStreamChatResponse<AdditionalMessageOptions>
>(
new TransformStream({
transform(chunk, controller) {
controller.enqueue({
response: chunk,
get sources() {
return [...taskStep.context.store.toolOutputs];
},
});
},
}),
);
} else {
return {
response: output,
sources: [...taskStep.context.store.toolOutputs],
get sources() {
return [...taskStep.context.store.toolOutputs];
},
} satisfies AgentChatResponse<AdditionalMessageOptions>;
}
}
+2 -2
View File
@@ -5,7 +5,7 @@ import { RetrieverQueryEngine } from "../engines/query/RetrieverQueryEngine.js";
import type { TransformComponent } from "../ingestion/types.js";
import type { BaseNodePostprocessor } from "../postprocessors/types.js";
import type { BaseSynthesizer } from "../synthesizers/types.js";
import type { BaseQueryEngine } from "../types.js";
import type { QueryEngine } from "../types.js";
import type { CloudRetrieveParams } from "./LlamaCloudRetriever.js";
import { LlamaCloudRetriever } from "./LlamaCloudRetriever.js";
import { getPipelineCreate } from "./config.js";
@@ -178,7 +178,7 @@ export class LlamaCloudIndex {
preFilters?: unknown;
nodePostprocessors?: BaseNodePostprocessor[];
} & CloudRetrieveParams,
): BaseQueryEngine {
): QueryEngine {
const retriever = new LlamaCloudRetriever({
...this.params,
...params,
@@ -0,0 +1,43 @@
import {
GEMINI_MODEL,
GeminiSessionStore,
type GeminiConfig,
type GeminiSession,
} from "../llm/gemini.js";
import { BaseEmbedding } from "./types.js";
/**
* GeminiEmbedding is an alias for Gemini that implements the BaseEmbedding interface.
*/
export class GeminiEmbedding extends BaseEmbedding {
model: GEMINI_MODEL;
temperature: number;
topP: number;
maxTokens?: number;
session: GeminiSession;
constructor(init?: GeminiConfig) {
super();
this.model = init?.model ?? GEMINI_MODEL.GEMINI_PRO;
this.temperature = init?.temperature ?? 0.1;
this.topP = init?.topP ?? 1;
this.maxTokens = init?.maxTokens ?? undefined;
this.session = init?.session ?? GeminiSessionStore.get();
}
private async getEmbedding(prompt: string): Promise<number[]> {
const client = this.session.gemini.getGenerativeModel({
model: this.model,
});
const result = await client.embedContent(prompt);
return result.embedding.values;
}
getTextEmbedding(text: string): Promise<number[]> {
return this.getEmbedding(text);
}
getQueryEmbedding(query: string): Promise<number[]> {
return this.getTextEmbedding(query);
}
}
@@ -0,0 +1,29 @@
import { getEnv } from "@llamaindex/env";
import { OpenAIEmbedding } from "./OpenAIEmbedding.js";
export class JinaAIEmbedding extends OpenAIEmbedding {
constructor(init?: Partial<OpenAIEmbedding>) {
const {
apiKey = getEnv("JINAAI_API_KEY"),
additionalSessionOptions = {},
model = "jina-embeddings-v2-base-en",
...rest
} = init ?? {};
if (!apiKey) {
throw new Error(
"Set Jina AI API Key in JINAAI_API_KEY env variable. Get one for free or top up your key at https://jina.ai/embeddings",
);
}
additionalSessionOptions.baseURL =
additionalSessionOptions.baseURL ?? "https://api.jina.ai/v1";
super({
apiKey,
additionalSessionOptions,
model,
...rest,
});
}
}
+2
View File
@@ -1,5 +1,7 @@
export * from "./ClipEmbedding.js";
export * from "./GeminiEmbedding.js";
export * from "./HuggingFaceEmbedding.js";
export * from "./JinaAIEmbedding.js";
export * from "./MistralAIEmbedding.js";
export * from "./MultiModalEmbedding.js";
export { OllamaEmbedding } from "./OllamaEmbedding.js";
@@ -12,7 +12,7 @@ import { wrapEventCaller } from "../../internal/context/EventCaller.js";
import type { ChatMessage, LLM } from "../../llm/index.js";
import { extractText, streamReducer } from "../../llm/utils.js";
import { PromptMixin } from "../../prompts/index.js";
import type { BaseQueryEngine } from "../../types.js";
import type { QueryEngine } from "../../types.js";
import type {
ChatEngine,
ChatEngineParamsNonStreaming,
@@ -33,13 +33,13 @@ export class CondenseQuestionChatEngine
extends PromptMixin
implements ChatEngine
{
queryEngine: BaseQueryEngine;
queryEngine: QueryEngine;
chatHistory: ChatHistory;
llm: LLM;
condenseMessagePrompt: CondenseQuestionPrompt;
constructor(init: {
queryEngine: BaseQueryEngine;
queryEngine: QueryEngine;
chatHistory: ChatMessage[];
serviceContext?: ServiceContext;
condenseMessagePrompt?: CondenseQuestionPrompt;
+8 -8
View File
@@ -23,21 +23,21 @@ export interface ChatEngineParamsNonStreaming extends ChatEngineParamsBase {
stream?: false | null;
}
export interface ChatEngineAgentParams extends ChatEngineParamsBase {
toolChoice?: string | Record<string, any>;
stream?: boolean;
}
/**
* A ChatEngine is used to handle back and forth chats between the application and the LLM.
*/
export interface ChatEngine {
export interface ChatEngine<
// synchronous response
R = Response,
// asynchronous response
AR extends AsyncIterable<unknown> = AsyncIterable<R>,
> {
/**
* 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>;
chat(params: ChatEngineParamsStreaming): Promise<AR>;
chat(params: ChatEngineParamsNonStreaming): Promise<R>;
/**
* Resets the chat history so that it's empty.
@@ -7,7 +7,7 @@ import { PromptMixin } from "../../prompts/Mixin.js";
import type { BaseSynthesizer } from "../../synthesizers/index.js";
import { ResponseSynthesizer } from "../../synthesizers/index.js";
import type {
BaseQueryEngine,
QueryEngine,
QueryEngineParamsNonStreaming,
QueryEngineParamsStreaming,
} from "../../types.js";
@@ -15,10 +15,7 @@ import type {
/**
* A query engine that uses a retriever to query an index and then synthesizes the response.
*/
export class RetrieverQueryEngine
extends PromptMixin
implements BaseQueryEngine
{
export class RetrieverQueryEngine extends PromptMixin implements QueryEngine {
retriever: BaseRetriever;
responseSynthesizer: BaseSynthesizer;
nodePostprocessors: BaseNodePostprocessor[];
@@ -7,14 +7,14 @@ import type { BaseSelector } from "../../selectors/index.js";
import { LLMSingleSelector } from "../../selectors/index.js";
import { TreeSummarize } from "../../synthesizers/index.js";
import type {
BaseQueryEngine,
QueryBundle,
QueryEngine,
QueryEngineParamsNonStreaming,
QueryEngineParamsStreaming,
} from "../../types.js";
type RouterQueryEngineTool = {
queryEngine: BaseQueryEngine;
queryEngine: QueryEngine;
description: string;
};
@@ -54,9 +54,9 @@ async function combineResponses(
/**
* A query engine that uses multiple query engines and selects the best one.
*/
export class RouterQueryEngine extends PromptMixin implements BaseQueryEngine {
export class RouterQueryEngine extends PromptMixin implements QueryEngine {
private selector: BaseSelector;
private queryEngines: BaseQueryEngine[];
private queryEngines: QueryEngine[];
private metadatas: RouterQueryEngineMetadata[];
private summarizer: TreeSummarize;
private verbose: boolean;
@@ -11,8 +11,8 @@ import {
} from "../../synthesizers/index.js";
import type {
BaseQueryEngine,
BaseTool,
QueryEngine,
QueryEngineParamsNonStreaming,
QueryEngineParamsStreaming,
ToolMetadata,
@@ -24,10 +24,7 @@ import type { BaseQuestionGenerator, SubQuestion } from "./types.js";
/**
* SubQuestionQueryEngine decomposes a question into subquestions and then
*/
export class SubQuestionQueryEngine
extends PromptMixin
implements BaseQueryEngine
{
export class SubQuestionQueryEngine extends PromptMixin implements QueryEngine {
responseSynthesizer: BaseSynthesizer;
questionGen: BaseQuestionGenerator;
queryEngines: BaseTool[];
+2 -2
View File
@@ -8,7 +8,7 @@ import type { BaseDocumentStore } from "../storage/docStore/types.js";
import type { BaseIndexStore } from "../storage/indexStore/types.js";
import type { VectorStore } from "../storage/vectorStore/types.js";
import type { BaseSynthesizer } from "../synthesizers/types.js";
import type { BaseQueryEngine } from "../types.js";
import type { QueryEngine } from "../types.js";
import { IndexStruct } from "./IndexStruct.js";
import { IndexStructType } from "./json-to-index-struct.js";
@@ -87,7 +87,7 @@ export abstract class BaseIndex<T> {
abstract asQueryEngine(options?: {
retriever?: BaseRetriever;
responseSynthesizer?: BaseSynthesizer;
}): BaseQueryEngine;
}): QueryEngine;
/**
* Insert a document into the index.
+2 -2
View File
@@ -17,7 +17,7 @@ import type { StorageContext } from "../../storage/StorageContext.js";
import { storageContextFromDefaults } from "../../storage/StorageContext.js";
import type { BaseDocumentStore } from "../../storage/docStore/types.js";
import type { BaseSynthesizer } from "../../synthesizers/index.js";
import type { BaseQueryEngine } from "../../types.js";
import type { QueryEngine } from "../../types.js";
import type { BaseIndexInit } from "../BaseIndex.js";
import { BaseIndex, KeywordTable } from "../BaseIndex.js";
import { IndexStructType } from "../json-to-index-struct.js";
@@ -234,7 +234,7 @@ export class KeywordTableIndex extends BaseIndex<KeywordTable> {
responseSynthesizer?: BaseSynthesizer;
preFilters?: unknown;
nodePostprocessors?: BaseNodePostprocessor[];
}): BaseQueryEngine {
}): QueryEngine {
const { retriever, responseSynthesizer } = options ?? {};
return new RetrieverQueryEngine(
retriever ?? this.asRetriever(),
+2 -2
View File
@@ -23,7 +23,7 @@ import {
CompactAndRefine,
ResponseSynthesizer,
} from "../../synthesizers/index.js";
import type { BaseQueryEngine } from "../../types.js";
import type { QueryEngine } from "../../types.js";
import type { BaseIndexInit } from "../BaseIndex.js";
import { BaseIndex } from "../BaseIndex.js";
import { IndexList, IndexStructType } from "../json-to-index-struct.js";
@@ -171,7 +171,7 @@ export class SummaryIndex extends BaseIndex<IndexList> {
responseSynthesizer?: BaseSynthesizer;
preFilters?: unknown;
nodePostprocessors?: BaseNodePostprocessor[];
}): BaseQueryEngine & RetrieverQueryEngine {
}): QueryEngine & RetrieverQueryEngine {
let { retriever, responseSynthesizer } = options ?? {};
if (!retriever) {
@@ -37,7 +37,7 @@ import type {
import type { BaseIndexStore } from "../../storage/indexStore/types.js";
import { VectorStoreQueryMode } from "../../storage/vectorStore/types.js";
import type { BaseSynthesizer } from "../../synthesizers/types.js";
import type { BaseQueryEngine } from "../../types.js";
import type { QueryEngine } from "../../types.js";
import type { BaseIndexInit } from "../BaseIndex.js";
import { BaseIndex } from "../BaseIndex.js";
import { IndexDict, IndexStructType } from "../json-to-index-struct.js";
@@ -284,7 +284,7 @@ export class VectorStoreIndex extends BaseIndex<IndexDict> {
responseSynthesizer?: BaseSynthesizer;
preFilters?: MetadataFilters;
nodePostprocessors?: BaseNodePostprocessor[];
}): BaseQueryEngine & RetrieverQueryEngine {
}): QueryEngine & RetrieverQueryEngine {
const { retriever, responseSynthesizer } = options ?? {};
return new RetrieverQueryEngine(
retriever ?? this.asRetriever(),
@@ -94,20 +94,20 @@ export class IngestionPipeline {
documents?: Document[],
nodes?: BaseNode[],
): Promise<BaseNode[]> {
const inputNodes: BaseNode[] = [];
const inputNodes: BaseNode[][] = [];
if (documents) {
inputNodes.push(...documents);
inputNodes.push(documents);
}
if (nodes) {
inputNodes.push(...nodes);
inputNodes.push(nodes);
}
if (this.documents) {
inputNodes.push(...this.documents);
inputNodes.push(this.documents);
}
if (this.reader) {
inputNodes.push(...(await this.reader.loadData()));
inputNodes.push(await this.reader.loadData());
}
return inputNodes;
return inputNodes.flat();
}
async run(
+8 -2
View File
@@ -1,6 +1,7 @@
import type { ClientOptions } from "@anthropic-ai/sdk";
import { Anthropic as SDKAnthropic } from "@anthropic-ai/sdk";
import type {
MessageCreateParamsNonStreaming,
Tool,
ToolResultBlockParam,
ToolUseBlock,
@@ -264,7 +265,7 @@ export class Anthropic extends ToolCallLLM<AnthropicAdditionalChatOptions> {
const anthropic = this.session.anthropic;
if (tools) {
const response = await anthropic.beta.tools.messages.create({
const params: MessageCreateParamsNonStreaming = {
messages: this.formatMessages<true>(messages),
tools: tools.map(Anthropic.toTool),
model: this.getModelName(this.model),
@@ -272,7 +273,12 @@ export class Anthropic extends ToolCallLLM<AnthropicAdditionalChatOptions> {
max_tokens: this.maxTokens ?? 4096,
top_p: this.topP,
...(systemPrompt && { system: systemPrompt }),
});
};
// Remove tools if there are none, as it will cause an error
if (tools.length === 0) {
delete params.tools;
}
const response = await anthropic.beta.tools.messages.create(params);
const toolUseBlock = response.content.find(
(content): content is ToolUseBlock => content.type === "tool_use",
+363
View File
@@ -0,0 +1,363 @@
import {
ChatSession,
GoogleGenerativeAI,
type Content as GeminiMessageContent,
type Part,
} from "@google/generative-ai";
import { getEnv } from "@llamaindex/env";
import { ToolCallLLM } from "./base.js";
import type {
ChatMessage,
ChatResponse,
ChatResponseChunk,
CompletionResponse,
LLMChatParamsNonStreaming,
LLMChatParamsStreaming,
LLMCompletionParamsNonStreaming,
LLMCompletionParamsStreaming,
LLMMetadata,
MessageContent,
MessageContentImageDetail,
MessageContentTextDetail,
MessageType,
ToolCallLLMMessageOptions,
} from "./types.js";
import { streamConverter, wrapLLMEvent } from "./utils.js";
// Session and Model Type Definitions
type GeminiSessionOptions = {
apiKey?: string;
};
export enum GEMINI_MODEL {
GEMINI_PRO = "gemini-pro",
GEMINI_PRO_VISION = "gemini-pro-vision",
EMBEDDING_001 = "embedding-001",
AQA = "aqa",
GEMINI_PRO_LATEST = "gemini-1.5-pro-latest",
}
export interface GeminiModelInfo {
contextWindow: number;
}
export const GEMINI_MODEL_INFO_MAP: Record<GEMINI_MODEL, GeminiModelInfo> = {
[GEMINI_MODEL.GEMINI_PRO]: { contextWindow: 30720 },
[GEMINI_MODEL.GEMINI_PRO_VISION]: { contextWindow: 12288 },
[GEMINI_MODEL.EMBEDDING_001]: { contextWindow: 2048 },
[GEMINI_MODEL.AQA]: { contextWindow: 7168 },
[GEMINI_MODEL.GEMINI_PRO_LATEST]: { contextWindow: 10 ** 6 },
};
const SUPPORT_TOOL_CALL_MODELS: GEMINI_MODEL[] = [
GEMINI_MODEL.GEMINI_PRO,
GEMINI_MODEL.GEMINI_PRO_VISION,
GEMINI_MODEL.EMBEDDING_001,
GEMINI_MODEL.AQA,
];
const DEFAULT_GEMINI_PARAMS = {
model: GEMINI_MODEL.GEMINI_PRO,
temperature: 0.1,
topP: 1,
maxTokens: undefined,
};
export type GeminiConfig = Partial<typeof DEFAULT_GEMINI_PARAMS> & {
session?: GeminiSession;
};
/// Chat Type Definitions
type GeminiMessageRole = "user" | "model";
export type GeminiAdditionalChatOptions = {};
export type GeminiChatParamsStreaming = LLMChatParamsStreaming<
GeminiAdditionalChatOptions,
ToolCallLLMMessageOptions
>;
export type GeminiChatStreamResponse = AsyncIterable<
ChatResponseChunk<ToolCallLLMMessageOptions>
>;
export type GeminiChatParamsNonStreaming = LLMChatParamsNonStreaming<
GeminiAdditionalChatOptions,
ToolCallLLMMessageOptions
>;
export type GeminiChatNonStreamResponse =
ChatResponse<ToolCallLLMMessageOptions>;
/**
* Gemini Session to manage the connection to the Gemini API
*/
export class GeminiSession {
gemini: GoogleGenerativeAI;
constructor(options: GeminiSessionOptions) {
if (!options.apiKey) {
options.apiKey = getEnv("GOOGLE_API_KEY");
}
if (!options.apiKey) {
throw new Error("Set Google API Key in GOOGLE_API_KEY env variable");
}
this.gemini = new GoogleGenerativeAI(options.apiKey);
}
}
/**
* Gemini Session Store to manage the current Gemini sessions
*/
export class GeminiSessionStore {
static sessions: Array<{
session: GeminiSession;
options: GeminiSessionOptions;
}> = [];
private static sessionMatched(
o1: GeminiSessionOptions,
o2: GeminiSessionOptions,
): boolean {
return o1.apiKey === o2.apiKey;
}
static get(options: GeminiSessionOptions = {}): GeminiSession {
let session = this.sessions.find((session) =>
this.sessionMatched(session.options, options),
)?.session;
if (!session) {
session = new GeminiSession(options);
this.sessions.push({ session, options });
}
return session;
}
}
/**
* Helper class providing utility functions for Gemini
*/
class GeminiHelper {
// Gemini only has user and model roles. Put the rest in user role.
public static readonly ROLES_TO_GEMINI: Record<
MessageType,
GeminiMessageRole
> = {
user: "user",
system: "user",
assistant: "user",
memory: "user",
};
public static readonly ROLES_FROM_GEMINI: Record<
GeminiMessageRole,
MessageType
> = {
user: "user",
model: "assistant",
};
public static mergeNeighboringSameRoleMessages(
messages: ChatMessage[],
): ChatMessage[] {
// Gemini does not support multiple messages of the same role in a row, so we merge them
const mergedMessages: ChatMessage[] = [];
let i: number = 0;
while (i < messages.length) {
const currentMessage: ChatMessage = messages[i];
// Initialize merged content with current message content
const mergedContent: MessageContent[] = [currentMessage.content];
// Check if the next message exists and has the same role
while (
i + 1 < messages.length &&
this.ROLES_TO_GEMINI[messages[i + 1].role] ===
this.ROLES_TO_GEMINI[currentMessage.role]
) {
i++;
const nextMessage: ChatMessage = messages[i];
mergedContent.push(nextMessage.content);
}
// Create a new ChatMessage object with merged content
const mergedMessage: ChatMessage = {
role: currentMessage.role,
content: mergedContent.join("\n"),
};
mergedMessages.push(mergedMessage);
i++;
}
return mergedMessages;
}
public static messageContentToGeminiParts(content: MessageContent): Part[] {
if (typeof content === "string") {
return [{ text: content }];
}
const parts: Part[] = [];
const imageContents = content.filter(
(i) => i.type === "image_url",
) as MessageContentImageDetail[];
parts.push(
...imageContents.map((i) => ({
fileData: {
mimeType: i.type,
fileUri: i.image_url.url,
},
})),
);
const textContents = content.filter(
(i) => i.type === "text",
) as MessageContentTextDetail[];
parts.push(...textContents.map((t) => ({ text: t.text })));
return parts;
}
public static chatMessageToGemini(
message: ChatMessage,
): GeminiMessageContent {
return {
role: this.ROLES_TO_GEMINI[message.role],
parts: this.messageContentToGeminiParts(message.content),
};
}
}
/**
* ToolCallLLM for Gemini
*/
export class Gemini extends ToolCallLLM<GeminiAdditionalChatOptions> {
model: GEMINI_MODEL;
temperature: number;
topP: number;
maxTokens?: number;
session: GeminiSession;
constructor(init?: GeminiConfig) {
super();
this.model = init?.model ?? GEMINI_MODEL.GEMINI_PRO;
this.temperature = init?.temperature ?? 0.1;
this.topP = init?.topP ?? 1;
this.maxTokens = init?.maxTokens ?? undefined;
this.session = init?.session ?? GeminiSessionStore.get();
}
get supportToolCall(): boolean {
return SUPPORT_TOOL_CALL_MODELS.includes(this.model);
}
get metadata(): LLMMetadata {
return {
model: this.model,
temperature: this.temperature,
topP: this.topP,
maxTokens: this.maxTokens,
contextWindow: GEMINI_MODEL_INFO_MAP[this.model].contextWindow,
tokenizer: undefined,
};
}
private prepareChat(
params: GeminiChatParamsStreaming | GeminiChatParamsNonStreaming,
): {
chat: ChatSession;
messageContent: Part[];
} {
const { messages } = params;
const mergedMessages =
GeminiHelper.mergeNeighboringSameRoleMessages(messages);
const history = mergedMessages.slice(0, -1);
const nextMessage = mergedMessages[mergedMessages.length - 1];
const messageContent = GeminiHelper.chatMessageToGemini(nextMessage).parts;
const client = this.session.gemini.getGenerativeModel(this.metadata);
const chat = client.startChat({
history: history.map(GeminiHelper.chatMessageToGemini),
});
return {
chat,
messageContent,
};
}
protected async nonStreamChat(
params: GeminiChatParamsNonStreaming,
): Promise<GeminiChatNonStreamResponse> {
const { chat, messageContent } = this.prepareChat(params);
const result = await chat.sendMessage(messageContent);
const { response } = result;
const topCandidate = response.candidates![0];
return {
raw: response,
message: {
content: response.text(),
role: GeminiHelper.ROLES_FROM_GEMINI[
topCandidate.content.role as GeminiMessageRole
],
},
};
}
protected async *streamChat(
params: GeminiChatParamsStreaming,
): GeminiChatStreamResponse {
const { chat, messageContent } = this.prepareChat(params);
const result = await chat.sendMessageStream(messageContent);
return streamConverter(result.stream, (response) => {
return {
text: response.text(),
raw: response,
};
});
}
chat(params: GeminiChatParamsStreaming): Promise<GeminiChatStreamResponse>;
chat(
params: GeminiChatParamsNonStreaming,
): Promise<GeminiChatNonStreamResponse>;
@wrapLLMEvent
async chat(
params: GeminiChatParamsStreaming | GeminiChatParamsNonStreaming,
): Promise<GeminiChatStreamResponse | GeminiChatNonStreamResponse> {
if (params.stream) return this.streamChat(params);
return this.nonStreamChat(params);
}
complete(
params: LLMCompletionParamsStreaming,
): Promise<AsyncIterable<CompletionResponse>>;
complete(
params: LLMCompletionParamsNonStreaming,
): Promise<CompletionResponse>;
async complete(
params: LLMCompletionParamsStreaming | LLMCompletionParamsNonStreaming,
): Promise<CompletionResponse | AsyncIterable<CompletionResponse>> {
const { prompt, stream } = params;
const client = this.session.gemini.getGenerativeModel(this.metadata);
if (stream) {
const result = await client.generateContentStream(
GeminiHelper.messageContentToGeminiParts(prompt),
);
return streamConverter(result.stream, (response) => {
return {
text: response.text(),
raw: response,
};
});
}
const result = await client.generateContent(
GeminiHelper.messageContentToGeminiParts(prompt),
);
return {
text: result.response.text(),
raw: result.response,
};
}
}
+1
View File
@@ -10,6 +10,7 @@ export * from "./openai.js";
export { Portkey } from "./portkey.js";
export * from "./replicate_ai.js";
// Note: The type aliases for replicate are to simplify usage for Llama 2 (we're using replicate for Llama 2 support)
export { GEMINI_MODEL, Gemini } from "./gemini.js";
export {
DeuceChatStrategy,
LlamaDeuce,
+8
View File
@@ -349,6 +349,14 @@ export class OpenAI extends ToolCallLLM<OpenAIAdditionalChatOptions> {
...Object.assign({}, this.additionalChatOptions, additionalChatOptions),
};
if (
Array.isArray(baseRequestParams.tools) &&
baseRequestParams.tools.length === 0
) {
// remove empty tools array to avoid OpenAI error
delete baseRequestParams.tools;
}
// Streaming
if (stream) {
return this.streamChat(baseRequestParams);
@@ -0,0 +1,89 @@
import { getEnv } from "@llamaindex/env";
import type { NodeWithScore } from "../../Node.js";
import { MetadataMode } from "../../Node.js";
import type { BaseNodePostprocessor } from "../types.js";
interface JinaAIRerankerResult {
index: number;
document?: {
text?: string;
};
relevance_score: number;
}
export class JinaAIReranker implements BaseNodePostprocessor {
model: string = "jina-reranker-v1-base-en";
topN?: number;
apiKey?: string = undefined;
constructor(init?: Partial<JinaAIReranker>) {
this.topN = init?.topN ?? 2;
this.model = init?.model ?? "jina-reranker-v1-base-en";
this.apiKey = getEnv("JINAAI_API_KEY");
if (!this.apiKey) {
throw new Error(
"Set Jina AI API Key in JINAAI_API_KEY env variable. Get one for free or top up your key at https://jina.ai/reranker",
);
}
}
async rerank(
query: string,
documents: string[],
topN: number | undefined = this.topN,
): Promise<JinaAIRerankerResult[]> {
const url = "https://api.jina.ai/v1/rerank";
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${this.apiKey}`,
};
const data = {
model: this.model,
query: query,
documents: documents,
top_n: topN,
};
try {
const response = await fetch(url, {
method: "POST",
headers: headers,
body: JSON.stringify(data),
});
const jsonData = await response.json();
return jsonData.results;
} catch (error) {
console.error("Error while reranking:", error);
throw new Error("Failed to rerank documents due to an API error");
}
}
async postprocessNodes(
nodes: NodeWithScore[],
query?: string,
): Promise<NodeWithScore[]> {
if (nodes.length === 0) {
return [];
}
if (query === undefined) {
throw new Error("JinaAIReranker requires a query");
}
const documents = nodes.map((n) => n.node.getContent(MetadataMode.ALL));
const results = await this.rerank(query, documents, this.topN);
const newNodes: NodeWithScore[] = [];
for (const result of results) {
const node = nodes[result.index];
newNodes.push({
node: node.node,
score: result.relevance_score,
});
}
return newNodes;
}
}
@@ -1 +1,2 @@
export * from "./CohereRerank.js";
export * from "./JinaAIReranker.js";
+1 -1
View File
@@ -19,7 +19,7 @@ export class ImageReader implements FileReader {
file: string,
fs: GenericFileSystem = defaultFS,
): Promise<Document[]> {
const dataBuffer = await fs.readFile(file);
const dataBuffer = await fs.readRawFile(file);
const blob = new Blob([dataBuffer]);
return [new ImageDocument({ image: blob, id_: file })];
}
+19 -20
View File
@@ -1,21 +1,9 @@
import type { Client } from "@notionhq/client";
import type { Crawler, Pages } from "notion-md-crawler";
import type { Crawler, CrawlerOptions, Page } from "notion-md-crawler";
import { crawler, pageToString } from "notion-md-crawler";
import { Document } from "../Node.js";
import type { BaseReader } from "./type.js";
type OptionalSerializers = Parameters<Crawler>[number]["serializers"];
/**
* Options for initializing the NotionReader class
* @typedef {Object} NotionReaderOptions
* @property {Client} client - The Notion Client object for API interactions
* @property {OptionalSerializers} [serializers] - Option to customize serialization. See [the url](https://github.com/TomPenguin/notion-md-crawler/tree/main) for details.
*/
type NotionReaderOptions = {
client: Client;
serializers?: OptionalSerializers;
};
type NotionReaderOptions = Pick<CrawlerOptions, "client" | "serializers">;
/**
* Notion pages are retrieved recursively and converted to Document objects.
@@ -25,7 +13,7 @@ type NotionReaderOptions = {
* Please refer to [this document](https://www.notion.so/help/create-integrations-with-the-notion-api) for details.
*/
export class NotionReader implements BaseReader {
private crawl: ReturnType<Crawler>;
private readonly crawl: ReturnType<Crawler>;
/**
* Constructor for the NotionReader class
@@ -37,10 +25,10 @@ export class NotionReader implements BaseReader {
/**
* Converts Pages to an array of Document objects
* @param {Pages} pages - The Notion pages to convert (Return value of `loadPages`)
* @param {Page} pages - The Notion pages to convert (Return value of `loadPages`)
* @returns {Document[]} An array of Document objects
*/
toDocuments(pages: Pages): Document[] {
toDocuments(pages: Page[]): Document[] {
return Object.values(pages).map((page) => {
const text = pageToString(page);
return new Document({
@@ -54,10 +42,21 @@ export class NotionReader implements BaseReader {
/**
* Loads recursively the Notion page with the specified root page ID.
* @param {string} rootPageId - The root Notion page ID
* @returns {Promise<Pages>} A Promise that resolves to a Pages object(Convertible with the `toDocuments` method)
* @returns {Promise<Page[]>} A Promise that resolves to a Pages object(Convertible with the `toDocuments` method)
*/
async loadPages(rootPageId: string): Promise<Pages> {
return this.crawl(rootPageId);
async loadPages(rootPageId: string): Promise<Page[]> {
const iter = this.crawl(rootPageId);
const pages: Page[] = [];
for await (const result of iter) {
if (result.success) {
pages.push(result.page);
} else {
console.error(
`Failed to load page (${result.failure.parentId}): ${result.failure.reason}`,
);
}
}
return pages;
}
/**
@@ -1,6 +1,6 @@
import type { CompleteFileSystem } from "@llamaindex/env";
import { defaultFS, path } from "@llamaindex/env";
import { Document } from "../Node.js";
import { Document, type Metadata } from "../Node.js";
import { walk } from "../storage/FileSystem.js";
import { TextFileReader } from "./TextFileReader.js";
import type { BaseReader } from "./type.js";
@@ -89,6 +89,7 @@ export class SimpleDirectoryReader implements BaseReader {
}
const fileDocs = await reader.loadData(filePath, fs);
fileDocs.forEach(addMetaData(filePath));
// Observer can still cancel addition of the resulting docs from this file
if (this.doObserverCheck("file", filePath, ReaderStatus.COMPLETE)) {
@@ -124,3 +125,10 @@ export class SimpleDirectoryReader implements BaseReader {
return true;
}
}
function addMetaData(filePath: string): (doc: Document<Metadata>) => void {
return (doc: Document<Metadata>) => {
doc.metadata["file_path"] = path.resolve(filePath);
doc.metadata["file_name"] = path.basename(filePath);
};
}
+3 -3
View File
@@ -1,5 +1,5 @@
import type { JSONSchemaType } from "ajv";
import type { BaseQueryEngine, BaseTool, ToolMetadata } from "../types.js";
import type { BaseTool, QueryEngine, ToolMetadata } from "../types.js";
const DEFAULT_NAME = "query_engine_tool";
const DEFAULT_DESCRIPTION =
@@ -17,7 +17,7 @@ const DEFAULT_PARAMETERS: JSONSchemaType<QueryEngineParam> = {
};
export type QueryEngineToolParams = {
queryEngine: BaseQueryEngine;
queryEngine: QueryEngine;
metadata: ToolMetadata<JSONSchemaType<QueryEngineParam>>;
};
@@ -26,7 +26,7 @@ export type QueryEngineParam = {
};
export class QueryEngineTool implements BaseTool<QueryEngineParam> {
private queryEngine: BaseQueryEngine;
private queryEngine: QueryEngine;
metadata: ToolMetadata<JSONSchemaType<QueryEngineParam>>;
constructor({ queryEngine, metadata }: QueryEngineToolParams) {
+1 -1
View File
@@ -22,7 +22,7 @@ export interface QueryEngineParamsNonStreaming extends QueryEngineParamsBase {
/**
* A query engine is a question answerer that can use one or more steps.
*/
export interface BaseQueryEngine {
export interface QueryEngine {
/**
* Query the query engine and get a response.
* @param params
+13 -1
View File
@@ -1,4 +1,4 @@
import { Document } from "llamaindex/Node";
import { Document, ImageDocument } from "llamaindex/Node";
import { describe, expect, test } from "vitest";
describe("Document", () => {
@@ -6,4 +6,16 @@ describe("Document", () => {
const doc = new Document({ text: "text", id_: "docId" });
expect(doc).toBeDefined();
});
test("should generate different hash for different image contents", () => {
const imageNode1 = new ImageDocument({
id_: "image",
image: "data:image/png;base64,sample_image_content1",
});
const imageNode2 = new ImageDocument({
id_: "image",
image: "data:image/png;base64,sample_image_content2",
});
expect(imageNode1.hash).not.toBe(imageNode2.hash);
});
});
@@ -0,0 +1,22 @@
import { Document } from "llamaindex/Node";
import { IngestionPipeline } from "llamaindex/ingestion/IngestionPipeline";
import { test } from "vitest";
// Refs: https://github.com/run-llama/LlamaIndexTS/pull/760
test("load large data should not cause RangeError #760", async () => {
const pipeline = new IngestionPipeline({
reader: {
loadData: async () => {
return Array.from(
{ length: 1e6 },
(_, i) =>
new Document({
id_: `${i}`,
text: "some text",
}),
);
},
},
});
await pipeline.prepareInput();
});
@@ -1,5 +1,20 @@
# test-edge-runtime
## 0.1.4
### Patch Changes
- Updated dependencies [6277105]
- @llamaindex/edge@0.2.13
## 0.1.3
### Patch Changes
- Updated dependencies [87142b2]
- Updated dependencies [87142b2]
- @llamaindex/edge@0.2.11
## 0.1.2
### Patch Changes
@@ -1,6 +1,6 @@
{
"name": "test-edge-runtime",
"version": "0.1.2",
"version": "0.1.4",
"private": true,
"scripts": {
"dev": "next dev",
@@ -14,7 +14,7 @@
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20",
"@types/node": "^20.12.7",
"@types/react": "^18",
"@types/react-dom": "^18",
"typescript": "^5"
+15 -3
View File
@@ -1,17 +1,17 @@
{
"name": "@llamaindex/edge",
"version": "0.2.10",
"version": "0.2.13",
"license": "MIT",
"type": "module",
"dependencies": {
"@anthropic-ai/sdk": "^0.20.6",
"@aws-crypto/sha256-js": "^5.2.0",
"@datastax/astra-db-ts": "^1.0.1",
"@google/generative-ai": "^0.8.0",
"@grpc/grpc-js": "^1.10.6",
"@llamaindex/cloud": "0.0.5",
"@llamaindex/env": "workspace:*",
"@mistralai/mistralai": "^0.1.3",
"@notionhq/client": "^2.2.15",
"@pinecone-database/pinecone": "^2.2.0",
"@qdrant/js-client-rest": "^1.8.2",
"@types/lodash": "^4.17.0",
@@ -30,7 +30,7 @@
"mammoth": "^1.7.1",
"md-utils-ts": "^2.0.0",
"mongodb": "^6.5.0",
"notion-md-crawler": "^0.0.2",
"notion-md-crawler": "^1.0.0",
"ollama": "^0.5.0",
"openai": "^4.38.0",
"papaparse": "^5.4.1",
@@ -82,5 +82,17 @@
"update:deps": "node scripts/update-deps.js",
"build:core": "pnpm --filter llamaindex build && cp -r ../core/dist . && rm -rf dist/cjs",
"build": "pnpm run update:deps && pnpm run build:core && pnpm copy"
},
"devDependencies": {
"@notionhq/client": "^2.2.15",
"@swc/cli": "^0.3.12",
"@swc/core": "^1.4.16",
"concurrently": "^8.2.2",
"glob": "^10.3.12",
"madge": "^7.0.0",
"typescript": "^5.4.5"
},
"peerDependencies": {
"@notionhq/client": "^2.2.15"
}
}
+2
View File
@@ -10,6 +10,8 @@ const edgePackagePath = path.join(process.cwd(), "package.json");
const edgePackage = readJson(edgePackagePath);
const corePackage = readJson(corePackagePath);
edgePackage.dependencies = corePackage.dependencies;
edgePackage.devDependencies = corePackage.devDependencies;
edgePackage.peerDependencies = corePackage.peerDependencies;
edgePackage.version = corePackage.version;
writeJson(edgePackagePath, edgePackage);
execSync("pnpm install --lockfile-only", { stdio: "inherit" });
+23
View File
@@ -1,5 +1,28 @@
# @llamaindex/experimental
## 0.0.16
### Patch Changes
- Updated dependencies [6277105]
- llamaindex@0.2.13
## 0.0.15
### Patch Changes
- Updated dependencies [d8d952d]
- llamaindex@0.2.12
## 0.0.14
### Patch Changes
- Updated dependencies [87142b2]
- Updated dependencies [5a6cc0e]
- Updated dependencies [87142b2]
- llamaindex@0.2.11
## 0.0.13
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@llamaindex/experimental",
"description": "Experimental package for LlamaIndexTS",
"version": "0.0.13",
"version": "0.0.16",
"type": "module",
"types": "dist/type/index.d.ts",
"main": "dist/cjs/index.js",
@@ -5,7 +5,7 @@ import { Response } from "llamaindex";
import { serviceContextFromDefaults, type ServiceContext } from "llamaindex";
import type {
BaseQueryEngine,
QueryEngine,
QueryEngineParamsNonStreaming,
QueryEngineParamsStreaming,
} from "llamaindex";
@@ -89,7 +89,7 @@ type OutputProcessor = typeof defaultOutputProcessor;
/**
* A JSON query engine that uses JSONPath to query a JSON object.
*/
export class JSONQueryEngine implements BaseQueryEngine {
export class JSONQueryEngine implements QueryEngine {
jsonValue: JSONSchemaType;
jsonSchema: JSONSchemaType;
serviceContext: ServiceContext;
+1030 -1110
View File
File diff suppressed because it is too large Load Diff