mirror of
https://github.com/run-llama/LlamaIndexTS.git
synced 2026-07-07 00:31:11 -04:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f322c5d202 | |||
| 376d29a78f | |||
| 224d507ab5 | |||
| 1f680d731d | |||
| f0a1cc51b4 | |||
| fee3280799 | |||
| 54925bf1ae | |||
| 91d02a4fc0 | |||
| 086b94038e | |||
| 5d5716b339 | |||
| fb6db454d4 | |||
| e4d4e0d024 | |||
| 17724d961e |
+7
-7
@@ -4,11 +4,11 @@
|
||||
|
||||
This is a monorepo built with Turborepo
|
||||
|
||||
Right now there are two packages of importance:
|
||||
Right now, for first-time contributors, these three packages are of the highest importance:
|
||||
|
||||
packages/llamaindex which is the main NPM library llamaindex
|
||||
|
||||
examples is where the demo code lives
|
||||
- `packages/llamaindex` which is the main NPM library `llamaindex`
|
||||
- `examples` is where the demo code lives
|
||||
- `apps/docs` is where the code for the documentation of https://ts.llamaindex.ai/ is located
|
||||
|
||||
### Turborepo docs
|
||||
|
||||
@@ -43,11 +43,11 @@ pnpm run test
|
||||
|
||||
To write new test cases write them in [packages/llamaindex/tests](/packages/llamaindex/tests)
|
||||
|
||||
We use Jest https://jestjs.io/ to write our test cases. Jest comes with a bunch of built in assertions using the expect function: https://jestjs.io/docs/expect
|
||||
We use Vitest https://vitest.dev to write our test cases. Vitest comes with a bunch of built-in assertions using the expect function: https://vitest.dev/api/expect.html#expect
|
||||
|
||||
### Demo applications
|
||||
|
||||
There is an existing ["example"](/examples/README.md) demos folder with mainly NodeJS scripts. Feel free to add additional demos to that folder. If you would like to try out your changes in the core package with a new demo, you need to run the build command in the README.
|
||||
There is an existing ["example"](/examples/README.md) demos folder with mainly NodeJS scripts. Feel free to add additional demos to that folder. If you would like to try out your changes in the `llamaindex` package with a new demo, you need to run the build command in the README.
|
||||
|
||||
You can create new demo applications in the apps folder. Just run pnpm init in the folder after you create it to create its own package.json
|
||||
|
||||
@@ -81,7 +81,7 @@ Any changes you make should be reflected in the browser. If you need to regenera
|
||||
|
||||
## Changeset
|
||||
|
||||
We use [changesets](https://github.com/changesets/changesets) for managing versions and changelogs. To create a new changeset, run:
|
||||
We use [changesets](https://github.com/changesets/changesets) for managing versions and changelogs. To create a new changeset, run in the root folder:
|
||||
|
||||
```
|
||||
pnpm changeset
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"llamaindex": minor
|
||||
"docs": minor
|
||||
---
|
||||
|
||||
Add deepseek llm class
|
||||
@@ -1,5 +1,24 @@
|
||||
# docs
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 376d29a: feat: added tool calling and agent support for llama3.1 504B
|
||||
- llamaindex@0.5.11
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 086b940: feat: add DeepSeek LLM
|
||||
- 5d5716b: feat: add a reader for JSON data
|
||||
- Updated dependencies [086b940]
|
||||
- Updated dependencies [5d5716b]
|
||||
- Updated dependencies [91d02a4]
|
||||
- Updated dependencies [fb6db45]
|
||||
- llamaindex@0.5.10
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -15,6 +15,7 @@ LlamaIndex.TS comes with a few built-in agents, but you can also create your own
|
||||
- Anthropic Agent both via Anthropic and Bedrock (in `@llamaIndex/community`)
|
||||
- Gemini Agent
|
||||
- ReACT Agent
|
||||
- Meta3.1 504B via Bedrock (in `@llamaIndex/community`)
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ Copy the URL in your browser and select the server you want your bot to join.
|
||||
#### DiscordReader()
|
||||
|
||||
- `discordToken?`: The Discord bot token.
|
||||
- `makeRequest?`: Optionally provide a custom request function for edge environments, e.g. `fetch`. See discord.js for more info.
|
||||
- `requestHandler?`: Optionally provide a custom request function for edge environments, e.g. `fetch`. See discord.js for more info.
|
||||
|
||||
#### DiscordReader.loadData
|
||||
|
||||
|
||||
@@ -16,7 +16,15 @@ It is a simple reader that reads all files from a directory and its subdirectori
|
||||
|
||||
<CodeBlock language="ts">{CodeSource}</CodeBlock>
|
||||
|
||||
Currently, it supports reading `.txt`, `.pdf`, `.csv`, `.md`, `.docx`, `.htm`, `.html`, `.jpg`, `.jpeg`, `.png` and `.gif` files, but support for other file types is planned.
|
||||
Currently, the following readers are mapped to specific file types:
|
||||
|
||||
- [TextFileReader](../../api/classes/TextFileReader.md): `.txt`
|
||||
- [PDFReader](../../api/classes/PDFReader.md): `.pdf`
|
||||
- [PapaCSVReader](../../api/classes/PapaCSVReader.md): `.csv`
|
||||
- [MarkdownReader](../../api/classes/MarkdownReader.md): `.md`
|
||||
- [DocxReader](../../api/classes/DocxReader.md): `.docx`
|
||||
- [HTMLReader](../../api/classes/HTMLReader.md): `.htm`, `.html`
|
||||
- [ImageReader](../../api/classes/ImageReader.md): `.jpg`, `.jpeg`, `.png`, `.gif`
|
||||
|
||||
You can modify the reader three different ways:
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
# JSONReader
|
||||
|
||||
A simple JSON data loader with various options.
|
||||
Either parses the entire string, cleaning it and treat each line as an embedding or performs a recursive depth-first traversal yielding JSON paths.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { JSONReader } from "llamaindex";
|
||||
|
||||
const file = "../../PATH/TO/FILE";
|
||||
const content = new TextEncoder().encode("JSON_CONTENT");
|
||||
|
||||
const reader = new JSONReader({ levelsBack: 0, collapseLength: 100 });
|
||||
const docsFromFile = reader.loadData(file);
|
||||
const docsFromContent = reader.loadDataAsContent(content);
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
Basic:
|
||||
|
||||
- `ensureAscii?`: Wether to ensure only ASCII characters be present in the output by converting non-ASCII characters to their unicode escape sequence. Default is `false`.
|
||||
|
||||
- `isJsonLines?`: Wether the JSON is in JSON Lines format. If true, will split into lines, remove empty one and parse each line as JSON. Default is `false`
|
||||
|
||||
- `cleanJson?`: Whether to clean the JSON by filtering out structural characters (`{}, [], and ,`). If set to false, it will just parse the JSON, not removing structural characters. Default is `true`.
|
||||
|
||||
Depth-First-Traversal:
|
||||
|
||||
- `levelsBack?`: Specifies how many levels up the JSON structure to include in the output. `cleanJson` will be ignored. If set to 0, all levels are included. If undefined, parses the entire JSON, treat each line as an embedding and create a document per top-level array. Default is `undefined`
|
||||
|
||||
- `collapseLength?`: The maximum length of JSON string representation to be collapsed into a single line. Only applicable when `levelsBack` is set. Default is `undefined`
|
||||
|
||||
#### Examples
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
Input:
|
||||
|
||||
```json
|
||||
{"a": {"1": {"key1": "value1"}, "2": {"key2": "value2"}}, "b": {"3": {"k3": "v3"}, "4": {"k4": "v4"}}}
|
||||
```
|
||||
|
||||
Default options:
|
||||
|
||||
`LevelsBack` = `undefined` & `cleanJson` = `true`
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
"a": {
|
||||
"1": {
|
||||
"key1": "value1"
|
||||
"2": {
|
||||
"key2": "value2"
|
||||
"b": {
|
||||
"3": {
|
||||
"k3": "v3"
|
||||
"4": {
|
||||
"k4": "v4"
|
||||
```
|
||||
|
||||
Depth-First Traversal all levels:
|
||||
|
||||
`levelsBack` = `0`
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
a 1 key1 value1
|
||||
a 2 key2 value2
|
||||
b 3 k3 v3
|
||||
b 4 k4 v4
|
||||
```
|
||||
|
||||
Depth-First Traversal and Collapse:
|
||||
|
||||
`levelsBack` = `0` & `collapseLength` = `35`
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
a 1 {"key1":"value1"}
|
||||
a 2 {"key2":"value2"}
|
||||
b {"3":{"k3":"v3"},"4":{"k4":"v4"}}
|
||||
```
|
||||
|
||||
Depth-First Traversal limited levels:
|
||||
|
||||
`levelsBack` = `2`
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
1 key1 value1
|
||||
2 key2 value2
|
||||
3 k3 v3
|
||||
4 k4 v4
|
||||
```
|
||||
|
||||
Uncleaned JSON:
|
||||
|
||||
`levelsBack` = `undefined` & `cleanJson` = `false`
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
{"a":{"1":{"key1":"value1"},"2":{"key2":"value2"}},"b":{"3":{"k3":"v3"},"4":{"k4":"v4"}}}
|
||||
```
|
||||
|
||||
ASCII-Conversion:
|
||||
|
||||
Input:
|
||||
|
||||
```json
|
||||
{ "message": "こんにちは世界" }
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
"message": "\u3053\u3093\u306b\u3061\u306f\u4e16\u754c"
|
||||
```
|
||||
|
||||
JSON Lines Format:
|
||||
|
||||
Input:
|
||||
|
||||
```json
|
||||
{"tweet": "Hello world"}\n{"tweet": "こんにちは世界"}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
"tweet": "Hello world"
|
||||
|
||||
"tweet": "こんにちは世界"
|
||||
```
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## API Reference
|
||||
|
||||
- [JSONReader](../../api/classes/JSONReader.md)
|
||||
@@ -41,7 +41,9 @@ They can be divided into two groups.
|
||||
- `doNotCache?` Optional. Set to true to not cache the document.
|
||||
- `fastMode?` Optional. Set to true to use the fast mode. This mode will skip OCR of images, and table/heading reconstruction. Note: Non-compatible with `gpt4oMode`.
|
||||
- `doNotUnrollColumns?` Optional. Set to true to keep the text according to document layout. Reduce reconstruction accuracy, and LLMs/embeddings performances in most cases.
|
||||
- `pageSeparator?` Optional. The page separator to use. Defaults is `\\n---\\n`.
|
||||
- `pageSeparator?` Optional. A templated page separator to use to split the text. If the results contain `{page_number}` (e.g. JSON mode), it will be replaced by the next page number. If not set the default separator `\\n---\\n` will be used.
|
||||
- `pagePrefix?` Optional. A templated prefix to add to the beginning of each page. If the results contain `{page_number}`, it will be replaced by the page number.
|
||||
- `pageSuffix?` Optional. A templated suffix to add to the end of each page. If the results contain `{page_number}`, it will be replaced by the page number.
|
||||
- `gpt4oMode` Deprecated. Use vendorMultimodal params. Set to true to use GPT-4o to extract content. Default is `false`.
|
||||
- `gpt4oApiKey?` Deprecated. Use vendorMultimodal params. Optional. Set the GPT-4o API key. Lowers the cost of parsing by using your own API key. Your OpenAI account will be charged. Can also be set in the environment variable `LLAMA_CLOUD_GPT4O_API_KEY`.
|
||||
- `boundingBox?` Optional. Specify an area of the document to parse. Expects the bounding box margins as a string in clockwise order, e.g. `boundingBox = "0.1,0,0,0"` to not parse the top 10% of the document.
|
||||
|
||||
@@ -31,7 +31,7 @@ META_LLAMA3_8B_INSTRUCT = "meta.llama3-8b-instruct-v1:0";
|
||||
META_LLAMA3_70B_INSTRUCT = "meta.llama3-70b-instruct-v1:0";
|
||||
META_LLAMA3_1_8B_INSTRUCT = "meta.llama3-1-8b-instruct-v1:0"; // available on us-west-2
|
||||
META_LLAMA3_1_70B_INSTRUCT = "meta.llama3-1-70b-instruct-v1:0"; // available on us-west-2
|
||||
META_LLAMA3_1_405B_INSTRUCT = "meta.llama3-1-405b-instruct-v1:0"; // preview only, available on us-west-2
|
||||
META_LLAMA3_1_405B_INSTRUCT = "meta.llama3-1-405b-instruct-v1:0"; // preview only, available on us-west-2, tool calling supported
|
||||
```
|
||||
|
||||
Sonnet, Haiku and Opus are multimodal, image_url only supports base64 data url format, e.g. `data:image/jpeg;base64,SGVsbG8sIFdvcmxkIQ==`
|
||||
@@ -67,3 +67,72 @@ async function main() {
|
||||
console.log(response.response);
|
||||
}
|
||||
```
|
||||
|
||||
## Agent Example
|
||||
|
||||
```ts
|
||||
import { BEDROCK_MODELS, Bedrock } from "@llamaindex/community";
|
||||
import { FunctionTool, LLMAgent } from "llamaindex";
|
||||
|
||||
const sumNumbers = FunctionTool.from(
|
||||
({ a, b }: { a: number; b: number }) => `${a + b}`,
|
||||
{
|
||||
name: "sumNumbers",
|
||||
description: "Use this function to sum two numbers",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "The first number",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "The second number",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const divideNumbers = FunctionTool.from(
|
||||
({ a, b }: { a: number; b: number }) => `${a / b}`,
|
||||
{
|
||||
name: "divideNumbers",
|
||||
description: "Use this function to divide two numbers",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "The dividend a to divide",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "The divisor b to divide by",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const bedrock = new Bedrock({
|
||||
model: BEDROCK_MODELS.META_LLAMA3_1_405B_INSTRUCT,
|
||||
...
|
||||
});
|
||||
|
||||
async function main() {
|
||||
const agent = new LLMAgent({
|
||||
llm: bedrock,
|
||||
tools: [sumNumbers, divideNumbers],
|
||||
});
|
||||
|
||||
const response = await agent.chat({
|
||||
message: "How much is 5 + 5? then divide by 2",
|
||||
});
|
||||
|
||||
console.log(response.message);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# DeepSeek LLM
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { DeepSeekLLM, Settings } from "llamaindex";
|
||||
|
||||
Settings.llm = new DeepSeekLLM({
|
||||
apiKey: "<YOUR_API_KEY>",
|
||||
model: "deepseek-coder", // or "deepseek-chat"
|
||||
});
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import { DeepSeekLLM, Document, VectorStoreIndex, Settings } from "llamaindex";
|
||||
|
||||
const deepseekLlm = new DeepSeekLLM({
|
||||
apiKey: "<YOUR_API_KEY>",
|
||||
model: "deepseek-coder", // or "deepseek-chat"
|
||||
});
|
||||
|
||||
async function main() {
|
||||
const response = await llm.deepseekLlm.chat({
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: "You are an AI assistant",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: "Tell me about San Francisco",
|
||||
},
|
||||
],
|
||||
stream: false,
|
||||
});
|
||||
console.log(response);
|
||||
}
|
||||
```
|
||||
|
||||
# Limitations
|
||||
|
||||
Currently does not support function calling.
|
||||
|
||||
[Currently does not support json-output param while still is very good at json generating.](https://platform.deepseek.com/api-docs/faq#does-your-api-support-json-output)
|
||||
|
||||
## API platform
|
||||
|
||||
- [DeepSeek platform](https://platform.deepseek.com/)
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"version": "0.0.50",
|
||||
"version": "0.0.52",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { JSONReader } from "llamaindex";
|
||||
|
||||
async function main() {
|
||||
// Data
|
||||
const file = "../data/tinytweets.json";
|
||||
const nonAsciiContent = '{"message": "こんにちは世界"}';
|
||||
const jsonlContent = '{"tweet": "Hello world"}\n{"tweet": "こんにちは世界"}';
|
||||
|
||||
// Convert strings to Uint8Array for loadDataAsContent
|
||||
const nonAsciiBuffer = new TextEncoder().encode(nonAsciiContent);
|
||||
const jsonlBuffer = new TextEncoder().encode(jsonlContent);
|
||||
|
||||
// Default settings
|
||||
const reader1 = new JSONReader();
|
||||
const docs1 = await reader1.loadData(file);
|
||||
console.log(docs1[0]);
|
||||
|
||||
// Unclean JSON
|
||||
const reader2 = new JSONReader({ cleanJson: false });
|
||||
const docs2 = await reader2.loadData(file);
|
||||
console.log(docs2[0]);
|
||||
|
||||
// Depth first yield of JSON structural paths, going back 2 levels
|
||||
const reader3 = new JSONReader({ levelsBack: 2 });
|
||||
const docs3 = await reader3.loadData(file);
|
||||
console.log(docs3[0]);
|
||||
|
||||
// Depth first yield of all levels
|
||||
const reader4 = new JSONReader({ levelsBack: 0 });
|
||||
const docs4 = await reader4.loadData(file);
|
||||
console.log(docs4[0]);
|
||||
|
||||
// Depth first yield of all levels, collapse structural paths below length 100
|
||||
const reader5 = new JSONReader({ levelsBack: 0, collapseLength: 100 });
|
||||
const docs5 = await reader5.loadData(file);
|
||||
console.log(docs5[0]);
|
||||
|
||||
// Convert ASCII to unichode escape sequences
|
||||
const reader6 = new JSONReader({ ensureAscii: true });
|
||||
const docs6 = await reader6.loadDataAsContent(nonAsciiBuffer);
|
||||
console.log(docs6[0]);
|
||||
|
||||
// JSON Lines Format
|
||||
const reader7 = new JSONReader({ isJsonLines: true });
|
||||
const docs7 = await reader7.loadDataAsContent(jsonlBuffer);
|
||||
console.log(docs7[0]);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -1,5 +1,23 @@
|
||||
# @llamaindex/autotool-02-next-example
|
||||
|
||||
## 0.1.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- llamaindex@0.5.11
|
||||
- @llamaindex/autotool@2.0.0
|
||||
|
||||
## 0.1.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [086b940]
|
||||
- Updated dependencies [5d5716b]
|
||||
- Updated dependencies [91d02a4]
|
||||
- Updated dependencies [fb6db45]
|
||||
- llamaindex@0.5.10
|
||||
- @llamaindex/autotool@2.0.0
|
||||
|
||||
## 0.1.34
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@llamaindex/autotool-02-next-example",
|
||||
"private": true,
|
||||
"version": "0.1.34",
|
||||
"version": "0.1.36",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"unplugin": "^1.10.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"llamaindex": "^0.5.9",
|
||||
"llamaindex": "^0.5.11",
|
||||
"openai": "^4",
|
||||
"typescript": "^4"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @llamaindex/cloud
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1f680d7: chore: bump llamacloud api
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
+835
-117
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@llamaindex/cloud",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @llamaindex/community
|
||||
|
||||
## 0.0.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 224d507: fix: prevent tool calling getting mixed with conversation
|
||||
- 376d29a: feat: added tool calling and agent support for llama3.1 504B
|
||||
|
||||
## 0.0.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [91d02a4]
|
||||
- @llamaindex/core@0.1.5
|
||||
|
||||
## 0.0.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
- Bedrock support for the Anthropic Claude Models [usage](https://ts.llamaindex.ai/modules/llms/available_llms/bedrock)
|
||||
- Bedrock support for the Meta LLama 2, 3 and 3.1 Models [usage](https://ts.llamaindex.ai/modules/llms/available_llms/bedrock)
|
||||
- Meta LLama3.1 405b tool call support
|
||||
|
||||
## LICENSE
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@llamaindex/community",
|
||||
"description": "Community package for LlamaIndexTS",
|
||||
"version": "0.0.24",
|
||||
"version": "0.0.26",
|
||||
"type": "module",
|
||||
"types": "dist/type/index.d.ts",
|
||||
"main": "dist/cjs/index.js",
|
||||
@@ -19,11 +19,11 @@
|
||||
"./llm/bedrock": {
|
||||
"import": {
|
||||
"types": "./dist/type/llm/bedrock.d.ts",
|
||||
"default": "./dist/llm/bedrock/base.js"
|
||||
"default": "./dist/llm/bedrock/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/type/llm/bedrock.d.ts",
|
||||
"default": "./dist/llm/bedrock/base.cjs"
|
||||
"default": "./dist/llm/bedrock/index.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -47,6 +47,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.613.0",
|
||||
"@llamaindex/core": "workspace:*"
|
||||
"@llamaindex/core": "workspace:*",
|
||||
"@llamaindex/env": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ export {
|
||||
BEDROCK_MODELS,
|
||||
BEDROCK_MODEL_MAX_TOKENS,
|
||||
Bedrock,
|
||||
} from "./llm/bedrock/base.js";
|
||||
} from "./llm/bedrock/index.js";
|
||||
|
||||
+6
-5
@@ -16,17 +16,18 @@ import {
|
||||
type BedrockChatStreamResponse,
|
||||
Provider,
|
||||
} from "../provider";
|
||||
import { toUtf8 } from "../utils";
|
||||
import type {
|
||||
AnthropicNoneStreamingResponse,
|
||||
AnthropicStreamEvent,
|
||||
AnthropicTextContent,
|
||||
ToolBlock,
|
||||
} from "../types";
|
||||
} from "./types";
|
||||
|
||||
import {
|
||||
mapBaseToolsToAnthropicTools,
|
||||
mapChatMessagesToAnthropicMessages,
|
||||
toUtf8,
|
||||
} from "../utils";
|
||||
} from "./utils";
|
||||
|
||||
export class AnthropicProvider extends Provider<AnthropicStreamEvent> {
|
||||
getResultFromResponse(
|
||||
@@ -69,6 +70,7 @@ export class AnthropicProvider extends Provider<AnthropicStreamEvent> {
|
||||
let tool: ToolBlock | undefined = undefined;
|
||||
// #TODO this should be broken down into a separate consumer
|
||||
for await (const response of stream) {
|
||||
const delta = this.getTextFromStreamResponse(response);
|
||||
const event = this.getStreamingEventResponse(response);
|
||||
if (
|
||||
event?.type === "content_block_start" &&
|
||||
@@ -114,11 +116,10 @@ export class AnthropicProvider extends Provider<AnthropicStreamEvent> {
|
||||
};
|
||||
}
|
||||
}
|
||||
const delta = this.getTextFromStreamResponse(response);
|
||||
if (!delta && !options) continue;
|
||||
|
||||
yield {
|
||||
delta,
|
||||
delta: options ? "" : delta,
|
||||
options,
|
||||
raw: response,
|
||||
};
|
||||
@@ -0,0 +1,142 @@
|
||||
import type { ToolMetadata } from "@llamaindex/core/llms";
|
||||
import type { InvocationMetrics } from "../types";
|
||||
|
||||
type Usage = {
|
||||
input_tokens: number;
|
||||
output_tokens: number;
|
||||
};
|
||||
|
||||
type Message = {
|
||||
id: string;
|
||||
type: string;
|
||||
role: string;
|
||||
content: string[];
|
||||
model: string;
|
||||
stop_reason: string | null;
|
||||
stop_sequence: string | null;
|
||||
usage: Usage;
|
||||
};
|
||||
|
||||
export type ToolBlock = {
|
||||
id: string;
|
||||
input: unknown;
|
||||
name: string;
|
||||
type: "tool_use";
|
||||
};
|
||||
|
||||
export type TextBlock = {
|
||||
type: "text";
|
||||
text: string;
|
||||
};
|
||||
|
||||
type ContentBlockStart = {
|
||||
type: "content_block_start";
|
||||
index: number;
|
||||
content_block: ToolBlock | TextBlock;
|
||||
};
|
||||
|
||||
type Delta =
|
||||
| {
|
||||
type: "text_delta";
|
||||
text: string;
|
||||
}
|
||||
| {
|
||||
type: "input_json_delta";
|
||||
partial_json: string;
|
||||
};
|
||||
|
||||
type ContentBlockDelta = {
|
||||
type: "content_block_delta";
|
||||
index: number;
|
||||
delta: Delta;
|
||||
};
|
||||
|
||||
type ContentBlockStop = {
|
||||
type: "content_block_stop";
|
||||
index: number;
|
||||
};
|
||||
|
||||
type MessageDelta = {
|
||||
type: "message_delta";
|
||||
delta: {
|
||||
stop_reason: string;
|
||||
stop_sequence: string | null;
|
||||
};
|
||||
usage: Usage;
|
||||
};
|
||||
|
||||
export type MessageStop = {
|
||||
type: "message_stop";
|
||||
"amazon-bedrock-invocationMetrics": InvocationMetrics;
|
||||
};
|
||||
|
||||
export type AnthropicStreamEvent =
|
||||
| { type: "message_start"; message: Message }
|
||||
| ContentBlockStart
|
||||
| ContentBlockDelta
|
||||
| ContentBlockStop
|
||||
| MessageDelta
|
||||
| MessageStop;
|
||||
|
||||
export type AnthropicContent =
|
||||
| AnthropicTextContent
|
||||
| AnthropicImageContent
|
||||
| AnthropicToolContent
|
||||
| AnthropicToolResultContent;
|
||||
|
||||
export type AnthropicTextContent = {
|
||||
type: "text";
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type AnthropicToolContent = {
|
||||
type: "tool_use";
|
||||
id: string;
|
||||
name: string;
|
||||
input: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type AnthropicToolResultContent = {
|
||||
type: "tool_result";
|
||||
tool_use_id: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type AnthropicMediaTypes =
|
||||
| "image/jpeg"
|
||||
| "image/png"
|
||||
| "image/webp"
|
||||
| "image/gif";
|
||||
|
||||
export type AnthropicImageSource = {
|
||||
type: "base64";
|
||||
media_type: AnthropicMediaTypes;
|
||||
data: string; // base64 encoded image bytes
|
||||
};
|
||||
|
||||
export type AnthropicImageContent = {
|
||||
type: "image";
|
||||
source: AnthropicImageSource;
|
||||
};
|
||||
|
||||
export type AnthropicMessage = {
|
||||
role: "user" | "assistant";
|
||||
content: AnthropicContent[];
|
||||
};
|
||||
|
||||
export type AnthropicNoneStreamingResponse = {
|
||||
id: string;
|
||||
type: "message";
|
||||
role: "assistant";
|
||||
content: AnthropicContent[];
|
||||
model: string;
|
||||
stop_reason: "end_turn" | "max_tokens" | "stop_sequence";
|
||||
stop_sequence?: string;
|
||||
usage: { input_tokens: number; output_tokens: number };
|
||||
};
|
||||
|
||||
export type AnthropicTool = {
|
||||
name: string;
|
||||
description: string;
|
||||
input_schema: ToolMetadata["parameters"];
|
||||
};
|
||||
@@ -0,0 +1,186 @@
|
||||
import type { JSONObject } from "@llamaindex/core/global";
|
||||
import type {
|
||||
BaseTool,
|
||||
ChatMessage,
|
||||
MessageContent,
|
||||
MessageContentDetail,
|
||||
ToolCallLLMMessageOptions,
|
||||
} from "@llamaindex/core/llms";
|
||||
import { mapMessageContentToMessageContentDetails } from "../utils";
|
||||
import type {
|
||||
AnthropicContent,
|
||||
AnthropicImageContent,
|
||||
AnthropicMediaTypes,
|
||||
AnthropicMessage,
|
||||
AnthropicTextContent,
|
||||
AnthropicTool,
|
||||
} from "./types.js";
|
||||
|
||||
const ACCEPTED_IMAGE_MIME_TYPES = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/gif",
|
||||
];
|
||||
|
||||
export const mergeNeighboringSameRoleMessages = (
|
||||
messages: AnthropicMessage[],
|
||||
): AnthropicMessage[] => {
|
||||
return messages.reduce(
|
||||
(result: AnthropicMessage[], current: AnthropicMessage, index: number) => {
|
||||
if (index > 0 && messages[index - 1].role === current.role) {
|
||||
result[result.length - 1].content = [
|
||||
...result[result.length - 1].content,
|
||||
...current.content,
|
||||
];
|
||||
} else {
|
||||
result.push(current);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
export const mapMessageContentDetailToAnthropicContent = <
|
||||
T extends MessageContentDetail,
|
||||
>(
|
||||
detail: T,
|
||||
): AnthropicContent => {
|
||||
let content: AnthropicContent;
|
||||
|
||||
if (detail.type === "text") {
|
||||
content = mapTextContent(detail.text);
|
||||
} else if (detail.type === "image_url") {
|
||||
content = mapImageContent(detail.image_url.url);
|
||||
} else {
|
||||
throw new Error("Unsupported content detail type");
|
||||
}
|
||||
return content;
|
||||
};
|
||||
|
||||
export const mapMessageContentToAnthropicContent = <T extends MessageContent>(
|
||||
content: T,
|
||||
): AnthropicContent[] => {
|
||||
return mapMessageContentToMessageContentDetails(content).map(
|
||||
mapMessageContentDetailToAnthropicContent,
|
||||
);
|
||||
};
|
||||
|
||||
export const mapBaseToolsToAnthropicTools = (
|
||||
tools?: BaseTool[],
|
||||
): AnthropicTool[] => {
|
||||
if (!tools) return [];
|
||||
return tools.map((tool: BaseTool) => {
|
||||
const {
|
||||
metadata: { parameters, ...options },
|
||||
} = tool;
|
||||
return {
|
||||
...options,
|
||||
input_schema: parameters,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const mapChatMessagesToAnthropicMessages = <
|
||||
T extends ChatMessage<ToolCallLLMMessageOptions>,
|
||||
>(
|
||||
messages: T[],
|
||||
): AnthropicMessage[] => {
|
||||
const mapped = messages
|
||||
.flatMap((msg: T): AnthropicMessage[] => {
|
||||
if (msg.options && "toolCall" in msg.options) {
|
||||
return [
|
||||
{
|
||||
role: "assistant",
|
||||
content: msg.options.toolCall.map((call) => ({
|
||||
type: "tool_use",
|
||||
id: call.id,
|
||||
name: call.name,
|
||||
input: call.input as JSONObject,
|
||||
})),
|
||||
},
|
||||
];
|
||||
}
|
||||
if (msg.options && "toolResult" in msg.options) {
|
||||
return [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: msg.options.toolResult.id,
|
||||
content: msg.options.toolResult.result,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
return mapMessageContentToMessageContentDetails(msg.content).map(
|
||||
(detail: MessageContentDetail): AnthropicMessage => {
|
||||
const content = mapMessageContentDetailToAnthropicContent(detail);
|
||||
|
||||
return {
|
||||
role: msg.role === "assistant" ? "assistant" : "user",
|
||||
content: [content],
|
||||
};
|
||||
},
|
||||
);
|
||||
})
|
||||
.filter((message: AnthropicMessage) => {
|
||||
const content = message.content[0];
|
||||
if (content.type === "text" && !content.text) return false;
|
||||
if (content.type === "image" && !content.source.data) return false;
|
||||
if (content.type === "image" && message.role === "assistant")
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
return mergeNeighboringSameRoleMessages(mapped);
|
||||
};
|
||||
|
||||
export const mapTextContent = (text: string): AnthropicTextContent => {
|
||||
return { type: "text", text };
|
||||
};
|
||||
|
||||
export const extractDataUrlComponents = (
|
||||
dataUrl: string,
|
||||
): {
|
||||
mimeType: string;
|
||||
base64: string;
|
||||
} => {
|
||||
const parts = dataUrl.split(";base64,");
|
||||
|
||||
if (parts.length !== 2 || !parts[0].startsWith("data:")) {
|
||||
throw new Error("Invalid data URL");
|
||||
}
|
||||
|
||||
const mimeType = parts[0].slice(5);
|
||||
const base64 = parts[1];
|
||||
|
||||
return {
|
||||
mimeType,
|
||||
base64,
|
||||
};
|
||||
};
|
||||
|
||||
export const mapImageContent = (imageUrl: string): AnthropicImageContent => {
|
||||
if (!imageUrl.startsWith("data:"))
|
||||
throw new Error(
|
||||
"For Anthropic please only use base64 data url, e.g.: data:image/jpeg;base64,SGVsbG8sIFdvcmxkIQ==",
|
||||
);
|
||||
const { mimeType, base64: data } = extractDataUrlComponents(imageUrl);
|
||||
if (!ACCEPTED_IMAGE_MIME_TYPES.includes(mimeType))
|
||||
throw new Error(
|
||||
`Anthropic only accepts the following mimeTypes: ${ACCEPTED_IMAGE_MIME_TYPES.join("\n")}`,
|
||||
);
|
||||
|
||||
return {
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: mimeType as AnthropicMediaTypes,
|
||||
data,
|
||||
},
|
||||
};
|
||||
};
|
||||
+11
-2
@@ -22,8 +22,16 @@ import {
|
||||
type BedrockChatStreamResponse,
|
||||
Provider,
|
||||
} from "./provider";
|
||||
import { PROVIDERS } from "./providers";
|
||||
import { mapMessageContentToMessageContentDetails } from "./utils.js";
|
||||
import { mapMessageContentToMessageContentDetails } from "./utils";
|
||||
|
||||
import { AnthropicProvider } from "./anthropic/provider";
|
||||
import { MetaProvider } from "./meta/provider";
|
||||
|
||||
// Other providers should go here
|
||||
export const PROVIDERS: { [key: string]: Provider } = {
|
||||
anthropic: new AnthropicProvider(),
|
||||
meta: new MetaProvider(),
|
||||
};
|
||||
|
||||
export type BedrockChatParamsStreaming = LLMChatParamsStreaming<
|
||||
BedrockAdditionalChatOptions,
|
||||
@@ -140,6 +148,7 @@ export const TOOL_CALL_MODELS = [
|
||||
BEDROCK_MODELS.ANTHROPIC_CLAUDE_3_HAIKU,
|
||||
BEDROCK_MODELS.ANTHROPIC_CLAUDE_3_OPUS,
|
||||
BEDROCK_MODELS.ANTHROPIC_CLAUDE_3_5_SONNET,
|
||||
BEDROCK_MODELS.META_LLAMA3_1_405B_INSTRUCT,
|
||||
];
|
||||
|
||||
const getProvider = (model: string): Provider => {
|
||||
@@ -0,0 +1,3 @@
|
||||
export const TOKENS = {
|
||||
TOOL_CALL: "<|python_tag|>",
|
||||
};
|
||||
@@ -0,0 +1,136 @@
|
||||
import type {
|
||||
InvokeModelCommandInput,
|
||||
InvokeModelWithResponseStreamCommandInput,
|
||||
ResponseStream,
|
||||
} from "@aws-sdk/client-bedrock-runtime";
|
||||
import type {
|
||||
BaseTool,
|
||||
ChatMessage,
|
||||
LLMMetadata,
|
||||
ToolCall,
|
||||
ToolCallLLMMessageOptions,
|
||||
} from "@llamaindex/core/llms";
|
||||
import { toUtf8 } from "../utils";
|
||||
import type { MetaNoneStreamingResponse, MetaStreamEvent } from "./types";
|
||||
|
||||
import { randomUUID } from "@llamaindex/env";
|
||||
import { Provider, type BedrockChatStreamResponse } from "../provider";
|
||||
import { TOKENS } from "./constants";
|
||||
import {
|
||||
mapChatMessagesToMetaLlama2Messages,
|
||||
mapChatMessagesToMetaLlama3Messages,
|
||||
} from "./utils";
|
||||
|
||||
export class MetaProvider extends Provider<MetaStreamEvent> {
|
||||
getResultFromResponse(
|
||||
response: Record<string, any>,
|
||||
): MetaNoneStreamingResponse {
|
||||
return JSON.parse(toUtf8(response.body));
|
||||
}
|
||||
|
||||
getToolsFromResponse<ToolContent>(
|
||||
response: Record<string, any>,
|
||||
): ToolContent[] {
|
||||
const result = this.getResultFromResponse(response);
|
||||
if (!result.generation.trim().startsWith(TOKENS.TOOL_CALL)) return [];
|
||||
const tool = JSON.parse(
|
||||
result.generation.trim().split(TOKENS.TOOL_CALL)[1],
|
||||
);
|
||||
return [
|
||||
{
|
||||
id: randomUUID(),
|
||||
name: tool.name,
|
||||
input: tool.parameters,
|
||||
} as ToolContent,
|
||||
];
|
||||
}
|
||||
|
||||
getTextFromResponse(response: Record<string, any>): string {
|
||||
const result = this.getResultFromResponse(response);
|
||||
if (result.generation.trim().startsWith(TOKENS.TOOL_CALL)) return "";
|
||||
return result.generation;
|
||||
}
|
||||
|
||||
getTextFromStreamResponse(response: Record<string, any>): string {
|
||||
const event = this.getStreamingEventResponse(response);
|
||||
if (event?.generation) {
|
||||
return event.generation;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
async *reduceStream(
|
||||
stream: AsyncIterable<ResponseStream>,
|
||||
): BedrockChatStreamResponse {
|
||||
const collecting: string[] = [];
|
||||
let toolId: string | undefined = undefined;
|
||||
for await (const response of stream) {
|
||||
const event = this.getStreamingEventResponse(response);
|
||||
const delta = this.getTextFromStreamResponse(response);
|
||||
// odd quirk of llama3.1, start token is \n\n
|
||||
if (
|
||||
!event?.generation.trim() &&
|
||||
event?.generation_token_count === 1 &&
|
||||
event.prompt_token_count !== null
|
||||
)
|
||||
continue;
|
||||
|
||||
if (delta === TOKENS.TOOL_CALL) {
|
||||
toolId = randomUUID();
|
||||
continue;
|
||||
}
|
||||
|
||||
let options: undefined | ToolCallLLMMessageOptions = undefined;
|
||||
if (toolId && event?.stop_reason === "stop") {
|
||||
const tool = JSON.parse(collecting.join(""));
|
||||
options = {
|
||||
toolCall: [
|
||||
{
|
||||
id: toolId,
|
||||
name: tool.name,
|
||||
input: tool.parameters,
|
||||
} as ToolCall,
|
||||
],
|
||||
};
|
||||
} else if (toolId && !event?.stop_reason) {
|
||||
collecting.push(delta);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!delta && !options) continue;
|
||||
|
||||
yield {
|
||||
delta: options ? "" : delta,
|
||||
options,
|
||||
raw: response,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getRequestBody<T extends ChatMessage>(
|
||||
metadata: LLMMetadata,
|
||||
messages: T[],
|
||||
tools?: BaseTool[],
|
||||
): InvokeModelCommandInput | InvokeModelWithResponseStreamCommandInput {
|
||||
let prompt: string = "";
|
||||
if (metadata.model.startsWith("meta.llama3")) {
|
||||
prompt = mapChatMessagesToMetaLlama3Messages(messages, tools);
|
||||
} else if (metadata.model.startsWith("meta.llama2")) {
|
||||
prompt = mapChatMessagesToMetaLlama2Messages(messages);
|
||||
} else {
|
||||
throw new Error(`Meta model ${metadata.model} is not supported`);
|
||||
}
|
||||
|
||||
return {
|
||||
modelId: metadata.model,
|
||||
contentType: "application/json",
|
||||
accept: "application/json",
|
||||
body: JSON.stringify({
|
||||
prompt,
|
||||
max_gen_len: metadata.maxTokens,
|
||||
temperature: metadata.temperature,
|
||||
top_p: metadata.topP,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { InvocationMetrics } from "../types";
|
||||
|
||||
export type MetaTextContent = string;
|
||||
|
||||
export type MetaMessage = {
|
||||
role: "user" | "assistant" | "system" | "ipython";
|
||||
content: MetaTextContent;
|
||||
};
|
||||
|
||||
type MetaResponse = {
|
||||
generation: string;
|
||||
prompt_token_count: number;
|
||||
generation_token_count: number;
|
||||
stop_reason: "stop" | "length";
|
||||
};
|
||||
|
||||
export type MetaStreamEvent = MetaResponse & {
|
||||
"amazon-bedrock-invocationMetrics": InvocationMetrics;
|
||||
};
|
||||
|
||||
export type MetaNoneStreamingResponse = MetaResponse;
|
||||
@@ -0,0 +1,198 @@
|
||||
import type {
|
||||
BaseTool,
|
||||
ChatMessage,
|
||||
MessageContentTextDetail,
|
||||
ToolCallLLMMessageOptions,
|
||||
} from "@llamaindex/core/llms";
|
||||
import type { MetaMessage } from "./types";
|
||||
|
||||
const getToolCallInstructionString = (tool: BaseTool): string => {
|
||||
return `Use the function '${tool.metadata.name}' to '${tool.metadata.description}'`;
|
||||
};
|
||||
|
||||
const getToolCallParametersString = (tool: BaseTool): string => {
|
||||
return JSON.stringify({
|
||||
name: tool.metadata.name,
|
||||
description: tool.metadata.description,
|
||||
parameters: tool.metadata.parameters
|
||||
? Object.entries(tool.metadata.parameters.properties).map(
|
||||
([name, definition]) => ({ [name]: definition }),
|
||||
)
|
||||
: {},
|
||||
});
|
||||
};
|
||||
|
||||
// ported from https://github.com/meta-llama/llama-agentic-system/blob/main/llama_agentic_system/system_prompt.py
|
||||
// NOTE: using json instead of the above xml style tool calling works more reliability
|
||||
export const getToolsPrompt = (tools?: BaseTool[]) => {
|
||||
if (!tools?.length) return "";
|
||||
|
||||
const customToolParams = tools.map((tool) => {
|
||||
return [
|
||||
getToolCallInstructionString(tool),
|
||||
getToolCallParametersString(tool),
|
||||
].join("\n\n");
|
||||
});
|
||||
|
||||
return `
|
||||
Environment: node
|
||||
|
||||
# Tool Instructions
|
||||
- Never use ipython, always use javascript in node
|
||||
|
||||
Cutting Knowledge Date: December 2023
|
||||
Today Date: ${new Date().toLocaleString("en-US", { year: "numeric", month: "long" })}
|
||||
|
||||
You have access to the following functions:
|
||||
|
||||
${customToolParams}
|
||||
|
||||
Think very carefully before calling functions.
|
||||
|
||||
If a you choose to call a function ONLY reply in the following json format:
|
||||
{
|
||||
"name": function_name,
|
||||
"parameters": parameters,
|
||||
}
|
||||
where
|
||||
|
||||
{
|
||||
"name": function_name,
|
||||
"parameters": parameters, => a JSON dict with the function argument name as key and function argument value as value.
|
||||
}
|
||||
|
||||
Here is an example,
|
||||
|
||||
{
|
||||
"name": "example_function_name",
|
||||
"parameters": {"example_name": "example_value"}
|
||||
}
|
||||
|
||||
Reminder:
|
||||
- Function calls MUST follow the specified format
|
||||
- Required parameters MUST be specified
|
||||
- Only call one function at a time
|
||||
- Put the entire function call reply on one line
|
||||
- Always add your sources when using search results to answer the user query
|
||||
`;
|
||||
};
|
||||
|
||||
export const mapChatRoleToMetaRole = (
|
||||
role: ChatMessage["role"],
|
||||
): MetaMessage["role"] => {
|
||||
if (role === "assistant") return "assistant";
|
||||
if (role === "user") return "user";
|
||||
return "system";
|
||||
};
|
||||
|
||||
export const mapChatMessagesToMetaMessages = <
|
||||
T extends ChatMessage<ToolCallLLMMessageOptions>,
|
||||
>(
|
||||
messages: T[],
|
||||
): MetaMessage[] => {
|
||||
return messages.flatMap((msg) => {
|
||||
if (msg.options && "toolCall" in msg.options) {
|
||||
return msg.options.toolCall.map((call) => ({
|
||||
role: "assistant",
|
||||
content: JSON.stringify({
|
||||
id: call.id,
|
||||
name: call.name,
|
||||
parameters: call.input,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
if (msg.options && "toolResult" in msg.options) {
|
||||
return {
|
||||
role: "ipython",
|
||||
content: JSON.stringify(msg.options.toolResult),
|
||||
};
|
||||
}
|
||||
|
||||
let content: string = "";
|
||||
if (typeof msg.content === "string") {
|
||||
content = msg.content;
|
||||
} else if (msg.content.length) {
|
||||
content = (msg.content[0] as MessageContentTextDetail).text;
|
||||
}
|
||||
return {
|
||||
role: mapChatRoleToMetaRole(msg.role),
|
||||
content,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Documentation at https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-3
|
||||
*/
|
||||
export const mapChatMessagesToMetaLlama3Messages = <T extends ChatMessage>(
|
||||
messages: T[],
|
||||
tools?: BaseTool[],
|
||||
): string => {
|
||||
const parts: string[] = [];
|
||||
if (tools?.length) {
|
||||
parts.push(
|
||||
"<|begin_of_text|>",
|
||||
"<|start_header_id|>system<|end_header_id|>",
|
||||
getToolsPrompt(tools),
|
||||
"<|eot_id|>",
|
||||
);
|
||||
}
|
||||
|
||||
const mapped = mapChatMessagesToMetaMessages(messages).map((message) => {
|
||||
return [
|
||||
"<|start_header_id|>",
|
||||
message.role,
|
||||
"<|end_header_id|>",
|
||||
message.content,
|
||||
"<|eot_id|>",
|
||||
].join("\n");
|
||||
});
|
||||
|
||||
parts.push(
|
||||
"<|begin_of_text|>",
|
||||
...mapped,
|
||||
"<|start_header_id|>assistant<|end_header_id|>",
|
||||
);
|
||||
return parts.join("\n");
|
||||
};
|
||||
|
||||
/**
|
||||
* Documentation at https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-2
|
||||
*/
|
||||
export const mapChatMessagesToMetaLlama2Messages = <T extends ChatMessage>(
|
||||
messages: T[],
|
||||
): string => {
|
||||
const mapped = mapChatMessagesToMetaMessages(messages);
|
||||
let output = "<s>";
|
||||
let insideInst = false;
|
||||
let needsStartAgain = false;
|
||||
for (const message of mapped) {
|
||||
if (needsStartAgain) {
|
||||
output += "<s>";
|
||||
needsStartAgain = false;
|
||||
}
|
||||
const text = message.content;
|
||||
if (message.role === "system") {
|
||||
if (!insideInst) {
|
||||
output += "[INST] ";
|
||||
insideInst = true;
|
||||
}
|
||||
output += `<<SYS>>\n${text}\n<</SYS>>\n`;
|
||||
} else if (message.role === "user") {
|
||||
output += text;
|
||||
if (insideInst) {
|
||||
output += " [/INST]";
|
||||
insideInst = false;
|
||||
}
|
||||
} else if (message.role === "assistant") {
|
||||
if (insideInst) {
|
||||
output += " [/INST]";
|
||||
insideInst = false;
|
||||
}
|
||||
output += ` ${text} </s>\n`;
|
||||
needsStartAgain = true;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
@@ -23,6 +23,7 @@ export type BedrockChatStreamResponse = AsyncIterable<
|
||||
export abstract class Provider<ProviderStreamEvent extends {} = {}> {
|
||||
abstract getTextFromResponse(response: Record<string, any>): string;
|
||||
|
||||
// Return tool calls from none streaming calls
|
||||
abstract getToolsFromResponse<T extends {} = {}>(
|
||||
response: Record<string, any>,
|
||||
): T[];
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { Provider } from "../provider";
|
||||
import { AnthropicProvider } from "./anthropic";
|
||||
import { MetaProvider } from "./meta";
|
||||
|
||||
// Other providers should go here
|
||||
export const PROVIDERS: { [key: string]: Provider } = {
|
||||
anthropic: new AnthropicProvider(),
|
||||
meta: new MetaProvider(),
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
import type {
|
||||
InvokeModelCommandInput,
|
||||
InvokeModelWithResponseStreamCommandInput,
|
||||
} from "@aws-sdk/client-bedrock-runtime";
|
||||
import type { ChatMessage, LLMMetadata } from "@llamaindex/core/llms";
|
||||
import type { MetaNoneStreamingResponse, MetaStreamEvent } from "../types";
|
||||
import {
|
||||
mapChatMessagesToMetaLlama2Messages,
|
||||
mapChatMessagesToMetaLlama3Messages,
|
||||
toUtf8,
|
||||
} from "../utils";
|
||||
|
||||
import { Provider } from "../provider";
|
||||
|
||||
export class MetaProvider extends Provider<MetaStreamEvent> {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
getResultFromResponse(
|
||||
response: Record<string, any>,
|
||||
): MetaNoneStreamingResponse {
|
||||
return JSON.parse(toUtf8(response.body));
|
||||
}
|
||||
|
||||
getToolsFromResponse(_response: Record<string, any>): never {
|
||||
throw new Error("Not supported by this provider.");
|
||||
}
|
||||
|
||||
getTextFromResponse(response: Record<string, any>): string {
|
||||
const result = this.getResultFromResponse(response);
|
||||
return result.generation;
|
||||
}
|
||||
|
||||
getTextFromStreamResponse(response: Record<string, any>): string {
|
||||
const event = this.getStreamingEventResponse(response);
|
||||
if (event?.generation) {
|
||||
return event.generation;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
getRequestBody<T extends ChatMessage>(
|
||||
metadata: LLMMetadata,
|
||||
messages: T[],
|
||||
): InvokeModelCommandInput | InvokeModelWithResponseStreamCommandInput {
|
||||
let promptFunction: (messages: ChatMessage[]) => string;
|
||||
|
||||
if (metadata.model.startsWith("meta.llama3")) {
|
||||
promptFunction = mapChatMessagesToMetaLlama3Messages;
|
||||
} else if (metadata.model.startsWith("meta.llama2")) {
|
||||
promptFunction = mapChatMessagesToMetaLlama2Messages;
|
||||
} else {
|
||||
throw new Error(`Meta model ${metadata.model} is not supported`);
|
||||
}
|
||||
|
||||
return {
|
||||
modelId: metadata.model,
|
||||
contentType: "application/json",
|
||||
accept: "application/json",
|
||||
body: JSON.stringify({
|
||||
prompt: promptFunction(messages),
|
||||
max_gen_len: metadata.maxTokens,
|
||||
temperature: metadata.temperature,
|
||||
top_p: metadata.topP,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,165 +1,11 @@
|
||||
type Usage = {
|
||||
input_tokens: number;
|
||||
output_tokens: number;
|
||||
};
|
||||
|
||||
type Message = {
|
||||
id: string;
|
||||
type: string;
|
||||
role: string;
|
||||
content: string[];
|
||||
model: string;
|
||||
stop_reason: string | null;
|
||||
stop_sequence: string | null;
|
||||
usage: Usage;
|
||||
};
|
||||
|
||||
export type ToolBlock = {
|
||||
id: string;
|
||||
input: unknown;
|
||||
name: string;
|
||||
type: "tool_use";
|
||||
};
|
||||
|
||||
export type TextBlock = {
|
||||
type: "text";
|
||||
text: string;
|
||||
};
|
||||
|
||||
type ContentBlockStart = {
|
||||
type: "content_block_start";
|
||||
index: number;
|
||||
content_block: ToolBlock | TextBlock;
|
||||
};
|
||||
|
||||
type Delta =
|
||||
| {
|
||||
type: "text_delta";
|
||||
text: string;
|
||||
}
|
||||
| {
|
||||
type: "input_json_delta";
|
||||
partial_json: string;
|
||||
};
|
||||
|
||||
type ContentBlockDelta = {
|
||||
type: "content_block_delta";
|
||||
index: number;
|
||||
delta: Delta;
|
||||
};
|
||||
|
||||
type ContentBlockStop = {
|
||||
type: "content_block_stop";
|
||||
index: number;
|
||||
};
|
||||
|
||||
type MessageDelta = {
|
||||
type: "message_delta";
|
||||
delta: {
|
||||
stop_reason: string;
|
||||
stop_sequence: string | null;
|
||||
};
|
||||
usage: Usage;
|
||||
};
|
||||
|
||||
type InvocationMetrics = {
|
||||
export type InvocationMetrics = {
|
||||
inputTokenCount: number;
|
||||
outputTokenCount: number;
|
||||
invocationLatency: number;
|
||||
firstByteLatency: number;
|
||||
};
|
||||
|
||||
type MessageStop = {
|
||||
type: "message_stop";
|
||||
"amazon-bedrock-invocationMetrics": InvocationMetrics;
|
||||
};
|
||||
|
||||
export type ToolChoice =
|
||||
| { type: "any" }
|
||||
| { type: "auto" }
|
||||
| { type: "tool"; name: string };
|
||||
|
||||
export type AnthropicStreamEvent =
|
||||
| { type: "message_start"; message: Message }
|
||||
| ContentBlockStart
|
||||
| ContentBlockDelta
|
||||
| ContentBlockStop
|
||||
| MessageDelta
|
||||
| MessageStop;
|
||||
|
||||
export type AnthropicContent =
|
||||
| AnthropicTextContent
|
||||
| AnthropicImageContent
|
||||
| AnthropicToolContent
|
||||
| AnthropicToolResultContent;
|
||||
|
||||
export type MetaTextContent = string;
|
||||
|
||||
export type AnthropicTextContent = {
|
||||
type: "text";
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type AnthropicToolContent = {
|
||||
type: "tool_use";
|
||||
id: string;
|
||||
name: string;
|
||||
input: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type AnthropicToolResultContent = {
|
||||
type: "tool_result";
|
||||
tool_use_id: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type AnthropicMediaTypes =
|
||||
| "image/jpeg"
|
||||
| "image/png"
|
||||
| "image/webp"
|
||||
| "image/gif";
|
||||
|
||||
export type AnthropicImageSource = {
|
||||
type: "base64";
|
||||
media_type: AnthropicMediaTypes;
|
||||
data: string; // base64 encoded image bytes
|
||||
};
|
||||
|
||||
export type AnthropicImageContent = {
|
||||
type: "image";
|
||||
source: AnthropicImageSource;
|
||||
};
|
||||
|
||||
export type AnthropicMessage = {
|
||||
role: "user" | "assistant";
|
||||
content: AnthropicContent[];
|
||||
};
|
||||
|
||||
export type MetaMessage = {
|
||||
role: "user" | "assistant" | "system";
|
||||
content: MetaTextContent;
|
||||
};
|
||||
|
||||
export type AnthropicNoneStreamingResponse = {
|
||||
id: string;
|
||||
type: "message";
|
||||
role: "assistant";
|
||||
content: AnthropicContent[];
|
||||
model: string;
|
||||
stop_reason: "end_turn" | "max_tokens" | "stop_sequence";
|
||||
stop_sequence?: string;
|
||||
usage: { input_tokens: number; output_tokens: number };
|
||||
};
|
||||
|
||||
type MetaResponse = {
|
||||
generation: string;
|
||||
prompt_token_count: number;
|
||||
generation_token_count: number;
|
||||
stop_reason: "stop" | "length";
|
||||
};
|
||||
|
||||
export type MetaStreamEvent = MetaResponse & {
|
||||
"amazon-bedrock-invocationMetrics": InvocationMetrics;
|
||||
};
|
||||
|
||||
export type MetaNoneStreamingResponse = MetaResponse;
|
||||
|
||||
@@ -1,28 +1,7 @@
|
||||
import type { JSONObject } from "@llamaindex/core/global";
|
||||
import type {
|
||||
BaseTool,
|
||||
ChatMessage,
|
||||
MessageContent,
|
||||
MessageContentDetail,
|
||||
MessageContentTextDetail,
|
||||
ToolCallLLMMessageOptions,
|
||||
ToolMetadata,
|
||||
} from "@llamaindex/core/llms";
|
||||
import type {
|
||||
AnthropicContent,
|
||||
AnthropicImageContent,
|
||||
AnthropicMediaTypes,
|
||||
AnthropicMessage,
|
||||
AnthropicTextContent,
|
||||
MetaMessage,
|
||||
} from "./types.js";
|
||||
|
||||
const ACCEPTED_IMAGE_MIME_TYPES = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/gif",
|
||||
];
|
||||
|
||||
export const mapMessageContentToMessageContentDetails = (
|
||||
content: MessageContent,
|
||||
@@ -30,250 +9,5 @@ export const mapMessageContentToMessageContentDetails = (
|
||||
return Array.isArray(content) ? content : [{ type: "text", text: content }];
|
||||
};
|
||||
|
||||
export const mergeNeighboringSameRoleMessages = (
|
||||
messages: AnthropicMessage[],
|
||||
): AnthropicMessage[] => {
|
||||
return messages.reduce(
|
||||
(result: AnthropicMessage[], current: AnthropicMessage, index: number) => {
|
||||
if (index > 0 && messages[index - 1].role === current.role) {
|
||||
result[result.length - 1].content = [
|
||||
...result[result.length - 1].content,
|
||||
...current.content,
|
||||
];
|
||||
} else {
|
||||
result.push(current);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
export const mapMessageContentDetailToAnthropicContent = <
|
||||
T extends MessageContentDetail,
|
||||
>(
|
||||
detail: T,
|
||||
): AnthropicContent => {
|
||||
let content: AnthropicContent;
|
||||
|
||||
if (detail.type === "text") {
|
||||
content = mapTextContent(detail.text);
|
||||
} else if (detail.type === "image_url") {
|
||||
content = mapImageContent(detail.image_url.url);
|
||||
} else {
|
||||
throw new Error("Unsupported content detail type");
|
||||
}
|
||||
return content;
|
||||
};
|
||||
|
||||
export const mapMessageContentToAnthropicContent = <T extends MessageContent>(
|
||||
content: T,
|
||||
): AnthropicContent[] => {
|
||||
return mapMessageContentToMessageContentDetails(content).map(
|
||||
mapMessageContentDetailToAnthropicContent,
|
||||
);
|
||||
};
|
||||
|
||||
type AnthropicTool = {
|
||||
name: string;
|
||||
description: string;
|
||||
input_schema: ToolMetadata["parameters"];
|
||||
};
|
||||
|
||||
export const mapBaseToolsToAnthropicTools = (
|
||||
tools?: BaseTool[],
|
||||
): AnthropicTool[] => {
|
||||
if (!tools) return [];
|
||||
return tools.map((tool: BaseTool) => {
|
||||
const {
|
||||
metadata: { parameters, ...options },
|
||||
} = tool;
|
||||
return {
|
||||
...options,
|
||||
input_schema: parameters,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const mapChatMessagesToAnthropicMessages = <
|
||||
T extends ChatMessage<ToolCallLLMMessageOptions>,
|
||||
>(
|
||||
messages: T[],
|
||||
): AnthropicMessage[] => {
|
||||
const mapped = messages
|
||||
.flatMap((msg: T): AnthropicMessage[] => {
|
||||
if (msg.options && "toolCall" in msg.options) {
|
||||
return [
|
||||
{
|
||||
role: "assistant",
|
||||
content: msg.options.toolCall.map((call) => ({
|
||||
type: "tool_use",
|
||||
id: call.id,
|
||||
name: call.name,
|
||||
input: call.input as JSONObject,
|
||||
})),
|
||||
},
|
||||
];
|
||||
}
|
||||
if (msg.options && "toolResult" in msg.options) {
|
||||
return [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: msg.options.toolResult.id,
|
||||
content: msg.options.toolResult.result,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
return mapMessageContentToMessageContentDetails(msg.content).map(
|
||||
(detail: MessageContentDetail): AnthropicMessage => {
|
||||
const content = mapMessageContentDetailToAnthropicContent(detail);
|
||||
|
||||
return {
|
||||
role: msg.role === "assistant" ? "assistant" : "user",
|
||||
content: [content],
|
||||
};
|
||||
},
|
||||
);
|
||||
})
|
||||
.filter((message: AnthropicMessage) => {
|
||||
const content = message.content[0];
|
||||
if (content.type === "text" && !content.text) return false;
|
||||
if (content.type === "image" && !content.source.data) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
return mergeNeighboringSameRoleMessages(mapped);
|
||||
};
|
||||
|
||||
export const mapChatMessagesToMetaMessages = <T extends ChatMessage>(
|
||||
messages: T[],
|
||||
): MetaMessage[] => {
|
||||
return messages.map((msg) => {
|
||||
let content: string = "";
|
||||
if (typeof msg.content === "string") {
|
||||
content = msg.content;
|
||||
} else if (msg.content.length) {
|
||||
content = (msg.content[0] as MessageContentTextDetail).text;
|
||||
}
|
||||
return {
|
||||
role:
|
||||
msg.role === "assistant"
|
||||
? "assistant"
|
||||
: msg.role === "user"
|
||||
? "user"
|
||||
: "system",
|
||||
content,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Documentation at https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-3
|
||||
*/
|
||||
export const mapChatMessagesToMetaLlama3Messages = <T extends ChatMessage>(
|
||||
messages: T[],
|
||||
): string => {
|
||||
const mapped = mapChatMessagesToMetaMessages(messages).map((message) => {
|
||||
const text = message.content;
|
||||
return `<|start_header_id|>${message.role}<|end_header_id|>\n${text}\n<|eot_id|>\n`;
|
||||
});
|
||||
return (
|
||||
"<|begin_of_text|>" +
|
||||
mapped.join("\n") +
|
||||
"\n<|start_header_id|>assistant<|end_header_id|>\n"
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Documentation at https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-2
|
||||
*/
|
||||
export const mapChatMessagesToMetaLlama2Messages = <T extends ChatMessage>(
|
||||
messages: T[],
|
||||
): string => {
|
||||
const mapped = mapChatMessagesToMetaMessages(messages);
|
||||
let output = "<s>";
|
||||
let insideInst = false;
|
||||
let needsStartAgain = false;
|
||||
for (const message of mapped) {
|
||||
if (needsStartAgain) {
|
||||
output += "<s>";
|
||||
needsStartAgain = false;
|
||||
}
|
||||
const text = message.content;
|
||||
if (message.role === "system") {
|
||||
if (!insideInst) {
|
||||
output += "[INST] ";
|
||||
insideInst = true;
|
||||
}
|
||||
output += `<<SYS>>\n${text}\n<</SYS>>\n`;
|
||||
} else if (message.role === "user") {
|
||||
output += text;
|
||||
if (insideInst) {
|
||||
output += " [/INST]";
|
||||
insideInst = false;
|
||||
}
|
||||
} else if (message.role === "assistant") {
|
||||
if (insideInst) {
|
||||
output += " [/INST]";
|
||||
insideInst = false;
|
||||
}
|
||||
output += ` ${text} </s>\n`;
|
||||
needsStartAgain = true;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
export const mapTextContent = (text: string): AnthropicTextContent => {
|
||||
return { type: "text", text };
|
||||
};
|
||||
|
||||
export const extractDataUrlComponents = (
|
||||
dataUrl: string,
|
||||
): {
|
||||
mimeType: string;
|
||||
base64: string;
|
||||
} => {
|
||||
const parts = dataUrl.split(";base64,");
|
||||
|
||||
if (parts.length !== 2 || !parts[0].startsWith("data:")) {
|
||||
throw new Error("Invalid data URL");
|
||||
}
|
||||
|
||||
const mimeType = parts[0].slice(5);
|
||||
const base64 = parts[1];
|
||||
|
||||
return {
|
||||
mimeType,
|
||||
base64,
|
||||
};
|
||||
};
|
||||
|
||||
export const mapImageContent = (imageUrl: string): AnthropicImageContent => {
|
||||
if (!imageUrl.startsWith("data:"))
|
||||
throw new Error(
|
||||
"For Anthropic please only use base64 data url, e.g.: data:image/jpeg;base64,SGVsbG8sIFdvcmxkIQ==",
|
||||
);
|
||||
const { mimeType, base64: data } = extractDataUrlComponents(imageUrl);
|
||||
if (!ACCEPTED_IMAGE_MIME_TYPES.includes(mimeType))
|
||||
throw new Error(
|
||||
`Anthropic only accepts the following mimeTypes: ${ACCEPTED_IMAGE_MIME_TYPES.join("\n")}`,
|
||||
);
|
||||
|
||||
return {
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: mimeType as AnthropicMediaTypes,
|
||||
data,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const toUtf8 = (input: Uint8Array): string =>
|
||||
new TextDecoder("utf-8").decode(input);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @llamaindex/core
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 91d02a4: feat: support transform component callable
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@llamaindex/core",
|
||||
"type": "module",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"description": "LlamaIndex Core Module",
|
||||
"exports": {
|
||||
"./node-parser": {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { type Tokenizers } from "@llamaindex/env";
|
||||
import type { MessageContentDetail } from "../llms";
|
||||
import type { TransformComponent } from "../schema";
|
||||
import { BaseNode, MetadataMode } from "../schema";
|
||||
import { BaseNode, MetadataMode, TransformComponent } from "../schema";
|
||||
import { extractSingleText } from "../utils";
|
||||
import { truncateMaxTokens } from "./tokenizer.js";
|
||||
import { SimilarityType, similarity } from "./utils.js";
|
||||
@@ -20,10 +19,29 @@ export type BaseEmbeddingOptions = {
|
||||
logProgress?: boolean;
|
||||
};
|
||||
|
||||
export abstract class BaseEmbedding implements TransformComponent {
|
||||
export abstract class BaseEmbedding extends TransformComponent {
|
||||
embedBatchSize = DEFAULT_EMBED_BATCH_SIZE;
|
||||
embedInfo?: EmbeddingInfo;
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
async (
|
||||
nodes: BaseNode[],
|
||||
options?: BaseEmbeddingOptions,
|
||||
): Promise<BaseNode[]> => {
|
||||
const texts = nodes.map((node) => node.getContent(MetadataMode.EMBED));
|
||||
|
||||
const embeddings = await this.getTextEmbeddingsBatch(texts, options);
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
nodes[i].embedding = embeddings[i];
|
||||
}
|
||||
|
||||
return nodes;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
similarity(
|
||||
embedding1: number[],
|
||||
embedding2: number[],
|
||||
@@ -76,21 +94,6 @@ export abstract class BaseEmbedding implements TransformComponent {
|
||||
);
|
||||
}
|
||||
|
||||
async transform(
|
||||
nodes: BaseNode[],
|
||||
options?: BaseEmbeddingOptions,
|
||||
): Promise<BaseNode[]> {
|
||||
const texts = nodes.map((node) => node.getContent(MetadataMode.EMBED));
|
||||
|
||||
const embeddings = await this.getTextEmbeddingsBatch(texts, options);
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
nodes[i].embedding = embeddings[i];
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
truncateMaxTokens(input: string[]): string[] {
|
||||
return input.map((s) => {
|
||||
// truncate to max tokens
|
||||
|
||||
@@ -5,13 +5,19 @@ import {
|
||||
MetadataMode,
|
||||
NodeRelationship,
|
||||
TextNode,
|
||||
type TransformComponent,
|
||||
TransformComponent,
|
||||
} from "../schema";
|
||||
|
||||
export abstract class NodeParser implements TransformComponent {
|
||||
export abstract class NodeParser extends TransformComponent {
|
||||
includeMetadata: boolean = true;
|
||||
includePrevNextRel: boolean = true;
|
||||
|
||||
constructor() {
|
||||
super(async (nodes: BaseNode[]): Promise<BaseNode[]> => {
|
||||
return this.getNodesFromDocuments(nodes as TextNode[]);
|
||||
});
|
||||
}
|
||||
|
||||
protected postProcessParsedNodes(
|
||||
nodes: TextNode[],
|
||||
parentDocMap: Map<string, TextNode>,
|
||||
@@ -90,10 +96,6 @@ export abstract class NodeParser implements TransformComponent {
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
async transform(nodes: BaseNode[], options?: {}): Promise<BaseNode[]> {
|
||||
return this.getNodesFromDocuments(nodes as TextNode[]);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class TextSplitter extends NodeParser {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from "./node";
|
||||
export type { TransformComponent } from "./type";
|
||||
export { TransformComponent } from "./type";
|
||||
export { EngineResponse } from "./type/engine–response";
|
||||
export * from "./zod";
|
||||
|
||||
@@ -1,8 +1,30 @@
|
||||
import { randomUUID } from "@llamaindex/env";
|
||||
import type { BaseNode } from "./node";
|
||||
|
||||
export interface TransformComponent {
|
||||
transform<Options extends Record<string, unknown>>(
|
||||
interface TransformComponentSignature {
|
||||
<Options extends Record<string, unknown>>(
|
||||
nodes: BaseNode[],
|
||||
options?: Options,
|
||||
): Promise<BaseNode[]>;
|
||||
}
|
||||
|
||||
export interface TransformComponent extends TransformComponentSignature {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export class TransformComponent {
|
||||
constructor(transformFn: TransformComponentSignature) {
|
||||
Object.defineProperties(
|
||||
transformFn,
|
||||
Object.getOwnPropertyDescriptors(this.constructor.prototype),
|
||||
);
|
||||
const transform = function transform(
|
||||
...args: Parameters<TransformComponentSignature>
|
||||
) {
|
||||
return transformFn(...args);
|
||||
};
|
||||
Reflect.setPrototypeOf(transform, new.target.prototype);
|
||||
transform.id = randomUUID();
|
||||
return transform;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @llamaindex/experimental
|
||||
|
||||
## 0.0.61
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- llamaindex@0.5.11
|
||||
|
||||
## 0.0.60
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [086b940]
|
||||
- Updated dependencies [5d5716b]
|
||||
- Updated dependencies [91d02a4]
|
||||
- Updated dependencies [fb6db45]
|
||||
- llamaindex@0.5.10
|
||||
|
||||
## 0.0.59
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@llamaindex/experimental",
|
||||
"description": "Experimental package for LlamaIndexTS",
|
||||
"version": "0.0.59",
|
||||
"version": "0.0.61",
|
||||
"type": "module",
|
||||
"types": "dist/type/index.d.ts",
|
||||
"main": "dist/cjs/index.js",
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# llamaindex
|
||||
|
||||
## 0.5.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1f680d7]
|
||||
- @llamaindex/cloud@0.2.1
|
||||
|
||||
## 0.5.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 086b940: feat: add DeepSeek LLM
|
||||
- 5d5716b: feat: add a reader for JSON data
|
||||
- 91d02a4: feat: support transform component callable
|
||||
- fb6db45: feat: add pageSeparator params to LlamaParseReader
|
||||
- Updated dependencies [91d02a4]
|
||||
- @llamaindex/core@0.1.5
|
||||
|
||||
## 0.5.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @llamaindex/cloudflare-worker-agent-test
|
||||
|
||||
## 0.0.45
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- llamaindex@0.5.11
|
||||
|
||||
## 0.0.44
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [086b940]
|
||||
- Updated dependencies [5d5716b]
|
||||
- Updated dependencies [91d02a4]
|
||||
- Updated dependencies [fb6db45]
|
||||
- llamaindex@0.5.10
|
||||
|
||||
## 0.0.43
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@llamaindex/cloudflare-worker-agent-test",
|
||||
"version": "0.0.43",
|
||||
"version": "0.0.45",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @llamaindex/next-agent-test
|
||||
|
||||
## 0.1.45
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- llamaindex@0.5.11
|
||||
|
||||
## 0.1.44
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [086b940]
|
||||
- Updated dependencies [5d5716b]
|
||||
- Updated dependencies [91d02a4]
|
||||
- Updated dependencies [fb6db45]
|
||||
- llamaindex@0.5.10
|
||||
|
||||
## 0.1.43
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@llamaindex/next-agent-test",
|
||||
"version": "0.1.43",
|
||||
"version": "0.1.45",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# test-edge-runtime
|
||||
|
||||
## 0.1.44
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- llamaindex@0.5.11
|
||||
|
||||
## 0.1.43
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [086b940]
|
||||
- Updated dependencies [5d5716b]
|
||||
- Updated dependencies [91d02a4]
|
||||
- Updated dependencies [fb6db45]
|
||||
- llamaindex@0.5.10
|
||||
|
||||
## 0.1.42
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@llamaindex/nextjs-edge-runtime-test",
|
||||
"version": "0.1.42",
|
||||
"version": "0.1.44",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @llamaindex/next-node-runtime
|
||||
|
||||
## 0.0.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- llamaindex@0.5.11
|
||||
|
||||
## 0.0.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [086b940]
|
||||
- Updated dependencies [5d5716b]
|
||||
- Updated dependencies [91d02a4]
|
||||
- Updated dependencies [fb6db45]
|
||||
- llamaindex@0.5.10
|
||||
|
||||
## 0.0.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@llamaindex/next-node-runtime-test",
|
||||
"version": "0.0.24",
|
||||
"version": "0.0.26",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @llamaindex/waku-query-engine-test
|
||||
|
||||
## 0.0.45
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- llamaindex@0.5.11
|
||||
|
||||
## 0.0.44
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [086b940]
|
||||
- Updated dependencies [5d5716b]
|
||||
- Updated dependencies [91d02a4]
|
||||
- Updated dependencies [fb6db45]
|
||||
- llamaindex@0.5.10
|
||||
|
||||
## 0.0.43
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@llamaindex/waku-query-engine-test",
|
||||
"version": "0.0.43",
|
||||
"version": "0.0.45",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
import { TransformComponent } from "@llamaindex/core/schema";
|
||||
import {
|
||||
BaseEmbedding,
|
||||
BaseNode,
|
||||
SimilarityType,
|
||||
type BaseEmbedding,
|
||||
type EmbeddingInfo,
|
||||
type MessageContentDetail,
|
||||
} from "llamaindex";
|
||||
|
||||
export class OpenAIEmbedding implements BaseEmbedding {
|
||||
export class OpenAIEmbedding
|
||||
extends TransformComponent
|
||||
implements BaseEmbedding
|
||||
{
|
||||
embedInfo?: EmbeddingInfo | undefined;
|
||||
embedBatchSize = 512;
|
||||
|
||||
constructor() {
|
||||
super(async (nodes: BaseNode[], _options?: any): Promise<BaseNode[]> => {
|
||||
nodes.forEach((node) => (node.embedding = [0]));
|
||||
return nodes;
|
||||
});
|
||||
}
|
||||
|
||||
async getQueryEmbedding(query: MessageContentDetail) {
|
||||
return [0];
|
||||
}
|
||||
@@ -34,11 +45,6 @@ export class OpenAIEmbedding implements BaseEmbedding {
|
||||
return 1;
|
||||
}
|
||||
|
||||
async transform(nodes: BaseNode[], _options?: any): Promise<BaseNode[]> {
|
||||
nodes.forEach((node) => (node.embedding = [0]));
|
||||
return nodes;
|
||||
}
|
||||
|
||||
truncateMaxTokens(input: string[]): string[] {
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "llamaindex",
|
||||
"version": "0.5.9",
|
||||
"version": "0.5.11",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import type { BaseNode, TransformComponent } from "@llamaindex/core/schema";
|
||||
import { MetadataMode, TextNode } from "@llamaindex/core/schema";
|
||||
import {
|
||||
BaseNode,
|
||||
MetadataMode,
|
||||
TextNode,
|
||||
TransformComponent,
|
||||
} from "@llamaindex/core/schema";
|
||||
import { defaultNodeTextTemplate } from "./prompts.js";
|
||||
|
||||
/*
|
||||
* Abstract class for all extractors.
|
||||
*/
|
||||
export abstract class BaseExtractor implements TransformComponent {
|
||||
export abstract class BaseExtractor extends TransformComponent {
|
||||
isTextNodeOnly: boolean = true;
|
||||
showProgress: boolean = true;
|
||||
metadataMode: MetadataMode = MetadataMode.ALL;
|
||||
@@ -13,16 +17,18 @@ export abstract class BaseExtractor implements TransformComponent {
|
||||
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,
|
||||
);
|
||||
constructor() {
|
||||
super(async (nodes: BaseNode[], options?: any): Promise<BaseNode[]> => {
|
||||
return this.processNodes(
|
||||
nodes,
|
||||
options?.excludedEmbedMetadataKeys,
|
||||
options?.excludedLlmMetadataKeys,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
abstract extract(nodes: BaseNode[]): Promise<Record<string, any>[]>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param nodes Nodes to extract metadata from.
|
||||
|
||||
@@ -172,7 +172,7 @@ export class VectorStoreIndex extends BaseIndex<IndexDict> {
|
||||
const embedModel =
|
||||
this.embedModel ?? this.vectorStores[type as ModalityType]?.embedModel;
|
||||
if (embedModel && nodes) {
|
||||
await embedModel.transform(nodes, {
|
||||
await embedModel(nodes, {
|
||||
logProgress: options?.logProgress,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export function getTransformationHash(
|
||||
const transformString: string = transformToJSON(transform);
|
||||
|
||||
const hash = createSHA256();
|
||||
hash.update(nodesStr + transformString);
|
||||
hash.update(nodesStr + transformString + transform.id);
|
||||
return hash.digest();
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export async function runTransformations(
|
||||
nodes = [...nodesToRun];
|
||||
}
|
||||
if (docStoreStrategy) {
|
||||
nodes = await docStoreStrategy.transform(nodes);
|
||||
nodes = await docStoreStrategy(nodes);
|
||||
}
|
||||
for (const transform of transformations) {
|
||||
if (cache) {
|
||||
@@ -49,11 +49,11 @@ export async function runTransformations(
|
||||
if (cachedNodes) {
|
||||
nodes = cachedNodes;
|
||||
} else {
|
||||
nodes = await transform.transform(nodes, transformOptions);
|
||||
nodes = await transform(nodes, transformOptions);
|
||||
await cache.put(hash, nodes);
|
||||
}
|
||||
} else {
|
||||
nodes = await transform.transform(nodes, transformOptions);
|
||||
nodes = await transform(nodes, transformOptions);
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
import type { BaseNode, TransformComponent } from "@llamaindex/core/schema";
|
||||
import { BaseNode, TransformComponent } from "@llamaindex/core/schema";
|
||||
import type { BaseDocumentStore } from "../../storage/docStore/types.js";
|
||||
|
||||
/**
|
||||
* Handle doc store duplicates by checking all hashes.
|
||||
*/
|
||||
export class DuplicatesStrategy implements TransformComponent {
|
||||
export class DuplicatesStrategy extends TransformComponent {
|
||||
private docStore: BaseDocumentStore;
|
||||
|
||||
constructor(docStore: BaseDocumentStore) {
|
||||
super(async (nodes: BaseNode[]): Promise<BaseNode[]> => {
|
||||
const hashes = await this.docStore.getAllDocumentHashes();
|
||||
const currentHashes = new Set<string>();
|
||||
const nodesToRun: BaseNode[] = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
if (!(node.hash in hashes) && !currentHashes.has(node.hash)) {
|
||||
await this.docStore.setDocumentHash(node.id_, node.hash);
|
||||
nodesToRun.push(node);
|
||||
currentHashes.add(node.hash);
|
||||
}
|
||||
}
|
||||
|
||||
await this.docStore.addDocuments(nodesToRun, true);
|
||||
|
||||
return nodesToRun;
|
||||
});
|
||||
this.docStore = docStore;
|
||||
}
|
||||
|
||||
async transform(nodes: BaseNode[]): Promise<BaseNode[]> {
|
||||
const hashes = await this.docStore.getAllDocumentHashes();
|
||||
const currentHashes = new Set<string>();
|
||||
const nodesToRun: BaseNode[] = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
if (!(node.hash in hashes) && !currentHashes.has(node.hash)) {
|
||||
await this.docStore.setDocumentHash(node.id_, node.hash);
|
||||
nodesToRun.push(node);
|
||||
currentHashes.add(node.hash);
|
||||
}
|
||||
}
|
||||
|
||||
await this.docStore.addDocuments(nodesToRun, true);
|
||||
|
||||
return nodesToRun;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { BaseNode, TransformComponent } from "@llamaindex/core/schema";
|
||||
import { BaseNode, TransformComponent } from "@llamaindex/core/schema";
|
||||
import type { BaseDocumentStore } from "../../storage/docStore/types.js";
|
||||
import type { VectorStore } from "../../storage/vectorStore/types.js";
|
||||
import { classify } from "./classify.js";
|
||||
@@ -7,43 +7,42 @@ import { classify } from "./classify.js";
|
||||
* Handle docstore upserts by checking hashes and ids.
|
||||
* Identify missing docs and delete them from docstore and vector store
|
||||
*/
|
||||
export class UpsertsAndDeleteStrategy implements TransformComponent {
|
||||
export class UpsertsAndDeleteStrategy extends TransformComponent {
|
||||
protected docStore: BaseDocumentStore;
|
||||
protected vectorStores?: VectorStore[];
|
||||
|
||||
constructor(docStore: BaseDocumentStore, vectorStores?: VectorStore[]) {
|
||||
super(async (nodes: BaseNode[]): Promise<BaseNode[]> => {
|
||||
const { dedupedNodes, missingDocs, unusedDocs } = await classify(
|
||||
this.docStore,
|
||||
nodes,
|
||||
);
|
||||
|
||||
// remove unused docs
|
||||
for (const refDocId of unusedDocs) {
|
||||
await this.docStore.deleteRefDoc(refDocId, false);
|
||||
if (this.vectorStores) {
|
||||
for (const vectorStore of this.vectorStores) {
|
||||
await vectorStore.delete(refDocId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove missing docs
|
||||
for (const docId of missingDocs) {
|
||||
await this.docStore.deleteDocument(docId, true);
|
||||
if (this.vectorStores) {
|
||||
for (const vectorStore of this.vectorStores) {
|
||||
await vectorStore.delete(docId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.docStore.addDocuments(dedupedNodes, true);
|
||||
|
||||
return dedupedNodes;
|
||||
});
|
||||
this.docStore = docStore;
|
||||
this.vectorStores = vectorStores;
|
||||
}
|
||||
|
||||
async transform(nodes: BaseNode[]): Promise<BaseNode[]> {
|
||||
const { dedupedNodes, missingDocs, unusedDocs } = await classify(
|
||||
this.docStore,
|
||||
nodes,
|
||||
);
|
||||
|
||||
// remove unused docs
|
||||
for (const refDocId of unusedDocs) {
|
||||
await this.docStore.deleteRefDoc(refDocId, false);
|
||||
if (this.vectorStores) {
|
||||
for (const vectorStore of this.vectorStores) {
|
||||
await vectorStore.delete(refDocId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove missing docs
|
||||
for (const docId of missingDocs) {
|
||||
await this.docStore.deleteDocument(docId, true);
|
||||
if (this.vectorStores) {
|
||||
for (const vectorStore of this.vectorStores) {
|
||||
await vectorStore.delete(docId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.docStore.addDocuments(dedupedNodes, true);
|
||||
|
||||
return dedupedNodes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { BaseNode, TransformComponent } from "@llamaindex/core/schema";
|
||||
import { BaseNode, TransformComponent } from "@llamaindex/core/schema";
|
||||
import type { BaseDocumentStore } from "../../storage/docStore/types.js";
|
||||
import type { VectorStore } from "../../storage/vectorStore/types.js";
|
||||
import { classify } from "./classify.js";
|
||||
@@ -6,28 +6,27 @@ import { classify } from "./classify.js";
|
||||
/**
|
||||
* Handles doc store upserts by checking hashes and ids.
|
||||
*/
|
||||
export class UpsertsStrategy implements TransformComponent {
|
||||
export class UpsertsStrategy extends TransformComponent {
|
||||
protected docStore: BaseDocumentStore;
|
||||
protected vectorStores?: VectorStore[];
|
||||
|
||||
constructor(docStore: BaseDocumentStore, vectorStores?: VectorStore[]) {
|
||||
super(async (nodes: BaseNode[]): Promise<BaseNode[]> => {
|
||||
const { dedupedNodes, unusedDocs } = await classify(this.docStore, nodes);
|
||||
// remove unused docs
|
||||
for (const refDocId of unusedDocs) {
|
||||
await this.docStore.deleteRefDoc(refDocId, false);
|
||||
if (this.vectorStores) {
|
||||
for (const vectorStore of this.vectorStores) {
|
||||
await vectorStore.delete(refDocId);
|
||||
}
|
||||
}
|
||||
}
|
||||
// add non-duplicate docs
|
||||
await this.docStore.addDocuments(dedupedNodes, true);
|
||||
return dedupedNodes;
|
||||
});
|
||||
this.docStore = docStore;
|
||||
this.vectorStores = vectorStores;
|
||||
}
|
||||
|
||||
async transform(nodes: BaseNode[]): Promise<BaseNode[]> {
|
||||
const { dedupedNodes, unusedDocs } = await classify(this.docStore, nodes);
|
||||
// remove unused docs
|
||||
for (const refDocId of unusedDocs) {
|
||||
await this.docStore.deleteRefDoc(refDocId, false);
|
||||
if (this.vectorStores) {
|
||||
for (const vectorStore of this.vectorStores) {
|
||||
await vectorStore.delete(refDocId);
|
||||
}
|
||||
}
|
||||
}
|
||||
// add non-duplicate docs
|
||||
await this.docStore.addDocuments(dedupedNodes, true);
|
||||
return dedupedNodes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TransformComponent } from "@llamaindex/core/schema";
|
||||
import { TransformComponent } from "@llamaindex/core/schema";
|
||||
import type { BaseDocumentStore } from "../../storage/docStore/types.js";
|
||||
import type { VectorStore } from "../../storage/vectorStore/types.js";
|
||||
import { DuplicatesStrategy } from "./DuplicatesStrategy.js";
|
||||
@@ -19,9 +19,9 @@ export enum DocStoreStrategy {
|
||||
NONE = "none", // no-op strategy
|
||||
}
|
||||
|
||||
class NoOpStrategy implements TransformComponent {
|
||||
async transform(nodes: any[]): Promise<any[]> {
|
||||
return nodes;
|
||||
class NoOpStrategy extends TransformComponent {
|
||||
constructor() {
|
||||
super(async (nodes) => nodes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { getEnv } from "@llamaindex/env";
|
||||
import { OpenAI } from "./openai.js";
|
||||
|
||||
export const DEEPSEEK_MODELS = {
|
||||
"deepseek-coder": { contextWindow: 128000 },
|
||||
"deepseek-chat": { contextWindow: 128000 },
|
||||
};
|
||||
|
||||
type DeepSeekModelName = keyof typeof DEEPSEEK_MODELS;
|
||||
const DEFAULT_MODEL: DeepSeekModelName = "deepseek-coder";
|
||||
|
||||
export class DeepSeekLLM extends OpenAI {
|
||||
constructor(init?: Partial<OpenAI> & { model?: DeepSeekModelName }) {
|
||||
const {
|
||||
apiKey = getEnv("DEEPSEEK_API_KEY"),
|
||||
additionalSessionOptions = {},
|
||||
model = DEFAULT_MODEL,
|
||||
...rest
|
||||
} = init ?? {};
|
||||
|
||||
if (!apiKey) {
|
||||
throw new Error("Set DeepSeek Key in DEEPSEEK_API_KEY env variable");
|
||||
}
|
||||
|
||||
additionalSessionOptions.baseURL =
|
||||
additionalSessionOptions.baseURL ?? "https://api.deepseek.com/v1";
|
||||
|
||||
super({
|
||||
apiKey,
|
||||
additionalSessionOptions,
|
||||
model,
|
||||
...rest,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -34,5 +34,6 @@ export {
|
||||
ReplicateSession,
|
||||
} from "./replicate_ai.js";
|
||||
|
||||
export { DeepSeekLLM } from "./deepseek.js";
|
||||
export { TogetherLLM } from "./together.js";
|
||||
export * from "./types.js";
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
import type { JSONValue } from "@llamaindex/core/global";
|
||||
import { Document } from "@llamaindex/core/schema";
|
||||
import { FileReader } from "./type.js";
|
||||
|
||||
export interface JSONReaderOptions {
|
||||
/**
|
||||
* Whether to ensure only ASCII characters.
|
||||
* Converts non-ASCII characters to their unicode escape sequence.
|
||||
* @default false
|
||||
*/
|
||||
ensureAscii?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the JSON is in JSON Lines format.
|
||||
* Split into lines, remove empty lines, parse each line as JSON.
|
||||
* @default false
|
||||
*/
|
||||
isJsonLines?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to clean the JSON by filtering out structural characters (`{}, [], and ,`).
|
||||
* If set to false, it will just parse the JSON, not removing structural characters.
|
||||
* @default true
|
||||
*/
|
||||
cleanJson?: boolean;
|
||||
|
||||
/**
|
||||
* Specifies how many levels up the JSON structure to include in the output. cleanJson will be ignored.
|
||||
* If set to 0, all levels are included. If undefined, parses the entire JSON and treats each line as an embedding.
|
||||
* @default undefined
|
||||
*/
|
||||
levelsBack?: number;
|
||||
|
||||
/**
|
||||
* The maximum length of JSON string representation to be collapsed into a single line.
|
||||
* Only applicable when `levelsBack` is set.
|
||||
* @default undefined
|
||||
*/
|
||||
collapseLength?: number;
|
||||
}
|
||||
|
||||
export class JSONReaderError extends Error {}
|
||||
export class JSONParseError extends JSONReaderError {}
|
||||
export class JSONStringifyError extends JSONReaderError {}
|
||||
|
||||
/**
|
||||
* A reader that reads JSON data and returns an array of Document objects.
|
||||
* Supports various options to modify the output.
|
||||
*/
|
||||
export class JSONReader<T extends JSONValue> extends FileReader {
|
||||
private options: JSONReaderOptions;
|
||||
|
||||
constructor(options: JSONReaderOptions = {}) {
|
||||
super();
|
||||
this.options = {
|
||||
ensureAscii: false,
|
||||
isJsonLines: false,
|
||||
cleanJson: true,
|
||||
...options,
|
||||
};
|
||||
this.validateOptions();
|
||||
}
|
||||
private validateOptions(): void {
|
||||
const { levelsBack, collapseLength } = this.options;
|
||||
if (levelsBack !== undefined && levelsBack < 0) {
|
||||
throw new JSONReaderError("levelsBack must not be negative");
|
||||
}
|
||||
if (collapseLength !== undefined && collapseLength < 0) {
|
||||
throw new JSONReaderError("collapseLength must not be negative");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads JSON data and returns an array of Document objects.
|
||||
*
|
||||
* @param {Uint8Array} content - The JSON data as a Uint8Array.
|
||||
* @return {Promise<Document[]>} A Promise that resolves to an array of Document objects.
|
||||
*/
|
||||
async loadDataAsContent(content: Uint8Array): Promise<Document[]> {
|
||||
const jsonStr = new TextDecoder("utf-8").decode(content);
|
||||
const parser = this.parseJsonString(jsonStr);
|
||||
const documents: Document[] = [];
|
||||
|
||||
for await (const data of parser) {
|
||||
documents.push(await this.createDocument(data));
|
||||
}
|
||||
return documents;
|
||||
}
|
||||
|
||||
private async *parseJsonString(jsonStr: string): AsyncGenerator<T> {
|
||||
if (this.options.isJsonLines) {
|
||||
yield* this.parseJsonLines(jsonStr);
|
||||
} else {
|
||||
yield* this.parseJson(jsonStr);
|
||||
}
|
||||
}
|
||||
|
||||
private async *parseJsonLines(jsonStr: string): AsyncGenerator<T> {
|
||||
// Process each line as a separate JSON object for JSON Lines format
|
||||
for (const line of jsonStr.split("\n")) {
|
||||
if (line.trim() !== "") {
|
||||
try {
|
||||
yield JSON.parse(line.trim());
|
||||
} catch (e) {
|
||||
throw new JSONParseError(
|
||||
`Error parsing JSON Line: ${e} in "${line.trim()}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async *parseJson(jsonStr: string): AsyncGenerator<T> {
|
||||
try {
|
||||
// TODO: Add streaming to handle large JSON files
|
||||
const parsedData = JSON.parse(jsonStr);
|
||||
|
||||
if (!this.options.cleanJson) {
|
||||
// Yield the parsed data directly if cleanJson is false
|
||||
yield parsedData;
|
||||
} else if (Array.isArray(parsedData)) {
|
||||
// Check if it's an Array, if so yield each item seperately, i.e. create a document per top-level array of the json
|
||||
for (const item of parsedData) {
|
||||
yield item;
|
||||
}
|
||||
} else {
|
||||
// If not an array, just yield the parsed data
|
||||
yield parsedData;
|
||||
}
|
||||
} catch (e) {
|
||||
throw new JSONParseError(`Error parsing JSON: ${e} in "${jsonStr}"`);
|
||||
}
|
||||
}
|
||||
|
||||
private async createDocument(data: T): Promise<Document> {
|
||||
const docText: string =
|
||||
this.options.levelsBack === undefined
|
||||
? this.formatJsonString(data)
|
||||
: await this.prepareDepthFirstYield(data);
|
||||
|
||||
return new Document({
|
||||
text: this.options.ensureAscii ? this.convertToAscii(docText) : docText,
|
||||
metadata: {
|
||||
doc_length: docText.length,
|
||||
traversal_data: {
|
||||
levels_back: this.options.levelsBack,
|
||||
collapse_length: this.options.collapseLength,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async prepareDepthFirstYield(data: T): Promise<string> {
|
||||
const levelsBack = this.options.levelsBack ?? 0;
|
||||
const results: string[] = [];
|
||||
for await (const value of this.depthFirstYield(
|
||||
data,
|
||||
levelsBack === 0 ? Infinity : levelsBack,
|
||||
[],
|
||||
this.options.collapseLength,
|
||||
)) {
|
||||
results.push(value);
|
||||
}
|
||||
return results.join("\n");
|
||||
}
|
||||
|
||||
// Note: JSON.stringify does not differentiate between indent "undefined/null"(= no whitespaces) and "0"(= no whitespaces, but linebreaks)
|
||||
// as python json.dumps does. Thats why we use indent 1 and remove the leading spaces.
|
||||
|
||||
private formatJsonString(data: T): string {
|
||||
try {
|
||||
const jsonStr = JSON.stringify(
|
||||
data,
|
||||
null,
|
||||
this.options.cleanJson ? 1 : 0,
|
||||
);
|
||||
if (this.options.cleanJson) {
|
||||
// Clean JSON by removing structural characters and unnecessary whitespace
|
||||
return jsonStr
|
||||
.split("\n")
|
||||
.filter((line) => !/^[{}\[\],]*$/.test(line.trim()))
|
||||
.map((line) => line.trimStart()) // Removes the indent
|
||||
.join("\n");
|
||||
}
|
||||
return jsonStr;
|
||||
} catch (e) {
|
||||
throw new JSONStringifyError(
|
||||
`Error stringifying JSON: ${e} in "${data}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A generator function that determines the next step in traversing the JSON data.
|
||||
* If the serialized JSON string is not null, it yields the string and returns.
|
||||
* If the JSON data is an object, it delegates the traversal to the depthFirstTraversal method.
|
||||
* Otherwise, it yields the JSON data as a string.
|
||||
*
|
||||
* @param jsonData - The JSON data to traverse.
|
||||
* @param levelsBack - The number of levels up the JSON structure to include in the output.
|
||||
* @param path - The current path in the JSON structure.
|
||||
* @param collapseLength - The maximum length of JSON string representation to be collapsed into a single line.
|
||||
* @throws {JSONReaderError} - Throws an error if there is an issue during the depth-first traversal.
|
||||
*/
|
||||
private async *depthFirstYield(
|
||||
jsonData: T,
|
||||
levelsBack: number,
|
||||
path: string[],
|
||||
collapseLength?: number,
|
||||
): AsyncGenerator<string> {
|
||||
try {
|
||||
const jsonStr = this.serializeAndCollapse(
|
||||
jsonData,
|
||||
levelsBack,
|
||||
path,
|
||||
collapseLength,
|
||||
);
|
||||
if (jsonStr !== null) {
|
||||
yield jsonStr;
|
||||
return;
|
||||
}
|
||||
|
||||
if (jsonData !== null && typeof jsonData === "object") {
|
||||
yield* this.depthFirstTraversal(
|
||||
jsonData,
|
||||
levelsBack,
|
||||
path,
|
||||
collapseLength,
|
||||
);
|
||||
} else {
|
||||
yield `${path.slice(-levelsBack).join(" ")} ${String(jsonData)}`;
|
||||
}
|
||||
} catch (e) {
|
||||
throw new JSONReaderError(
|
||||
`Error during depth first traversal at path ${path.join(" ")}: ${e}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private serializeAndCollapse(
|
||||
jsonData: T,
|
||||
levelsBack: number,
|
||||
path: string[],
|
||||
collapseLength?: number,
|
||||
): string | null {
|
||||
try {
|
||||
const jsonStr = JSON.stringify(jsonData);
|
||||
return collapseLength !== undefined && jsonStr.length <= collapseLength
|
||||
? `${path.slice(-levelsBack).join(" ")} ${jsonStr}`
|
||||
: null;
|
||||
} catch (e) {
|
||||
throw new JSONStringifyError(`Error stringifying JSON data: ${e}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A generator function that performs a depth-first traversal of the JSON data.
|
||||
* If the JSON data is an array, it traverses each item in the array.
|
||||
* If the JSON data is an object, it traverses each key-value pair in the object.
|
||||
* For each traversed item or value, it performs a depth-first yield.
|
||||
*
|
||||
* @param jsonData - The JSON data to traverse.
|
||||
* @param levelsBack - The number of levels up the JSON structure to include in the output.
|
||||
* @param path - The current path in the JSON structure.
|
||||
* @param collapseLength - The maximum length of JSON string representation to be collapsed into a single line.
|
||||
* @throws {JSONReaderError} - Throws an error if there is an issue during the depth-first traversal of the object.
|
||||
*/
|
||||
private async *depthFirstTraversal(
|
||||
jsonData: T,
|
||||
levelsBack: number,
|
||||
path: string[],
|
||||
collapseLength?: number,
|
||||
): AsyncGenerator<string> {
|
||||
try {
|
||||
if (Array.isArray(jsonData)) {
|
||||
for (const item of jsonData) {
|
||||
yield* this.depthFirstYield(item, levelsBack, path, collapseLength);
|
||||
}
|
||||
} else if (jsonData !== null && typeof jsonData === "object") {
|
||||
const originalLength = path.length;
|
||||
for (const [key, value] of Object.entries(jsonData)) {
|
||||
path.push(key);
|
||||
if (value !== null) {
|
||||
yield* this.depthFirstYield(
|
||||
value as T,
|
||||
levelsBack,
|
||||
path,
|
||||
collapseLength,
|
||||
);
|
||||
}
|
||||
path.length = originalLength; // Reset path length to original. Avoids cloning the path array every time.
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
throw new JSONReaderError(
|
||||
`Error during depth-first traversal of object: ${e}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private convertToAscii(str: string): string {
|
||||
return str.replace(
|
||||
/[\u007F-\uFFFF]/g,
|
||||
(char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -127,8 +127,12 @@ export class LlamaParseReader extends FileReader {
|
||||
fastMode?: boolean;
|
||||
// Wether to keep column in the text according to document layout. Reduce reconstruction accuracy, and LLM's/embedings performances in most cases.
|
||||
doNotUnrollColumns?: boolean;
|
||||
// The page separator to use to split the text. Default is None, which means the parser will use the default separator '\\n---\\n'.
|
||||
// A templated page separator to use to split the text. If the results contain `{page_number}` (e.g. JSON mode), it will be replaced by the next page number. If not set the default separator '\\n---\\n' will be used.
|
||||
pageSeparator?: string;
|
||||
//A templated prefix to add to the beginning of each page. If the results contain `{page_number}`, it will be replaced by the page number.>
|
||||
pagePrefix?: string;
|
||||
// A templated suffix to add to the end of each page. If the results contain `{page_number}`, it will be replaced by the page number.
|
||||
pageSuffix?: string;
|
||||
// Deprecated. Use vendorMultimodal params. Whether to use gpt-4o to extract text from documents.
|
||||
gpt4oMode: boolean = false;
|
||||
// Deprecated. Use vendorMultimodal params. The API key for the GPT-4o API. Optional, lowers the cost of parsing. Can be set as an env variable: LLAMA_CLOUD_GPT4O_API_KEY.
|
||||
@@ -198,6 +202,8 @@ export class LlamaParseReader extends FileReader {
|
||||
fast_mode: this.fastMode?.toString(),
|
||||
do_not_unroll_columns: this.doNotUnrollColumns?.toString(),
|
||||
page_separator: this.pageSeparator,
|
||||
page_prefix: this.pagePrefix,
|
||||
page_suffix: this.pageSuffix,
|
||||
gpt4o_mode: this.gpt4oMode?.toString(),
|
||||
gpt4o_api_key: this.gpt4oApiKey,
|
||||
bounding_box: this.boundingBox,
|
||||
@@ -207,8 +213,17 @@ export class LlamaParseReader extends FileReader {
|
||||
vendor_multimodal_api_key: this.vendorMultimodalApiKey,
|
||||
};
|
||||
|
||||
// Filter out params with invalid values that would cause issues on the backend.
|
||||
const filteredParams = this.filterSpecificParams(LlamaParseBodyParams, [
|
||||
"page_separator",
|
||||
"page_prefix",
|
||||
"page_suffix",
|
||||
"bounding_box",
|
||||
"target_pages",
|
||||
]);
|
||||
|
||||
// Appends body with any defined LlamaParseBodyParams
|
||||
Object.entries(LlamaParseBodyParams).forEach(([key, value]) => {
|
||||
Object.entries(filteredParams).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
body.append(key, value);
|
||||
}
|
||||
@@ -330,13 +345,18 @@ export class LlamaParseReader extends FileReader {
|
||||
* Loads data from a file and returns an array of JSON objects.
|
||||
* To be used with resultType = "json"
|
||||
*
|
||||
* @param {string} file - The path to the file to be loaded.
|
||||
* @param {string} filePathOrContent - The file path to the file or the content of the file as a Buffer
|
||||
* @return {Promise<Record<string, any>[]>} A Promise that resolves to an array of JSON objects.
|
||||
*/
|
||||
async loadJson(file: string): Promise<Record<string, any>[]> {
|
||||
async loadJson(
|
||||
filePathOrContent: string | Uint8Array,
|
||||
): Promise<Record<string, any>[]> {
|
||||
let jobId;
|
||||
const isFilePath = typeof filePathOrContent === "string";
|
||||
try {
|
||||
const data = await fs.readFile(file);
|
||||
const data = isFilePath
|
||||
? await fs.readFile(filePathOrContent)
|
||||
: filePathOrContent;
|
||||
// Creates a job for the file
|
||||
jobId = await this.createJob(data);
|
||||
if (this.verbose) {
|
||||
@@ -346,7 +366,7 @@ export class LlamaParseReader extends FileReader {
|
||||
// Return results as an array of JSON objects (same format as Python version of the reader)
|
||||
const resultJson = await this.getJobResult(jobId, "json");
|
||||
resultJson.job_id = jobId;
|
||||
resultJson.file_path = file;
|
||||
resultJson.file_path = isFilePath ? filePathOrContent : undefined;
|
||||
return [resultJson];
|
||||
} catch (e) {
|
||||
console.error(`Error while parsing the file under job id ${jobId}`, e);
|
||||
@@ -447,6 +467,24 @@ export class LlamaParseReader extends FileReader {
|
||||
await fs.writeFile(imagePath, buffer);
|
||||
}
|
||||
|
||||
// Filters out invalid values (null, undefined, empty string) of specific params.
|
||||
private filterSpecificParams(
|
||||
params: Record<string, any>,
|
||||
keysToCheck: string[],
|
||||
): Record<string, any> {
|
||||
const filteredParams: Record<string, any> = {};
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (keysToCheck.includes(key)) {
|
||||
if (value !== null && value !== undefined && value !== "") {
|
||||
filteredParams[key] = value;
|
||||
}
|
||||
} else {
|
||||
filteredParams[key] = value;
|
||||
}
|
||||
}
|
||||
return filteredParams;
|
||||
}
|
||||
|
||||
static async getMimeType(
|
||||
data: Uint8Array,
|
||||
): Promise<{ mime: string; extension: string }> {
|
||||
|
||||
@@ -4,6 +4,7 @@ export * from "./DiscordReader.js";
|
||||
export * from "./DocxReader.js";
|
||||
export * from "./HTMLReader.js";
|
||||
export * from "./ImageReader.js";
|
||||
export * from "./JSONReader.js";
|
||||
export * from "./LlamaParseReader.js";
|
||||
export * from "./MarkdownReader.js";
|
||||
export * from "./NotionReader.js";
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @llamaindex/core-test
|
||||
|
||||
## 0.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5d5716b: feat: add a reader for JSON data
|
||||
|
||||
## 0.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@llamaindex/llamaindex-test",
|
||||
"private": true,
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.6",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "vitest run"
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import {
|
||||
JSONParseError,
|
||||
JSONReader,
|
||||
JSONReaderError,
|
||||
type JSONValue,
|
||||
} from "llamaindex";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
const content = new TextEncoder().encode(
|
||||
'{"a": {"1": {"key1": "value1"}, "2": {"key2": "value2"}}, "b": {"c": "d"}}',
|
||||
);
|
||||
|
||||
describe("JSONReader", () => {
|
||||
let reader: JSONReader<JSONValue>;
|
||||
|
||||
beforeEach(() => {
|
||||
reader = new JSONReader();
|
||||
});
|
||||
|
||||
describe("constructor", () => {
|
||||
it("should set default options", () => {
|
||||
expect(reader["options"]).toEqual({
|
||||
ensureAscii: false,
|
||||
isJsonLines: false,
|
||||
cleanJson: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should validate options", () => {
|
||||
expect(() => new JSONReader({ levelsBack: -1 })).toThrow(JSONReaderError);
|
||||
expect(() => new JSONReader({ collapseLength: -1 })).toThrow(
|
||||
JSONReaderError,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("loadDataAsContent", () => {
|
||||
it("should load and parse valid JSON content", async () => {
|
||||
const docs = await reader.loadDataAsContent(content);
|
||||
expect(docs).toHaveLength(1);
|
||||
expect(docs[0].text).toContain('"key1": "value1"');
|
||||
});
|
||||
|
||||
it("should throw JSONParseError for invalid JSON content", async () => {
|
||||
const content = new TextEncoder().encode("invalid json");
|
||||
await expect(reader.loadDataAsContent(content)).rejects.toThrow(
|
||||
JSONParseError,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isJsonLines option", () => {
|
||||
it("should handle JSON Lines format", async () => {
|
||||
reader = new JSONReader({ isJsonLines: true });
|
||||
const content = new TextEncoder().encode(
|
||||
'{"key1": "value1"}\n{"key2": "value2"}\n',
|
||||
);
|
||||
const docs = await reader.loadDataAsContent(content);
|
||||
expect(docs).toHaveLength(2);
|
||||
expect(docs[0].text).toBe('"key1": "value1"');
|
||||
expect(docs[1].text).toBe('"key2": "value2"');
|
||||
});
|
||||
|
||||
it("should skip empty lines in JSON Lines format", async () => {
|
||||
reader = new JSONReader({ isJsonLines: true });
|
||||
const content = new TextEncoder().encode(
|
||||
'{"key1": "value1"}\n\n{"key2": "value2"}\n',
|
||||
);
|
||||
const docs = await reader.loadDataAsContent(content);
|
||||
expect(docs).toHaveLength(2);
|
||||
expect(docs[0].text).toBe('"key1": "value1"');
|
||||
expect(docs[1].text).toBe('"key2": "value2"');
|
||||
});
|
||||
});
|
||||
|
||||
describe("ensureAscii option", () => {
|
||||
it("should convert non-ASCII characters to unicode escape sequences", async () => {
|
||||
reader = new JSONReader({ ensureAscii: true });
|
||||
const content = new TextEncoder().encode('{"key": "valüe"}');
|
||||
const docs = await reader.loadDataAsContent(content);
|
||||
expect(docs[0].text).toBe('"key": "val\\u00fce"');
|
||||
});
|
||||
|
||||
it("should not alter ASCII characters", async () => {
|
||||
reader = new JSONReader({ ensureAscii: true });
|
||||
const content = new TextEncoder().encode('{"key": "value"}');
|
||||
const docs = await reader.loadDataAsContent(content);
|
||||
expect(docs[0].text).toBe('"key": "value"');
|
||||
});
|
||||
});
|
||||
|
||||
describe("levelsBack option", () => {
|
||||
it("should create document with levelsBack option", async () => {
|
||||
reader = new JSONReader({ levelsBack: 1 });
|
||||
const docs = await reader.loadDataAsContent(content);
|
||||
expect(docs[0].text).toContain("key1 value1");
|
||||
expect(docs[0].text).toContain("c d");
|
||||
});
|
||||
|
||||
it("should traverse all levels with levelsBack 0", async () => {
|
||||
reader = new JSONReader({ levelsBack: 0 });
|
||||
const docs = await reader.loadDataAsContent(content);
|
||||
expect(docs[0].text).toContain("a 1 key1 value1");
|
||||
expect(docs[0].text).toContain("a 2 key2 value2");
|
||||
expect(docs[0].text).toContain("b c d");
|
||||
});
|
||||
});
|
||||
describe("collapseLength option", () => {
|
||||
it("should collapse values based on collapseLength", async () => {
|
||||
reader = new JSONReader({ collapseLength: 10, levelsBack: 0 });
|
||||
const docs = await reader.loadDataAsContent(content);
|
||||
expect(docs[0].text).toContain('a 1 key1 "value1"');
|
||||
expect(docs[0].text).toContain('b {"c":"d"}');
|
||||
expect(docs[0].metadata.traversal_data.collapse_length).toBe(10);
|
||||
expect(docs[0].metadata.traversal_data.levels_back).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cleanJson option", () => {
|
||||
it("should remove JSON structural characters", async () => {
|
||||
reader = new JSONReader({ cleanJson: true });
|
||||
const docs = await reader.loadDataAsContent(content);
|
||||
expect(docs[0].text).toContain('"key1": "value1"');
|
||||
expect(docs[0].text).toContain('"a": {');
|
||||
});
|
||||
|
||||
it("should not remove JSON structural characters, but white spaces", async () => {
|
||||
reader = new JSONReader({ cleanJson: false });
|
||||
const docs = await reader.loadDataAsContent(content);
|
||||
expect(docs[0].text).toBe(
|
||||
'{"a":{"1":{"key1":"value1"},"2":{"key2":"value2"}},"b":{"c":"d"}}',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Generated
+3
@@ -352,6 +352,9 @@ importers:
|
||||
'@llamaindex/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
'@llamaindex/env':
|
||||
specifier: workspace:*
|
||||
version: link:../env
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^20.14.2
|
||||
|
||||
Reference in New Issue
Block a user