@langchain/community
Version:
Third-party integrations for LangChain.js
299 lines (298 loc) • 12 kB
JavaScript
import { AIMessage, AIMessageChunk, HumanMessage, SystemMessage, ToolMessage, isAIMessage } from "@langchain/core/messages";
import { concat } from "@langchain/core/utils/stream";
//#region src/utils/bedrock/anthropic.ts
function extractToolCalls(content) {
const toolCalls = [];
for (const block of content) if (block.type === "tool_use") toolCalls.push({
name: block.name,
args: block.input,
id: block.id,
type: "tool_call"
});
return toolCalls;
}
function _formatImage(imageUrl) {
const match = imageUrl.match(/^data:(image\/.+);base64,(.+)$/);
if (match === null) throw new Error(["Anthropic only supports base64-encoded images currently.", "Example: data:image/png;base64,/9j/4AAQSk..."].join("\n\n"));
return {
type: "base64",
media_type: match[1] ?? "",
data: match[2] ?? ""
};
}
function _ensureMessageContents(messages) {
const updatedMsgs = [];
for (const message of messages) if (ToolMessage.isInstance(message)) if (typeof message.content === "string") {
const previousMessage = updatedMsgs[updatedMsgs.length - 1];
if (previousMessage?._getType() === "human" && Array.isArray(previousMessage.content) && "type" in previousMessage.content[0] && previousMessage.content[0].type === "tool_result") previousMessage.content.push({
type: "tool_result",
content: message.content,
tool_use_id: message.tool_call_id
});
else updatedMsgs.push(new HumanMessage({ content: [{
type: "tool_result",
content: message.content,
tool_use_id: message.tool_call_id
}] }));
} else updatedMsgs.push(new HumanMessage({ content: [{
type: "tool_result",
content: _formatContent(message.content),
tool_use_id: message.tool_call_id
}] }));
else if (SystemMessage.isInstance(message) || HumanMessage.isInstance(message) || AIMessage.isInstance(message)) updatedMsgs.push(message);
else throw new Error(`Message type "${message._getType()}" is not supported.`);
return updatedMsgs;
}
function _convertLangChainToolCallToAnthropic(toolCall) {
if (toolCall.id === void 0) throw new Error(`Anthropic requires all tool calls to have an "id".`);
return {
type: "tool_use",
id: toolCall.id,
name: toolCall.name,
input: toolCall.args
};
}
function _formatContent(content) {
if (typeof content === "string") return content;
else return content.flatMap((contentPart) => {
if ("source_type" in contentPart) {
const sourceType = contentPart.source_type;
const blockType = contentPart.type;
if (sourceType === "base64" && blockType === "image") return {
type: "image",
source: {
type: "base64",
media_type: contentPart.mime_type ?? "",
data: contentPart.data ?? ""
}
};
else if (sourceType === "base64" && blockType === "file") return {
type: "document",
source: {
type: "base64",
media_type: contentPart.mime_type ?? "",
data: contentPart.data ?? ""
}
};
else if (sourceType === "url" && blockType === "file") return {
type: "document",
source: {
type: "url",
url: contentPart.url ?? ""
}
};
else if (sourceType === "text") return {
type: "text",
text: contentPart.text ?? ""
};
return [];
} else if (contentPart.type === "image_url") {
let imageUrl;
if (typeof contentPart.image_url === "string") imageUrl = contentPart.image_url;
else if (typeof contentPart.image_url === "object" && contentPart.image_url !== null && "url" in contentPart.image_url && typeof contentPart.image_url.url === "string") imageUrl = contentPart.image_url.url;
else return [];
return {
type: "image",
source: _formatImage(imageUrl)
};
} else if (contentPart.type === "image") {
if ("url" in contentPart && typeof contentPart.url === "string") return {
type: "image",
source: _formatImage(contentPart.url)
};
else if ("data" in contentPart && (typeof contentPart.data === "string" || contentPart.data instanceof Uint8Array)) return {
type: "image",
source: {
type: "base64",
media_type: "mimeType" in contentPart && typeof contentPart.mimeType === "string" ? contentPart.mimeType : "image/jpeg",
data: typeof contentPart.data === "string" ? contentPart.data : Buffer.from(contentPart.data).toString("base64")
}
};
return [];
} else if (contentPart.type === "file") {
if ("url" in contentPart && typeof contentPart.url === "string") return {
type: "document",
source: {
type: "url",
url: contentPart.url
}
};
else if ("data" in contentPart && (typeof contentPart.data === "string" || contentPart.data instanceof Uint8Array)) return {
type: "document",
source: {
type: "base64",
media_type: "mimeType" in contentPart && typeof contentPart.mimeType === "string" ? contentPart.mimeType : "application/pdf",
data: typeof contentPart.data === "string" ? contentPart.data : Buffer.from(contentPart.data).toString("base64")
}
};
return [];
} else if (contentPart.type === "document") return { ...contentPart };
else if (contentPart.type === "text" || contentPart.type === "text_delta") {
if (contentPart.text === "") return [];
return {
type: "text",
text: contentPart.text
};
} else if (contentPart.type === "tool_use" || contentPart.type === "tool_result") return { ...contentPart };
else if (contentPart.type === "input_json_delta") return [];
else return [];
});
}
function formatMessagesForAnthropic(messages) {
const mergedMessages = _ensureMessageContents(messages);
let system;
if (mergedMessages.length > 0 && mergedMessages[0]._getType() === "system") {
if (typeof messages[0].content !== "string") throw new Error("System message content must be a string.");
system = messages[0].content;
}
return {
messages: (system !== void 0 ? mergedMessages.slice(1) : mergedMessages).map((message) => {
let role;
if (message._getType() === "human") role = "user";
else if (message._getType() === "ai") role = "assistant";
else if (message._getType() === "tool") role = "user";
else if (message._getType() === "system") throw new Error("System messages are only permitted as the first passed message.");
else throw new Error(`Message type "${message._getType()}" is not supported.`);
if (isAIMessage(message) && !!message.tool_calls?.length) if (typeof message.content === "string") if (message.content === "") return {
role,
content: message.tool_calls.map(_convertLangChainToolCallToAnthropic)
};
else return {
role,
content: [{
type: "text",
text: message.content
}, ...message.tool_calls.map(_convertLangChainToolCallToAnthropic)]
};
else {
const formattedContent = _formatContent(message.content);
if (Array.isArray(formattedContent)) {
const formattedToolsContent = message.tool_calls.map(_convertLangChainToolCallToAnthropic);
return {
role,
content: [...formattedContent, ...formattedToolsContent]
};
}
return {
role,
content: formattedContent
};
}
else return {
role,
content: _formatContent(message.content)
};
}),
system
};
}
function isAnthropicTool(tool) {
if (typeof tool !== "object" || !tool) return false;
return "input_schema" in tool;
}
function _makeMessageChunkFromAnthropicEvent(data, fields) {
if (data.type === "message_start") {
const { content, usage, ...additionalKwargs } = data.message;
const filteredAdditionalKwargs = {};
for (const [key, value] of Object.entries(additionalKwargs)) if (value !== void 0 && value !== null) filteredAdditionalKwargs[key] = value;
return new AIMessageChunk({
content: fields.coerceContentToString ? "" : [],
additional_kwargs: filteredAdditionalKwargs
});
} else if (data.type === "message_delta") {
let usageMetadata;
return new AIMessageChunk({
content: fields.coerceContentToString ? "" : [],
additional_kwargs: { ...data.delta },
usage_metadata: usageMetadata
});
} else if (data.type === "content_block_start" && data.content_block.type === "tool_use") return new AIMessageChunk({
content: fields.coerceContentToString ? "" : [{
index: data.index,
...data.content_block,
input: ""
}],
additional_kwargs: {}
});
else if (data.type === "content_block_delta" && data.delta.type === "text_delta") {
const content = data.delta?.text;
if (content !== void 0) return new AIMessageChunk({
content: fields.coerceContentToString ? content : [{
index: data.index,
...data.delta
}],
additional_kwargs: {}
});
} else if (data.type === "content_block_delta" && data.delta.type === "input_json_delta") return new AIMessageChunk({
content: fields.coerceContentToString ? "" : [{
index: data.index,
input: data.delta.partial_json,
type: data.delta.type
}],
additional_kwargs: {}
});
else if (data.type === "message_stop" && data["amazon-bedrock-invocationMetrics"] !== void 0) return new AIMessageChunk({
content: "",
response_metadata: { "amazon-bedrock-invocationMetrics": data["amazon-bedrock-invocationMetrics"] },
usage_metadata: {
input_tokens: data["amazon-bedrock-invocationMetrics"].inputTokenCount,
output_tokens: data["amazon-bedrock-invocationMetrics"].outputTokenCount,
total_tokens: data["amazon-bedrock-invocationMetrics"].inputTokenCount + data["amazon-bedrock-invocationMetrics"].outputTokenCount
}
});
return null;
}
function extractToolCallChunk(chunk) {
let newToolCallChunk;
const toolUseChunks = Array.isArray(chunk.content) ? chunk.content.find((c) => c.type === "tool_use") : void 0;
if (toolUseChunks && "index" in toolUseChunks && typeof toolUseChunks.index === "number" && "name" in toolUseChunks && typeof toolUseChunks.name === "string" && "id" in toolUseChunks && typeof toolUseChunks.id === "string") newToolCallChunk = {
args: "",
id: toolUseChunks.id,
name: toolUseChunks.name,
index: toolUseChunks.index,
type: "tool_call_chunk"
};
const inputJsonDeltaChunks = Array.isArray(chunk.content) ? chunk.content.find((c) => c.type === "input_json_delta") : void 0;
if (inputJsonDeltaChunks && "index" in inputJsonDeltaChunks && typeof inputJsonDeltaChunks.index === "number" && "input" in inputJsonDeltaChunks) if (typeof inputJsonDeltaChunks.input === "string") newToolCallChunk = {
args: inputJsonDeltaChunks.input,
index: inputJsonDeltaChunks.index,
type: "tool_call_chunk"
};
else newToolCallChunk = {
args: JSON.stringify(inputJsonDeltaChunks.input, null, 2),
index: inputJsonDeltaChunks.index,
type: "tool_call_chunk"
};
return newToolCallChunk;
}
function extractToken(chunk) {
return typeof chunk.content === "string" && chunk.content !== "" ? chunk.content : void 0;
}
function extractToolUseContent(chunk, concatenatedChunks) {
let newConcatenatedChunks = concatenatedChunks;
let toolUseContent;
if (!newConcatenatedChunks) newConcatenatedChunks = chunk;
else newConcatenatedChunks = concat(newConcatenatedChunks, chunk);
if (Array.isArray(newConcatenatedChunks.content) && newConcatenatedChunks.content.find((c) => c.type === "tool_use")) try {
const toolUseMsg = newConcatenatedChunks.content.find((c) => c.type === "tool_use");
if (!toolUseMsg || !("input" in toolUseMsg || "name" in toolUseMsg || "id" in toolUseMsg)) return;
if (typeof toolUseMsg.id !== "string" || typeof toolUseMsg.name !== "string" || typeof toolUseMsg.input !== "string") return;
const parsedArgs = JSON.parse(toolUseMsg.input);
if (parsedArgs) toolUseContent = {
type: "tool_use",
id: toolUseMsg.id,
name: toolUseMsg.name,
input: parsedArgs
};
} catch (_) {}
return {
toolUseContent,
concatenatedChunks: newConcatenatedChunks
};
}
function _toolsInParams(params) {
return !!(params.tools && params.tools.length > 0);
}
//#endregion
export { _makeMessageChunkFromAnthropicEvent, _toolsInParams, extractToken, extractToolCallChunk, extractToolCalls, extractToolUseContent, formatMessagesForAnthropic, isAnthropicTool };
//# sourceMappingURL=anthropic.js.map