Skip to content
Snippets Groups Projects
Unverified Commit d211b7ab authored by Erik's avatar Erik Committed by GitHub
Browse files

add tool call history support in chat messages (#1565)

parent 057ee146
No related branches found
No related tags found
No related merge requests found
---
"@llamaindex/anthropic": patch
"llamaindex": patch
"@llamaindex/core": patch
---
added support for tool calls with results in message history for athropic agent
import type { MessageParam } from "@anthropic-ai/sdk/resources/messages";
import { setEnvs } from "@llamaindex/env"; import { setEnvs } from "@llamaindex/env";
import { Anthropic } from "llamaindex"; import { Anthropic, OpenAI, type ChatMessage } from "llamaindex";
import { beforeAll, describe, expect, test } from "vitest"; import { beforeAll, describe, expect, test } from "vitest";
beforeAll(() => { beforeAll(() => {
...@@ -8,11 +9,25 @@ beforeAll(() => { ...@@ -8,11 +9,25 @@ beforeAll(() => {
}); });
}); });
describe("Anthropic llm", () => { describe("Message Formatting", () => {
test("format messages", () => { describe("Basic Message Formatting", () => {
const anthropic = new Anthropic(); test("OpenAI formats basic user and assistant messages correctly", () => {
expect( const inputMessages: ChatMessage[] = [
anthropic.formatMessages([ { content: "Hello", role: "user" },
{ content: "Hi there!", role: "assistant" },
{ content: "Be helpful", role: "system" },
];
const expectedOutput = [
{ role: "user", content: "Hello" },
{ role: "assistant", content: "Hi there!" },
{ role: "system", content: "Be helpful" },
];
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.", content: "You are a helpful assistant.",
role: "assistant", role: "assistant",
...@@ -21,20 +36,53 @@ describe("Anthropic llm", () => { ...@@ -21,20 +36,53 @@ describe("Anthropic llm", () => {
content: "Hello?", content: "Hello?",
role: "user", role: "user",
}, },
]), ];
).toEqual([ const expectedOutput: MessageParam[] = [
{ {
content: "You are a helpful assistant.", content: "You are a helpful assistant.",
role: "assistant", role: "assistant",
}, },
{ {
content: "Hello?", content: "Hello?",
role: "user", 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" },
{ content: "Hello", role: "user" },
];
const expectedOutput = [
{ role: "system", content: "You are a coding assistant" },
{ role: "user", content: "Hello" },
];
expect(OpenAI.toOpenAIMessage(inputMessages)).toEqual(expectedOutput);
});
expect( test("Anthropic handles multi-turn conversation correctly", () => {
anthropic.formatMessages([ 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.", content: "You are a helpful assistant.",
role: "assistant", role: "assistant",
...@@ -51,24 +99,58 @@ describe("Anthropic llm", () => { ...@@ -51,24 +99,58 @@ describe("Anthropic llm", () => {
content: "What is your name?", content: "What is your name?",
role: "user", role: "user",
}, },
]), ];
).toEqual([ const expectedOutput: MessageParam[] = [
{
content: "You are a helpful assistant.",
role: "assistant",
},
{
content: "Hello?\nWhat is your name?",
role: "user",
},
]);
expect(
anthropic.formatMessages([
{ {
content: "You are a helpful assistant.", content: "You are a helpful assistant.",
role: "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: [ content: [
{ {
...@@ -84,29 +166,180 @@ describe("Anthropic llm", () => { ...@@ -84,29 +166,180 @@ describe("Anthropic llm", () => {
], ],
role: "user", role: "user",
}, },
]), ];
).toEqual([ 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?",
},
{ {
content: "You are a helpful assistant.",
role: "assistant", role: "assistant",
content: "Let me check the weather.",
options: {
toolCall: [
{
id: "call_123",
name: "weather",
input: JSON.stringify({ location: "London" }),
},
],
},
}, },
{ {
content: [ role: "assistant",
{ content: "The weather in London is sunny, +20°C",
text: "What do you see in the image?", options: {
type: "text", toolResult: {
id: "call_123",
}, },
{ },
source: { },
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", ];
media_type: "image/jpeg",
type: "base64", test("OpenAI formats tool calls correctly", () => {
const expectedOutput = [
{
role: "user",
content: "What's the weather in London?",
},
{
role: "assistant",
content: "Let me check the weather.",
tool_calls: [
{
id: "call_123",
type: "function",
function: {
name: "weather",
arguments: JSON.stringify({ location: "London" }),
},
},
],
},
{
role: "tool",
content: "The weather in London is sunny, +20°C",
tool_call_id: "call_123",
},
];
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: "image", {
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[] = [
{
role: "assistant",
content: "Let me check both weather and time.",
options: {
toolCall: [
{
id: "weather_123",
name: "weather",
input: JSON.stringify({ location: "London" }),
},
{
id: "time_456",
name: "time",
input: JSON.stringify({ timezone: "GMT" }),
},
],
}, },
], },
role: "user", ];
},
]); const expectedOutput = [
{
role: "assistant",
content: "Let me check both weather and time.",
tool_calls: [
{
id: "weather_123",
type: "function",
function: {
name: "weather",
arguments: JSON.stringify({ location: "London" }),
},
},
{
id: "time_456",
type: "function",
function: {
name: "time",
arguments: JSON.stringify({ timezone: "GMT" }),
},
},
],
},
];
expect(OpenAI.toOpenAIMessage(multiToolMessages)).toEqual(expectedOutput);
});
}); });
}); });
...@@ -4,19 +4,12 @@ import type { ...@@ -4,19 +4,12 @@ import type {
BetaCacheControlEphemeral, BetaCacheControlEphemeral,
BetaTextBlockParam, BetaTextBlockParam,
} from "@anthropic-ai/sdk/resources/beta/index"; } from "@anthropic-ai/sdk/resources/beta/index";
import type { TextBlock } from "@anthropic-ai/sdk/resources/index";
import type { import type {
TextBlock,
TextBlockParam,
} from "@anthropic-ai/sdk/resources/index";
import type {
ImageBlockParam,
MessageCreateParamsNonStreaming,
MessageParam, MessageParam,
Model, Model,
Tool, Tool,
ToolResultBlockParam,
ToolUseBlock, ToolUseBlock,
ToolUseBlockParam,
} from "@anthropic-ai/sdk/resources/messages"; } from "@anthropic-ai/sdk/resources/messages";
import { wrapLLMEvent } from "@llamaindex/core/decorator"; import { wrapLLMEvent } from "@llamaindex/core/decorator";
import type { import type {
...@@ -193,99 +186,128 @@ export class Anthropic extends ToolCallLLM< ...@@ -193,99 +186,128 @@ export class Anthropic extends ToolCallLLM<
formatMessages( formatMessages(
messages: ChatMessage<ToolCallLLMMessageOptions>[], messages: ChatMessage<ToolCallLLMMessageOptions>[],
): MessageParam[] { ): MessageParam[] {
const result: MessageParam[] = messages const formattedMessages = messages.flatMap((message) => {
.filter( const options = message.options ?? {};
(message) => message.role === "user" || message.role === "assistant", if (message.role === "system") {
) // Skip system messages
.map((message) => { return [];
const options = message.options ?? {}; }
if ("toolResult" in options) {
const { id, isError } = options.toolResult; if ("toolCall" in options) {
return { const formattedMessage: MessageParam = {
role: "user", role: "assistant",
content: [ content: [
{ {
type: "tool_result", type: "text" as const,
is_error: isError, text: extractText(message.content),
content: [ },
{ ...options.toolCall.map((tool) => ({
type: "text", type: "tool_use" as const,
text: extractText(message.content), id: tool.id,
}, name: tool.name,
], input:
tool_use_id: id, typeof tool.input === "string"
}, ? JSON.parse(tool.input)
] satisfies ToolResultBlockParam[], : tool.input,
} satisfies MessageParam; })),
} else if ("toolCall" in options) { ],
const aiThinkingText = extractText(message.content); };
return {
role: "assistant", return formattedMessage;
content: [ }
// this could be empty when you call two tools in one query
...(aiThinkingText.trim() // Handle tool results
? [ if ("toolResult" in options) {
{ const formattedMessage: MessageParam = {
type: "text", role: "user",
text: aiThinkingText, content: [
} satisfies TextBlockParam, {
] type: "tool_result" as const,
: []), tool_use_id: options.toolResult.id,
...options.toolCall.map( content: extractText(message.content),
(toolCall) => },
({ ],
type: "tool_use", };
id: toolCall.id,
name: toolCall.name, return formattedMessage;
input: toolCall.input, }
}) satisfies ToolUseBlockParam,
), // Handle regular messages
], if (typeof message.content === "string") {
} satisfies MessageParam; const role: "user" | "assistant" =
} message.role === "assistant" ? "assistant" : "user";
return { return {
content: role,
typeof message.content === "string" content: message.content,
? message.content
: message.content.map(
(content): TextBlockParam | ImageBlockParam =>
content.type === "text"
? {
type: "text",
text: content.text,
}
: {
type: "image",
source: {
data: content.image_url.url.substring(
content.image_url.url.indexOf(",") + 1,
),
media_type:
`image/${content.image_url.url.substring("data:image/".length, content.image_url.url.indexOf(";base64"))}` as
| "image/jpeg"
| "image/png"
| "image/gif"
| "image/webp",
type: "base64",
},
},
),
role: message.role as "user" | "assistant",
} satisfies MessageParam; } satisfies MessageParam;
}); }
// merge messages with the same role
// in case of 'messages: roles must alternate between "user" and "assistant", but found multiple "user" roles in a row' // Handle multi-modal content
const realResult: MessageParam[] = []; const role: "user" | "assistant" =
for (let i = 0; i < result.length; i++) { message.role === "assistant" ? "assistant" : "user";
return {
role,
content: message.content.map((content) => {
if (content.type === "text") {
return {
type: "text" as const,
text: content.text,
};
}
return {
type: "image" as const,
source: {
type: "base64" as const,
media_type: `image/${content.image_url.url.substring(
"data:image/".length,
content.image_url.url.indexOf(";base64"),
)}` as "image/jpeg" | "image/png" | "image/gif" | "image/webp",
data: content.image_url.url.substring(
content.image_url.url.indexOf(",") + 1,
),
},
};
}),
} satisfies MessageParam;
});
return this.mergeConsecutiveMessages(formattedMessages);
}
// Add helper method to prepare tools for API call
private prepareToolsForAPI(tools: BaseTool[]): Tool[] {
return tools.map((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,
};
});
}
private mergeConsecutiveMessages(messages: MessageParam[]): MessageParam[] {
const result: MessageParam[] = [];
for (let i = 0; i < messages.length; i++) {
if (i === 0) { if (i === 0) {
realResult.push(result[i]!); result.push(messages[i]!);
continue; continue;
} }
const current = result[i]!;
const previous = result[i - 1]!; const current = messages[i]!;
const previous = result[result.length - 1]!;
if (current.role === previous.role) { if (current.role === previous.role) {
// merge two messages with the same role // Merge content based on type
if (Array.isArray(previous.content)) { if (Array.isArray(previous.content)) {
if (Array.isArray(current.content)) { if (Array.isArray(current.content)) {
previous.content.push(...current.content); previous.content.push(...current.content);
...@@ -298,25 +320,19 @@ export class Anthropic extends ToolCallLLM< ...@@ -298,25 +320,19 @@ export class Anthropic extends ToolCallLLM<
} else { } else {
if (Array.isArray(current.content)) { if (Array.isArray(current.content)) {
previous.content = [ previous.content = [
{ { type: "text", text: previous.content },
type: "text",
text: previous.content,
},
...current.content, ...current.content,
]; ];
} else { } else {
previous.content += `\n${current.content}`; previous.content = `${previous.content}\n${current.content}`;
} }
} }
// no need to push the message } else {
} result.push(current);
// if the roles are different, just push the message
else {
realResult.push(current);
} }
} }
return realResult; return result;
} }
chat( chat(
...@@ -336,130 +352,104 @@ export class Anthropic extends ToolCallLLM< ...@@ -336,130 +352,104 @@ export class Anthropic extends ToolCallLLM<
@wrapLLMEvent @wrapLLMEvent
async chat( async chat(
params: params:
| LLMChatParamsNonStreaming< | LLMChatParamsNonStreaming<AnthropicToolCallLLMMessageOptions>
AnthropicAdditionalChatOptions, | LLMChatParamsStreaming<AnthropicToolCallLLMMessageOptions>,
AnthropicToolCallLLMMessageOptions
>
| LLMChatParamsStreaming<
AnthropicAdditionalChatOptions,
AnthropicToolCallLLMMessageOptions
>,
): Promise< ): Promise<
| ChatResponse<AnthropicToolCallLLMMessageOptions> | ChatResponse<AnthropicToolCallLLMMessageOptions>
| AsyncIterable<ChatResponseChunk<AnthropicToolCallLLMMessageOptions>> | AsyncIterable<ChatResponseChunk<AnthropicToolCallLLMMessageOptions>>
> { > {
let { messages } = params; const { messages, stream, tools } = params;
const { stream, tools } = params;
let systemPrompt: string | Array<BetaTextBlockParam> | null = null;
// Handle system messages
let systemPrompt: string | BetaTextBlockParam[] | null = null;
const systemMessages = messages.filter( const systemMessages = messages.filter(
(message) => message.role === "system", (message) => message.role === "system",
); );
if (systemMessages.length > 0) { if (systemMessages.length > 0) {
systemPrompt = systemMessages.map((message) => systemPrompt = systemMessages.map((message): BetaTextBlockParam => {
message.options && "cache_control" in message.options const textContent = extractText(message.content);
? { if (message.options && "cache_control" in message.options) {
type: "text", return {
text: extractText(message.content), type: "text" as const,
cache_control: message.options.cache_control, text: textContent,
} cache_control: message.options
: { .cache_control as BetaCacheControlEphemeral,
type: "text", };
text: extractText(message.content), }
}, return {
); type: "text" as const,
messages = messages.filter((message) => message.role !== "system"); text: textContent,
};
});
} }
const beta = const beta =
systemPrompt?.find((message) => "cache_control" in message) !== undefined; Array.isArray(systemPrompt) &&
systemPrompt.some((message) => "cache_control" in message);
// case: Non-streaming
let anthropic = this.session.anthropic; let anthropic = this.session.anthropic;
if (beta) { if (beta) {
// @ts-expect-error type casting // @ts-expect-error type casting
anthropic = anthropic.beta.promptCaching; anthropic = anthropic.beta.promptCaching;
} }
// case: Streaming
if (stream) { if (stream) {
if (tools) { if (tools) {
console.error("Tools are not supported in streaming mode"); console.error("Tools are not supported in streaming mode");
} }
return this.streamChat(messages, systemPrompt, anthropic); return this.streamChat(
messages.filter((m) => m.role !== "system"),
systemPrompt,
anthropic,
);
} }
if (tools) { const apiParams = {
const params: MessageCreateParamsNonStreaming = { model: this.getModelName(this.model),
messages: this.formatMessages(messages), messages: this.mergeConsecutiveMessages(
tools: tools.map(Anthropic.toTool), this.formatMessages(messages.filter((m) => m.role !== "system")),
model: this.getModelName(this.model), ),
temperature: this.temperature, max_tokens: this.maxTokens ?? 4096,
max_tokens: this.maxTokens ?? 4096, temperature: this.temperature,
top_p: this.topP, top_p: this.topP,
...(systemPrompt && { system: systemPrompt }), ...(systemPrompt && { system: systemPrompt }),
}; };
// Remove tools if there are none, as it will cause an error
if (tools.length === 0) {
delete params.tools;
}
const response = await anthropic.messages.create(params);
const toolUseBlock = response.content.filter(
(content): content is ToolUseBlock => content.type === "tool_use",
);
return { if (tools?.length) {
raw: response, Object.assign(apiParams, {
message: { tools: this.prepareToolsForAPI(tools),
content: response.content
.filter((content): content is TextBlock => content.type === "text")
.map((content) => ({
type: "text",
text: content.text,
})),
role: "assistant",
options:
toolUseBlock.length > 0
? {
toolCall: toolUseBlock.map((block) => ({
id: block.id,
name: block.name,
input:
typeof block.input === "object"
? JSON.stringify(block.input)
: `${block.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
.filter((content): content is TextBlock => content.type === "text")
.map((content) => ({
type: "text",
text: content.text,
})),
role: "assistant",
options: {},
},
};
} }
const response = await anthropic.messages.create(apiParams);
const toolUseBlock = response.content.filter(
(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" as const,
text: content.text,
})),
role: "assistant",
options:
toolUseBlock.length > 0
? {
toolCall: toolUseBlock.map((block) => ({
id: block.id,
name: block.name,
input: JSON.stringify(block.input),
})),
}
: {},
},
};
} }
protected async *streamChat( protected async *streamChat(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment