Compare commits

...

16 Commits

Author SHA1 Message Date
Marcus Schiesser bcc3d0b4d1 release v0.2.9 2024-04-17 13:53:31 +08:00
Marcus Schiesser 238ca86534 fix: google fonts not reachable during build 2024-04-17 13:47:11 +08:00
Marcus Schiesser 1f3efe8947 fix: ensure to use build for examples (#729) 2024-04-17 10:40:02 +07:00
yemiscale3 89324b4067 docs: Add Langtrace to observability tools (#726)
Co-authored-by: Yi Ding <yi.s.ding@gmail.com>
2024-04-16 20:13:59 -07:00
Alex Yang 8cc848aee6 docs: fix example code (#727) 2024-04-16 16:00:26 -05:00
Alex Yang cd54a7a66b docs: remove verbose (#725) 2024-04-16 15:54:40 -05:00
Marcus Schiesser dca02f7277 refactor: VectorStoreIndex: use TransformerComponent to calc embeddings (#721) 2024-04-16 10:01:26 +07:00
Marcus Schiesser b757fa9aa3 fix: type-check of modified example 2024-04-16 10:35:26 +08:00
Marcus Schiesser bc594a0674 doc: update vector index example to show source nodes 2024-04-16 10:27:34 +08:00
Alex Yang 208282d62f feat: init anthropic agent (part 2) (#719) 2024-04-15 16:22:47 -05:00
Wessel 060880abfe fix: toolretriever for Agent OpenAI broken (#718) 2024-04-15 13:45:19 -05:00
Marcus Schiesser 728b35e774 chore: remove LLM.ts (#720) 2024-04-14 23:35:15 -05:00
Alex Yang bdaa043404 feat: init claude function call (part 1) (#717) 2024-04-14 15:55:34 -05:00
Alex Yang a55cf8d870 fix: type import 2024-04-14 01:30:54 -05:00
Alex Yang cf4244fd3a chore: put eslint into top level (#716) 2024-04-13 20:39:27 -05:00
Marcus Schiesser 76c3fd64ad feat: add scores to source nodes (#714) 2024-04-12 09:28:46 -07:00
83 changed files with 14248 additions and 10140 deletions
@@ -1,12 +1,22 @@
module.exports = {
root: true,
extends: [
"next",
"turbo",
"prettier",
"plugin:@typescript-eslint/recommended-type-checked-only",
],
parserOptions: {
project: true,
__tsconfigRootDir: __dirname,
},
settings: {
react: {
version: "999.999.999",
},
},
rules: {
"@next/next/no-html-link-for-pages": "off",
"max-params": ["error", 4],
"prefer-const": "error",
"@typescript-eslint/no-floating-promises": [
"error",
{
@@ -54,22 +64,13 @@ module.exports = {
"@typescript-eslint/triple-slash-reference": "off",
"@typescript-eslint/unbound-method": "off",
},
// NOTE I think because we've temporarily removed all of the NextJS stuff
// from the turborepo not having next in the devDeps causes an error on only
// clean clones of the repo
// Not sure if this is a missing dependency in the package.json or just my not
// understanding how turborepo is supposed to work.
// Anyways, planning to add back a Next.JS example soon
parserOptions: {
babelOptions: {
presets: [require.resolve("next/babel")],
overrides: [
{
files: ["examples/**/*.ts"],
rules: {
"turbo/no-undeclared-env-vars": "off",
},
},
project: true,
__tsconfigRootDir: __dirname,
},
settings: {
react: {
version: "999.999.999",
},
},
],
ignorePatterns: ["dist/", "lib/"],
};
-23
View File
@@ -1,23 +0,0 @@
module.exports = {
root: true,
// This tells ESLint to load the config from the package `eslint-config-custom`
extends: ["custom"],
settings: {
next: {
rootDir: ["apps/*/"],
},
},
rules: {
"max-params": ["error", 4],
"prefer-const": "error",
},
overrides: [
{
files: ["examples/**/*.ts"],
rules: {
"turbo/no-undeclared-env-vars": "off",
},
},
],
ignorePatterns: ["dist/", "lib/"],
};
@@ -14,8 +14,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: latest
- name: Setup Node.js
uses: actions/setup-node@v4
with:
+3
View File
@@ -68,6 +68,9 @@ jobs:
run: pnpm install
- name: Build
run: pnpm run build --filter llamaindex
- name: Use Build For Examples
run: pnpm link ../packages/core/
working-directory: ./examples
- name: Run Type Check
run: pnpm run type-check
- name: Run Circular Dependency Check
+1 -1
View File
@@ -154,7 +154,7 @@ If you need any of those classes, you have to import them instead directly. Here
import { PineconeVectorStore } from "@llamaindex/edge/storage/vectorStore/PineconeVectorStore";
```
As the `PDFReader` is not with the Edge runtime, here's how to use the `SimpleDirectoryReader` with the `LlamaParseReader` to load PDFs:
As the `PDFReader` is not working with the Edge runtime, here's how to use the `SimpleDirectoryReader` with the `LlamaParseReader` to load PDFs:
```typescript
import { SimpleDirectoryReader } from "@llamaindex/edge/readers/SimpleDirectoryReader";
-1
View File
@@ -67,7 +67,6 @@ async function main() {
// Create an OpenAIAgent with the function tools
const agent = new OpenAIAgent({
tools: [sumFunctionTool, divideFunctionTool],
verbose: true,
});
// Chat with the agent
@@ -221,7 +221,6 @@ for (const title of wikiTitles) {
const agent = new OpenAIAgent({
tools: queryEngineTools,
llm,
verbose: true,
});
documentAgents[title] = agent;
@@ -282,7 +281,6 @@ const objectIndex = await ObjectIndex.fromObjects(
const topAgent = new OpenAIAgent({
toolRetriever: await objectIndex.asRetriever({}),
llm,
verbose: true,
prefixMessages: [
{
content:
-2
View File
@@ -88,7 +88,6 @@ Now we can create an OpenAIAgent with the function tools.
```ts
const agent = new OpenAIAgent({
tools: [sumFunctionTool, divideFunctionTool],
verbose: true,
});
```
@@ -169,7 +168,6 @@ async function main() {
// Create an OpenAIAgent with the function tools
const agent = new OpenAIAgent({
tools: [sumFunctionTool, divideFunctionTool],
verbose: true,
});
// Chat with the agent
@@ -64,7 +64,6 @@ const queryEngineTool = new QueryEngineTool({
const agent = new OpenAIAgent({
tools: [queryEngineTool],
verbose: true,
});
```
@@ -114,7 +113,6 @@ async function main() {
// Create an OpenAIAgent with the function tools
const agent = new OpenAIAgent({
tools: [queryEngineTool],
verbose: true,
});
// Chat with the agent
@@ -90,7 +90,6 @@ Now we can create an OpenAIAgent with the function tools.
```ts
const agent = new ReActAgent({
tools: [sumFunctionTool, divideFunctionTool],
verbose: true,
});
```
@@ -185,7 +184,6 @@ async function main() {
// Create an OpenAIAgent with the function tools
const agent = new OpenAIAgent({
tools: [sumFunctionTool, divideFunctionTool],
verbose: true,
});
// Chat with the agent
@@ -3,7 +3,7 @@
## Usage
```ts
import { Ollama, Settings } from "llamaindex";
import { Ollama, Settings, DeuceChatStrategy } from "llamaindex";
Settings.llm = new LlamaDeuce({ chatStrategy: DeuceChatStrategy.META });
```
@@ -11,7 +11,12 @@ Settings.llm = new LlamaDeuce({ chatStrategy: DeuceChatStrategy.META });
## Usage with Replication
```ts
import { Ollama, ReplicateSession, Settings } from "llamaindex";
import {
Ollama,
ReplicateSession,
Settings,
DeuceChatStrategy,
} from "llamaindex";
const replicateSession = new ReplicateSession({
replicateKey,
@@ -48,7 +53,13 @@ const results = await queryEngine.query({
## Full Example
```ts
import { LlamaDeuce, Document, VectorStoreIndex, Settings } from "llamaindex";
import {
LlamaDeuce,
Document,
VectorStoreIndex,
Settings,
DeuceChatStrategy,
} from "llamaindex";
// Use the LlamaDeuce LLM
Settings.llm = new LlamaDeuce({ chatStrategy: DeuceChatStrategy.META });
+29
View File
@@ -14,6 +14,9 @@ Configure a variable once, and you'll be able to do things like the following:
Each provider has similarities and differences. Take a look below for the full set of guides for each one!
- [OpenLLMetry](#openllmetry)
- [Langtrace](#langtrace)
## OpenLLMetry
[OpenLLMetry](https://github.com/traceloop/openllmetry-js) is an open-source project based on OpenTelemetry for tracing and monitoring
@@ -33,3 +36,29 @@ traceloop.initialize({
disableBatch: true,
});
```
## Langtrace
Enhance your observability with Langtrace, a robust open-source tool supports OpenTelemetry and is designed to trace, evaluate, and manage LLM applications seamlessly. Langtrace integrates directly with LlamaIndex, offering detailed, real-time insights into performance metrics such as accuracy, evaluations, and latency.
#### Install
- Self-host or sign-up and generate an API key using [Langtrace](https://www.langtrace.ai) Cloud
```bash
npm install @langtrase/typescript-sdk
```
#### Initialize
```js
import * as Langtrace from "@langtrase/typescript-sdk";
Langtrace.init({ api_key: "<YOUR_API_KEY>" });
```
Features:
- OpenTelemetry compliant, ensuring broad compatibility with observability platforms.
- Provides comprehensive logs and detailed traces of all components.
- Real-time monitoring of accuracy, evaluations, usage, costs, and latency.
- For more configuration options and details, visit [Langtrace Docs](https://docs.langtrace.ai/introduction).
+43
View File
@@ -0,0 +1,43 @@
import { FunctionTool, Settings, WikipediaTool } from "llamaindex";
import { AnthropicAgent } from "llamaindex/agent/anthropic";
Settings.callbackManager.on("llm-tool-call", (event) => {
console.log("llm-tool-call", event.detail.payload.toolCall);
});
const agent = new AnthropicAgent({
tools: [
FunctionTool.from<{ location: string }>(
(query) => {
return `The weather in ${query.location} is sunny`;
},
{
name: "weather",
description: "Get the weather",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "The location to get the weather for",
},
},
required: ["location"],
},
},
),
new WikipediaTool(),
],
});
async function main() {
// https://docs.anthropic.com/claude/docs/tool-use#tool-use-best-practices-and-limitations
const { response } = await agent.chat({
message:
"What is the weather in New York? What's the history of New York from Wikipedia in 3 sentences?",
});
console.log(response);
}
void main();
+2
View File
@@ -4,6 +4,7 @@ import {
VectorStoreIndex,
storageContextFromDefaults,
} from "llamaindex";
import { DocStoreStrategy } from "llamaindex/ingestion/strategies/index";
import * as path from "path";
@@ -31,6 +32,7 @@ async function generateDatasource() {
});
await VectorStoreIndex.fromDocuments(documents, {
storageContext,
docStoreStrategy: DocStoreStrategy.NONE,
});
});
console.log(`Storage successfully generated in ${ms / 1000}s.`);
+4 -1
View File
@@ -35,7 +35,10 @@ async function main() {
const stream = await llm.chat({ ...args, stream: true });
for await (const chunk of stream) {
process.stdout.write(chunk.delta);
console.log(chunk.options?.toolCalls?.[0]);
if (chunk.options && "toolCall" in chunk.options) {
console.log("Tool call:");
console.log(chunk.options.toolCall);
}
}
}
+17 -4
View File
@@ -1,6 +1,11 @@
import fs from "node:fs/promises";
import { Document, VectorStoreIndex } from "llamaindex";
import {
Document,
MetadataMode,
NodeWithScore,
VectorStoreIndex,
} from "llamaindex";
async function main() {
// Load essay from abramov.txt in Node
@@ -16,12 +21,20 @@ async function main() {
// Query the index
const queryEngine = index.asQueryEngine();
const response = await queryEngine.query({
const { response, sourceNodes } = await queryEngine.query({
query: "What did the author do in college?",
});
// Output response
console.log(response.toString());
// Output response with sources
console.log(response);
if (sourceNodes) {
sourceNodes.forEach((source: NodeWithScore, index: number) => {
console.log(
`\n${index}: Score: ${source.score} - ${source.node.getContent(MetadataMode.NONE).substring(0, 50)}...\n`,
);
});
}
}
main().catch(console.error);
+7 -3
View File
@@ -15,13 +15,17 @@
"release": "pnpm run check-minor-version && pnpm run build:release && changeset publish",
"release-snapshot": "pnpm run check-minor-version && pnpm run build:release && changeset publish --tag snapshot",
"check-minor-version": "node ./scripts/check-minor-version",
"new-version": "pnpm run build:release && changeset version && pnpm run check-minor-version",
"new-version": "changeset version && pnpm run check-minor-version && pnpm run build:release",
"new-snapshot": "pnpm run build:release && changeset version --snapshot"
},
"devDependencies": {
"@changesets/cli": "^2.27.1",
"@typescript-eslint/eslint-plugin": "^7.7.0",
"eslint": "^8.57.0",
"eslint-config-custom": "workspace:*",
"eslint-config-next": "^13.5.6",
"eslint-config-prettier": "^8.10.0",
"eslint-config-turbo": "^1.13.2",
"eslint-plugin-react": "7.28.0",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"prettier": "^3.2.5",
@@ -29,7 +33,7 @@
"turbo": "^1.13.2",
"typescript": "^5.4.5"
},
"packageManager": "pnpm@8.15.6+sha256.01c01eeb990e379b31ef19c03e9d06a14afa5250b82e81303f88721c99ff2e6f",
"packageManager": "pnpm@9.0.1+sha256.46d50ee2afecb42b185ebbd662dc7bdd52ef5be56bf035bb615cab81a75345df",
"pnpm": {
"overrides": {
"trim": "1.0.1",
+11
View File
@@ -1,5 +1,16 @@
# llamaindex
## 0.2.9
### Patch Changes
- 76c3fd6: Add score to source nodes response
- 208282d: feat: init anthropic agent
remove the `tool` | `function` type in `MessageType`. Replace with `assistant` instead.
This is because these two types are only available for `OpenAI`.
Since `OpenAI` deprecates the function type, we support the Claude 3 tool call.
## 0.2.8
### Patch Changes
@@ -28,6 +28,7 @@ export class OpenAIEmbedding implements BaseEmbedding {
}
async transform(nodes: BaseNode[], _options?: any): Promise<BaseNode[]> {
nodes.forEach((node) => (node.embedding = [0]));
return nodes;
}
}
@@ -0,0 +1,3 @@
import { OpenAI } from "./open_ai.js";
export class Anthropic extends OpenAI {}
+3 -2
View File
@@ -9,7 +9,7 @@ import type {
LLMCompletionParamsStreaming,
} from "llamaindex/llm/types";
import { extractText } from "llamaindex/llm/utils";
import { strictEqual } from "node:assert";
import { deepStrictEqual, strictEqual } from "node:assert";
import { llmCompleteMockStorage } from "../../node/utils.js";
export function getOpenAISession() {
@@ -21,6 +21,7 @@ export function isFunctionCallingModel() {
}
export class OpenAI implements LLM {
supportToolCall = true;
get metadata() {
return {
model: "mock-model",
@@ -48,7 +49,7 @@ export class OpenAI implements LLM {
strictEqual(chatMessage.length, params.messages.length);
for (let i = 0; i < chatMessage.length; i++) {
strictEqual(chatMessage[i].role, params.messages[i].role);
strictEqual(chatMessage[i].content, params.messages[i].content);
deepStrictEqual(chatMessage[i].content, params.messages[i].content);
}
if (llmCompleteMockStorage.llmEventEnd.length > 0) {
+129
View File
@@ -0,0 +1,129 @@
import { consola } from "consola";
import { Anthropic, FunctionTool, Settings, type LLM } from "llamaindex";
import { AnthropicAgent } from "llamaindex/agent/anthropic";
import { ok } from "node:assert";
import { beforeEach, test } from "node:test";
import { sumNumbersTool } from "./fixtures/tools.js";
import { mockLLMEvent } from "./utils.js";
let llm: LLM;
beforeEach(async () => {
Settings.llm = new Anthropic({
model: "claude-3-opus",
});
llm = Settings.llm;
});
await test("anthropic llm", async (t) => {
await mockLLMEvent(t, "llm-anthropic");
await t.test("llm.chat", async () => {
const response = await llm.chat({
messages: [
{
content: "Hello",
role: "user",
options: {},
},
],
});
consola.debug("response:", response);
ok(typeof response.message.content === "string");
});
await t.test("stream llm.chat", async () => {
const iter = await llm.chat({
stream: true,
messages: [
{
content: "hello",
role: "user",
},
],
});
for await (const chunk of iter) {
consola.debug("chunk:", chunk);
ok(typeof chunk.delta === "string");
}
});
});
await test("anthropic agent", async (t) => {
await mockLLMEvent(t, "anthropic-agent");
await t.test("chat", async () => {
const agent = new AnthropicAgent({
tools: [
{
call: async () => {
return "35 degrees and sunny in San Francisco";
},
metadata: {
name: "Weather",
description: "Get the weather",
parameters: {
type: "object",
properties: {
location: { type: "string" },
},
required: ["location"],
},
},
},
],
});
const result = await agent.chat({
message: "What is the weather in San Francisco?",
});
consola.debug("response:", result.response);
ok(typeof result.response === "string");
ok(result.response.includes("35"));
});
await t.test("async function", async () => {
const uniqueId = "123456789";
const showUniqueId = FunctionTool.from<{
firstName: string;
lastName: string;
}>(
async ({ firstName, lastName }) => {
ok(typeof firstName === "string");
ok(typeof lastName === "string");
const fullName = firstName + lastName;
ok(fullName.toLowerCase().includes("alex"));
ok(fullName.toLowerCase().includes("yang"));
return uniqueId;
},
{
name: "unique_id",
description: "show user unique id",
parameters: {
type: "object",
properties: {
firstName: { type: "string" },
lastName: { type: "string" },
},
required: ["firstName", "lastName"],
},
},
);
const agent = new AnthropicAgent({
tools: [showUniqueId],
});
const { response } = await agent.chat({
message: "My name is Alex Yang. What is my unique id?",
});
consola.debug("response:", response);
ok(response.includes(uniqueId));
});
await t.test("sum numbers", async () => {
const openaiAgent = new AnthropicAgent({
tools: [sumNumbersTool],
});
const response = await openaiAgent.chat({
message: "how much is 1 + 1?",
});
ok(response.response.includes("2"));
});
});
+47
View File
@@ -0,0 +1,47 @@
import { FunctionTool } from "llamaindex";
function sumNumbers({ a, b }: { a: number; b: number }) {
return `${a + b}`;
}
function divideNumbers({ a, b }: { a: number; b: number }) {
return `${a / b}`;
}
export const sumNumbersTool = FunctionTool.from(sumNumbers, {
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"],
},
});
export const divideNumbersTool = FunctionTool.from(divideNumbers, {
name: "divideNumbers",
description: "Use this function to divide two numbers",
parameters: {
type: "object",
properties: {
a: {
type: "number",
description: "The first number",
},
b: {
type: "number",
description: "The second number",
},
},
required: ["a", "b"],
},
});
+14 -73
View File
@@ -10,8 +10,9 @@ import {
VectorStoreIndex,
type LLM,
} from "llamaindex";
import { ok } from "node:assert";
import { ok, strictEqual } from "node:assert";
import { beforeEach, test } from "node:test";
import { divideNumbersTool, sumNumbersTool } from "./fixtures/tools.js";
import { mockLLMEvent } from "./utils.js";
let llm: LLM;
@@ -22,15 +23,7 @@ beforeEach(async () => {
llm = Settings.llm;
});
function sumNumbers({ a, b }: { a: number; b: number }) {
return `${a + b}`;
}
function divideNumbers({ a, b }: { a: number; b: number }) {
return `${a / b}`;
}
await test("llm", async (t) => {
await test("openai llm", async (t) => {
await mockLLMEvent(t, "llm");
await t.test("llm.chat", async () => {
const response = await llm.chat({
@@ -166,27 +159,8 @@ await test("agent", async (t) => {
});
await t.test("sum numbers", async () => {
const sumFunctionTool = new FunctionTool(sumNumbers, {
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 openaiAgent = new OpenAIAgent({
tools: [sumFunctionTool],
tools: [sumNumbersTool],
});
const response = await openaiAgent.chat({
@@ -199,51 +173,12 @@ await test("agent", async (t) => {
await test("agent stream", async (t) => {
await mockLLMEvent(t, "agent_stream");
await t.test("sum numbers stream", async () => {
const sumJSON = {
type: "object",
properties: {
a: {
type: "number",
description: "The first number",
},
b: {
type: "number",
description: "The second number",
},
},
required: ["a", "b"],
} as const;
const divideJSON = {
type: "object",
properties: {
a: {
type: "number",
description: "The dividend",
},
b: {
type: "number",
description: "The divisor",
},
},
required: ["a", "b"],
} as const;
const functionTool = FunctionTool.from(sumNumbers, {
name: "sumNumbers",
description: "Use this function to sum two numbers",
parameters: sumJSON,
});
const functionTool2 = FunctionTool.from(divideNumbers, {
name: "divideNumbers",
description: "Use this function to divide two numbers",
parameters: divideJSON,
});
await t.test("sum numbers stream", async (t) => {
const fn = t.mock.fn(() => {});
Settings.callbackManager.on("llm-tool-call", fn);
const agent = new OpenAIAgent({
tools: [functionTool, functionTool2],
tools: [sumNumbersTool, divideNumbersTool],
});
const { response } = await agent.chat({
@@ -257,13 +192,17 @@ await test("agent stream", async (t) => {
message += chunk.response;
}
strictEqual(fn.mock.callCount(), 2);
ok(message.includes("28"));
Settings.callbackManager.off("llm-tool-call", fn);
});
});
await test("queryEngine", async (t) => {
await mockLLMEvent(t, "queryEngine_subquestion");
await t.test("subquestion", async () => {
const fn = t.mock.fn(() => {});
Settings.callbackManager.on("llm-tool-call", fn);
const document = new Document({
text: "Bill Gates stole from Apple.\n Steve Jobs stole from Xerox.",
});
@@ -288,5 +227,7 @@ await test("queryEngine", async (t) => {
});
ok(response.includes("Apple"));
strictEqual(fn.mock.callCount(), 0);
Settings.callbackManager.off("llm-tool-call", fn);
});
});
+49 -73
View File
@@ -20,24 +20,21 @@
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "Weather",
"arguments": "{\"location\":\"San Francisco\"}"
}
}
]
"toolCall": {
"id": "HIDDEN",
"name": "Weather",
"input": "{\"location\":\"San Francisco\"}"
}
}
},
{
"content": "35 degrees and sunny in San Francisco",
"role": "tool",
"role": "user",
"options": {
"name": "Weather",
"tool_call_id": "HIDDEN"
"toolResult": {
"id": "HIDDEN",
"isError": false
}
}
}
]
@@ -62,24 +59,21 @@
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "unique_id",
"arguments": "{\"firstName\":\"Alex\",\"lastName\":\"Yang\"}"
}
}
]
"toolCall": {
"id": "HIDDEN",
"name": "unique_id",
"input": "{\"firstName\":\"Alex\",\"lastName\":\"Yang\"}"
}
}
},
{
"content": "123456789",
"role": "tool",
"role": "user",
"options": {
"name": "unique_id",
"tool_call_id": "HIDDEN"
"toolResult": {
"id": "HIDDEN",
"isError": false
}
}
}
]
@@ -104,24 +98,21 @@
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "sumNumbers",
"arguments": "{\"a\":1,\"b\":1}"
}
}
]
"toolCall": {
"id": "HIDDEN",
"name": "sumNumbers",
"input": "{\"a\":1,\"b\":1}"
}
}
},
{
"content": "2",
"role": "tool",
"role": "user",
"options": {
"name": "sumNumbers",
"tool_call_id": "HIDDEN"
"toolResult": {
"id": "HIDDEN",
"isError": false
}
}
}
]
@@ -168,16 +159,11 @@
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "Weather",
"arguments": "{\"location\":\"San Francisco\"}"
}
}
]
"toolCall": {
"id": "HIDDEN",
"name": "Weather",
"input": "{\"location\":\"San Francisco\"}"
}
}
}
}
@@ -255,16 +241,11 @@
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "unique_id",
"arguments": "{\"firstName\":\"Alex\",\"lastName\":\"Yang\"}"
}
}
]
"toolCall": {
"id": "HIDDEN",
"name": "unique_id",
"input": "{\"firstName\":\"Alex\",\"lastName\":\"Yang\"}"
}
}
}
}
@@ -342,16 +323,11 @@
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "sumNumbers",
"arguments": "{\"a\":1,\"b\":1}"
}
}
]
"toolCall": {
"id": "HIDDEN",
"name": "sumNumbers",
"input": "{\"a\":1,\"b\":1}"
}
}
}
}
@@ -369,7 +345,7 @@
"index": 0,
"message": {
"role": "assistant",
"content": "The sum of 1 + 1 is 2."
"content": "1 + 1 is equal to 2."
},
"logprobs": null,
"finish_reason": "stop"
@@ -377,13 +353,13 @@
],
"usage": {
"prompt_tokens": 97,
"completion_tokens": 13,
"total_tokens": 110
"completion_tokens": 11,
"total_tokens": 108
},
"system_fingerprint": "HIDDEN"
},
"message": {
"content": "The sum of 1 + 1 is 2.",
"content": "1 + 1 is equal to 2.",
"role": "assistant",
"options": {}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,403 @@
{
"llmEventStart": [
{
"id": "PRESERVE_0",
"messages": [
{
"role": "user",
"content": "What is the weather in San Francisco?",
"options": {}
}
]
},
{
"id": "PRESERVE_1",
"messages": [
{
"role": "user",
"content": "What is the weather in San Francisco?",
"options": {}
},
{
"content": [
{
"type": "text",
"text": "<thinking>\nThe user is asking for the weather in a specific location, San Francisco. The Weather function is the relevant tool to answer this request, as it returns weather information for a given location.\n\nThe Weather function has one required parameter:\n- location (string): The user has directly provided the location of \"San Francisco\"\n\nSince the required location parameter has been provided by the user, we have all the necessary information to call the Weather function.\n</thinking>"
}
],
"role": "assistant",
"options": {
"toolCall": {
"id": "HIDDEN",
"name": "Weather",
"input": {
"location": "San Francisco"
}
}
}
},
{
"content": "35 degrees and sunny in San Francisco",
"role": "user",
"options": {
"toolResult": {
"isError": false,
"id": "HIDDEN"
}
}
}
]
},
{
"id": "PRESERVE_2",
"messages": [
{
"role": "user",
"content": "My name is Alex Yang. What is my unique id?",
"options": {}
}
]
},
{
"id": "PRESERVE_3",
"messages": [
{
"role": "user",
"content": "My name is Alex Yang. What is my unique id?",
"options": {}
},
{
"content": [
{
"type": "text",
"text": "<thinking>\nThe unique_id function is the relevant tool to answer the user's request for their unique ID. It requires two parameters:\nfirstName: The user provided their first name, which is \"Alex\"\nlastName: The user also provided their last name, \"Yang\"\nSince the user has provided all the required parameters, we can proceed with calling the unique_id function.\n</thinking>"
}
],
"role": "assistant",
"options": {
"toolCall": {
"id": "HIDDEN",
"name": "unique_id",
"input": {
"firstName": "Alex",
"lastName": "Yang"
}
}
}
},
{
"content": "123456789",
"role": "user",
"options": {
"toolResult": {
"isError": false,
"id": "HIDDEN"
}
}
}
]
},
{
"id": "PRESERVE_4",
"messages": [
{
"role": "user",
"content": "how much is 1 + 1?",
"options": {}
}
]
},
{
"id": "PRESERVE_5",
"messages": [
{
"role": "user",
"content": "how much is 1 + 1?",
"options": {}
},
{
"content": [
{
"type": "text",
"text": "<thinking>\nThe user is asking to sum the numbers 1 and 1. The relevant tool to use is the sumNumbers function, which takes two number parameters a and b.\nThe user has directly provided the values for the parameters:\na = 1 \nb = 1\nSince all the required parameters have been provided, we can proceed with calling the function.\n</thinking>"
}
],
"role": "assistant",
"options": {
"toolCall": {
"id": "HIDDEN",
"name": "sumNumbers",
"input": {
"a": 1,
"b": 1
}
}
}
},
{
"content": "2",
"role": "user",
"options": {
"toolResult": {
"isError": false,
"id": "HIDDEN"
}
}
}
]
}
],
"llmEventEnd": [
{
"id": "PRESERVE_0",
"response": {
"raw": {
"id": "HIDDEN",
"type": "message",
"role": "assistant",
"model": "claude-3-opus-20240229",
"stop_sequence": null,
"usage": {
"input_tokens": 462,
"output_tokens": 147
},
"content": [
{
"type": "text",
"text": "<thinking>\nThe user is asking for the weather in a specific location, San Francisco. The Weather function is the relevant tool to answer this request, as it returns weather information for a given location.\n\nThe Weather function has one required parameter:\n- location (string): The user has directly provided the location of \"San Francisco\"\n\nSince the required location parameter has been provided by the user, we have all the necessary information to call the Weather function.\n</thinking>"
},
{
"type": "tool_use",
"id": "HIDDEN",
"name": "Weather",
"input": {
"location": "San Francisco"
}
}
],
"stop_reason": "tool_use"
},
"message": {
"content": [
{
"type": "text",
"text": "<thinking>\nThe user is asking for the weather in a specific location, San Francisco. The Weather function is the relevant tool to answer this request, as it returns weather information for a given location.\n\nThe Weather function has one required parameter:\n- location (string): The user has directly provided the location of \"San Francisco\"\n\nSince the required location parameter has been provided by the user, we have all the necessary information to call the Weather function.\n</thinking>"
}
],
"role": "assistant",
"options": {
"toolCall": {
"id": "HIDDEN",
"name": "Weather",
"input": {
"location": "San Francisco"
}
}
}
}
}
},
{
"id": "PRESERVE_1",
"response": {
"raw": {
"id": "HIDDEN",
"type": "message",
"role": "assistant",
"model": "claude-3-opus-20240229",
"stop_sequence": null,
"usage": {
"input_tokens": 628,
"output_tokens": 18
},
"content": [
{
"type": "text",
"text": "The current weather in San Francisco is 35 degrees and sunny."
}
],
"stop_reason": "end_turn"
},
"message": {
"content": [
{
"type": "text",
"text": "The current weather in San Francisco is 35 degrees and sunny."
}
],
"role": "assistant",
"options": {}
}
}
},
{
"id": "PRESERVE_2",
"response": {
"raw": {
"id": "HIDDEN",
"type": "message",
"role": "assistant",
"model": "claude-3-opus-20240229",
"stop_sequence": null,
"usage": {
"input_tokens": 482,
"output_tokens": 152
},
"content": [
{
"type": "text",
"text": "<thinking>\nThe unique_id function is the relevant tool to answer the user's request for their unique ID. It requires two parameters:\nfirstName: The user provided their first name, which is \"Alex\"\nlastName: The user also provided their last name, \"Yang\"\nSince the user has provided all the required parameters, we can proceed with calling the unique_id function.\n</thinking>"
},
{
"type": "tool_use",
"id": "HIDDEN",
"name": "unique_id",
"input": {
"firstName": "Alex",
"lastName": "Yang"
}
}
],
"stop_reason": "tool_use"
},
"message": {
"content": [
{
"type": "text",
"text": "<thinking>\nThe unique_id function is the relevant tool to answer the user's request for their unique ID. It requires two parameters:\nfirstName: The user provided their first name, which is \"Alex\"\nlastName: The user also provided their last name, \"Yang\"\nSince the user has provided all the required parameters, we can proceed with calling the unique_id function.\n</thinking>"
}
],
"role": "assistant",
"options": {
"toolCall": {
"id": "HIDDEN",
"name": "unique_id",
"input": {
"firstName": "Alex",
"lastName": "Yang"
}
}
}
}
}
},
{
"id": "PRESERVE_3",
"response": {
"raw": {
"id": "HIDDEN",
"type": "message",
"role": "assistant",
"model": "claude-3-opus-20240229",
"stop_sequence": null,
"usage": {
"input_tokens": 648,
"output_tokens": 13
},
"content": [
{
"type": "text",
"text": "Your unique ID is 123456789."
}
],
"stop_reason": "end_turn"
},
"message": {
"content": [
{
"type": "text",
"text": "Your unique ID is 123456789."
}
],
"role": "assistant",
"options": {}
}
}
},
{
"id": "PRESERVE_4",
"response": {
"raw": {
"id": "HIDDEN",
"type": "message",
"role": "assistant",
"model": "claude-3-opus-20240229",
"stop_sequence": null,
"usage": {
"input_tokens": 498,
"output_tokens": 151
},
"content": [
{
"type": "text",
"text": "<thinking>\nThe user is asking to sum the numbers 1 and 1. The relevant tool to use is the sumNumbers function, which takes two number parameters a and b.\nThe user has directly provided the values for the parameters:\na = 1 \nb = 1\nSince all the required parameters have been provided, we can proceed with calling the function.\n</thinking>"
},
{
"type": "tool_use",
"id": "HIDDEN",
"name": "sumNumbers",
"input": {
"a": 1,
"b": 1
}
}
],
"stop_reason": "tool_use"
},
"message": {
"content": [
{
"type": "text",
"text": "<thinking>\nThe user is asking to sum the numbers 1 and 1. The relevant tool to use is the sumNumbers function, which takes two number parameters a and b.\nThe user has directly provided the values for the parameters:\na = 1 \nb = 1\nSince all the required parameters have been provided, we can proceed with calling the function.\n</thinking>"
}
],
"role": "assistant",
"options": {
"toolCall": {
"id": "HIDDEN",
"name": "sumNumbers",
"input": {
"a": 1,
"b": 1
}
}
}
}
}
},
{
"id": "PRESERVE_5",
"response": {
"raw": {
"id": "HIDDEN",
"type": "message",
"role": "assistant",
"model": "claude-3-opus-20240229",
"stop_sequence": null,
"usage": {
"input_tokens": 661,
"output_tokens": 16
},
"content": [
{
"type": "text",
"text": "So 1 + 1 = 2."
}
],
"stop_reason": "end_turn"
},
"message": {
"content": [
{
"type": "text",
"text": "So 1 + 1 = 2."
}
],
"role": "assistant",
"options": {}
}
}
}
],
"llmEventStream": []
}
@@ -20,24 +20,21 @@
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "Weather",
"arguments": "{\"location\":\"San Jose\"}"
}
}
]
"toolCall": {
"id": "HIDDEN",
"name": "Weather",
"input": "{\"location\":\"San Jose\"}"
}
}
},
{
"content": "45 degrees and sunny in San Jose",
"role": "tool",
"role": "user",
"options": {
"name": "Weather",
"tool_call_id": "HIDDEN"
"toolResult": {
"id": "HIDDEN",
"isError": false
}
}
}
]
@@ -84,16 +81,11 @@
"content": "",
"role": "assistant",
"options": {
"toolCalls": [
{
"id": "HIDDEN",
"type": "function",
"function": {
"name": "Weather",
"arguments": "{\"location\":\"San Jose\"}"
}
}
]
"toolCall": {
"id": "HIDDEN",
"name": "Weather",
"input": "{\"location\":\"San Jose\"}"
}
}
}
}
@@ -0,0 +1,310 @@
{
"llmEventStart": [
{
"id": "PRESERVE_0",
"messages": [
{
"content": "Hello",
"role": "user",
"options": {}
}
]
},
{
"id": "PRESERVE_1",
"messages": [
{
"content": "hello",
"role": "user"
}
]
}
],
"llmEventEnd": [
{
"id": "PRESERVE_0",
"response": {
"raw": {
"id": "HIDDEN",
"type": "message",
"role": "assistant",
"model": "claude-3-opus-20240229",
"stop_sequence": null,
"usage": {
"input_tokens": 8,
"output_tokens": 12
},
"content": [
{
"type": "text",
"text": "Hello! How can I assist you today?"
}
],
"stop_reason": "end_turn"
},
"message": {
"content": "Hello! How can I assist you today?",
"role": "assistant",
"options": {}
}
}
},
{
"id": "PRESERVE_1",
"response": {
"raw": [
{
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": "Hello"
}
},
"delta": "Hello",
"options": {}
},
{
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": "!"
}
},
"delta": "!",
"options": {}
},
{
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": " How"
}
},
"delta": " How",
"options": {}
},
{
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": " can"
}
},
"delta": " can",
"options": {}
},
{
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": " I"
}
},
"delta": " I",
"options": {}
},
{
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": " assist"
}
},
"delta": " assist",
"options": {}
},
{
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": " you"
}
},
"delta": " you",
"options": {}
},
{
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": " today"
}
},
"delta": " today",
"options": {}
},
{
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": "?"
}
},
"delta": "?",
"options": {}
}
],
"message": {
"content": "Hello! How can I assist you today?",
"role": "assistant",
"options": {}
}
}
}
],
"llmEventStream": [
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": "Hello"
}
},
"delta": "Hello",
"options": {}
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": "!"
}
},
"delta": "!",
"options": {}
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": " How"
}
},
"delta": " How",
"options": {}
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": " can"
}
},
"delta": " can",
"options": {}
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": " I"
}
},
"delta": " I",
"options": {}
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": " assist"
}
},
"delta": " assist",
"options": {}
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": " you"
}
},
"delta": " you",
"options": {}
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": " today"
}
},
"delta": " today",
"options": {}
}
},
{
"id": "PRESERVE_1",
"chunk": {
"raw": {
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": "?"
}
},
"delta": "?",
"options": {}
}
}
]
}
+1
View File
@@ -1,3 +1,4 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import {
Settings,
type LLMEndEvent,
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "llamaindex",
"version": "0.2.8",
"version": "0.2.9",
"expectedMinorVersion": "2",
"license": "MIT",
"type": "module",
"dependencies": {
"@anthropic-ai/sdk": "^0.18.0",
"@anthropic-ai/sdk": "^0.20.4",
"@aws-crypto/sha256-js": "^5.2.0",
"@datastax/astra-db-ts": "^0.1.4",
"@grpc/grpc-js": "^1.10.6",
+12 -7
View File
@@ -8,20 +8,22 @@ import { extractText } from "./llm/utils.js";
/**
* A ChatHistory is used to keep the state of back and forth chat messages
*/
export abstract class ChatHistory {
abstract get messages(): ChatMessage[];
export abstract class ChatHistory<
AdditionalMessageOptions extends object = object,
> {
abstract get messages(): ChatMessage<AdditionalMessageOptions>[];
/**
* Adds a message to the chat history.
* @param message
*/
abstract addMessage(message: ChatMessage): void;
abstract addMessage(message: ChatMessage<AdditionalMessageOptions>): void;
/**
* Returns the messages that should be used as input to the LLM.
*/
abstract requestMessages(
transientMessages?: ChatMessage[],
): Promise<ChatMessage[]>;
transientMessages?: ChatMessage<AdditionalMessageOptions>[],
): Promise<ChatMessage<AdditionalMessageOptions>[]>;
/**
* Resets the chat history so that it's empty.
@@ -31,7 +33,7 @@ export abstract class ChatHistory {
/**
* Returns the new messages since the last call to this function (or since calling the constructor)
*/
abstract newMessages(): ChatMessage[];
abstract newMessages(): ChatMessage<AdditionalMessageOptions>[];
}
export class SimpleChatHistory extends ChatHistory {
@@ -108,6 +110,7 @@ export class SummaryChatHistory extends ChatHistory {
context: messagesToHistoryStr(messagesToSummarize),
}),
role: "user" as MessageType,
options: {},
},
];
// remove oldest message until the chat history is short enough for the context window
@@ -116,7 +119,9 @@ export class SummaryChatHistory extends ChatHistory {
this.tokenizer(promptMessages[0].content).length > this.tokensToSummarize
);
const response = await this.llm.chat({ messages: promptMessages });
const response = await this.llm.chat({
messages: promptMessages,
});
return { content: response.message.content, role: "memory" };
}
+3 -3
View File
@@ -1,14 +1,14 @@
import type { BaseNode } from "./Node.js";
import type { NodeWithScore } from "./Node.js";
/**
* Response is the output of a LLM
*/
export class Response {
response: string;
sourceNodes?: BaseNode[];
sourceNodes?: NodeWithScore[];
metadata: Record<string, unknown> = {};
constructor(response: string, sourceNodes?: BaseNode[]) {
constructor(response: string, sourceNodes?: NodeWithScore[]) {
this.response = response;
this.sourceNodes = sourceNodes || [];
}
+170
View File
@@ -0,0 +1,170 @@
import { Settings } from "../Settings.js";
import {
AgentChatResponse,
type ChatEngineParamsNonStreaming,
} from "../engines/chat/index.js";
import { wrapEventCaller } from "../internal/context/EventCaller.js";
import { getCallbackManager } from "../internal/settings/CallbackManager.js";
import { prettifyError } from "../internal/utils.js";
import { Anthropic } from "../llm/anthropic.js";
import type {
ChatMessage,
ChatResponse,
ToolCallLLMMessageOptions,
} from "../llm/index.js";
import { extractText } from "../llm/utils.js";
import { ObjectRetriever } from "../objects/index.js";
import type { BaseToolWithCall } from "../types.js";
const MAX_TOOL_CALLS = 10;
type AnthropicParamsBase = {
llm?: Anthropic;
chatHistory?: ChatMessage<ToolCallLLMMessageOptions>[];
};
type AnthropicParamsWithTools = AnthropicParamsBase & {
tools: BaseToolWithCall[];
};
type AnthropicParamsWithToolRetriever = AnthropicParamsBase & {
toolRetriever: ObjectRetriever<BaseToolWithCall>;
};
export type AnthropicAgentParams =
| AnthropicParamsWithTools
| AnthropicParamsWithToolRetriever;
type AgentContext = {
toolCalls: number;
llm: Anthropic;
tools: BaseToolWithCall[];
messages: ChatMessage<ToolCallLLMMessageOptions>[];
shouldContinue: (context: AgentContext) => boolean;
};
type TaskResult = {
response: ChatResponse<ToolCallLLMMessageOptions>;
chatHistory: ChatMessage<ToolCallLLMMessageOptions>[];
};
async function task(
context: AgentContext,
input: ChatMessage<ToolCallLLMMessageOptions>,
): Promise<TaskResult> {
const { llm, tools, messages } = context;
const nextMessages: ChatMessage<ToolCallLLMMessageOptions>[] = [
...messages,
input,
];
const response = await llm.chat({
stream: false,
tools,
messages: nextMessages,
});
const options = response.message.options ?? {};
if ("toolCall" in options) {
const { toolCall } = options;
const { input, name, id } = toolCall;
const targetTool = tools.find((tool) => tool.metadata.name === name);
let output: string;
let isError = true;
if (!context.shouldContinue(context)) {
output = "Error: Tool call limit reached";
} else if (!targetTool) {
output = `Error: Tool ${name} not found`;
} else {
try {
getCallbackManager().dispatchEvent("llm-tool-call", {
payload: {
toolCall: {
name,
input,
},
},
});
output = await targetTool.call(input);
isError = false;
} catch (error: unknown) {
output = prettifyError(error);
}
}
return task(
{
...context,
toolCalls: context.toolCalls + 1,
messages: [...nextMessages, response.message],
},
{
content: output,
role: "user",
options: {
toolResult: {
isError,
id,
},
},
},
);
} else {
return { response, chatHistory: [...nextMessages, response.message] };
}
}
export class AnthropicAgent {
readonly #llm: Anthropic;
readonly #tools:
| BaseToolWithCall[]
| ((query: string) => Promise<BaseToolWithCall[]>) = [];
#chatHistory: ChatMessage<ToolCallLLMMessageOptions>[] = [];
constructor(params: AnthropicAgentParams) {
this.#llm =
params.llm ?? Settings.llm instanceof Anthropic
? (Settings.llm as Anthropic)
: new Anthropic();
if ("tools" in params) {
this.#tools = params.tools;
} else if ("toolRetriever" in params) {
this.#tools = params.toolRetriever.retrieve.bind(params.toolRetriever);
}
if (Array.isArray(params.chatHistory)) {
this.#chatHistory = params.chatHistory;
}
}
static shouldContinue(context: AgentContext): boolean {
return context.toolCalls < MAX_TOOL_CALLS;
}
public reset(): void {
this.#chatHistory = [];
}
getTools(query: string): Promise<BaseToolWithCall[]> | BaseToolWithCall[] {
return typeof this.#tools === "function" ? this.#tools(query) : this.#tools;
}
@wrapEventCaller
async chat(
params: ChatEngineParamsNonStreaming,
): Promise<Promise<AgentChatResponse>> {
const { chatHistory, response } = await task(
{
llm: this.#llm,
tools: await this.getTools(extractText(params.message)),
toolCalls: 0,
messages: [...this.#chatHistory],
// do we need this?
shouldContinue: AnthropicAgent.shouldContinue,
},
{
role: "user",
content: params.message,
options: {},
},
);
this.#chatHistory = [...chatHistory];
return new AgentChatResponse(extractText(response.message.content));
}
}
+2
View File
@@ -1,3 +1,5 @@
// Not exporting the AnthropicAgent because it is not ready to ship yet.
// export { AnthropicAgent, type AnthropicAgentParams } from "./anthropic.js";
export * from "./openai/base.js";
export * from "./openai/worker.js";
export * from "./react/base.js";
+3 -2
View File
@@ -14,7 +14,7 @@ type OpenAIAgentParams = {
prefixMessages?: ChatMessage[];
maxFunctionCalls?: number;
defaultToolChoice?: string;
toolRetriever?: ObjectRetriever;
toolRetriever?: ObjectRetriever<BaseTool>;
systemPrompt?: string;
};
@@ -56,7 +56,7 @@ export class OpenAIAgent extends AgentRunner {
];
}
if (!llm?.metadata.isFunctionCallingModel) {
if (!llm?.supportToolCall) {
throw new Error("LLM model must be a function-calling model");
}
@@ -73,6 +73,7 @@ export class OpenAIAgent extends AgentRunner {
llm,
memory,
defaultToolChoice,
// @ts-expect-error 2322
chatHistory: prefixMessages,
});
}
+56 -61
View File
@@ -14,7 +14,8 @@ import {
type ChatResponseChunk,
type LLMChatParamsBase,
type OpenAIAdditionalChatOptions,
type OpenAIAdditionalMessageOptions,
type ToolCallLLMMessageOptions,
type ToolCallOptions,
} from "../../llm/index.js";
import { extractText } from "../../llm/utils.js";
import { ChatMemoryBuffer } from "../../memory/ChatMemoryBuffer.js";
@@ -25,28 +26,25 @@ import type { BaseTool } from "../../types.js";
import type { AgentWorker, Task } from "../types.js";
import { TaskStep, TaskStepOutput } from "../types.js";
import { addUserStepToMemory, getFunctionByName } from "../utils.js";
import type { OpenAIToolCall } from "./types/chat.js";
async function callFunction(
tools: BaseTool[],
toolCall: OpenAIToolCall,
): Promise<[ChatMessage, ToolOutput]> {
const id_ = toolCall.id;
const functionCall = toolCall.function;
const name = toolCall.function.name;
const argumentsStr = toolCall.function.arguments;
toolCall: ToolCallOptions["toolCall"],
): Promise<[ChatMessage<ToolCallLLMMessageOptions>, ToolOutput]> {
const id = toolCall.id;
const name = toolCall.name;
const input = toolCall.input;
if (Settings.debug) {
console.log("=== Calling Function ===");
console.log(`Calling function: ${name} with args: ${argumentsStr}`);
console.log(`Calling function: ${name} with args: ${input}`);
}
const tool = getFunctionByName(tools, name);
const argumentDict = JSON.parse(argumentsStr);
// Call tool
// Use default error message
const output = await callToolWithErrorHandling(tool, argumentDict);
const output = await callToolWithErrorHandling(tool, input);
if (Settings.debug) {
console.log(`Got output ${output}`);
@@ -56,10 +54,12 @@ async function callFunction(
return [
{
content: `${output}`,
role: "tool",
role: "user",
options: {
name,
tool_call_id: id_,
toolResult: {
id,
isError: false,
},
},
},
output,
@@ -71,7 +71,7 @@ type OpenAIAgentWorkerParams = {
llm?: OpenAI;
prefixMessages?: ChatMessage[];
maxFunctionCalls?: number;
toolRetriever?: ObjectRetriever;
toolRetriever?: ObjectRetriever<BaseTool>;
};
type CallFunctionOutput = {
@@ -107,9 +107,9 @@ export class OpenAIAgentWorker
}
this.prefixMessages = prefixMessages || [];
if (Array.isArray(tools) && tools.length > 0 && toolRetriever) {
if (tools.length > 0 && toolRetriever) {
throw new Error("Cannot specify both tools and tool_retriever");
} else if (Array.isArray(tools)) {
} else if (tools.length > 0) {
this._getTools = async () => tools;
} else if (toolRetriever) {
// fixme: this won't work, type mismatch
@@ -120,7 +120,7 @@ export class OpenAIAgentWorker
}
}
public getAllMessages(task: Task): ChatMessage[] {
public getAllMessages(task: Task): ChatMessage<ToolCallLLMMessageOptions>[] {
return [
...this.prefixMessages,
...task.memory.get(),
@@ -128,30 +128,33 @@ export class OpenAIAgentWorker
];
}
public getLatestToolCalls(task: Task): OpenAIToolCall[] | null {
public getLatestToolCall(task: Task): ToolCallOptions["toolCall"] | null {
const chatHistory: ChatMessage[] = task.extraState.newMemory.getAll();
if (chatHistory.length === 0) {
return null;
}
// fixme
return chatHistory[chatHistory.length - 1].options?.toolCalls as any;
// @ts-expect-error fixme
return chatHistory[chatHistory.length - 1].options?.toolCall;
}
private _getLlmChatParams(
task: Task,
openaiTools: BaseTool[],
tools: BaseTool[],
toolChoice: ChatCompletionToolChoiceOption = "auto",
): LLMChatParamsBase<OpenAIAdditionalChatOptions> {
): LLMChatParamsBase<OpenAIAdditionalChatOptions, ToolCallLLMMessageOptions> {
const llmChatParams = {
messages: this.getAllMessages(task),
tools: undefined as BaseTool[] | undefined,
additionalChatOptions: {} as OpenAIAdditionalChatOptions,
} satisfies LLMChatParamsBase<OpenAIAdditionalChatOptions>;
} satisfies LLMChatParamsBase<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>;
if (openaiTools.length > 0) {
llmChatParams.tools = openaiTools;
if (tools.length > 0) {
llmChatParams.tools = tools;
llmChatParams.additionalChatOptions.tool_choice = toolChoice;
}
@@ -172,7 +175,10 @@ export class OpenAIAgentWorker
private async _getStreamAiResponse(
task: Task,
llmChatParams: LLMChatParamsBase<OpenAIAdditionalChatOptions>,
llmChatParams: LLMChatParamsBase<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<StreamingAgentChatResponse | AgentChatResponse> {
const stream = await this.llm.chat({
stream: true,
@@ -180,7 +186,7 @@ export class OpenAIAgentWorker
});
const responseChunkStream = new ReadableStream<
ChatResponseChunk<OpenAIAdditionalMessageOptions>
ChatResponseChunk<ToolCallLLMMessageOptions>
>({
async start(controller) {
for await (const chunk of stream) {
@@ -198,11 +204,11 @@ export class OpenAIAgentWorker
}
// check if first chunk has tool calls, if so, this is a function call
// otherwise, it's a regular message
const hasToolCalls: boolean =
!!value.options?.toolCalls?.length &&
value.options?.toolCalls?.length > 0;
const hasToolCall: boolean = !!(
value.options && "toolCall" in value.options
);
if (hasToolCalls) {
if (hasToolCall) {
return this._processMessage(task, {
content: await pipeline(finalStream, async (iterator) => {
let content = "";
@@ -247,7 +253,10 @@ export class OpenAIAgentWorker
private async _getAgentResponse(
task: Task,
mode: ChatResponseMode,
llmChatParams: LLMChatParamsBase<OpenAIAdditionalChatOptions>,
llmChatParams: LLMChatParamsBase<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<AgentChatResponse | StreamingAgentChatResponse> {
if (mode === ChatResponseMode.WAIT) {
const chatResponse = await this.llm.chat({
@@ -268,14 +277,8 @@ export class OpenAIAgentWorker
async callFunction(
tools: BaseTool[],
toolCall: OpenAIToolCall,
toolCall: ToolCallOptions["toolCall"],
): Promise<CallFunctionOutput> {
const functionCall = toolCall.function;
if (!functionCall) {
throw new Error("Invalid tool_call object");
}
const functionMessage = await callFunction(tools, toolCall);
const message = functionMessage[0];
@@ -309,18 +312,14 @@ export class OpenAIAgentWorker
}
private _shouldContinue(
toolCalls: OpenAIToolCall[] | null,
toolCall: ToolCallOptions["toolCall"] | null,
nFunctionCalls: number,
): boolean {
): toolCall is ToolCallOptions["toolCall"] {
if (nFunctionCalls > this.maxFunctionCalls) {
return false;
}
if (toolCalls?.length === 0) {
return false;
}
return true;
return !!toolCall;
}
async getTools(input: string): Promise<BaseTool[]> {
@@ -347,29 +346,25 @@ export class OpenAIAgentWorker
llmChatParams,
);
const latestToolCalls = this.getLatestToolCalls(task) || [];
const latestToolCall = this.getLatestToolCall(task) ?? null;
let isDone: boolean;
let newSteps: TaskStep[] = [];
let newSteps: TaskStep[];
if (
!this._shouldContinue(latestToolCalls, task.extraState.nFunctionCalls)
) {
if (!this._shouldContinue(latestToolCall, task.extraState.nFunctionCalls)) {
isDone = true;
newSteps = [];
} else {
isDone = false;
for (const toolCall of latestToolCalls) {
const { message, toolOutput } = await this.callFunction(
tools,
toolCall,
);
const { message, toolOutput } = await this.callFunction(
tools,
latestToolCall,
);
task.extraState.sources.push(toolOutput);
task.extraState.newMemory.put(message);
task.extraState.sources.push(toolOutput);
task.extraState.newMemory.put(message);
task.extraState.nFunctionCalls += 1;
}
task.extraState.nFunctionCalls += 1;
newSteps = [step.getNextStep(randomUUID(), undefined)];
}
+2 -1
View File
@@ -12,7 +12,7 @@ type ReActAgentParams = {
prefixMessages?: ChatMessage[];
maxInteractions?: number;
defaultToolChoice?: string;
toolRetriever?: ObjectRetriever;
toolRetriever?: ObjectRetriever<BaseTool>;
};
/**
@@ -41,6 +41,7 @@ export class ReActAgent extends AgentRunner {
agentWorker: stepEngine,
memory,
defaultToolChoice,
// @ts-expect-error 2322
chatHistory: prefixMessages,
});
}
+10 -1
View File
@@ -2,6 +2,7 @@ import { randomUUID } from "@llamaindex/env";
import type { ChatMessage } from "cohere-ai/api";
import { Settings } from "../../Settings.js";
import { AgentChatResponse } from "../../engines/chat/index.js";
import { getCallbackManager } from "../../internal/settings/CallbackManager.js";
import { type ChatResponse, type LLM } from "../../llm/index.js";
import { extractText } from "../../llm/utils.js";
import { ChatMemoryBuffer } from "../../memory/ChatMemoryBuffer.js";
@@ -25,7 +26,7 @@ type ReActAgentWorkerParams = {
maxInteractions?: number;
reactChatFormatter?: ReActChatFormatter | undefined;
outputParser?: ReActOutputParser | undefined;
toolRetriever?: ObjectRetriever | undefined;
toolRetriever?: ObjectRetriever<BaseTool> | undefined;
};
function addUserStepToReasoning(
@@ -194,6 +195,14 @@ export class ReActAgentWorker implements AgentWorker<ChatParams> {
const tool = toolsDict[actionReasoningStep.action];
getCallbackManager().dispatchEvent("llm-tool-call", {
payload: {
toolCall: {
name: tool.metadata.name,
input: JSON.stringify(actionReasoningStep.actionInput),
},
},
});
const toolOutput = await tool.call!(actionReasoningStep.actionInput);
task.extraState.sources.push(
+3 -2
View File
@@ -1,11 +1,12 @@
import { randomUUID } from "@llamaindex/env";
import type { ChatHistory } from "../../ChatHistory.js";
import type { ChatEngineAgentParams } from "../../engines/chat/index.js";
import {
AgentChatResponse,
ChatResponseMode,
StreamingAgentChatResponse,
} from "../../engines/chat/index.js";
import type { ChatMessage, LLM } from "../../llm/index.js";
import type { LLM } from "../../llm/index.js";
import { ChatMemoryBuffer } from "../../memory/ChatMemoryBuffer.js";
import type { BaseMemory } from "../../memory/types.js";
import type { AgentWorker, TaskStepOutput } from "../types.js";
@@ -30,7 +31,7 @@ const validateStepFromArgs = (
type AgentRunnerParams = {
agentWorker: AgentWorker;
chatHistory?: ChatMessage[];
chatHistory?: ChatHistory;
state?: AgentState;
memory?: BaseMemory;
llm?: LLM;
@@ -9,6 +9,7 @@ import type {
LLMEndEvent,
LLMStartEvent,
LLMStreamEvent,
LLMToolCallEvent,
} from "../llm/types.js";
export class LlamaIndexCustomEvent<T = any> extends CustomEvent<T> {
@@ -48,6 +49,7 @@ export interface LlamaIndexEventMaps {
stream: CustomEvent<StreamCallbackResponse>;
"llm-start": LLMStartEvent;
"llm-end": LLMEndEvent;
"llm-tool-call": LLMToolCallEvent;
"llm-stream": LLMStreamEvent;
}
@@ -203,8 +205,10 @@ export class CallbackManager implements CallbackManagerMethods {
if (!handlers) {
return;
}
handlers.forEach((handler) =>
handler(LlamaIndexCustomEvent.fromEvent(event, detail)),
);
queueMicrotask(() => {
handlers.forEach((handler) =>
handler(LlamaIndexCustomEvent.fromEvent(event, detail)),
);
});
}
}
@@ -1,5 +1,10 @@
import type { ImageType } from "../Node.js";
import { BaseEmbedding } from "./types.js";
import {
MetadataMode,
splitNodesByType,
type BaseNode,
type ImageType,
} from "../Node.js";
import { BaseEmbedding, batchEmbeddings } from "./types.js";
/*
* Base class for Multi Modal embeddings.
@@ -8,9 +13,39 @@ import { BaseEmbedding } from "./types.js";
export abstract class MultiModalEmbedding extends BaseEmbedding {
abstract getImageEmbedding(images: ImageType): Promise<number[]>;
/**
* Optionally override this method to retrieve multiple image embeddings in a single request
* @param texts
*/
async getImageEmbeddings(images: ImageType[]): Promise<number[][]> {
return Promise.all(
images.map((imgFilePath) => this.getImageEmbedding(imgFilePath)),
);
}
async transform(nodes: BaseNode[], _options?: any): Promise<BaseNode[]> {
const { imageNodes, textNodes } = splitNodesByType(nodes);
const embeddings = await batchEmbeddings(
textNodes.map((node) => node.getContent(MetadataMode.EMBED)),
this.getTextEmbeddings.bind(this),
this.embedBatchSize,
_options,
);
for (let i = 0; i < textNodes.length; i++) {
textNodes[i].embedding = embeddings[i];
}
const imageEmbeddings = await batchEmbeddings(
imageNodes.map((n) => n.image),
this.getImageEmbeddings.bind(this),
this.embedBatchSize,
_options,
);
for (let i = 0; i < imageNodes.length; i++) {
imageNodes[i].embedding = imageEmbeddings[i];
}
return nodes;
}
}
+41 -24
View File
@@ -5,6 +5,8 @@ import { SimilarityType, similarity } from "./utils.js";
const DEFAULT_EMBED_BATCH_SIZE = 10;
type EmbedFunc<T> = (values: T[]) => Promise<Array<number[]>>;
export abstract class BaseEmbedding implements TransformComponent {
embedBatchSize = DEFAULT_EMBED_BATCH_SIZE;
@@ -45,35 +47,18 @@ export abstract class BaseEmbedding implements TransformComponent {
logProgress?: boolean;
},
): Promise<Array<number[]>> {
const resultEmbeddings: Array<number[]> = [];
const chunkSize = this.embedBatchSize;
const queue: string[] = texts;
const curBatch: string[] = [];
for (let i = 0; i < queue.length; i++) {
curBatch.push(queue[i]);
if (i == queue.length - 1 || curBatch.length == chunkSize) {
const embeddings = await this.getTextEmbeddings(curBatch);
resultEmbeddings.push(...embeddings);
if (options?.logProgress) {
console.log(`getting embedding progress: ${i} / ${queue.length}`);
}
curBatch.length = 0;
}
}
return resultEmbeddings;
return await batchEmbeddings(
texts,
this.getTextEmbeddings.bind(this),
this.embedBatchSize,
options,
);
}
async transform(nodes: BaseNode[], _options?: any): Promise<BaseNode[]> {
const texts = nodes.map((node) => node.getContent(MetadataMode.EMBED));
const embeddings = await this.getTextEmbeddingsBatch(texts);
const embeddings = await this.getTextEmbeddingsBatch(texts, _options);
for (let i = 0; i < nodes.length; i++) {
nodes[i].embedding = embeddings[i];
@@ -82,3 +67,35 @@ export abstract class BaseEmbedding implements TransformComponent {
return nodes;
}
}
export async function batchEmbeddings<T>(
values: T[],
embedFunc: EmbedFunc<T>,
chunkSize: number,
options?: {
logProgress?: boolean;
},
): Promise<Array<number[]>> {
const resultEmbeddings: Array<number[]> = [];
const queue: T[] = values;
const curBatch: T[] = [];
for (let i = 0; i < queue.length; i++) {
curBatch.push(queue[i]);
if (i == queue.length - 1 || curBatch.length == chunkSize) {
const embeddings = await embedFunc(curBatch);
resultEmbeddings.push(...embeddings);
if (options?.logProgress) {
console.log(`getting embedding progress: ${i} / ${queue.length}`);
}
curBatch.length = 0;
}
}
return resultEmbeddings;
}
@@ -113,10 +113,9 @@ export class ContextChatEngine extends PromptMixin implements ChatEngine {
});
const textOnly = extractText(message);
const context = await this.contextGenerator.generate(textOnly);
const nodes = context.nodes.map((r) => r.node);
const messages = await chatHistory.requestMessages(
context ? [context.message] : undefined,
);
return { nodes, messages };
return { nodes: context.nodes, messages };
}
}
+5 -5
View File
@@ -1,5 +1,5 @@
import type { ChatHistory } from "../../ChatHistory.js";
import type { BaseNode, NodeWithScore } from "../../Node.js";
import type { NodeWithScore } from "../../Node.js";
import type { Response } from "../../Response.js";
import type { ChatMessage } from "../../llm/index.js";
import type { MessageContent } from "../../llm/types.js";
@@ -66,12 +66,12 @@ export enum ChatResponseMode {
export class AgentChatResponse {
response: string;
sources: ToolOutput[];
sourceNodes?: BaseNode[];
sourceNodes?: NodeWithScore[];
constructor(
response: string,
sources?: ToolOutput[],
sourceNodes?: BaseNode[],
sourceNodes?: NodeWithScore[],
) {
this.response = response;
this.sources = sources || [];
@@ -91,12 +91,12 @@ export class StreamingAgentChatResponse {
response: AsyncIterable<Response>;
sources: ToolOutput[];
sourceNodes?: BaseNode[];
sourceNodes?: NodeWithScore[];
constructor(
response: AsyncIterable<Response>,
sources?: ToolOutput[],
sourceNodes?: BaseNode[],
sourceNodes?: NodeWithScore[],
) {
this.response = response;
this.sources = sources ?? [];
@@ -1,4 +1,4 @@
import type { BaseNode } from "../../Node.js";
import type { NodeWithScore } from "../../Node.js";
import { Response } from "../../Response.js";
import type { ServiceContext } from "../../ServiceContext.js";
import { llmFromSettingsOrContext } from "../../Settings.js";
@@ -33,7 +33,7 @@ async function combineResponses(
}
const responseStrs: string[] = [];
const sourceNodes: BaseNode[] = [];
const sourceNodes: NodeWithScore[] = [];
for (const response of responses) {
if (response?.sourceNodes) {
+1 -1
View File
@@ -111,7 +111,7 @@ export class CorrectnessEvaluator extends PromptMixin implements BaseEvaluator {
if (response) {
for (const node of response.sourceNodes || []) {
contexts.push(node.getContent(MetadataMode.ALL));
contexts.push(node.node.getContent(MetadataMode.ALL));
}
}
+1 -1
View File
@@ -137,7 +137,7 @@ export class FaithfulnessEvaluator
if (response) {
for (const node of response.sourceNodes || []) {
contexts.push(node.getContent(MetadataMode.ALL));
contexts.push(node.node.getContent(MetadataMode.ALL));
}
}
+1 -1
View File
@@ -126,7 +126,7 @@ export class RelevancyEvaluator extends PromptMixin implements BaseEvaluator {
if (response) {
for (const node of response.sourceNodes || []) {
contexts.push(node.getContent(MetadataMode.ALL));
contexts.push(node.node.getContent(MetadataMode.ALL));
}
}
+17 -54
View File
@@ -4,12 +4,7 @@ import type {
Metadata,
NodeWithScore,
} from "../../Node.js";
import {
ImageNode,
MetadataMode,
ObjectType,
splitNodesByType,
} from "../../Node.js";
import { ImageNode, ObjectType, splitNodesByType } from "../../Node.js";
import type { BaseRetriever, RetrieveParams } from "../../Retriever.js";
import type { ServiceContext } from "../../ServiceContext.js";
import {
@@ -179,14 +174,21 @@ export class VectorStoreIndex extends BaseIndex<IndexDict> {
nodes: BaseNode[],
options?: { logProgress?: boolean },
): Promise<BaseNode[]> {
const texts = nodes.map((node) => node.getContent(MetadataMode.EMBED));
const embeddings = await this.embedModel.getTextEmbeddingsBatch(texts, {
const { imageNodes, textNodes } = splitNodesByType(nodes);
if (imageNodes.length > 0) {
if (!this.imageEmbedModel) {
throw new Error(
"Cannot calculate image nodes embedding without 'imageEmbedModel' set",
);
}
await this.imageEmbedModel.transform(imageNodes, {
logProgress: options?.logProgress,
});
}
await this.embedModel.transform(textNodes, {
logProgress: options?.logProgress,
});
return nodes.map((node, i) => {
node.embedding = embeddings[i];
return node;
});
return nodes;
}
/**
@@ -324,25 +326,15 @@ export class VectorStoreIndex extends BaseIndex<IndexDict> {
if (!nodes || nodes.length === 0) {
return;
}
nodes = await this.getNodeEmbeddingResults(nodes, options);
const { imageNodes, textNodes } = splitNodesByType(nodes);
if (imageNodes.length > 0) {
if (!this.imageVectorStore) {
throw new Error("Cannot insert image nodes without image vector store");
}
const imageNodesWithEmbedding = await this.getImageNodeEmbeddingResults(
imageNodes,
options,
);
await this.insertNodesToStore(
this.imageVectorStore,
imageNodesWithEmbedding,
);
await this.insertNodesToStore(this.imageVectorStore, imageNodes);
}
const embeddingResults = await this.getNodeEmbeddingResults(
textNodes,
options,
);
await this.insertNodesToStore(this.vectorStore, embeddingResults);
await this.insertNodesToStore(this.vectorStore, textNodes);
await this.indexStore.addIndexStruct(this.indexStruct);
}
@@ -378,35 +370,6 @@ export class VectorStoreIndex extends BaseIndex<IndexDict> {
await this.indexStore.addIndexStruct(this.indexStruct);
}
}
/**
* Calculates the embeddings for the given image nodes.
*
* @param nodes - An array of ImageNode objects representing the nodes for which embeddings are to be calculated.
* @param {Object} [options] - An optional object containing additional parameters.
* @param {boolean} [options.logProgress] - A boolean indicating whether to log progress to the console (useful for debugging).
*/
async getImageNodeEmbeddingResults(
nodes: ImageNode[],
options?: { logProgress?: boolean },
): Promise<ImageNode[]> {
if (!this.imageEmbedModel) {
return [];
}
const nodesWithEmbeddings: ImageNode[] = [];
for (let i = 0; i < nodes.length; ++i) {
const node = nodes[i];
if (options?.logProgress) {
console.log(`Getting embedding for node ${i + 1}/${nodes.length}`);
}
node.embedding = await this.imageEmbedModel.getImageEmbedding(node.image);
nodesWithEmbeddings.push(node);
}
return nodesWithEmbeddings;
}
}
/**
+11
View File
@@ -5,3 +5,14 @@ export const isAsyncGenerator = (obj: unknown): obj is AsyncGenerator => {
export const isGenerator = (obj: unknown): obj is Generator => {
return obj != null && typeof obj === "object" && Symbol.iterator in obj;
};
/**
* Prettify an error for AI to read
*/
export function prettifyError(error: unknown): string {
if (error instanceof Error) {
return `Error(${error.name}): ${error.message}`;
} else {
return `${error}`;
}
}
-373
View File
@@ -1,373 +0,0 @@
import { type StreamCallbackResponse } from "../callbacks/CallbackManager.js";
import type { LLMOptions } from "portkey-ai";
import { getCallbackManager } from "../internal/settings/CallbackManager.js";
import { BaseLLM } from "./base.js";
import type { PortkeySession } from "./portkey.js";
import { getPortkeySession } from "./portkey.js";
import { ReplicateSession } from "./replicate_ai.js";
import type {
ChatMessage,
ChatResponse,
ChatResponseChunk,
LLMChatParamsNonStreaming,
LLMChatParamsStreaming,
LLMMetadata,
MessageType,
} from "./types.js";
import { extractText, wrapLLMEvent } from "./utils.js";
export const ALL_AVAILABLE_LLAMADEUCE_MODELS = {
"Llama-2-70b-chat-old": {
contextWindow: 4096,
replicateApi:
"replicate/llama70b-v2-chat:e951f18578850b652510200860fc4ea62b3b16fac280f83ff32282f87bbd2e48",
//^ Previous 70b model. This is also actually 4 bit, although not exllama.
},
"Llama-2-70b-chat-4bit": {
contextWindow: 4096,
replicateApi:
"meta/llama-2-70b-chat:02e509c789964a7ea8736978a43525956ef40397be9033abf9fd2badfe68c9e3",
//^ Model is based off of exllama 4bit.
},
"Llama-2-13b-chat-old": {
contextWindow: 4096,
replicateApi:
"a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5",
},
//^ Last known good 13b non-quantized model. In future versions they add the SYS and INST tags themselves
"Llama-2-13b-chat-4bit": {
contextWindow: 4096,
replicateApi:
"meta/llama-2-13b-chat:f4e2de70d66816a838a89eeeb621910adffb0dd0baba3976c96980970978018d",
},
"Llama-2-7b-chat-old": {
contextWindow: 4096,
replicateApi:
"a16z-infra/llama7b-v2-chat:4f0a4744c7295c024a1de15e1a63c880d3da035fa1f49bfd344fe076074c8eea",
//^ Last (somewhat) known good 7b non-quantized model. In future versions they add the SYS and INST
// tags themselves
// https://github.com/replicate/cog-llama-template/commit/fa5ce83912cf82fc2b9c01a4e9dc9bff6f2ef137
// Problem is that they fix the max_new_tokens issue in the same commit. :-(
},
"Llama-2-7b-chat-4bit": {
contextWindow: 4096,
replicateApi:
"meta/llama-2-7b-chat:13c3cdee13ee059ab779f0291d29054dab00a47dad8261375654de5540165fb0",
},
};
export enum DeuceChatStrategy {
A16Z = "a16z",
META = "meta",
METAWBOS = "metawbos",
//^ This is not exactly right because SentencePiece puts the BOS and EOS token IDs in after tokenization
// Unfortunately any string only API won't support these properly.
REPLICATE4BIT = "replicate4bit",
//^ To satisfy Replicate's 4 bit models' requirements where they also insert some INST tags
REPLICATE4BITWNEWLINES = "replicate4bitwnewlines",
//^ Replicate's documentation recommends using newlines: https://replicate.com/blog/how-to-prompt-llama
}
/**
* Llama2 LLM implementation
*/
export class LlamaDeuce extends BaseLLM {
model: keyof typeof ALL_AVAILABLE_LLAMADEUCE_MODELS;
chatStrategy: DeuceChatStrategy;
temperature: number;
topP: number;
maxTokens?: number;
replicateSession: ReplicateSession;
constructor(init?: Partial<LlamaDeuce>) {
super();
this.model = init?.model ?? "Llama-2-70b-chat-4bit";
this.chatStrategy =
init?.chatStrategy ??
(this.model.endsWith("4bit")
? DeuceChatStrategy.REPLICATE4BITWNEWLINES // With the newer Replicate models they do the system message themselves.
: DeuceChatStrategy.METAWBOS); // With BOS and EOS seems to work best, although they all have problems past a certain point
this.temperature = init?.temperature ?? 0.1; // minimum temperature is 0.01 for Replicate endpoint
this.topP = init?.topP ?? 1;
this.maxTokens =
init?.maxTokens ??
ALL_AVAILABLE_LLAMADEUCE_MODELS[this.model].contextWindow; // For Replicate, the default is 500 tokens which is too low.
this.replicateSession = init?.replicateSession ?? new ReplicateSession();
}
get metadata() {
return {
model: this.model,
temperature: this.temperature,
topP: this.topP,
maxTokens: this.maxTokens,
contextWindow: ALL_AVAILABLE_LLAMADEUCE_MODELS[this.model].contextWindow,
tokenizer: undefined,
};
}
mapMessagesToPrompt(messages: ChatMessage[]) {
if (this.chatStrategy === DeuceChatStrategy.A16Z) {
return this.mapMessagesToPromptA16Z(messages);
} else if (this.chatStrategy === DeuceChatStrategy.META) {
return this.mapMessagesToPromptMeta(messages);
} else if (this.chatStrategy === DeuceChatStrategy.METAWBOS) {
return this.mapMessagesToPromptMeta(messages, { withBos: true });
} else if (this.chatStrategy === DeuceChatStrategy.REPLICATE4BIT) {
return this.mapMessagesToPromptMeta(messages, {
replicate4Bit: true,
withNewlines: true,
});
} else if (this.chatStrategy === DeuceChatStrategy.REPLICATE4BITWNEWLINES) {
return this.mapMessagesToPromptMeta(messages, {
replicate4Bit: true,
withNewlines: true,
});
} else {
return this.mapMessagesToPromptMeta(messages);
}
}
mapMessagesToPromptA16Z(messages: ChatMessage[]) {
return {
prompt:
messages.reduce((acc, message) => {
return (
(acc && `${acc}\n\n`) +
`${this.mapMessageTypeA16Z(message.role)}${message.content}`
);
}, "") + "\n\nAssistant:",
//^ Here we're differing from A16Z by omitting the space. Generally spaces at the end of prompts decrease performance due to tokenization
systemPrompt: undefined,
};
}
mapMessageTypeA16Z(messageType: MessageType): string {
switch (messageType) {
case "user":
return "User: ";
case "assistant":
return "Assistant: ";
case "system":
return "";
default:
throw new Error("Unsupported LlamaDeuce message type");
}
}
mapMessagesToPromptMeta(
messages: ChatMessage[],
opts?: {
withBos?: boolean;
replicate4Bit?: boolean;
withNewlines?: boolean;
},
) {
const {
withBos = false,
replicate4Bit = false,
withNewlines = false,
} = opts ?? {};
const DEFAULT_SYSTEM_PROMPT = `You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.
If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.`;
const B_SYS = "<<SYS>>\n";
const E_SYS = "\n<</SYS>>\n\n";
const B_INST = "[INST]";
const E_INST = "[/INST]";
const BOS = "<s>";
const EOS = "</s>";
if (messages.length === 0) {
return { prompt: "", systemPrompt: undefined };
}
messages = [...messages]; // so we can use shift without mutating the original array
let systemPrompt = undefined;
if (messages[0].role === "system") {
const systemMessage = messages.shift()!;
if (replicate4Bit) {
systemPrompt = systemMessage.content;
} else {
const systemStr = `${B_SYS}${systemMessage.content}${E_SYS}`;
// TS Bug: https://github.com/microsoft/TypeScript/issues/9998
// @ts-ignore
if (messages[0].role !== "user") {
throw new Error(
"LlamaDeuce: if there is a system message, the second message must be a user message.",
);
}
const userContent = messages[0].content;
messages[0].content = `${systemStr}${userContent}`;
}
} else {
if (!replicate4Bit) {
messages[0].content = `${B_SYS}${DEFAULT_SYSTEM_PROMPT}${E_SYS}${messages[0].content}`;
}
}
return {
prompt: messages.reduce((acc, message, index) => {
const content = extractText(message.content);
if (index % 2 === 0) {
return (
`${acc}${withBos ? BOS : ""}${B_INST} ${content.trim()} ${E_INST}` +
(withNewlines ? "\n" : "")
);
} else {
return (
`${acc} ${content.trim()}` +
(withNewlines ? "\n" : " ") +
(withBos ? EOS : "")
); // Yes, the EOS comes after the space. This is not a mistake.
}
}, ""),
systemPrompt,
};
}
chat(
params: LLMChatParamsStreaming,
): Promise<AsyncIterable<ChatResponseChunk>>;
chat(params: LLMChatParamsNonStreaming): Promise<ChatResponse>;
@wrapLLMEvent
async chat(
params: LLMChatParamsNonStreaming | LLMChatParamsStreaming,
): Promise<ChatResponse | AsyncIterable<ChatResponseChunk>> {
const { messages, stream } = params;
const api = ALL_AVAILABLE_LLAMADEUCE_MODELS[this.model]
.replicateApi as `${string}/${string}:${string}`;
const { prompt, systemPrompt } = this.mapMessagesToPrompt(messages);
const replicateOptions: any = {
input: {
prompt,
system_prompt: systemPrompt,
temperature: this.temperature,
top_p: this.topP,
},
};
if (this.model.endsWith("4bit")) {
replicateOptions.input.max_new_tokens = this.maxTokens;
} else {
replicateOptions.input.max_length = this.maxTokens;
}
//TODO: Add streaming for this
if (stream) {
throw new Error("Streaming not supported for LlamaDeuce");
}
//Non-streaming
const response = await this.replicateSession.replicate.run(
api,
replicateOptions,
);
return {
raw: response,
message: {
content: (response as Array<string>).join("").trimStart(),
//^ We need to do this because Replicate returns a list of strings (for streaming functionality which is not exposed by the run function)
role: "assistant",
},
};
}
}
export class Portkey extends BaseLLM {
apiKey?: string = undefined;
baseURL?: string = undefined;
mode?: string = undefined;
llms?: [LLMOptions] | null = undefined;
session: PortkeySession;
constructor(init?: Partial<Portkey>) {
super();
this.apiKey = init?.apiKey;
this.baseURL = init?.baseURL;
this.mode = init?.mode;
this.llms = init?.llms;
this.session = getPortkeySession({
apiKey: this.apiKey,
baseURL: this.baseURL,
llms: this.llms,
mode: this.mode,
});
}
get metadata(): LLMMetadata {
throw new Error("metadata not implemented for Portkey");
}
chat(
params: LLMChatParamsStreaming,
): Promise<AsyncIterable<ChatResponseChunk>>;
chat(params: LLMChatParamsNonStreaming): Promise<ChatResponse>;
@wrapLLMEvent
async chat(
params: LLMChatParamsNonStreaming | LLMChatParamsStreaming,
): Promise<ChatResponse | AsyncIterable<ChatResponseChunk>> {
const { messages, stream, additionalChatOptions } = params;
if (stream) {
return this.streamChat(messages, additionalChatOptions);
} else {
const bodyParams = additionalChatOptions || {};
const response = await this.session.portkey.chatCompletions.create({
messages: messages.map((message) => ({
content: extractText(message.content),
role: message.role,
})),
...bodyParams,
});
const content = response.choices[0].message?.content ?? "";
const role = response.choices[0].message?.role || "assistant";
return { raw: response, message: { content, role: role as MessageType } };
}
}
async *streamChat(
messages: ChatMessage[],
params?: Record<string, any>,
): AsyncIterable<ChatResponseChunk> {
const chunkStream = await this.session.portkey.chatCompletions.create({
messages: messages.map((message) => ({
content: extractText(message.content),
role: message.role,
})),
...params,
stream: true,
});
//Indices
let idx_counter: number = 0;
for await (const part of chunkStream) {
//Increment
part.choices[0].index = idx_counter;
const is_done: boolean =
part.choices[0].finish_reason === "stop" ? true : false;
//onLLMStream Callback
const stream_callback: StreamCallbackResponse = {
index: idx_counter,
isDone: is_done,
// token: part,
};
getCallbackManager().dispatchEvent("stream", stream_callback);
idx_counter++;
yield { raw: part, delta: part.choices[0].delta?.content ?? "" };
}
return;
}
}
+176 -30
View File
@@ -1,15 +1,30 @@
import type { ClientOptions } from "@anthropic-ai/sdk";
import { Anthropic as SDKAnthropic } from "@anthropic-ai/sdk";
import type {
Tool,
ToolResultBlockParam,
ToolUseBlock,
ToolUseBlockParam,
ToolsBetaContentBlock,
ToolsBetaMessageParam,
} from "@anthropic-ai/sdk/resources/beta/tools/messages";
import type {
TextBlock,
TextBlockParam,
} from "@anthropic-ai/sdk/resources/index";
import type { MessageParam } from "@anthropic-ai/sdk/resources/messages";
import { getEnv } from "@llamaindex/env";
import _ from "lodash";
import type { BaseTool } from "../types.js";
import { ToolCallLLM } from "./base.js";
import type {
ChatMessage,
ChatResponse,
ChatResponseChunk,
LLMChatParamsNonStreaming,
LLMChatParamsStreaming,
} from "llamaindex";
import _ from "lodash";
import { BaseLLM } from "./base.js";
ToolCallLLMMessageOptions,
} from "./types.js";
import { extractText, wrapLLMEvent } from "./utils.js";
export class AnthropicSession {
@@ -81,7 +96,9 @@ const AVAILABLE_ANTHROPIC_MODELS_WITHOUT_DATE: { [key: string]: string } = {
"claude-3-haiku": "claude-3-haiku-20240307",
} as { [key in keyof typeof ALL_AVAILABLE_ANTHROPIC_MODELS]: string };
export class Anthropic extends BaseLLM {
export type AnthropicAdditionalChatOptions = {};
export class Anthropic extends ToolCallLLM<AnthropicAdditionalChatOptions> {
// Per completion Anthropic params
model: keyof typeof ALL_AVAILABLE_ANTHROPIC_MODELS;
temperature: number;
@@ -113,6 +130,10 @@ export class Anthropic extends BaseLLM {
});
}
get supportToolCall() {
return this.model.startsWith("claude-3");
}
get metadata() {
return {
model: this.model,
@@ -131,30 +152,93 @@ export class Anthropic extends BaseLLM {
return model;
};
formatMessages(messages: ChatMessage[]) {
return messages.map((message) => {
formatMessages<Beta = false>(
messages: ChatMessage<ToolCallLLMMessageOptions>[],
): Beta extends true ? ToolsBetaMessageParam[] : MessageParam[] {
return messages.map<any>((message) => {
if (message.role !== "user" && message.role !== "assistant") {
throw new Error("Unsupported Anthropic role");
}
const options = message.options ?? {};
if ("toolResult" in options) {
const { id, isError } = options.toolResult;
return {
role: "user",
content: [
{
type: "tool_result",
is_error: isError,
content: [
{
type: "text",
text: extractText(message.content),
},
],
tool_use_id: id,
},
] satisfies ToolResultBlockParam[],
} satisfies ToolsBetaMessageParam;
} else if ("toolCall" in options) {
const aiThinkingText = extractText(message.content);
return {
role: "assistant",
content: [
// this could be empty when you call two tools in one query
...(aiThinkingText.trim()
? [
{
type: "text",
text: aiThinkingText,
} satisfies TextBlockParam,
]
: []),
{
type: "tool_use",
id: options.toolCall.id,
name: options.toolCall.name,
input: options.toolCall.input,
} satisfies ToolUseBlockParam,
] satisfies ToolsBetaContentBlock[],
} satisfies ToolsBetaMessageParam;
}
return {
content: extractText(message.content),
role: message.role,
};
} satisfies MessageParam;
});
}
chat(
params: LLMChatParamsStreaming,
): Promise<AsyncIterable<ChatResponseChunk>>;
chat(params: LLMChatParamsNonStreaming): Promise<ChatResponse>;
params: LLMChatParamsStreaming<
AnthropicAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<AsyncIterable<ChatResponseChunk<ToolCallLLMMessageOptions>>>;
chat(
params: LLMChatParamsNonStreaming<
AnthropicAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<ChatResponse<ToolCallLLMMessageOptions>>;
@wrapLLMEvent
async chat(
params: LLMChatParamsNonStreaming | LLMChatParamsStreaming,
): Promise<ChatResponse | AsyncIterable<ChatResponseChunk>> {
params:
| LLMChatParamsNonStreaming<
AnthropicAdditionalChatOptions,
ToolCallLLMMessageOptions
>
| LLMChatParamsStreaming<
AnthropicAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<
| ChatResponse<ToolCallLLMMessageOptions>
| AsyncIterable<ChatResponseChunk<ToolCallLLMMessageOptions>>
> {
let { messages } = params;
const { stream } = params;
const { stream, tools } = params;
let systemPrompt: string | null = null;
@@ -169,34 +253,80 @@ export class Anthropic extends BaseLLM {
messages = messages.filter((message) => message.role !== "system");
}
//Streaming
// case: Streaming
if (stream) {
if (tools) {
console.error("Tools are not supported in streaming mode");
}
return this.streamChat(messages, systemPrompt);
}
// case: Non-streaming
const anthropic = this.session.anthropic;
//Non-streaming
const response = await this.session.anthropic.messages.create({
model: this.getModelName(this.model),
messages: this.formatMessages(messages),
max_tokens: this.maxTokens ?? 4096,
temperature: this.temperature,
top_p: this.topP,
...(systemPrompt && { system: systemPrompt }),
});
if (tools) {
const response = await anthropic.beta.tools.messages.create({
messages: this.formatMessages<true>(messages),
tools: tools.map(Anthropic.toTool),
model: this.getModelName(this.model),
temperature: this.temperature,
max_tokens: this.maxTokens ?? 4096,
top_p: this.topP,
...(systemPrompt && { system: systemPrompt }),
});
return {
raw: response,
message: { content: response.content[0].text, role: "assistant" },
};
const toolUseBlock = response.content.find(
(content): content is ToolUseBlock => content.type === "tool_use",
);
return {
raw: response,
message: {
content: response.content
.filter((content): content is TextBlock => content.type === "text")
.map((content) => ({
type: "text",
text: content.text,
})),
role: "assistant",
options: toolUseBlock
? {
toolCall: {
id: toolUseBlock.id,
name: toolUseBlock.name,
input: toolUseBlock.input,
},
}
: {},
},
};
} else {
const response = await anthropic.messages.create({
model: this.getModelName(this.model),
messages: this.formatMessages(messages),
max_tokens: this.maxTokens ?? 4096,
temperature: this.temperature,
top_p: this.topP,
...(systemPrompt && { system: systemPrompt }),
});
return {
raw: response,
message: {
content: response.content[0].text,
role: "assistant",
options: {},
},
};
}
}
protected async *streamChat(
messages: ChatMessage[],
messages: ChatMessage<ToolCallLLMMessageOptions>[],
systemPrompt?: string | null,
): AsyncIterable<ChatResponseChunk> {
): AsyncIterable<ChatResponseChunk<ToolCallLLMMessageOptions>> {
const stream = await this.session.anthropic.messages.create({
model: this.getModelName(this.model),
messages: this.formatMessages(messages),
messages: this.formatMessages<false>(messages),
max_tokens: this.maxTokens ?? 4096,
temperature: this.temperature,
top_p: this.topP,
@@ -215,8 +345,24 @@ export class Anthropic extends BaseLLM {
yield {
raw: part,
delta: content,
options: {},
};
}
return;
}
static toTool(tool: BaseTool): Tool {
if (tool.metadata.parameters?.type !== "object") {
throw new TypeError("Tool parameters must be an object");
}
return {
input_schema: {
type: "object",
properties: tool.metadata.parameters.properties,
required: tool.metadata.parameters.required,
},
name: tool.metadata.name,
description: tool.metadata.description,
};
}
}
+17 -10
View File
@@ -8,18 +8,13 @@ import type {
LLMCompletionParamsNonStreaming,
LLMCompletionParamsStreaming,
LLMMetadata,
ToolCallLLMMessageOptions,
} from "./types.js";
import { extractText, streamConverter } from "./utils.js";
export abstract class BaseLLM<
AdditionalChatOptions extends Record<string, unknown> = Record<
string,
unknown
>,
AdditionalMessageOptions extends Record<string, unknown> = Record<
string,
unknown
>,
AdditionalChatOptions extends object = object,
AdditionalMessageOptions extends object = object,
> implements LLM<AdditionalChatOptions>
{
abstract metadata: LLMMetadata;
@@ -56,9 +51,21 @@ export abstract class BaseLLM<
}
abstract chat(
params: LLMChatParamsStreaming<AdditionalChatOptions>,
params: LLMChatParamsStreaming<
AdditionalChatOptions,
AdditionalMessageOptions
>,
): Promise<AsyncIterable<ChatResponseChunk>>;
abstract chat(
params: LLMChatParamsNonStreaming<AdditionalChatOptions>,
params: LLMChatParamsNonStreaming<
AdditionalChatOptions,
AdditionalMessageOptions
>,
): Promise<ChatResponse<AdditionalMessageOptions>>;
}
export abstract class ToolCallLLM<
AdditionalChatOptions extends object = object,
> extends BaseLLM<AdditionalChatOptions, ToolCallLLMMessageOptions> {
abstract supportToolCall: boolean;
}
+7 -1
View File
@@ -1,4 +1,3 @@
export * from "./LLM.js";
export { Anthropic } from "./anthropic.js";
export { FireworksLLM } from "./fireworks.js";
export { Groq } from "./groq.js";
@@ -9,5 +8,12 @@ export {
} from "./mistral.js";
export { Ollama } from "./ollama.js";
export * from "./open_ai.js";
export { Portkey } from "./portkey.js";
export * from "./replicate_ai.js";
// Note: The type aliases for replicate are to simplify usage for Llama 2 (we're using replicate for Llama 2 support)
export {
ReplicateChatStrategy as DeuceChatStrategy,
ReplicateLLM as LlamaDeuce,
} from "./replicate_ai.js";
export { TogetherLLM } from "./together.js";
export * from "./types.js";
+84 -96
View File
@@ -9,11 +9,11 @@ import { OpenAI as OrigOpenAI } from "openai";
import type {
ChatCompletionAssistantMessageParam,
ChatCompletionFunctionMessageParam,
ChatCompletionMessageToolCall,
ChatCompletionRole,
ChatCompletionSystemMessageParam,
ChatCompletionTool,
ChatCompletionToolMessageParam,
ChatCompletionUserMessageParam,
} from "openai/resources/chat/completions";
import type { ChatCompletionMessageParam } from "openai/resources/index.js";
@@ -28,7 +28,7 @@ import {
getAzureModel,
shouldUseAzure,
} from "./azure.js";
import { BaseLLM } from "./base.js";
import { ToolCallLLM } from "./base.js";
import type {
ChatMessage,
ChatResponse,
@@ -37,8 +37,9 @@ import type {
LLMChatParamsNonStreaming,
LLMChatParamsStreaming,
LLMMetadata,
MessageToolCall,
MessageType,
ToolCallLLMMessageOptions,
ToolCallOptions,
} from "./types.js";
import { extractText, wrapLLMEvent } from "./utils.js";
@@ -143,14 +144,7 @@ export function isFunctionCallingModel(llm: LLM): llm is OpenAI {
return isChatModel && !isOld;
}
export type OpenAIAdditionalMetadata = {
isFunctionCallingModel: boolean;
};
export type OpenAIAdditionalMessageOptions = {
functionName?: string;
toolCalls?: ChatCompletionMessageToolCall[];
};
export type OpenAIAdditionalMetadata = {};
export type OpenAIAdditionalChatOptions = Omit<
Partial<OpenAILLM.Chat.ChatCompletionCreateParams>,
@@ -164,10 +158,7 @@ export type OpenAIAdditionalChatOptions = Omit<
| "toolChoice"
>;
export class OpenAI extends BaseLLM<
OpenAIAdditionalChatOptions,
OpenAIAdditionalMessageOptions
> {
export class OpenAI extends ToolCallLLM<OpenAIAdditionalChatOptions> {
// Per completion OpenAI params
model: keyof typeof ALL_AVAILABLE_OPENAI_MODELS | string;
temperature: number;
@@ -238,6 +229,10 @@ export class OpenAI extends BaseLLM<
}
}
get supportToolCall() {
return isFunctionCallingModel(this);
}
get metadata(): LLMMetadata & OpenAIAdditionalMetadata {
const contextWindow =
ALL_AVAILABLE_OPENAI_MODELS[
@@ -250,7 +245,6 @@ export class OpenAI extends BaseLLM<
maxTokens: this.maxTokens,
contextWindow,
tokenizer: Tokenizers.CL100K_BASE,
isFunctionCallingModel: isFunctionCallingModel(this),
};
}
@@ -262,47 +256,46 @@ export class OpenAI extends BaseLLM<
return "assistant";
case "system":
return "system";
case "function":
return "function";
case "tool":
return "tool";
default:
return "user";
}
}
static toOpenAIMessage(
messages: ChatMessage<OpenAIAdditionalMessageOptions>[],
messages: ChatMessage<ToolCallLLMMessageOptions>[],
): ChatCompletionMessageParam[] {
return messages.map((message) => {
const options: OpenAIAdditionalMessageOptions = message.options ?? {};
if (message.role === "user") {
const options = message.options ?? {};
if ("toolResult" in options) {
return {
tool_call_id: options.toolResult.id,
role: "tool",
content: extractText(message.content),
} satisfies ChatCompletionToolMessageParam;
} else if ("toolCall" in options) {
return {
role: "assistant",
content: extractText(message.content),
tool_calls: [
{
id: options.toolCall.id,
type: "function",
function: {
name: options.toolCall.name,
arguments:
typeof options.toolCall.input === "string"
? options.toolCall.input
: JSON.stringify(options.toolCall.input),
},
},
],
} satisfies ChatCompletionAssistantMessageParam;
} else if (message.role === "user") {
return {
role: "user",
content: message.content,
} satisfies ChatCompletionUserMessageParam;
}
if (typeof message.content !== "string") {
console.warn("Message content is not a string");
}
if (message.role === "function") {
if (!options.functionName) {
console.warn("Function message does not have a name");
}
return {
role: "function",
name: options.functionName ?? "UNKNOWN",
content: extractText(message.content),
// todo: remove this since this is deprecated in the OpenAI API
} satisfies ChatCompletionFunctionMessageParam;
}
if (message.role === "assistant") {
return {
role: "assistant",
content: extractText(message.content),
tool_calls: options.toolCalls,
} satisfies ChatCompletionAssistantMessageParam;
}
const response:
| ChatCompletionSystemMessageParam
@@ -312,27 +305,38 @@ export class OpenAI extends BaseLLM<
role: OpenAI.toOpenAIRole(message.role) as never,
// fixme: should not extract text, but assert content is string
content: extractText(message.content),
...options,
};
return response;
});
}
chat(
params: LLMChatParamsStreaming<OpenAIAdditionalChatOptions>,
): Promise<AsyncIterable<ChatResponseChunk<OpenAIAdditionalMessageOptions>>>;
params: LLMChatParamsStreaming<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<AsyncIterable<ChatResponseChunk<ToolCallLLMMessageOptions>>>;
chat(
params: LLMChatParamsNonStreaming<OpenAIAdditionalChatOptions>,
): Promise<ChatResponse<OpenAIAdditionalMessageOptions>>;
params: LLMChatParamsNonStreaming<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<ChatResponse<ToolCallLLMMessageOptions>>;
@wrapEventCaller
@wrapLLMEvent
async chat(
params:
| LLMChatParamsNonStreaming<OpenAIAdditionalChatOptions>
| LLMChatParamsStreaming<OpenAIAdditionalChatOptions>,
| LLMChatParamsNonStreaming<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>
| LLMChatParamsStreaming<
OpenAIAdditionalChatOptions,
ToolCallLLMMessageOptions
>,
): Promise<
| ChatResponse<OpenAIAdditionalMessageOptions>
| AsyncIterable<ChatResponseChunk<OpenAIAdditionalMessageOptions>>
| ChatResponse<ToolCallLLMMessageOptions>
| AsyncIterable<ChatResponseChunk<ToolCallLLMMessageOptions>>
> {
const { messages, stream, tools, additionalChatOptions } = params;
const baseRequestParams: OpenAILLM.Chat.ChatCompletionCreateParams = {
@@ -358,18 +362,21 @@ export class OpenAI extends BaseLLM<
const content = response.choices[0].message?.content ?? "";
const options: OpenAIAdditionalMessageOptions = {};
if (response.choices[0].message?.tool_calls) {
options.toolCalls = response.choices[0].message.tool_calls;
}
return {
raw: response,
message: {
content,
role: response.choices[0].message.role,
options,
options: response.choices[0].message?.tool_calls
? {
toolCall: {
id: response.choices[0].message.tool_calls[0].id,
name: response.choices[0].message.tool_calls[0].function.name,
input:
response.choices[0].message.tool_calls[0].function.arguments,
},
}
: {},
},
};
}
@@ -377,7 +384,7 @@ export class OpenAI extends BaseLLM<
@wrapEventCaller
protected async *streamChat(
baseRequestParams: OpenAILLM.Chat.ChatCompletionCreateParams,
): AsyncIterable<ChatResponseChunk<OpenAIAdditionalMessageOptions>> {
): AsyncIterable<ChatResponseChunk<ToolCallLLMMessageOptions>> {
const stream: AsyncIterable<OpenAILLM.Chat.ChatCompletionChunk> =
await this.session.openai.chat.completions.create({
...baseRequestParams,
@@ -387,13 +394,26 @@ export class OpenAI extends BaseLLM<
// TODO: add callback to streamConverter and use streamConverter here
//Indices
let idxCounter: number = 0;
const toolCalls: MessageToolCall[] = [];
let toolCallOptions: ToolCallOptions | null = null;
for await (const part of stream) {
if (!part.choices.length) continue;
const choice = part.choices[0];
// skip parts that don't have any content
if (!(choice.delta.content || choice.delta.tool_calls)) continue;
updateToolCalls(toolCalls, choice.delta.tool_calls);
if (choice.delta.tool_calls?.[0].id) {
toolCallOptions = {
toolCall: {
name: choice.delta.tool_calls[0].function!.name!,
id: choice.delta.tool_calls[0].id,
input: choice.delta.tool_calls[0].function!.arguments!,
},
};
} else {
if (choice.delta.tool_calls?.[0].function?.arguments) {
toolCallOptions!.toolCall.input +=
choice.delta.tool_calls[0].function.arguments;
}
}
const isDone: boolean = choice.finish_reason !== null;
@@ -405,8 +425,7 @@ export class OpenAI extends BaseLLM<
yield {
raw: part,
// add tool calls to final chunk
options: toolCalls.length > 0 ? { toolCalls: toolCalls } : {},
options: toolCallOptions ? toolCallOptions : {},
delta: choice.delta.content ?? "",
};
}
@@ -424,34 +443,3 @@ export class OpenAI extends BaseLLM<
};
}
}
function updateToolCalls(
toolCalls: MessageToolCall[],
toolCallDeltas?: OpenAILLM.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall[],
) {
function augmentToolCall(
toolCall?: MessageToolCall,
toolCallDelta?: OpenAILLM.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall,
) {
toolCall =
toolCall ??
({ function: { name: "", arguments: "" } } as MessageToolCall);
toolCall.id = toolCall.id ?? toolCallDelta?.id;
toolCall.type = toolCall.type ?? toolCallDelta?.type;
if (toolCallDelta?.function?.arguments) {
toolCall.function.arguments += toolCallDelta.function.arguments;
}
if (toolCallDelta?.function?.name) {
toolCall.function.name += toolCallDelta.function.name;
}
return toolCall;
}
if (toolCallDeltas) {
toolCallDeltas?.forEach((toolCall) => {
toolCalls[toolCall.index] = augmentToolCall(
toolCalls[toolCall.index],
toolCall,
);
});
}
}
+106 -4
View File
@@ -1,7 +1,20 @@
import { getEnv } from "@llamaindex/env";
import _ from "lodash";
import type { LLMOptions } from "portkey-ai";
import { Portkey } from "portkey-ai";
import { Portkey as OrigPortKey } from "portkey-ai";
import { type StreamCallbackResponse } from "../callbacks/CallbackManager.js";
import { getCallbackManager } from "../internal/settings/CallbackManager.js";
import { BaseLLM } from "./base.js";
import type {
ChatMessage,
ChatResponse,
ChatResponseChunk,
LLMChatParamsNonStreaming,
LLMChatParamsStreaming,
LLMMetadata,
MessageType,
} from "./types.js";
import { extractText, wrapLLMEvent } from "./utils.js";
interface PortkeyOptions {
apiKey?: string;
@@ -11,7 +24,7 @@ interface PortkeyOptions {
}
export class PortkeySession {
portkey: Portkey;
portkey: OrigPortKey;
constructor(options: PortkeyOptions = {}) {
if (!options.apiKey) {
@@ -22,13 +35,13 @@ export class PortkeySession {
options.baseURL = getEnv("PORTKEY_BASE_URL") ?? "https://api.portkey.ai";
}
this.portkey = new Portkey({});
this.portkey = new OrigPortKey({});
this.portkey.llms = [{}];
if (!options.apiKey) {
throw new Error("Set Portkey ApiKey in PORTKEY_API_KEY env variable");
}
this.portkey = new Portkey(options);
this.portkey = new OrigPortKey(options);
}
}
@@ -54,3 +67,92 @@ export function getPortkeySession(options: PortkeyOptions = {}) {
}
return session;
}
export class Portkey extends BaseLLM {
apiKey?: string = undefined;
baseURL?: string = undefined;
mode?: string = undefined;
llms?: [LLMOptions] | null = undefined;
session: PortkeySession;
constructor(init?: Partial<Portkey>) {
super();
this.apiKey = init?.apiKey;
this.baseURL = init?.baseURL;
this.mode = init?.mode;
this.llms = init?.llms;
this.session = getPortkeySession({
apiKey: this.apiKey,
baseURL: this.baseURL,
llms: this.llms,
mode: this.mode,
});
}
get metadata(): LLMMetadata {
throw new Error("metadata not implemented for Portkey");
}
chat(
params: LLMChatParamsStreaming,
): Promise<AsyncIterable<ChatResponseChunk>>;
chat(params: LLMChatParamsNonStreaming): Promise<ChatResponse>;
@wrapLLMEvent
async chat(
params: LLMChatParamsNonStreaming | LLMChatParamsStreaming,
): Promise<ChatResponse | AsyncIterable<ChatResponseChunk>> {
const { messages, stream, additionalChatOptions } = params;
if (stream) {
return this.streamChat(messages, additionalChatOptions);
} else {
const bodyParams = additionalChatOptions || {};
const response = await this.session.portkey.chatCompletions.create({
messages: messages.map((message) => ({
content: extractText(message.content),
role: message.role,
})),
...bodyParams,
});
const content = response.choices[0].message?.content ?? "";
const role = response.choices[0].message?.role || "assistant";
return { raw: response, message: { content, role: role as MessageType } };
}
}
async *streamChat(
messages: ChatMessage[],
params?: Record<string, any>,
): AsyncIterable<ChatResponseChunk> {
const chunkStream = await this.session.portkey.chatCompletions.create({
messages: messages.map((message) => ({
content: extractText(message.content),
role: message.role,
})),
...params,
stream: true,
});
//Indices
let idx_counter: number = 0;
for await (const part of chunkStream) {
//Increment
part.choices[0].index = idx_counter;
const is_done: boolean =
part.choices[0].finish_reason === "stop" ? true : false;
//onLLMStream Callback
const stream_callback: StreamCallbackResponse = {
index: idx_counter,
isDone: is_done,
// token: part,
};
getCallbackManager().dispatchEvent("stream", stream_callback);
idx_counter++;
yield { raw: part, delta: part.choices[0].delta?.content ?? "" };
}
return;
}
}
+274 -5
View File
@@ -1,5 +1,15 @@
import { getEnv } from "@llamaindex/env";
import Replicate from "replicate";
import { BaseLLM } from "./base.js";
import type {
ChatMessage,
ChatResponse,
ChatResponseChunk,
LLMChatParamsNonStreaming,
LLMChatParamsStreaming,
MessageType,
} from "./types.js";
import { extractText, wrapLLMEvent } from "./utils.js";
export class ReplicateSession {
replicateKey: string | null = null;
@@ -20,12 +30,271 @@ export class ReplicateSession {
}
}
let defaultReplicateSession: ReplicateSession | null = null;
export const ALL_AVAILABLE_REPLICATE_MODELS = {
// TODO: add more models from replicate
"Llama-2-70b-chat-old": {
contextWindow: 4096,
replicateApi:
"replicate/llama70b-v2-chat:e951f18578850b652510200860fc4ea62b3b16fac280f83ff32282f87bbd2e48",
//^ Previous 70b model. This is also actually 4 bit, although not exllama.
},
"Llama-2-70b-chat-4bit": {
contextWindow: 4096,
replicateApi:
"meta/llama-2-70b-chat:02e509c789964a7ea8736978a43525956ef40397be9033abf9fd2badfe68c9e3",
//^ Model is based off of exllama 4bit.
},
"Llama-2-13b-chat-old": {
contextWindow: 4096,
replicateApi:
"a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5",
},
//^ Last known good 13b non-quantized model. In future versions they add the SYS and INST tags themselves
"Llama-2-13b-chat-4bit": {
contextWindow: 4096,
replicateApi:
"meta/llama-2-13b-chat:f4e2de70d66816a838a89eeeb621910adffb0dd0baba3976c96980970978018d",
},
"Llama-2-7b-chat-old": {
contextWindow: 4096,
replicateApi:
"a16z-infra/llama7b-v2-chat:4f0a4744c7295c024a1de15e1a63c880d3da035fa1f49bfd344fe076074c8eea",
//^ Last (somewhat) known good 7b non-quantized model. In future versions they add the SYS and INST
// tags themselves
// https://github.com/replicate/cog-llama-template/commit/fa5ce83912cf82fc2b9c01a4e9dc9bff6f2ef137
// Problem is that they fix the max_new_tokens issue in the same commit. :-(
},
"Llama-2-7b-chat-4bit": {
contextWindow: 4096,
replicateApi:
"meta/llama-2-7b-chat:13c3cdee13ee059ab779f0291d29054dab00a47dad8261375654de5540165fb0",
},
};
export function getReplicateSession(replicateKey: string | null = null) {
if (!defaultReplicateSession) {
defaultReplicateSession = new ReplicateSession(replicateKey);
export enum ReplicateChatStrategy {
A16Z = "a16z",
META = "meta",
METAWBOS = "metawbos",
//^ This is not exactly right because SentencePiece puts the BOS and EOS token IDs in after tokenization
// Unfortunately any string only API won't support these properly.
REPLICATE4BIT = "replicate4bit",
//^ To satisfy Replicate's 4 bit models' requirements where they also insert some INST tags
REPLICATE4BITWNEWLINES = "replicate4bitwnewlines",
//^ Replicate's documentation recommends using newlines: https://replicate.com/blog/how-to-prompt-llama
}
/**
* Replicate LLM implementation used
*/
export class ReplicateLLM extends BaseLLM {
model: keyof typeof ALL_AVAILABLE_REPLICATE_MODELS;
chatStrategy: ReplicateChatStrategy;
temperature: number;
topP: number;
maxTokens?: number;
replicateSession: ReplicateSession;
constructor(init?: Partial<ReplicateLLM>) {
super();
this.model = init?.model ?? "Llama-2-70b-chat-4bit";
this.chatStrategy =
init?.chatStrategy ??
(this.model.endsWith("4bit")
? ReplicateChatStrategy.REPLICATE4BITWNEWLINES // With the newer Replicate models they do the system message themselves.
: ReplicateChatStrategy.METAWBOS); // With BOS and EOS seems to work best, although they all have problems past a certain point
this.temperature = init?.temperature ?? 0.1; // minimum temperature is 0.01 for Replicate endpoint
this.topP = init?.topP ?? 1;
this.maxTokens =
init?.maxTokens ??
ALL_AVAILABLE_REPLICATE_MODELS[this.model].contextWindow; // For Replicate, the default is 500 tokens which is too low.
this.replicateSession = init?.replicateSession ?? new ReplicateSession();
}
return defaultReplicateSession;
get metadata() {
return {
model: this.model,
temperature: this.temperature,
topP: this.topP,
maxTokens: this.maxTokens,
contextWindow: ALL_AVAILABLE_REPLICATE_MODELS[this.model].contextWindow,
tokenizer: undefined,
};
}
mapMessagesToPrompt(messages: ChatMessage[]) {
if (this.chatStrategy === ReplicateChatStrategy.A16Z) {
return this.mapMessagesToPromptA16Z(messages);
} else if (this.chatStrategy === ReplicateChatStrategy.META) {
return this.mapMessagesToPromptMeta(messages);
} else if (this.chatStrategy === ReplicateChatStrategy.METAWBOS) {
return this.mapMessagesToPromptMeta(messages, { withBos: true });
} else if (this.chatStrategy === ReplicateChatStrategy.REPLICATE4BIT) {
return this.mapMessagesToPromptMeta(messages, {
replicate4Bit: true,
withNewlines: true,
});
} else if (
this.chatStrategy === ReplicateChatStrategy.REPLICATE4BITWNEWLINES
) {
return this.mapMessagesToPromptMeta(messages, {
replicate4Bit: true,
withNewlines: true,
});
} else {
return this.mapMessagesToPromptMeta(messages);
}
}
mapMessagesToPromptA16Z(messages: ChatMessage[]) {
return {
prompt:
messages.reduce((acc, message) => {
return (
(acc && `${acc}\n\n`) +
`${this.mapMessageTypeA16Z(message.role)}${message.content}`
);
}, "") + "\n\nAssistant:",
//^ Here we're differing from A16Z by omitting the space. Generally spaces at the end of prompts decrease performance due to tokenization
systemPrompt: undefined,
};
}
mapMessageTypeA16Z(messageType: MessageType): string {
switch (messageType) {
case "user":
return "User: ";
case "assistant":
return "Assistant: ";
case "system":
return "";
default:
throw new Error("Unsupported ReplicateLLM message type");
}
}
mapMessagesToPromptMeta(
messages: ChatMessage[],
opts?: {
withBos?: boolean;
replicate4Bit?: boolean;
withNewlines?: boolean;
},
) {
const {
withBos = false,
replicate4Bit = false,
withNewlines = false,
} = opts ?? {};
const DEFAULT_SYSTEM_PROMPT = `You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.
If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.`;
const B_SYS = "<<SYS>>\n";
const E_SYS = "\n<</SYS>>\n\n";
const B_INST = "[INST]";
const E_INST = "[/INST]";
const BOS = "<s>";
const EOS = "</s>";
if (messages.length === 0) {
return { prompt: "", systemPrompt: undefined };
}
messages = [...messages]; // so we can use shift without mutating the original array
let systemPrompt = undefined;
if (messages[0].role === "system") {
const systemMessage = messages.shift()!;
if (replicate4Bit) {
systemPrompt = systemMessage.content;
} else {
const systemStr = `${B_SYS}${systemMessage.content}${E_SYS}`;
// TS Bug: https://github.com/microsoft/TypeScript/issues/9998
// @ts-ignore
if (messages[0].role !== "user") {
throw new Error(
"ReplicateLLM: if there is a system message, the second message must be a user message.",
);
}
const userContent = messages[0].content;
messages[0].content = `${systemStr}${userContent}`;
}
} else {
if (!replicate4Bit) {
messages[0].content = `${B_SYS}${DEFAULT_SYSTEM_PROMPT}${E_SYS}${messages[0].content}`;
}
}
return {
prompt: messages.reduce((acc, message, index) => {
const content = extractText(message.content);
if (index % 2 === 0) {
return (
`${acc}${withBos ? BOS : ""}${B_INST} ${content.trim()} ${E_INST}` +
(withNewlines ? "\n" : "")
);
} else {
return (
`${acc} ${content.trim()}` +
(withNewlines ? "\n" : " ") +
(withBos ? EOS : "")
); // Yes, the EOS comes after the space. This is not a mistake.
}
}, ""),
systemPrompt,
};
}
chat(
params: LLMChatParamsStreaming,
): Promise<AsyncIterable<ChatResponseChunk>>;
chat(params: LLMChatParamsNonStreaming): Promise<ChatResponse>;
@wrapLLMEvent
async chat(
params: LLMChatParamsNonStreaming | LLMChatParamsStreaming,
): Promise<ChatResponse | AsyncIterable<ChatResponseChunk>> {
const { messages, stream } = params;
const api = ALL_AVAILABLE_REPLICATE_MODELS[this.model]
.replicateApi as `${string}/${string}:${string}`;
const { prompt, systemPrompt } = this.mapMessagesToPrompt(messages);
const replicateOptions: any = {
input: {
prompt,
system_prompt: systemPrompt,
temperature: this.temperature,
top_p: this.topP,
},
};
if (this.model.endsWith("4bit")) {
replicateOptions.input.max_new_tokens = this.maxTokens;
} else {
replicateOptions.input.max_length = this.maxTokens;
}
//TODO: Add streaming for this
if (stream) {
throw new Error("Streaming not supported for ReplicateLLM");
}
//Non-streaming
const response = await this.replicateSession.replicate.run(
api,
replicateOptions,
);
return {
raw: response,
message: {
content: (response as Array<string>).join("").trimStart(),
//^ We need to do this because Replicate returns a list of strings (for streaming functionality which is not exposed by the run function)
role: "assistant",
},
};
}
}
+77 -126
View File
@@ -1,54 +1,42 @@
import type { Tokenizers } from "../GlobalsHelper.js";
import type { BaseTool, UUID } from "../types.js";
type LLMBaseEvent<
Type extends string,
Payload extends Record<string, unknown>,
> = CustomEvent<{
type LLMBaseEvent<Payload extends Record<string, unknown>> = CustomEvent<{
payload: Payload;
}>;
export type LLMStartEvent = LLMBaseEvent<
"llm-start",
{
id: UUID;
messages: ChatMessage[];
}
>;
export type LLMEndEvent = LLMBaseEvent<
"llm-end",
{
id: UUID;
response: ChatResponse;
}
>;
export type LLMStreamEvent = LLMBaseEvent<
"llm-stream",
{
id: UUID;
chunk: ChatResponseChunk;
}
>;
export type LLMStartEvent = LLMBaseEvent<{
id: UUID;
messages: ChatMessage[];
}>;
export type LLMToolCallEvent = LLMBaseEvent<{
// fixme: id is missing in the context
// id: UUID;
toolCall: Omit<ToolCallOptions["toolCall"], "id">;
}>;
export type LLMEndEvent = LLMBaseEvent<{
id: UUID;
response: ChatResponse;
}>;
export type LLMStreamEvent = LLMBaseEvent<{
id: UUID;
chunk: ChatResponseChunk;
}>;
/**
* @internal
*/
export interface LLMChat<
AdditionalChatOptions extends Record<string, unknown> = Record<
string,
unknown
>,
AdditionalMessageOptions extends Record<string, unknown> = Record<
string,
unknown
>,
AdditionalChatOptions extends object = object,
AdditionalMessageOptions extends object = object,
> {
chat(
params:
| LLMChatParamsStreaming<AdditionalChatOptions>
| LLMChatParamsNonStreaming<AdditionalChatOptions>,
): Promise<
ChatResponse<AdditionalMessageOptions> | AsyncIterable<ChatResponseChunk>
| ChatResponse<AdditionalMessageOptions>
| AsyncIterable<ChatResponseChunk<AdditionalMessageOptions>>
>;
}
@@ -56,24 +44,24 @@ export interface LLMChat<
* Unified language model interface
*/
export interface LLM<
AdditionalChatOptions extends Record<string, unknown> = Record<
string,
unknown
>,
AdditionalMessageOptions extends Record<string, unknown> = Record<
string,
unknown
>,
AdditionalChatOptions extends object = object,
AdditionalMessageOptions extends object = object,
> extends LLMChat<AdditionalChatOptions> {
metadata: LLMMetadata;
/**
* Get a chat response from the LLM
*/
chat(
params: LLMChatParamsStreaming<AdditionalChatOptions>,
params: LLMChatParamsStreaming<
AdditionalChatOptions,
AdditionalMessageOptions
>,
): Promise<AsyncIterable<ChatResponseChunk>>;
chat(
params: LLMChatParamsNonStreaming<AdditionalChatOptions>,
params: LLMChatParamsNonStreaming<
AdditionalChatOptions,
AdditionalMessageOptions
>,
): Promise<ChatResponse<AdditionalMessageOptions>>;
/**
@@ -87,48 +75,16 @@ export interface LLM<
): Promise<CompletionResponse>;
}
// todo: remove "generic", "function", "memory";
export type MessageType =
| "user"
| "assistant"
| "system"
/**
* @deprecated
*/
| "generic"
/**
* @deprecated
*/
| "function"
/**
* @deprecated
*/
| "memory"
| "tool";
export type MessageType = "user" | "assistant" | "system" | "memory";
export type ChatMessage<
AdditionalMessageOptions extends Record<string, unknown> = Record<
string,
unknown
>,
> =
AdditionalMessageOptions extends Record<string, unknown>
? {
content: MessageContent;
role: MessageType;
options?: AdditionalMessageOptions;
}
: {
content: MessageContent;
role: MessageType;
options: AdditionalMessageOptions;
};
export type ChatMessage<AdditionalMessageOptions extends object = object> = {
content: MessageContent;
role: MessageType;
options?: undefined | AdditionalMessageOptions;
};
export interface ChatResponse<
AdditionalMessageOptions extends Record<string, unknown> = Record<
string,
unknown
>,
AdditionalMessageOptions extends object = object,
> {
message: ChatMessage<AdditionalMessageOptions>;
/**
@@ -140,22 +96,12 @@ export interface ChatResponse<
}
export type ChatResponseChunk<
AdditionalMessageOptions extends Record<string, unknown> = Record<
string,
unknown
>,
> =
AdditionalMessageOptions extends Record<string, unknown>
? {
raw: object | null;
delta: string;
options?: AdditionalMessageOptions;
}
: {
raw: object | null;
delta: string;
options: AdditionalMessageOptions;
};
AdditionalMessageOptions extends object = object,
> = {
raw: object | null;
delta: string;
options?: undefined | AdditionalMessageOptions;
};
export interface CompletionResponse {
text: string;
@@ -177,36 +123,25 @@ export type LLMMetadata = {
};
export interface LLMChatParamsBase<
AdditionalChatOptions extends Record<string, unknown> = Record<
string,
unknown
>,
AdditionalMessageOptions extends Record<string, unknown> = Record<
string,
unknown
>,
AdditionalChatOptions extends object = object,
AdditionalMessageOptions extends object = object,
> {
messages: ChatMessage<AdditionalMessageOptions>[];
additionalChatOptions?: AdditionalChatOptions;
tools?: BaseTool[];
additionalKwargs?: Record<string, unknown>;
}
export interface LLMChatParamsStreaming<
AdditionalChatOptions extends Record<string, unknown> = Record<
string,
unknown
>,
> extends LLMChatParamsBase<AdditionalChatOptions> {
AdditionalChatOptions extends object = object,
AdditionalMessageOptions extends object = object,
> extends LLMChatParamsBase<AdditionalChatOptions, AdditionalMessageOptions> {
stream: true;
}
export interface LLMChatParamsNonStreaming<
AdditionalChatOptions extends Record<string, unknown> = Record<
string,
unknown
>,
> extends LLMChatParamsBase<AdditionalChatOptions> {
AdditionalChatOptions extends object = object,
AdditionalMessageOptions extends object = object,
> extends LLMChatParamsBase<AdditionalChatOptions, AdditionalMessageOptions> {
stream?: false;
}
@@ -242,13 +177,29 @@ export type MessageContentDetail =
*/
export type MessageContent = string | MessageContentDetail[];
interface Function {
arguments: string;
export type ToolCall = {
name: string;
}
export interface MessageToolCall {
// for now, claude-3-opus will give object, gpt-3/4 will give string
// todo: unify this to always be an object
input: unknown;
id: string;
function: Function;
type: "function";
}
};
export type ToolResult = {
id: string;
result: string;
isError: boolean;
};
export type ToolCallOptions = {
toolCall: ToolCall;
};
export type ToolResultOptions = {
toolResult: ToolResult;
};
export type ToolCallLLMMessageOptions =
| ToolResultOptions
| ToolCallOptions
| {};
+15 -5
View File
@@ -61,14 +61,24 @@ export function extractText(message: MessageContent): string {
/**
* @internal
*/
export function wrapLLMEvent(
originalMethod: LLMChat["chat"],
export function wrapLLMEvent<
AdditionalChatOptions extends object = object,
AdditionalMessageOptions extends object = object,
>(
originalMethod: LLMChat<
AdditionalChatOptions,
AdditionalMessageOptions
>["chat"],
_context: ClassMethodDecoratorContext,
) {
return async function withLLMEvent(
this: LLM,
...params: Parameters<LLMChat["chat"]>
): ReturnType<LLMChat["chat"]> {
this: LLM<AdditionalChatOptions, AdditionalMessageOptions>,
...params: Parameters<
LLMChat<AdditionalChatOptions, AdditionalMessageOptions>["chat"]
>
): ReturnType<
LLMChat<AdditionalChatOptions, AdditionalMessageOptions>["chat"]
> {
const id = randomUUID();
getCallbackManager().dispatchEvent("llm-start", {
payload: {
+12 -19
View File
@@ -1,3 +1,4 @@
import type { ChatHistory } from "../ChatHistory.js";
import type { ChatMessage, LLM } from "../llm/index.js";
import { SimpleChatStore } from "../storage/chatStore/SimpleChatStore.js";
import type { BaseChatStore } from "../storage/chatStore/types.js";
@@ -6,25 +7,17 @@ import type { BaseMemory } from "./types.js";
const DEFAULT_TOKEN_LIMIT_RATIO = 0.75;
const DEFAULT_TOKEN_LIMIT = 3000;
type ChatMemoryBufferParams<
AdditionalMessageOptions extends Record<string, unknown> = Record<
string,
unknown
>,
> = {
tokenLimit?: number;
chatStore?: BaseChatStore<AdditionalMessageOptions>;
chatStoreKey?: string;
chatHistory?: ChatMessage<AdditionalMessageOptions>[];
llm?: LLM<Record<string, unknown>, AdditionalMessageOptions>;
};
type ChatMemoryBufferParams<AdditionalMessageOptions extends object = object> =
{
tokenLimit?: number;
chatStore?: BaseChatStore<AdditionalMessageOptions>;
chatStoreKey?: string;
chatHistory?: ChatHistory<AdditionalMessageOptions>;
llm?: LLM<object, AdditionalMessageOptions>;
};
export class ChatMemoryBuffer<
AdditionalMessageOptions extends Record<string, unknown> = Record<
string,
unknown
>,
> implements BaseMemory<AdditionalMessageOptions>
export class ChatMemoryBuffer<AdditionalMessageOptions extends object = object>
implements BaseMemory<AdditionalMessageOptions>
{
tokenLimit: number;
@@ -47,7 +40,7 @@ export class ChatMemoryBuffer<
}
if (init?.chatHistory) {
this.chatStore.setMessages(this.chatStoreKey, init.chatHistory);
this.chatStore.setMessages(this.chatStoreKey, init.chatHistory.messages);
}
}
+1 -6
View File
@@ -1,11 +1,6 @@
import type { ChatMessage } from "../llm/index.js";
export interface BaseMemory<
AdditionalMessageOptions extends Record<string, unknown> = Record<
string,
unknown
>,
> {
export interface BaseMemory<AdditionalMessageOptions extends object = object> {
tokenLimit: number;
get(...args: unknown[]): ChatMessage<AdditionalMessageOptions>[];
getAll(): ChatMessage<AdditionalMessageOptions>[];
+3 -3
View File
@@ -50,7 +50,7 @@ export abstract class BaseObjectNodeMapping {
type QueryType = string;
export class ObjectRetriever {
export class ObjectRetriever<T = unknown> {
_retriever: BaseRetriever;
_objectNodeMapping: BaseObjectNodeMapping;
@@ -68,7 +68,7 @@ export class ObjectRetriever {
}
// Translating the retrieve method
async retrieve(strOrQueryBundle: QueryType): Promise<any> {
async retrieve(strOrQueryBundle: QueryType): Promise<T[]> {
const nodes = await this.retriever.retrieve({ query: strOrQueryBundle });
const objs = nodes.map((n) => this._objectNodeMapping.fromNode(n.node));
return objs;
@@ -180,7 +180,7 @@ export class ObjectIndex {
return this._objectNodeMapping.objNodeMapping();
}
async asRetriever(kwargs: any): Promise<ObjectRetriever> {
async asRetriever(kwargs: any): Promise<ObjectRetriever<any>> {
return new ObjectRetriever(
this._index.asRetriever(kwargs),
this._objectNodeMapping,
@@ -6,10 +6,7 @@ import type { BaseChatStore } from "./types.js";
* This could lead to memory leaks if the messages are not properly cleaned up.
*/
export class SimpleChatStore<
AdditionalMessageOptions extends Record<string, unknown> = Record<
string,
unknown
>,
AdditionalMessageOptions extends object = Record<string, unknown>,
> implements BaseChatStore<AdditionalMessageOptions>
{
store: { [key: string]: ChatMessage<AdditionalMessageOptions>[] } = {};
+1 -4
View File
@@ -1,10 +1,7 @@
import type { ChatMessage } from "../../llm/index.js";
export interface BaseChatStore<
AdditionalMessageOptions extends Record<string, unknown> = Record<
string,
unknown
>,
AdditionalMessageOptions extends object = object,
> {
setMessages(
key: string,
@@ -91,6 +91,6 @@ export class MultiModalResponseSynthesizer
prompt,
});
return new Response(response.text, nodes);
return new Response(response.text, nodesWithScore);
}
}
@@ -69,19 +69,21 @@ export class ResponseSynthesizer
const textChunks: string[] = nodesWithScore.map(({ node }) =>
node.getContent(this.metadataMode),
);
const nodes = nodesWithScore.map(({ node }) => node);
if (stream) {
const response = await this.responseBuilder.getResponse({
query,
textChunks,
stream,
});
return streamConverter(response, (chunk) => new Response(chunk, nodes));
return streamConverter(
response,
(chunk) => new Response(chunk, nodesWithScore),
);
}
const response = await this.responseBuilder.getResponse({
query,
textChunks,
});
return new Response(response, nodes);
return new Response(response, nodesWithScore);
}
}
+16 -10
View File
@@ -1,27 +1,33 @@
import { getCallbackManager } from "../internal/settings/CallbackManager.js";
import type { BaseTool } from "../types.js";
import { ToolOutput } from "./types.js";
export async function callToolWithErrorHandling(
tool: BaseTool,
inputDict: { [key: string]: any },
input: unknown,
): Promise<ToolOutput> {
if (!tool.call) {
return new ToolOutput(
"Error: Tool does not have a call function.",
tool.metadata.name,
{ kwargs: inputDict },
input,
null,
);
}
try {
const value = await tool.call(inputDict);
return new ToolOutput(value, tool.metadata.name, inputDict, value);
} catch (e) {
return new ToolOutput(
`Error: ${e}`,
tool.metadata.name,
{ kwargs: inputDict },
e,
getCallbackManager().dispatchEvent("llm-tool-call", {
payload: {
toolCall: {
name: tool.metadata.name,
input,
},
},
});
const value = await tool.call(
typeof input === "string" ? JSON.parse(input) : input,
);
return new ToolOutput(value, tool.metadata.name, input, value);
} catch (e) {
return new ToolOutput(`Error: ${e}`, tool.metadata.name, input, e);
}
}
+1 -1
View File
@@ -67,7 +67,7 @@ export interface BaseTool<Input = any> {
Input extends Known ? ToolMetadata<JSONSchemaType<Input>> : ToolMetadata;
}
export type ToolWithCall<Input = unknown> = Omit<BaseTool<Input>, "call"> & {
export type BaseToolWithCall<Input = any> = Omit<BaseTool<Input>, "call"> & {
call: NonNullable<Pick<BaseTool<Input>, "call">["call"]>;
};
+56
View File
@@ -0,0 +1,56 @@
import type { ChatMessage, MessageContent, MessageType } from "llamaindex";
import { expectTypeOf, test } from "vitest";
import type { ChatResponse } from "../src/index.js";
test("chat message type", () => {
// if generic is not provided, `options` is not required
expectTypeOf<ChatMessage>().toMatchTypeOf<{
content: MessageContent;
role: MessageType;
}>();
expectTypeOf<ChatMessage>().toMatchTypeOf<{
content: MessageContent;
role: MessageType;
options?: object;
}>();
expectTypeOf<ChatMessage>().not.toMatchTypeOf<{
content: MessageContent;
role: MessageType;
options: Record<string, unknown>;
}>();
type Options = {
a: string;
b: number;
};
expectTypeOf<ChatMessage<Options>>().toMatchTypeOf<{
content: MessageContent;
role: MessageType;
options?: Options;
}>();
});
test("chat response type", () => {
// if generic is not provided, `options` is not required
expectTypeOf<ChatResponse>().toMatchTypeOf<{
message: ChatMessage;
raw: object | null;
}>();
expectTypeOf<ChatResponse>().toMatchTypeOf<{
message: ChatMessage;
raw: object | null;
options?: Record<string, unknown>;
}>();
expectTypeOf<ChatResponse>().not.toMatchTypeOf<{
message: ChatMessage;
raw: object | null;
options: Record<string, unknown>;
}>();
type Options = {
a: string;
b: number;
};
expectTypeOf<ChatResponse<Options>>().toMatchTypeOf<{
message: ChatMessage<Options>;
raw: object | null;
}>();
});
+1
View File
@@ -1,3 +1,4 @@
.turbo
README.md
LICENSE
CHANGELOG.md
-8
View File
@@ -1,8 +0,0 @@
# @llamaindex/edge
## 0.2.7
### Patch Changes
- Updated dependencies
- @llamaindex/env@0.0.7
@@ -5,8 +5,7 @@
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"start": "next start"
},
"dependencies": {
"@llamaindex/edge": "workspace:*",
@@ -1,9 +1,6 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
@@ -18,7 +15,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
<body>{children}</body>
</html>
);
}
+3 -3
View File
@@ -1,10 +1,10 @@
{
"name": "@llamaindex/edge",
"version": "0.2.8",
"version": "0.2.9",
"license": "MIT",
"type": "module",
"dependencies": {
"@anthropic-ai/sdk": "^0.18.0",
"@anthropic-ai/sdk": "^0.20.4",
"@aws-crypto/sha256-js": "^5.2.0",
"@datastax/astra-db-ts": "^0.1.4",
"@grpc/grpc-js": "^1.10.6",
@@ -78,7 +78,7 @@
"directory": "packages/edge"
},
"scripts": {
"copy": "cp -r ../../README.md ../../LICENSE .",
"copy": "cp -r ../../README.md ../../LICENSE ../core/CHANGELOG.md .",
"update:deps": "node scripts/update-deps.js",
"build:core": "pnpm --filter llamaindex build && cp -r ../core/dist . && rm -rf dist/cjs",
"build": "pnpm run update:deps && pnpm run build:core && pnpm copy"
@@ -1,20 +0,0 @@
{
"name": "eslint-config-custom",
"private": true,
"version": "0.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"eslint-config-next": "^13.5.6",
"eslint-config-prettier": "^8.10.0",
"eslint-config-turbo": "^1.11.3",
"eslint-plugin-react": "7.28.0",
"@typescript-eslint/eslint-plugin": "^7.5.0"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"next": "^13.5.6"
}
}
+8
View File
@@ -1,5 +1,13 @@
# @llamaindex/experimental
## 0.0.12
### Patch Changes
- Updated dependencies [76c3fd6]
- Updated dependencies [208282d]
- llamaindex@0.2.9
## 0.0.11
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@llamaindex/experimental",
"description": "Experimental package for LlamaIndexTS",
"version": "0.0.11",
"version": "0.0.12",
"type": "module",
"types": "dist/type/index.d.ts",
"main": "dist/cjs/index.js",
-20
View File
@@ -1,20 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"composite": false,
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"inlineSources": false,
"isolatedModules": true,
"moduleResolution": "node",
"noUnusedLocals": false,
"noUnusedParameters": false,
"preserveWatchOutput": true,
"skipLibCheck": true,
"strict": true
},
"exclude": ["node_modules"]
}
-21
View File
@@ -1,21 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Next.js",
"extends": "./base.json",
"compilerOptions": {
"plugins": [{ "name": "next" }],
"allowJs": true,
"declaration": false,
"declarationMap": false,
"incremental": true,
"jsx": "preserve",
"lib": ["dom", "dom.iterable", "esnext"],
"module": "esnext",
"noEmit": true,
"resolveJsonModule": true,
"strict": false,
"target": "es5"
},
"include": ["src", "next-env.d.ts"],
"exclude": ["node_modules"]
}
-9
View File
@@ -1,9 +0,0 @@
{
"name": "tsconfig",
"version": "0.0.0",
"private": true,
"license": "MIT",
"publishConfig": {
"access": "public"
}
}
-11
View File
@@ -1,11 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "React Library",
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["ES2015", "DOM"],
"module": "ESNext",
"target": "es6"
}
}
+10938 -8048
View File
File diff suppressed because it is too large Load Diff