diff --git a/.changeset/shiny-maps-thank.md b/.changeset/shiny-maps-thank.md new file mode 100644 index 0000000000000000000000000000000000000000..c80e1b67ba9c6b73b4a545800e0b9a892d5cf505 --- /dev/null +++ b/.changeset/shiny-maps-thank.md @@ -0,0 +1,6 @@ +--- +"@llamaindex/anthropic": patch +"@llamaindex/llamaindex-test": patch +--- + +fix: missing condition to stringify tool input diff --git a/packages/llamaindex/tests/llm/index.test.ts b/packages/llamaindex/tests/llm/index.test.ts index 1509df3a190a33f7d70438b3948a4b33a59e1204..ac1d0fc44a1338880814a94e9a26335034a7d2ce 100644 --- a/packages/llamaindex/tests/llm/index.test.ts +++ b/packages/llamaindex/tests/llm/index.test.ts @@ -1,6 +1,5 @@ -import type { MessageParam } from "@anthropic-ai/sdk/resources/messages"; import { setEnvs } from "@llamaindex/env"; -import { Anthropic, OpenAI, type ChatMessage } from "llamaindex"; +import { OpenAI, type ChatMessage } from "llamaindex"; import { beforeAll, describe, expect, test } from "vitest"; beforeAll(() => { @@ -25,32 +24,6 @@ describe("Message Formatting", () => { expect(OpenAI.toOpenAIMessage(inputMessages)).toEqual(expectedOutput); }); - test("Anthropic formats basic messages correctly", () => { - const anthropic = new Anthropic(); - const inputMessages: ChatMessage[] = [ - { - content: "You are a helpful assistant.", - role: "assistant", - }, - { - content: "Hello?", - role: "user", - }, - ]; - const expectedOutput: MessageParam[] = [ - { - content: "You are a helpful assistant.", - role: "assistant", - }, - { - content: "Hello?", - role: "user", - }, - ]; - - expect(anthropic.formatMessages(inputMessages)).toEqual(expectedOutput); - }); - test("OpenAI handles system messages correctly", () => { const inputMessages: ChatMessage[] = [ { content: "You are a coding assistant", role: "system" }, @@ -62,133 +35,6 @@ describe("Message Formatting", () => { ]; expect(OpenAI.toOpenAIMessage(inputMessages)).toEqual(expectedOutput); }); - - test("Anthropic handles multi-turn conversation correctly", () => { - const anthropic = new Anthropic(); - const inputMessages: ChatMessage[] = [ - { content: "Hi", role: "user" }, - { content: "Hello! How can I help?", role: "assistant" }, - { content: "What's the weather?", role: "user" }, - ]; - const expectedOutput: MessageParam[] = [ - { content: "Hi", role: "user" }, - { content: "Hello! How can I help?", role: "assistant" }, - { content: "What's the weather?", role: "user" }, - ]; - expect(anthropic.formatMessages(inputMessages)).toEqual(expectedOutput); - }); - }); - - describe("Advanced Message Formatting", () => { - test("Anthropic filters out system messages", () => { - const anthropic = new Anthropic(); - const inputMessages: ChatMessage[] = [ - { - content: "You are a helpful assistant.", - role: "assistant", - }, - { - content: "Hello?", - role: "user", - }, - { - content: "I am a system message.", - role: "system", - }, - { - content: "What is your name?", - role: "user", - }, - ]; - const expectedOutput: MessageParam[] = [ - { - content: "You are a helpful assistant.", - role: "assistant", - }, - { - content: "Hello?\nWhat is your name?", - role: "user", - }, - ]; - - expect(anthropic.formatMessages(inputMessages)).toEqual(expectedOutput); - }); - - test("Anthropic merges consecutive messages from the same role", () => { - const anthropic = new Anthropic(); - const inputMessages: ChatMessage[] = [ - { - content: "Hello?", - role: "user", - }, - { - content: "How are you?", - role: "user", - }, - { - content: "I am fine, thank you!", - role: "assistant", - }, - { - content: "And you?", - role: "assistant", - }, - ]; - const expectedOutput: MessageParam[] = [ - { - content: "Hello?\nHow are you?", - role: "user", - }, - { - content: "I am fine, thank you!\nAnd you?", - role: "assistant", - }, - ]; - - expect(anthropic.formatMessages(inputMessages)).toEqual(expectedOutput); - }); - - test("Anthropic handles image content", () => { - const anthropic = new Anthropic(); - const inputMessages: ChatMessage[] = [ - { - content: [ - { - text: "What do you see in the image?", - type: "text", - }, - { - type: "image_url", - image_url: { - url: `data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAQDAwQDAwQEAwQFBAQFBgoHBgYGBg0JCggKDw0QEA8NDw4RExgUERIXEg4PFRwVFxkZGxsbEBQdHx0aHxgaGxr/2wBDAQQFBQYFBgwHBwwaEQ8RGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhr/wAARCAAgACADASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAACAQHCQb/xAAvEAABAgUCBAUDBQEAAAAAAAACAQMEBQYHERIhAAgTYSIxMkJxI2KCFBVBUVKh/8QAGAEAAwEBAAAAAAAAAAAAAAAAAwQFAQL/xAAnEQABBAECAwkAAAAAAAAAAAACAQMEEQAFMiExYRITFCJBcXKBof/aAAwDAQACEQMRAD8Aufmb5mnbWREFRdvIMZ3cWcaBh2NHUGEFwtIKQp63CX0h+S7YQgRzGSq6kgqGAS8NQRc6fmkIMWwSxJEyP+m0bwggQr5iIom6KnnxXty61jK+uJUVUxzxm/M5g5EASr6G9WGwTsIIIp2FOHJfi0kyvzS9Cv0zGwEF+2whOAUY4a6mnm2lREURLPoTggNG5tS6xpmOT4GQptwNUZc6sbexzcZRVSTKTOgudMPEL0j7E2uQNOxIqcaYcqXNaxe2HKnauBiAraDZ6n0k0tTBpPNwE9pptqDP3DtlBC1Q8qNw5K4AwLEunYkWMwcYg6fnqoH/ADPHA2/qeZWquhJJ3pODmEhmg/qGl2XAloebL5HWK/K8dOMOM7xVPfJrMhmQiq0SFXOlyPc+jIq3lwakpeYNq27K491kfvbzls07ECiSdlThhWKvj1LLx0VVLWGqSBuFJ1jc3WBEUb8K4TUieHz3xni7ea3lSZvZDhUVImxAVtBso39VdLUe0nk2a+0030n+K7YUc95/J66tRIp3SVXUpGyUI7wvPxDBoJ/UaLIuIqtuInRwiiqp4z3XbBYr3cGp9P30zJXiSjk1HLsqdIvxvzV1q8ZtB3ppa5bkwZkDz7LsF09Qxgi0Roa6UUU1LnxYH5JP74D1LUjNrkXigabc6kZM5vPFZi3NPi3dVXnFT+EQUM17IvEi1tL1xUkcEHb+lo6duvRUO644wwSDpaPWgG7sAApIKqqqm4jvxo1yvcrjdoTiqtrQ2I+u5nr19ItbUA2a5IAX3GvuP8U2ypMS5pSwFC5peTtM0lnSkMWVVUJb48a+8//Z`, - }, - }, - ], - role: "user", - }, - ]; - const expectedOutput: MessageParam[] = [ - { - role: "user", - content: [ - { - type: "text", - text: "What do you see in the image?", - }, - { - type: "image", - source: { - type: "base64", - media_type: "image/jpeg", - data: "/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAQDAwQDAwQEAwQFBAQFBgoHBgYGBg0JCggKDw0QEA8NDw4RExgUERIXEg4PFRwVFxkZGxsbEBQdHx0aHxgaGxr/2wBDAQQFBQYFBgwHBwwaEQ8RGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhr/wAARCAAgACADASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAACAQHCQb/xAAvEAABAgUCBAUDBQEAAAAAAAACAQMEBQYHERIhAAgTYSIxMkJxI2KCFBVBUVKh/8QAGAEAAwEBAAAAAAAAAAAAAAAAAwQFAQL/xAAnEQABBAECAwkAAAAAAAAAAAACAQMEEQAFMiExYRITFCJBcXKBof/aAAwDAQACEQMRAD8Aufmb5mnbWREFRdvIMZ3cWcaBh2NHUGEFwtIKQp63CX0h+S7YQgRzGSq6kgqGAS8NQRc6fmkIMWwSxJEyP+m0bwggQr5iIom6KnnxXty61jK+uJUVUxzxm/M5g5EASr6G9WGwTsIIIp2FOHJfi0kyvzS9Cv0zGwEF+2whOAUY4a6mnm2lREURLPoTggNG5tS6xpmOT4GQptwNUZc6sbexzcZRVSTKTOgudMPEL0j7E2uQNOxIqcaYcqXNaxe2HKnauBiAraDZ6n0k0tTBpPNwE9pptqDP3DtlBC1Q8qNw5K4AwLEunYkWMwcYg6fnqoH/ADPHA2/qeZWquhJJ3pODmEhmg/qGl2XAloebL5HWK/K8dOMOM7xVPfJrMhmQiq0SFXOlyPc+jIq3lwakpeYNq27K491kfvbzls07ECiSdlThhWKvj1LLx0VVLWGqSBuFJ1jc3WBEUb8K4TUieHz3xni7ea3lSZvZDhUVImxAVtBso39VdLUe0nk2a+0030n+K7YUc95/J66tRIp3SVXUpGyUI7wvPxDBoJ/UaLIuIqtuInRwiiqp4z3XbBYr3cGp9P30zJXiSjk1HLsqdIvxvzV1q8ZtB3ppa5bkwZkDz7LsF09Qxgi0Roa6UUU1LnxYH5JP74D1LUjNrkXigabc6kZM5vPFZi3NPi3dVXnFT+EQUM17IvEi1tL1xUkcEHb+lo6duvRUO644wwSDpaPWgG7sAApIKqqqm4jvxo1yvcrjdoTiqtrQ2I+u5nr19ItbUA2a5IAX3GvuP8U2ypMS5pSwFC5peTtM0lnSkMWVVUJb48a+8//Z", - }, - }, - ], - }, - ]; - - expect(anthropic.formatMessages(inputMessages)).toEqual(expectedOutput); - }); }); describe("Tool Message Formatting", () => { @@ -251,47 +97,6 @@ describe("Message Formatting", () => { expect(OpenAI.toOpenAIMessage(toolCallMessages)).toEqual(expectedOutput); }); - test("Anthropic formats tool calls correctly", () => { - const anthropic = new Anthropic(); - const expectedOutput: MessageParam[] = [ - { - role: "user", - content: "What's the weather in London?", - }, - { - role: "assistant", - content: [ - { - type: "text", - text: "Let me check the weather.", - }, - { - type: "tool_use", - id: "call_123", - name: "weather", - input: { - location: "London", - }, - }, - ], - }, - { - role: "user", // anthropic considers all that comes not from their inference API is user role - content: [ - { - type: "tool_result", - tool_use_id: "call_123", - content: "The weather in London is sunny, +20°C", - }, - ], - }, - ]; - - expect(anthropic.formatMessages(toolCallMessages)).toEqual( - expectedOutput, - ); - }); - test("OpenAI formats multiple tool calls correctly", () => { const multiToolMessages: ChatMessage[] = [ { diff --git a/packages/llamaindex/tests/package.json b/packages/llamaindex/tests/package.json index d0700dfaeda4c73addc97ef90456de98eb69ae81..afbf4bf2994d5207b1413e95334460788df54558 100644 --- a/packages/llamaindex/tests/package.json +++ b/packages/llamaindex/tests/package.json @@ -16,7 +16,6 @@ "dotenv": "^16.4.5", "llamaindex": "workspace:*", "msw": "^2.6.5", - "@anthropic-ai/sdk": "0.32.1", "vitest": "^2.1.5" } } diff --git a/packages/providers/anthropic/package.json b/packages/providers/anthropic/package.json index 51abb1a14dafb2db071198e68860641f61ccc636..9819c5bdf1cf751bf3ab0fca75841f70e9bfa70d 100644 --- a/packages/providers/anthropic/package.json +++ b/packages/providers/anthropic/package.json @@ -27,7 +27,8 @@ }, "scripts": { "build": "bunchee", - "dev": "bunchee --watch" + "dev": "bunchee --watch", + "test": "vitest run" }, "devDependencies": { "bunchee": "6.2.0" @@ -36,6 +37,7 @@ "@anthropic-ai/sdk": "0.32.1", "@llamaindex/core": "workspace:*", "@llamaindex/env": "workspace:*", - "remeda": "^2.17.3" + "remeda": "^2.17.3", + "vitest": "^2.1.5" } } diff --git a/packages/providers/anthropic/src/llm.ts b/packages/providers/anthropic/src/llm.ts index 853d3238ade218ec762ccfe06635aa9b3c359c8f..ca9747d9d1cb0d649d501ec72df169125e6ccb28 100644 --- a/packages/providers/anthropic/src/llm.ts +++ b/packages/providers/anthropic/src/llm.ts @@ -12,6 +12,7 @@ import type { ToolUseBlock, } from "@anthropic-ai/sdk/resources/messages"; import { wrapLLMEvent } from "@llamaindex/core/decorator"; +import type { JSONObject } from "@llamaindex/core/global"; import type { BaseTool, ChatMessage, @@ -183,6 +184,18 @@ export class Anthropic extends ToolCallLLM< return model; }; + parseToolInput = (input: string | JSONObject) => { + if (typeof input === "object" && !Array.isArray(input)) return input; + + if (typeof input === "string") { + const parsed = JSON.parse(input); + if (typeof parsed === "object" && !Array.isArray(parsed)) return parsed; + } + + console.error("Invalid tool input:", input); + throw new Error("Tool input must be a dictionary"); + }; + formatMessages( messages: ChatMessage<ToolCallLLMMessageOptions>[], ): MessageParam[] { @@ -205,10 +218,7 @@ export class Anthropic extends ToolCallLLM< type: "tool_use" as const, id: tool.id, name: tool.name, - input: - typeof tool.input === "string" - ? JSON.parse(tool.input) - : tool.input, + input: this.parseToolInput(tool.input), })), ], }; @@ -444,7 +454,10 @@ export class Anthropic extends ToolCallLLM< toolCall: toolUseBlock.map((block) => ({ id: block.id, name: block.name, - input: JSON.stringify(block.input), + input: + typeof block.input === "string" + ? block.input + : JSON.stringify(block.input), })), } : {}, diff --git a/packages/providers/anthropic/tests/index.test.ts b/packages/providers/anthropic/tests/index.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..73c907138c07a034b178aadfbc48c57a38cb8fda --- /dev/null +++ b/packages/providers/anthropic/tests/index.test.ts @@ -0,0 +1,279 @@ +import type { MessageParam } from "@anthropic-ai/sdk/resources/messages"; +import type { ChatMessage } from "@llamaindex/core/llms"; +import { setEnvs } from "@llamaindex/env"; +import { beforeAll, describe, expect, test } from "vitest"; +import { Anthropic } from "../src/index"; + +beforeAll(() => { + setEnvs({ + ANTHROPIC_API_KEY: "valid", + }); +}); + +describe("Message Formatting", () => { + describe("Basic Message Formatting", () => { + test("Anthropic formats basic messages correctly", () => { + const anthropic = new Anthropic(); + const inputMessages: ChatMessage[] = [ + { + content: "You are a helpful assistant.", + role: "assistant", + }, + { + content: "Hello?", + role: "user", + }, + ]; + const expectedOutput: MessageParam[] = [ + { + content: "You are a helpful assistant.", + role: "assistant", + }, + { + content: "Hello?", + role: "user", + }, + ]; + + expect(anthropic.formatMessages(inputMessages)).toEqual(expectedOutput); + }); + + test("Anthropic handles multi-turn conversation correctly", () => { + const anthropic = new Anthropic(); + const inputMessages: ChatMessage[] = [ + { content: "Hi", role: "user" }, + { content: "Hello! How can I help?", role: "assistant" }, + { content: "What's the weather?", role: "user" }, + ]; + const expectedOutput: MessageParam[] = [ + { content: "Hi", role: "user" }, + { content: "Hello! How can I help?", role: "assistant" }, + { content: "What's the weather?", role: "user" }, + ]; + expect(anthropic.formatMessages(inputMessages)).toEqual(expectedOutput); + }); + }); + + describe("Advanced Message Formatting", () => { + test("Anthropic filters out system messages", () => { + const anthropic = new Anthropic(); + const inputMessages: ChatMessage[] = [ + { + content: "You are a helpful assistant.", + role: "assistant", + }, + { + content: "Hello?", + role: "user", + }, + { + content: "I am a system message.", + role: "system", + }, + { + content: "What is your name?", + role: "user", + }, + ]; + const expectedOutput: MessageParam[] = [ + { + content: "You are a helpful assistant.", + role: "assistant", + }, + { + content: "Hello?\nWhat is your name?", + role: "user", + }, + ]; + + expect(anthropic.formatMessages(inputMessages)).toEqual(expectedOutput); + }); + + test("Anthropic merges consecutive messages from the same role", () => { + const anthropic = new Anthropic(); + const inputMessages: ChatMessage[] = [ + { + content: "Hello?", + role: "user", + }, + { + content: "How are you?", + role: "user", + }, + { + content: "I am fine, thank you!", + role: "assistant", + }, + { + content: "And you?", + role: "assistant", + }, + ]; + const expectedOutput: MessageParam[] = [ + { + content: "Hello?\nHow are you?", + role: "user", + }, + { + content: "I am fine, thank you!\nAnd you?", + role: "assistant", + }, + ]; + + expect(anthropic.formatMessages(inputMessages)).toEqual(expectedOutput); + }); + + test("Anthropic handles image content", () => { + const anthropic = new Anthropic(); + const inputMessages: ChatMessage[] = [ + { + content: [ + { + text: "What do you see in the image?", + type: "text", + }, + { + type: "image_url", + image_url: { + url: `data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAQDAwQDAwQEAwQFBAQFBgoHBgYGBg0JCggKDw0QEA8NDw4RExgUERIXEg4PFRwVFxkZGxsbEBQdHx0aHxgaGxr/2wBDAQQFBQYFBgwHBwwaEQ8RGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhr/wAARCAAgACADASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAACAQHCQb/xAAvEAABAgUCBAUDBQEAAAAAAAACAQMEBQYHERIhAAgTYSIxMkJxI2KCFBVBUVKh/8QAGAEAAwEBAAAAAAAAAAAAAAAAAwQFAQL/xAAnEQABBAECAwkAAAAAAAAAAAACAQMEEQAFMiExYRITFCJBcXKBof/aAAwDAQACEQMRAD8Aufmb5mnbWREFRdvIMZ3cWcaBh2NHUGEFwtIKQp63CX0h+S7YQgRzGSq6kgqGAS8NQRc6fmkIMWwSxJEyP+m0bwggQr5iIom6KnnxXty61jK+uJUVUxzxm/M5g5EASr6G9WGwTsIIIp2FOHJfi0kyvzS9Cv0zGwEF+2whOAUY4a6mnm2lREURLPoTggNG5tS6xpmOT4GQptwNUZc6sbexzcZRVSTKTOgudMPEL0j7E2uQNOxIqcaYcqXNaxe2HKnauBiAraDZ6n0k0tTBpPNwE9pptqDP3DtlBC1Q8qNw5K4AwLEunYkWMwcYg6fnqoH/ADPHA2/qeZWquhJJ3pODmEhmg/qGl2XAloebL5HWK/K8dOMOM7xVPfJrMhmQiq0SFXOlyPc+jIq3lwakpeYNq27K491kfvbzls07ECiSdlThhWKvj1LLx0VVLWGqSBuFJ1jc3WBEUb8K4TUieHz3xni7ea3lSZvZDhUVImxAVtBso39VdLUe0nk2a+0030n+K7YUc95/J66tRIp3SVXUpGyUI7wvPxDBoJ/UaLIuIqtuInRwiiqp4z3XbBYr3cGp9P30zJXiSjk1HLsqdIvxvzV1q8ZtB3ppa5bkwZkDz7LsF09Qxgi0Roa6UUU1LnxYH5JP74D1LUjNrkXigabc6kZM5vPFZi3NPi3dVXnFT+EQUM17IvEi1tL1xUkcEHb+lo6duvRUO644wwSDpaPWgG7sAApIKqqqm4jvxo1yvcrjdoTiqtrQ2I+u5nr19ItbUA2a5IAX3GvuP8U2ypMS5pSwFC5peTtM0lnSkMWVVUJb48a+8//Z`, + }, + }, + ], + role: "user", + }, + ]; + const expectedOutput: MessageParam[] = [ + { + role: "user", + content: [ + { + type: "text", + text: "What do you see in the image?", + }, + { + type: "image", + source: { + type: "base64", + media_type: "image/jpeg", + data: "/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAQDAwQDAwQEAwQFBAQFBgoHBgYGBg0JCggKDw0QEA8NDw4RExgUERIXEg4PFRwVFxkZGxsbEBQdHx0aHxgaGxr/2wBDAQQFBQYFBgwHBwwaEQ8RGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhr/wAARCAAgACADASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAACAQHCQb/xAAvEAABAgUCBAUDBQEAAAAAAAACAQMEBQYHERIhAAgTYSIxMkJxI2KCFBVBUVKh/8QAGAEAAwEBAAAAAAAAAAAAAAAAAwQFAQL/xAAnEQABBAECAwkAAAAAAAAAAAACAQMEEQAFMiExYRITFCJBcXKBof/aAAwDAQACEQMRAD8Aufmb5mnbWREFRdvIMZ3cWcaBh2NHUGEFwtIKQp63CX0h+S7YQgRzGSq6kgqGAS8NQRc6fmkIMWwSxJEyP+m0bwggQr5iIom6KnnxXty61jK+uJUVUxzxm/M5g5EASr6G9WGwTsIIIp2FOHJfi0kyvzS9Cv0zGwEF+2whOAUY4a6mnm2lREURLPoTggNG5tS6xpmOT4GQptwNUZc6sbexzcZRVSTKTOgudMPEL0j7E2uQNOxIqcaYcqXNaxe2HKnauBiAraDZ6n0k0tTBpPNwE9pptqDP3DtlBC1Q8qNw5K4AwLEunYkWMwcYg6fnqoH/ADPHA2/qeZWquhJJ3pODmEhmg/qGl2XAloebL5HWK/K8dOMOM7xVPfJrMhmQiq0SFXOlyPc+jIq3lwakpeYNq27K491kfvbzls07ECiSdlThhWKvj1LLx0VVLWGqSBuFJ1jc3WBEUb8K4TUieHz3xni7ea3lSZvZDhUVImxAVtBso39VdLUe0nk2a+0030n+K7YUc95/J66tRIp3SVXUpGyUI7wvPxDBoJ/UaLIuIqtuInRwiiqp4z3XbBYr3cGp9P30zJXiSjk1HLsqdIvxvzV1q8ZtB3ppa5bkwZkDz7LsF09Qxgi0Roa6UUU1LnxYH5JP74D1LUjNrkXigabc6kZM5vPFZi3NPi3dVXnFT+EQUM17IvEi1tL1xUkcEHb+lo6duvRUO644wwSDpaPWgG7sAApIKqqqm4jvxo1yvcrjdoTiqtrQ2I+u5nr19ItbUA2a5IAX3GvuP8U2ypMS5pSwFC5peTtM0lnSkMWVVUJb48a+8//Z", + }, + }, + ], + }, + ]; + + expect(anthropic.formatMessages(inputMessages)).toEqual(expectedOutput); + }); + }); + + describe("Tool Message Formatting", () => { + const toolCallMessages: ChatMessage[] = [ + { + role: "user", + content: "What's the weather in London?", + }, + { + role: "assistant", + content: "Let me check the weather.", + options: { + toolCall: [ + { + id: "call_123", + name: "weather", + input: JSON.stringify({ location: "London" }), + }, + ], + }, + }, + { + role: "assistant", + content: "The weather in London is sunny, +20°C", + options: { + toolResult: { + id: "call_123", + }, + }, + }, + ]; + + test("Anthropic formats tool calls correctly", () => { + const anthropic = new Anthropic(); + const expectedOutput: MessageParam[] = [ + { + role: "user", + content: "What's the weather in London?", + }, + { + role: "assistant", + content: [ + { + type: "text", + text: "Let me check the weather.", + }, + { + type: "tool_use", + id: "call_123", + name: "weather", + input: { + location: "London", + }, + }, + ], + }, + { + role: "user", // anthropic considers all that comes not from their inference API is user role + content: [ + { + type: "tool_result", + tool_use_id: "call_123", + content: "The weather in London is sunny, +20°C", + }, + ], + }, + ]; + + expect(anthropic.formatMessages(toolCallMessages)).toEqual( + expectedOutput, + ); + }); + + test("Anthropic throws error for invalid tool input", () => { + const anthropic = new Anthropic(); + const invalidToolMessages: ChatMessage[] = [ + { + role: "assistant", + content: "Let me check that for you", + options: { + toolCall: [ + { + id: "toolu_123", + name: "search_tool", + input: '"{\\"query\\":\\"test\\"}}"', // Invalid JSON string + }, + ], + }, + }, + ]; + + expect(() => anthropic.formatMessages(invalidToolMessages)).toThrow(); + + const stringToolMessages: ChatMessage[] = [ + { + role: "assistant", + content: "Let me check that for you", + options: { + toolCall: [ + { + id: "toolu_123", + name: "search_tool", + input: "not a json string", + }, + ], + }, + }, + ]; + + expect(() => anthropic.formatMessages(stringToolMessages)).toThrow(); + }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfc24b1d773de98dea7c936d0033a9e8f069b6b4..3c224b9fe23572a4d85b82ba4c65dc49899a2f26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1059,9 +1059,6 @@ importers: packages/llamaindex/tests: devDependencies: - '@anthropic-ai/sdk': - specifier: 0.32.1 - version: 0.32.1(encoding@0.1.13) '@azure/cosmos': specifier: ^4.1.1 version: 4.1.1 @@ -1135,6 +1132,9 @@ importers: remeda: specifier: ^2.17.3 version: 2.17.3 + vitest: + specifier: ^2.1.5 + version: 2.1.5(@edge-runtime/vm@4.0.4)(@types/node@22.9.0)(happy-dom@15.11.6)(msw@2.6.5(@types/node@22.9.0)(typescript@5.7.2))(terser@5.37.0) devDependencies: bunchee: specifier: 6.2.0 @@ -11645,37 +11645,6 @@ packages: terser: optional: true - vite@5.4.11: - resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - vite@5.4.12: resolution: {integrity: sha512-KwUaKB27TvWwDJr1GjjWthLMATbGEbeWYZIbGZ5qFIsgPP3vWzLu4cVooqhm5/Z2SPDUMjyPVjTztm5tYKwQxA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -16470,14 +16439,14 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.5(msw@2.6.5(@types/node@22.9.0)(typescript@5.7.2))(vite@5.4.11(@types/node@22.9.0)(terser@5.37.0))': + '@vitest/mocker@2.1.5(msw@2.6.5(@types/node@22.9.0)(typescript@5.7.2))(vite@5.4.12(@types/node@22.9.0)(terser@5.37.0))': dependencies: '@vitest/spy': 2.1.5 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: msw: 2.6.5(@types/node@22.9.0)(typescript@5.7.2) - vite: 5.4.11(@types/node@22.9.0)(terser@5.37.0) + vite: 5.4.12(@types/node@22.9.0)(terser@5.37.0) '@vitest/pretty-format@2.1.5': dependencies: @@ -24301,16 +24270,6 @@ snapshots: fsevents: 2.3.3 terser: 5.37.0 - vite@5.4.11(@types/node@22.9.0)(terser@5.37.0): - dependencies: - esbuild: 0.21.5 - postcss: 8.5.1 - rollup: 4.34.1 - optionalDependencies: - '@types/node': 22.9.0 - fsevents: 2.3.3 - terser: 5.37.0 - vite@5.4.12(@types/node@22.9.0)(terser@5.37.0): dependencies: esbuild: 0.21.5 @@ -24324,7 +24283,7 @@ snapshots: vitest@2.1.5(@edge-runtime/vm@4.0.4)(@types/node@22.9.0)(happy-dom@15.11.6)(msw@2.6.5(@types/node@22.9.0)(typescript@5.7.2))(terser@5.37.0): dependencies: '@vitest/expect': 2.1.5 - '@vitest/mocker': 2.1.5(msw@2.6.5(@types/node@22.9.0)(typescript@5.7.2))(vite@5.4.11(@types/node@22.9.0)(terser@5.37.0)) + '@vitest/mocker': 2.1.5(msw@2.6.5(@types/node@22.9.0)(typescript@5.7.2))(vite@5.4.12(@types/node@22.9.0)(terser@5.37.0)) '@vitest/pretty-format': 2.1.8 '@vitest/runner': 2.1.5 '@vitest/snapshot': 2.1.5 @@ -24340,7 +24299,7 @@ snapshots: tinyexec: 0.3.1 tinypool: 1.0.2 tinyrainbow: 1.2.0 - vite: 5.4.11(@types/node@22.9.0)(terser@5.37.0) + vite: 5.4.12(@types/node@22.9.0)(terser@5.37.0) vite-node: 2.1.5(@types/node@22.9.0)(terser@5.37.0) why-is-node-running: 2.3.0 optionalDependencies: