@langchain/core
Version:
Core LangChain.js abstractions and schemas
353 lines (352 loc) • 14.3 kB
JavaScript
import { parsePartialJson } from "../utils/json.js";
import { BaseMessage, BaseMessageChunk, mergeContent, _mergeDicts, _mergeLists, } from "./base.js";
import { defaultToolCallParser, } from "./tool.js";
/**
* Represents an AI message in a conversation.
*/
export class AIMessage extends BaseMessage {
get lc_aliases() {
// exclude snake case conversion to pascal case
return {
...super.lc_aliases,
tool_calls: "tool_calls",
invalid_tool_calls: "invalid_tool_calls",
};
}
constructor(fields,
/** @deprecated */
kwargs) {
let initParams;
if (typeof fields === "string") {
initParams = {
content: fields,
tool_calls: [],
invalid_tool_calls: [],
additional_kwargs: kwargs ?? {},
};
}
else {
initParams = fields;
const rawToolCalls = initParams.additional_kwargs?.tool_calls;
const toolCalls = initParams.tool_calls;
if (!(rawToolCalls == null) &&
rawToolCalls.length > 0 &&
(toolCalls === undefined || toolCalls.length === 0)) {
console.warn([
"New LangChain packages are available that more efficiently handle",
"tool calling.\n\nPlease upgrade your packages to versions that set",
"message tool calls. e.g., `yarn add @langchain/anthropic`,",
"yarn add @langchain/openai`, etc.",
].join(" "));
}
try {
if (!(rawToolCalls == null) && toolCalls === undefined) {
const [toolCalls, invalidToolCalls] = defaultToolCallParser(rawToolCalls);
initParams.tool_calls = toolCalls ?? [];
initParams.invalid_tool_calls = invalidToolCalls ?? [];
}
else {
initParams.tool_calls = initParams.tool_calls ?? [];
initParams.invalid_tool_calls = initParams.invalid_tool_calls ?? [];
}
}
catch (e) {
// Do nothing if parsing fails
initParams.tool_calls = [];
initParams.invalid_tool_calls = [];
}
}
// Sadly, TypeScript only allows super() calls at root if the class has
// properties with initializers, so we have to check types twice.
super(initParams);
// These are typed as optional to avoid breaking changes and allow for casting
// from BaseMessage.
Object.defineProperty(this, "tool_calls", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "invalid_tool_calls", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
/**
* If provided, token usage information associated with the message.
*/
Object.defineProperty(this, "usage_metadata", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
if (typeof initParams !== "string") {
this.tool_calls = initParams.tool_calls ?? this.tool_calls;
this.invalid_tool_calls =
initParams.invalid_tool_calls ?? this.invalid_tool_calls;
}
this.usage_metadata = initParams.usage_metadata;
}
static lc_name() {
return "AIMessage";
}
_getType() {
return "ai";
}
get _printableFields() {
return {
...super._printableFields,
tool_calls: this.tool_calls,
invalid_tool_calls: this.invalid_tool_calls,
usage_metadata: this.usage_metadata,
};
}
}
export function isAIMessage(x) {
return x._getType() === "ai";
}
export function isAIMessageChunk(x) {
return x._getType() === "ai";
}
/**
* Represents a chunk of an AI message, which can be concatenated with
* other AI message chunks.
*/
export class AIMessageChunk extends BaseMessageChunk {
constructor(fields) {
let initParams;
if (typeof fields === "string") {
initParams = {
content: fields,
tool_calls: [],
invalid_tool_calls: [],
tool_call_chunks: [],
};
}
else if (fields.tool_call_chunks === undefined) {
initParams = {
...fields,
tool_calls: fields.tool_calls ?? [],
invalid_tool_calls: [],
tool_call_chunks: [],
usage_metadata: fields.usage_metadata !== undefined
? fields.usage_metadata
: undefined,
};
}
else {
const groupedToolCallChunks = fields.tool_call_chunks.reduce((acc, chunk) => {
const matchedChunkIndex = acc.findIndex(([match]) => {
// If chunk has an id and index, match if both are present
if ("id" in chunk &&
chunk.id &&
"index" in chunk &&
chunk.index !== undefined) {
return chunk.id === match.id && chunk.index === match.index;
}
// If chunk has an id, we match on id
if ("id" in chunk && chunk.id) {
return chunk.id === match.id;
}
// If chunk has an index, we match on index
if ("index" in chunk && chunk.index !== undefined) {
return chunk.index === match.index;
}
return false;
});
if (matchedChunkIndex !== -1) {
acc[matchedChunkIndex].push(chunk);
}
else {
acc.push([chunk]);
}
return acc;
}, []);
const toolCalls = [];
const invalidToolCalls = [];
for (const chunks of groupedToolCallChunks) {
let parsedArgs = {};
const name = chunks[0]?.name ?? "";
const joinedArgs = chunks.map((c) => c.args || "").join("");
const argsStr = joinedArgs.length ? joinedArgs : "{}";
const id = chunks[0]?.id;
try {
parsedArgs = parsePartialJson(argsStr);
if (!id ||
parsedArgs === null ||
typeof parsedArgs !== "object" ||
Array.isArray(parsedArgs)) {
throw new Error("Malformed tool call chunk args.");
}
toolCalls.push({
name,
args: parsedArgs,
id,
type: "tool_call",
});
}
catch (e) {
invalidToolCalls.push({
name,
args: argsStr,
id,
error: "Malformed args.",
type: "invalid_tool_call",
});
}
}
initParams = {
...fields,
tool_calls: toolCalls,
invalid_tool_calls: invalidToolCalls,
usage_metadata: fields.usage_metadata !== undefined
? fields.usage_metadata
: undefined,
};
}
// Sadly, TypeScript only allows super() calls at root if the class has
// properties with initializers, so we have to check types twice.
super(initParams);
// Must redeclare tool call fields since there is no multiple inheritance in JS.
// These are typed as optional to avoid breaking changes and allow for casting
// from BaseMessage.
Object.defineProperty(this, "tool_calls", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "invalid_tool_calls", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "tool_call_chunks", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
/**
* If provided, token usage information associated with the message.
*/
Object.defineProperty(this, "usage_metadata", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.tool_call_chunks =
initParams.tool_call_chunks ?? this.tool_call_chunks;
this.tool_calls = initParams.tool_calls ?? this.tool_calls;
this.invalid_tool_calls =
initParams.invalid_tool_calls ?? this.invalid_tool_calls;
this.usage_metadata = initParams.usage_metadata;
}
get lc_aliases() {
// exclude snake case conversion to pascal case
return {
...super.lc_aliases,
tool_calls: "tool_calls",
invalid_tool_calls: "invalid_tool_calls",
tool_call_chunks: "tool_call_chunks",
};
}
static lc_name() {
return "AIMessageChunk";
}
_getType() {
return "ai";
}
get _printableFields() {
return {
...super._printableFields,
tool_calls: this.tool_calls,
tool_call_chunks: this.tool_call_chunks,
invalid_tool_calls: this.invalid_tool_calls,
usage_metadata: this.usage_metadata,
};
}
concat(chunk) {
const combinedFields = {
content: mergeContent(this.content, chunk.content),
additional_kwargs: _mergeDicts(this.additional_kwargs, chunk.additional_kwargs),
response_metadata: _mergeDicts(this.response_metadata, chunk.response_metadata),
tool_call_chunks: [],
id: this.id ?? chunk.id,
};
if (this.tool_call_chunks !== undefined ||
chunk.tool_call_chunks !== undefined) {
const rawToolCalls = _mergeLists(this.tool_call_chunks, chunk.tool_call_chunks);
if (rawToolCalls !== undefined && rawToolCalls.length > 0) {
combinedFields.tool_call_chunks = rawToolCalls;
}
}
if (this.usage_metadata !== undefined ||
chunk.usage_metadata !== undefined) {
const inputTokenDetails = {
...((this.usage_metadata?.input_token_details?.audio !== undefined ||
chunk.usage_metadata?.input_token_details?.audio !== undefined) && {
audio: (this.usage_metadata?.input_token_details?.audio ?? 0) +
(chunk.usage_metadata?.input_token_details?.audio ?? 0),
}),
...((this.usage_metadata?.input_token_details?.cache_read !==
undefined ||
chunk.usage_metadata?.input_token_details?.cache_read !==
undefined) && {
cache_read: (this.usage_metadata?.input_token_details?.cache_read ?? 0) +
(chunk.usage_metadata?.input_token_details?.cache_read ?? 0),
}),
...((this.usage_metadata?.input_token_details?.cache_creation !==
undefined ||
chunk.usage_metadata?.input_token_details?.cache_creation !==
undefined) && {
cache_creation: (this.usage_metadata?.input_token_details?.cache_creation ?? 0) +
(chunk.usage_metadata?.input_token_details?.cache_creation ?? 0),
}),
};
const outputTokenDetails = {
...((this.usage_metadata?.output_token_details?.audio !== undefined ||
chunk.usage_metadata?.output_token_details?.audio !== undefined) && {
audio: (this.usage_metadata?.output_token_details?.audio ?? 0) +
(chunk.usage_metadata?.output_token_details?.audio ?? 0),
}),
...((this.usage_metadata?.output_token_details?.reasoning !==
undefined ||
chunk.usage_metadata?.output_token_details?.reasoning !==
undefined) && {
reasoning: (this.usage_metadata?.output_token_details?.reasoning ?? 0) +
(chunk.usage_metadata?.output_token_details?.reasoning ?? 0),
}),
};
const left = this.usage_metadata ?? {
input_tokens: 0,
output_tokens: 0,
total_tokens: 0,
};
const right = chunk.usage_metadata ?? {
input_tokens: 0,
output_tokens: 0,
total_tokens: 0,
};
const usage_metadata = {
input_tokens: left.input_tokens + right.input_tokens,
output_tokens: left.output_tokens + right.output_tokens,
total_tokens: left.total_tokens + right.total_tokens,
// Do not include `input_token_details` / `output_token_details` keys in combined fields
// unless their values are defined.
...(Object.keys(inputTokenDetails).length > 0 && {
input_token_details: inputTokenDetails,
}),
...(Object.keys(outputTokenDetails).length > 0 && {
output_token_details: outputTokenDetails,
}),
};
combinedFields.usage_metadata = usage_metadata;
}
return new AIMessageChunk(combinedFields);
}
}