-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlangchain.ts
More file actions
126 lines (111 loc) · 3.29 KB
/
langchain.ts
File metadata and controls
126 lines (111 loc) · 3.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { ChatAnthropic } from "@langchain/anthropic";
import type { AdapterOptions } from "./types.js";
import {
getApiKey,
mapReasoningToThinking,
type ReasoningEffort,
} from "./anthropic.js";
export type AgentModelPurpose = "primary" | "summary";
type LangChainMessageLike = {
content?: unknown;
text?: string;
type?: string;
getType?: () => string;
_getType?: () => string;
};
type LangChainModelCallRequest = {
systemMessage: LangChainMessageLike & {
concat: (content: string) => LangChainMessageLike;
};
messages: LangChainMessageLike[];
};
function getAgentReasoningEffort(
purpose: AgentModelPurpose,
): ReasoningEffort {
return purpose === "summary" ? "minimal" : "low";
}
function isSystemMessage(message: LangChainMessageLike): boolean {
return (
message._getType?.() === "system" ||
message.getType?.() === "system" ||
message.type === "system"
);
}
function contentToText(content: unknown, text?: string): string {
if (text) return text;
if (typeof content === "string") return content;
if (!Array.isArray(content)) return "";
return content
.map((block) => {
if (typeof block === "string") return block;
if (block && typeof block === "object" && "text" in block) {
return String(block.text ?? "");
}
return "";
})
.join("");
}
function normalizeAnthropicSystemMessages<T extends LangChainModelCallRequest>(
request: T,
): T {
const existingSystemText = contentToText(
request.systemMessage.content,
request.systemMessage.text,
);
const extraSystemText = request.messages
.filter(isSystemMessage)
.map((message) => contentToText(message.content, message.text))
.filter((text) => text && text !== existingSystemText)
.join("\n\n");
if (!extraSystemText) {
return {
...request,
messages: request.messages.filter((message) => !isSystemMessage(message)),
};
}
const systemMessage = request.systemMessage.concat(extraSystemText);
return {
...request,
systemMessage,
messages: request.messages.filter((message) => !isSystemMessage(message)),
};
}
function createAnthropicSystemMessageMiddleware() {
return {
name: "AnthropicSystemMessageMiddleware",
async wrapModelCall(
request: LangChainModelCallRequest,
handler: (request: LangChainModelCallRequest) => unknown,
) {
return handler(normalizeAnthropicSystemMessages(request));
},
};
}
export function createLangChainAgentSpec(params: {
options: AdapterOptions;
maxTokens: number;
purpose: AgentModelPurpose;
}) {
const extraRequestBodyParameters = {
...(params.options.extraRequestBodyParameters || {}),
} as Record<string, unknown> & {
thinking?: { type: "enabled"; budget_tokens: number };
};
const thinking =
extraRequestBodyParameters.thinking ||
mapReasoningToThinking(
getAgentReasoningEffort(params.purpose),
params.maxTokens,
);
delete extraRequestBodyParameters.thinking;
return {
model: new ChatAnthropic({
model: params.options.model || "claude-sonnet-4-5-20250929",
apiKey: getApiKey(params.options),
maxTokens: params.maxTokens,
...(thinking ? { thinking } : {}),
invocationKwargs: extraRequestBodyParameters,
} as any),
middleware: [createAnthropicSystemMessageMiddleware()],
};
}