@langchain/anthropic
Version:
Anthropic integrations for LangChain.js
301 lines (300 loc) • 7.01 kB
JavaScript
//#region src/utils/stream_events.ts
/**
* Convert an async iterable of raw Anthropic stream events into
* LangChain `ChatModelStreamEvent`s with typed deltas.
*/
async function* convertAnthropicStream(source, options = {}) {
const shouldStreamUsage = options.streamUsage ?? true;
const blockAccumulators = /* @__PURE__ */ new Map();
let usageSnapshot;
let stopReason = null;
for await (const data of source) switch (data.type) {
case "message_start": {
const { usage, id, model } = data.message;
if (usage && shouldStreamUsage) usageSnapshot = buildUsageSnapshot(usage);
yield {
event: "message-start",
id,
...usageSnapshot ? { usage: usageSnapshot } : {}
};
yield {
event: "provider",
provider: "anthropic",
name: "message_start",
payload: {
model,
id
}
};
break;
}
case "message_delta":
stopReason = data.delta.stop_reason;
if (shouldStreamUsage && data.usage) {
if (!usageSnapshot) usageSnapshot = {
input_tokens: 0,
output_tokens: data.usage.output_tokens,
total_tokens: data.usage.output_tokens
};
else usageSnapshot = {
...usageSnapshot,
output_tokens: usageSnapshot.output_tokens + data.usage.output_tokens,
total_tokens: usageSnapshot.input_tokens + usageSnapshot.output_tokens + data.usage.output_tokens
};
yield {
event: "usage",
usage: usageSnapshot
};
}
if ("context_management" in data.delta && data.delta.context_management) yield {
event: "provider",
provider: "anthropic",
name: "context_management",
payload: data.delta.context_management
};
break;
case "message_stop":
yield {
event: "message-finish",
reason: mapStopReason(stopReason),
...usageSnapshot ? { usage: usageSnapshot } : {},
metadata: { model_provider: "anthropic" }
};
break;
case "content_block_start": {
const { index, content_block } = data;
const mapped = mapBlockToContentBlock(content_block, index);
blockAccumulators.set(index, { ...mapped });
yield {
event: "content-block-start",
index,
content: mapped
};
break;
}
case "content_block_delta": {
const { index, delta } = data;
const acc = blockAccumulators.get(index);
if (!acc) break;
const { contentDelta, accumulated } = applyAnthropicDelta(acc, delta);
blockAccumulators.set(index, accumulated);
yield {
event: "content-block-delta",
index,
delta: contentDelta
};
break;
}
case "content_block_stop": {
const { index } = data;
const acc = blockAccumulators.get(index);
if (!acc) break;
yield {
event: "content-block-finish",
index,
content: finalizeBlock(acc)
};
blockAccumulators.delete(index);
break;
}
default:
yield {
event: "provider",
provider: "anthropic",
name: data.type,
payload: data
};
break;
}
}
function mapStopReason(stopReason) {
switch (stopReason) {
case "end_turn":
case "stop_sequence": return "stop";
case "tool_use": return "tool_use";
case "max_tokens": return "length";
default: return "stop";
}
}
function buildUsageSnapshot(usage) {
const cacheCreation = usage.cache_creation_input_tokens ?? 0;
const cacheRead = usage.cache_read_input_tokens ?? 0;
const totalInput = usage.input_tokens + cacheCreation + cacheRead;
return {
input_tokens: totalInput,
output_tokens: usage.output_tokens,
total_tokens: totalInput + usage.output_tokens,
input_token_details: {
cache_creation: cacheCreation,
cache_read: cacheRead
}
};
}
function mapBlockToContentBlock(block, index) {
switch (block.type) {
case "text": return {
type: "text",
text: block.text ?? "",
index
};
case "thinking": return {
type: "reasoning",
reasoning: block.thinking ?? "",
index
};
case "redacted_thinking": return {
type: "non_standard",
value: { ...block },
index
};
case "tool_use": return {
type: "tool_call_chunk",
id: block.id,
name: block.name,
args: "",
index
};
case "server_tool_use": return {
type: "server_tool_call_chunk",
id: block.id,
name: block.name,
args: "",
index
};
default: return {
type: "non_standard",
value: { ...block },
index
};
}
}
/**
* Map an Anthropic content_block_delta to a content block delta
* and update the accumulated state.
*/
function applyAnthropicDelta(accumulated, delta) {
switch (delta.type) {
case "text_delta": return {
contentDelta: {
type: "text-delta",
text: delta.text
},
accumulated: {
...accumulated,
text: (accumulated.text ?? "") + delta.text
}
};
case "thinking_delta": return {
contentDelta: {
type: "reasoning-delta",
reasoning: delta.thinking
},
accumulated: {
...accumulated,
reasoning: (accumulated.reasoning ?? "") + delta.thinking
}
};
case "input_json_delta": {
const newArgs = (accumulated.args ?? "") + delta.partial_json;
return {
contentDelta: {
type: "block-delta",
fields: {
type: accumulated.type,
args: newArgs
}
},
accumulated: {
...accumulated,
args: newArgs
}
};
}
case "citations_delta": {
const annotations = [...accumulated.annotations ?? [], delta.citation];
return {
contentDelta: {
type: "block-delta",
fields: {
type: accumulated.type,
annotations
}
},
accumulated: {
...accumulated,
annotations
}
};
}
case "signature_delta": return {
contentDelta: {
type: "block-delta",
fields: {
type: accumulated.type,
signature: delta.signature
}
},
accumulated: {
...accumulated,
signature: delta.signature
}
};
case "compaction_delta": return {
contentDelta: {
type: "block-delta",
fields: {
type: "non_standard",
value: {
...accumulated.value ?? {},
compaction: delta
}
}
},
accumulated: {
...accumulated,
value: {
...accumulated.value ?? {},
compaction: delta
}
}
};
default: return {
contentDelta: {
type: "block-delta",
fields: {
type: accumulated.type,
...delta
}
},
accumulated
};
}
}
function finalizeBlock(accumulated) {
if (accumulated.type === "tool_call_chunk" || accumulated.type === "server_tool_call_chunk") {
const finalType = accumulated.type === "tool_call_chunk" ? "tool_call" : "server_tool_call";
let parsedArgs;
try {
parsedArgs = JSON.parse(accumulated.args || "{}");
} catch {
return {
type: "invalid_tool_call",
id: accumulated.id,
name: accumulated.name,
args: accumulated.args,
error: "Failed to parse tool call arguments as JSON"
};
}
return {
type: finalType,
id: accumulated.id,
name: accumulated.name,
args: parsedArgs
};
}
const { index: _index, ...rest } = accumulated;
return rest;
}
//#endregion
exports.convertAnthropicStream = convertAnthropicStream;
//# sourceMappingURL=stream_events.cjs.map