@langchain/langgraph
Version:
232 lines (231 loc) • 8.16 kB
JavaScript
import "../constants.js";
import { BaseMessage, BaseMessageChunk, ToolMessage } from "@langchain/core/messages";
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
//#region src/pregel/messages-v2.ts
function getResponseMetadata(message) {
if ("response_metadata" in message && typeof message.response_metadata === "object" && message.response_metadata != null) return message.response_metadata;
}
function getUsageMetadata(message) {
if ("usage_metadata" in message && typeof message.usage_metadata === "object" && message.usage_metadata != null) return message.usage_metadata;
}
function startBlockFor(block) {
switch (block.type) {
case "text": return {
type: "text",
text: ""
};
case "reasoning": return {
type: "reasoning",
reasoning: ""
};
case "tool_call":
case "tool_call_chunk": return {
type: "tool_call_chunk",
...block.id != null ? { id: block.id } : {},
...block.name != null ? { name: block.name } : {},
args: ""
};
default: return block;
}
}
function deltaFor(block) {
switch (block.type) {
case "text": {
const text = typeof block.text === "string" ? block.text : "";
return text.length > 0 ? {
event: "content-block-delta",
index: typeof block.index === "number" ? block.index : 0,
delta: {
type: "text-delta",
text
}
} : void 0;
}
case "reasoning": {
const reasoning = typeof block.reasoning === "string" ? block.reasoning : "";
return reasoning.length > 0 ? {
event: "content-block-delta",
index: typeof block.index === "number" ? block.index : 0,
delta: {
type: "reasoning-delta",
reasoning
}
} : void 0;
}
case "tool_call_chunk": return {
event: "content-block-delta",
index: typeof block.index === "number" ? block.index : 0,
delta: {
type: "block-delta",
fields: {
...block,
type: "tool_call_chunk"
}
}
};
default: return;
}
}
/**
* A callback handler that implements protocol-native stream_mode=messages.
*
* LangChain Core owns chat model content-block event construction. This handler
* only captures LangGraph metadata, forwards Core events to the Pregel messages
* channel, and emits a small non-streaming fallback for models that cannot
* produce stream events.
*/
var StreamProtocolMessagesHandler = class extends BaseCallbackHandler {
name = "StreamProtocolMessagesHandler";
streamFn;
metadatas = {};
seen = {};
streamedRunIds = /* @__PURE__ */ new Set();
stableMessageIdMap = {};
lc_prefer_chat_model_stream_events = true;
awaitHandlers = true;
constructor(streamFn) {
super();
this.streamFn = streamFn;
}
normalizeMessageId(message, runId) {
let messageId = message.id;
if (runId != null) if (ToolMessage.isInstance(message)) messageId ??= `run-${runId}-tool-${message.tool_call_id}`;
else {
if (messageId == null || messageId === `run-${runId}`) messageId = this.stableMessageIdMap[runId] ?? messageId ?? `run-${runId}`;
this.stableMessageIdMap[runId] ??= messageId;
}
if (messageId !== message.id) {
message.id = messageId;
message.lc_kwargs.id = messageId;
}
if (message.id != null) this.seen[message.id] = message;
return message.id;
}
emit(meta, data, runId) {
const metadata = runId != null ? {
...meta[1],
run_id: runId
} : meta[1];
this.streamFn([
meta[0],
"messages",
[data, metadata]
]);
}
emitFinalMessage(meta, message, runId, dedupe = false) {
const existingId = message.id ?? (runId != null ? this.stableMessageIdMap[runId] : void 0);
if (dedupe && existingId != null && this.seen[existingId] !== void 0) return;
const messageId = this.normalizeMessageId(message, runId);
const role = message.type === "human" ? "human" : message.type === "system" ? "system" : message.type === "tool" ? "tool" : "ai";
const toolCallId = role === "tool" && ToolMessage.isInstance(message) ? message.tool_call_id : void 0;
this.emit(meta, {
event: "message-start",
...messageId != null ? { id: messageId } : {},
...role !== "ai" ? { role } : {},
...typeof toolCallId === "string" ? { tool_call_id: toolCallId } : {}
}, runId);
(Array.isArray(message.content) ? message.content : typeof message.content === "string" && message.content.length > 0 ? [{
type: "text",
text: message.content
}] : []).forEach((block, offset) => {
const index = typeof block.index === "number" ? block.index : offset;
this.emit(meta, {
event: "content-block-start",
index,
content: startBlockFor(block)
}, runId);
const delta = deltaFor({
...block,
index
});
if (delta != null) this.emit(meta, delta, runId);
this.emit(meta, {
event: "content-block-finish",
index,
content: block
}, runId);
});
this.emit(meta, {
event: "message-finish",
...getUsageMetadata(message) != null ? { usage: getUsageMetadata(message) } : {},
...getResponseMetadata(message) != null ? { responseMetadata: getResponseMetadata(message) } : {}
}, runId);
}
handleChatModelStart(_llm, _messages, runId, _parentRunId, _extraParams, tags, metadata, name) {
if (metadata && (!tags || !tags.includes("langsmith:nostream") && !tags.includes("nostream"))) this.metadatas[runId] = [metadata.langgraph_checkpoint_ns.split("|"), {
tags,
name,
...metadata
}];
}
handleLLMNewToken() {}
handleChatModelStreamEvent(event, runId) {
const meta = this.metadatas[runId];
if (meta === void 0) return;
let forwarded = event;
if (event.event === "message-start") {
this.streamedRunIds.add(runId);
const id = event.id ?? `run-${runId}`;
this.seen[id] = true;
this.stableMessageIdMap[runId] ??= id;
if (event.id == null) forwarded = {
...event,
id
};
}
this.emit(meta, forwarded, runId);
}
handleLLMEnd(output, runId) {
const meta = this.metadatas[runId];
if (meta === void 0) return;
const chatGeneration = output.generations?.[0]?.[0];
const message = BaseMessage.isInstance(chatGeneration?.message) ? chatGeneration.message : void 0;
if (message != null) if (this.streamedRunIds.has(runId)) {
const messageId = this.normalizeMessageId(message, runId);
if (messageId != null) this.seen[messageId] = message;
} else this.emitFinalMessage(meta, message, runId, true);
this.streamedRunIds.delete(runId);
delete this.metadatas[runId];
delete this.stableMessageIdMap[runId];
}
handleLLMError(_err, runId) {
this.streamedRunIds.delete(runId);
delete this.metadatas[runId];
delete this.stableMessageIdMap[runId];
}
handleChainStart(_chain, inputs, runId, _parentRunId, tags, metadata, _runType, name) {
if (metadata !== void 0 && name === metadata.langgraph_node && (tags === void 0 || !tags.includes("langsmith:hidden"))) {
this.metadatas[runId] = [metadata.langgraph_checkpoint_ns.split("|"), {
tags,
name,
...metadata
}];
if (typeof inputs === "object") {
for (const value of Object.values(inputs)) if ((BaseMessage.isInstance(value) || BaseMessageChunk.isInstance(value)) && value.id !== void 0) this.seen[value.id] = value;
else if (Array.isArray(value)) {
for (const item of value) if ((BaseMessage.isInstance(item) || BaseMessageChunk.isInstance(item)) && item.id !== void 0) this.seen[item.id] = item;
}
}
}
}
handleChainEnd(outputs, runId) {
const meta = this.metadatas[runId];
delete this.metadatas[runId];
if (meta === void 0) return;
const emitMessage = (value) => {
if (BaseMessage.isInstance(value) && !ToolMessage.isInstance(value)) this.emitFinalMessage(meta, value, runId, true);
};
if (BaseMessage.isInstance(outputs)) emitMessage(outputs);
else if (Array.isArray(outputs)) for (const value of outputs) emitMessage(value);
else if (outputs != null && typeof outputs === "object") for (const value of Object.values(outputs)) if (Array.isArray(value)) for (const item of value) emitMessage(item);
else emitMessage(value);
delete this.stableMessageIdMap[runId];
}
handleChainError(_err, runId) {
delete this.metadatas[runId];
delete this.stableMessageIdMap[runId];
}
};
//#endregion
export { StreamProtocolMessagesHandler };
//# sourceMappingURL=messages-v2.js.map