@langchain/anthropic 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,22 @@ const messages_1 = require("@langchain/core/messages");
6
6
  const outputs_1 = require("@langchain/core/outputs");
7
7
  const env_1 = require("@langchain/core/utils/env");
8
8
  const chat_models_1 = require("@langchain/core/language_models/chat_models");
9
+ function _formatImage(imageUrl) {
10
+ const regex = /^data:(image\/.+);base64,(.+)$/;
11
+ const match = imageUrl.match(regex);
12
+ if (match === null) {
13
+ throw new Error([
14
+ "Anthropic only supports base64-encoded images currently.",
15
+ "Example: data:image/png;base64,/9j/4AAQSk...",
16
+ ].join("\n\n"));
17
+ }
18
+ return {
19
+ type: "base64",
20
+ media_type: match[1] ?? "",
21
+ data: match[2] ?? "",
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ };
24
+ }
9
25
  /**
10
26
  * Wrapper around Anthropic large language models.
11
27
  *
@@ -243,16 +259,13 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
243
259
  let system;
244
260
  if (messages.length > 0 && messages[0]._getType() === "system") {
245
261
  if (typeof messages[0].content !== "string") {
246
- throw new Error("Currently only string content messages are supported.");
262
+ throw new Error("System message content must be a string.");
247
263
  }
248
264
  system = messages[0].content;
249
265
  }
250
266
  const conversationMessages = system !== undefined ? messages.slice(1) : messages;
251
267
  const formattedMessages = conversationMessages.map((message) => {
252
268
  let role;
253
- if (typeof message.content !== "string") {
254
- throw new Error("Currently only string content messages are supported.");
255
- }
256
269
  if (message._getType() === "human") {
257
270
  role = "user";
258
271
  }
@@ -265,10 +278,35 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
265
278
  else {
266
279
  throw new Error(`Message type "${message._getType()}" is not supported.`);
267
280
  }
268
- return {
269
- role,
270
- content: message.content,
271
- };
281
+ if (typeof message.content === "string") {
282
+ return {
283
+ role,
284
+ content: message.content,
285
+ };
286
+ }
287
+ else {
288
+ return {
289
+ role,
290
+ content: message.content.map((contentPart) => {
291
+ if (contentPart.type === "image_url") {
292
+ let source;
293
+ if (typeof contentPart.image_url === "string") {
294
+ source = _formatImage(contentPart.image_url);
295
+ }
296
+ else {
297
+ source = _formatImage(contentPart.image_url.url);
298
+ }
299
+ return {
300
+ type: "image",
301
+ source,
302
+ };
303
+ }
304
+ else {
305
+ return contentPart;
306
+ }
307
+ }),
308
+ };
309
+ }
272
310
  });
273
311
  return {
274
312
  messages: formattedMessages,
@@ -374,10 +412,6 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
374
412
  _llmType() {
375
413
  return "anthropic";
376
414
  }
377
- /** @ignore */
378
- _combineLLMOutput() {
379
- return [];
380
- }
381
415
  }
382
416
  exports.ChatAnthropicMessages = ChatAnthropicMessages;
383
417
  class ChatAnthropic extends ChatAnthropicMessages {
@@ -165,8 +165,6 @@ export declare class ChatAnthropicMessages<CallOptions extends BaseLanguageModel
165
165
  signal?: AbortSignal;
166
166
  }): Promise<Anthropic.Message>;
167
167
  _llmType(): string;
168
- /** @ignore */
169
- _combineLLMOutput(): never[];
170
168
  }
171
169
  export declare class ChatAnthropic extends ChatAnthropicMessages {
172
170
  }
@@ -3,6 +3,22 @@ import { AIMessage, AIMessageChunk, } from "@langchain/core/messages";
3
3
  import { ChatGenerationChunk } from "@langchain/core/outputs";
4
4
  import { getEnvironmentVariable } from "@langchain/core/utils/env";
5
5
  import { BaseChatModel, } from "@langchain/core/language_models/chat_models";
6
+ function _formatImage(imageUrl) {
7
+ const regex = /^data:(image\/.+);base64,(.+)$/;
8
+ const match = imageUrl.match(regex);
9
+ if (match === null) {
10
+ throw new Error([
11
+ "Anthropic only supports base64-encoded images currently.",
12
+ "Example: data:image/png;base64,/9j/4AAQSk...",
13
+ ].join("\n\n"));
14
+ }
15
+ return {
16
+ type: "base64",
17
+ media_type: match[1] ?? "",
18
+ data: match[2] ?? "",
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ };
21
+ }
6
22
  /**
7
23
  * Wrapper around Anthropic large language models.
8
24
  *
@@ -240,16 +256,13 @@ export class ChatAnthropicMessages extends BaseChatModel {
240
256
  let system;
241
257
  if (messages.length > 0 && messages[0]._getType() === "system") {
242
258
  if (typeof messages[0].content !== "string") {
243
- throw new Error("Currently only string content messages are supported.");
259
+ throw new Error("System message content must be a string.");
244
260
  }
245
261
  system = messages[0].content;
246
262
  }
247
263
  const conversationMessages = system !== undefined ? messages.slice(1) : messages;
248
264
  const formattedMessages = conversationMessages.map((message) => {
249
265
  let role;
250
- if (typeof message.content !== "string") {
251
- throw new Error("Currently only string content messages are supported.");
252
- }
253
266
  if (message._getType() === "human") {
254
267
  role = "user";
255
268
  }
@@ -262,10 +275,35 @@ export class ChatAnthropicMessages extends BaseChatModel {
262
275
  else {
263
276
  throw new Error(`Message type "${message._getType()}" is not supported.`);
264
277
  }
265
- return {
266
- role,
267
- content: message.content,
268
- };
278
+ if (typeof message.content === "string") {
279
+ return {
280
+ role,
281
+ content: message.content,
282
+ };
283
+ }
284
+ else {
285
+ return {
286
+ role,
287
+ content: message.content.map((contentPart) => {
288
+ if (contentPart.type === "image_url") {
289
+ let source;
290
+ if (typeof contentPart.image_url === "string") {
291
+ source = _formatImage(contentPart.image_url);
292
+ }
293
+ else {
294
+ source = _formatImage(contentPart.image_url.url);
295
+ }
296
+ return {
297
+ type: "image",
298
+ source,
299
+ };
300
+ }
301
+ else {
302
+ return contentPart;
303
+ }
304
+ }),
305
+ };
306
+ }
269
307
  });
270
308
  return {
271
309
  messages: formattedMessages,
@@ -371,10 +409,6 @@ export class ChatAnthropicMessages extends BaseChatModel {
371
409
  _llmType() {
372
410
  return "anthropic";
373
411
  }
374
- /** @ignore */
375
- _combineLLMOutput() {
376
- return [];
377
- }
378
412
  }
379
413
  export class ChatAnthropic extends ChatAnthropicMessages {
380
414
  }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./tool_calling.cjs"), exports);
@@ -0,0 +1 @@
1
+ export * from "./tool_calling.js";
@@ -0,0 +1 @@
1
+ export * from "./tool_calling.js";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,268 @@
1
+ /* eslint-disable no-process-env */
2
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
3
+ import { test } from "@jest/globals";
4
+ import { z } from "zod";
5
+ import { zodToJsonSchema } from "zod-to-json-schema";
6
+ import { HumanMessage } from "@langchain/core/messages";
7
+ import { ChatPromptTemplate } from "@langchain/core/prompts";
8
+ import { ChatAnthropicTools } from "../tool_calling.js";
9
+ test("Test ChatAnthropicTools", async () => {
10
+ const chat = new ChatAnthropicTools({
11
+ modelName: "claude-3-sonnet-20240229",
12
+ maxRetries: 0,
13
+ });
14
+ const message = new HumanMessage("Hello!");
15
+ const res = await chat.invoke([message]);
16
+ console.log(JSON.stringify(res));
17
+ });
18
+ test("Test ChatAnthropicTools streaming", async () => {
19
+ const chat = new ChatAnthropicTools({
20
+ modelName: "claude-3-sonnet-20240229",
21
+ maxRetries: 0,
22
+ });
23
+ const message = new HumanMessage("Hello!");
24
+ const stream = await chat.stream([message]);
25
+ const chunks = [];
26
+ for await (const chunk of stream) {
27
+ console.log(chunk);
28
+ chunks.push(chunk);
29
+ }
30
+ expect(chunks.length).toBeGreaterThan(1);
31
+ });
32
+ test("Test ChatAnthropicTools with tools", async () => {
33
+ const chat = new ChatAnthropicTools({
34
+ modelName: "claude-3-sonnet-20240229",
35
+ temperature: 0.1,
36
+ maxRetries: 0,
37
+ }).bind({
38
+ tools: [
39
+ {
40
+ type: "function",
41
+ function: {
42
+ name: "get_current_weather",
43
+ description: "Get the current weather in a given location",
44
+ parameters: {
45
+ type: "object",
46
+ properties: {
47
+ location: {
48
+ type: "string",
49
+ description: "The city and state, e.g. San Francisco, CA",
50
+ },
51
+ unit: {
52
+ type: "string",
53
+ enum: ["celsius", "fahrenheit"],
54
+ },
55
+ },
56
+ required: ["location"],
57
+ },
58
+ },
59
+ },
60
+ ],
61
+ });
62
+ const message = new HumanMessage("What is the weather in San Francisco?");
63
+ const res = await chat.invoke([message]);
64
+ console.log(JSON.stringify(res));
65
+ expect(res.additional_kwargs.tool_calls).toBeDefined();
66
+ expect(res.additional_kwargs.tool_calls?.[0].function.name).toEqual("get_current_weather");
67
+ });
68
+ test("Test ChatAnthropicTools with a forced function call", async () => {
69
+ const chat = new ChatAnthropicTools({
70
+ modelName: "claude-3-sonnet-20240229",
71
+ temperature: 0.1,
72
+ maxRetries: 0,
73
+ }).bind({
74
+ tools: [
75
+ {
76
+ type: "function",
77
+ function: {
78
+ name: "extract_data",
79
+ description: "Return information about the input",
80
+ parameters: {
81
+ type: "object",
82
+ properties: {
83
+ sentiment: {
84
+ type: "string",
85
+ description: "The city and state, e.g. San Francisco, CA",
86
+ },
87
+ aggressiveness: {
88
+ type: "integer",
89
+ description: "How aggressive the input is from 1 to 10",
90
+ },
91
+ language: {
92
+ type: "string",
93
+ description: "The language the input is in",
94
+ },
95
+ },
96
+ required: ["sentiment", "aggressiveness"],
97
+ },
98
+ },
99
+ },
100
+ ],
101
+ tool_choice: { type: "function", function: { name: "extract_data" } },
102
+ });
103
+ const message = new HumanMessage("Extract the desired information from the following passage:\n\nthis is really cool");
104
+ const res = await chat.invoke([message]);
105
+ console.log(JSON.stringify(res));
106
+ expect(res.additional_kwargs.tool_calls).toBeDefined();
107
+ expect(res.additional_kwargs.tool_calls?.[0]?.function.name).toEqual("extract_data");
108
+ });
109
+ test("ChatAnthropicTools with Zod schema", async () => {
110
+ const schema = z.object({
111
+ people: z.array(z.object({
112
+ name: z.string().describe("The name of a person"),
113
+ height: z.number().describe("The person's height"),
114
+ hairColor: z.optional(z.string()).describe("The person's hair color"),
115
+ })),
116
+ });
117
+ const chat = new ChatAnthropicTools({
118
+ modelName: "claude-3-sonnet-20240229",
119
+ temperature: 0.1,
120
+ maxRetries: 0,
121
+ }).bind({
122
+ tools: [
123
+ {
124
+ type: "function",
125
+ function: {
126
+ name: "information_extraction",
127
+ description: "Extracts the relevant information from the passage.",
128
+ parameters: zodToJsonSchema(schema),
129
+ },
130
+ },
131
+ ],
132
+ tool_choice: {
133
+ type: "function",
134
+ function: {
135
+ name: "information_extraction",
136
+ },
137
+ },
138
+ });
139
+ const message = new HumanMessage("Alex is 5 feet tall. Claudia is 1 foot taller than Alex and jumps higher than him. Claudia is a brunette and Alex is blonde.");
140
+ const res = await chat.invoke([message]);
141
+ console.log(JSON.stringify(res));
142
+ expect(res.additional_kwargs.tool_calls).toBeDefined();
143
+ expect(res.additional_kwargs.tool_calls?.[0]?.function.name).toEqual("information_extraction");
144
+ expect(JSON.parse(res.additional_kwargs.tool_calls?.[0]?.function.arguments ?? "")).toEqual({
145
+ people: expect.arrayContaining([
146
+ { name: "Alex", height: 5, hairColor: "blonde" },
147
+ { name: "Claudia", height: 6, hairColor: "brunette" },
148
+ ]),
149
+ });
150
+ });
151
+ test("ChatAnthropicTools with parallel tool calling", async () => {
152
+ const schema = z.object({
153
+ name: z.string().describe("The name of a person"),
154
+ height: z.number().describe("The person's height"),
155
+ hairColor: z.optional(z.string()).describe("The person's hair color"),
156
+ });
157
+ const chat = new ChatAnthropicTools({
158
+ modelName: "claude-3-sonnet-20240229",
159
+ temperature: 0.1,
160
+ maxRetries: 0,
161
+ }).bind({
162
+ tools: [
163
+ {
164
+ type: "function",
165
+ function: {
166
+ name: "person",
167
+ description: "A person mentioned in the passage.",
168
+ parameters: zodToJsonSchema(schema),
169
+ },
170
+ },
171
+ ],
172
+ tool_choice: {
173
+ type: "function",
174
+ function: {
175
+ name: "person",
176
+ },
177
+ },
178
+ });
179
+ console.log(zodToJsonSchema(schema));
180
+ const message = new HumanMessage("Alex is 5 feet tall. Claudia is 1 foot taller than Alex and jumps higher than him. Claudia is a brunette and Alex is blonde.");
181
+ const res = await chat.invoke([message]);
182
+ console.log(JSON.stringify(res));
183
+ expect(res.additional_kwargs.tool_calls).toBeDefined();
184
+ expect(res.additional_kwargs.tool_calls?.map((toolCall) => JSON.parse(toolCall.function.arguments ?? ""))).toEqual(expect.arrayContaining([
185
+ { name: "Alex", height: 5, hairColor: "blonde" },
186
+ { name: "Claudia", height: 6, hairColor: "brunette" },
187
+ ]));
188
+ });
189
+ test("Test ChatAnthropic withStructuredOutput", async () => {
190
+ const runnable = new ChatAnthropicTools({
191
+ modelName: "claude-3-sonnet-20240229",
192
+ maxRetries: 0,
193
+ }).withStructuredOutput({
194
+ schema: z.object({
195
+ name: z.string().describe("The name of a person"),
196
+ height: z.number().describe("The person's height"),
197
+ hairColor: z.optional(z.string()).describe("The person's hair color"),
198
+ }),
199
+ name: "person",
200
+ });
201
+ const message = new HumanMessage("Alex is 5 feet tall. Alex is blonde.");
202
+ const res = await runnable.invoke([message]);
203
+ console.log(JSON.stringify(res, null, 2));
204
+ expect(res).toEqual({ name: "Alex", height: 5, hairColor: "blonde" });
205
+ });
206
+ test("Test ChatAnthropic withStructuredOutput on a single array item", async () => {
207
+ const runnable = new ChatAnthropicTools({
208
+ modelName: "claude-3-sonnet-20240229",
209
+ maxRetries: 0,
210
+ }).withStructuredOutput({
211
+ schema: z.object({
212
+ people: z.array(z.object({
213
+ name: z.string().describe("The name of a person"),
214
+ height: z.number().describe("The person's height"),
215
+ hairColor: z.optional(z.string()).describe("The person's hair color"),
216
+ })),
217
+ }),
218
+ });
219
+ const message = new HumanMessage("Alex is 5 feet tall. Alex is blonde.");
220
+ const res = await runnable.invoke([message]);
221
+ console.log(JSON.stringify(res, null, 2));
222
+ expect(res).toEqual({
223
+ people: [{ hairColor: "blonde", height: 5, name: "Alex" }],
224
+ });
225
+ });
226
+ test("Test ChatAnthropic withStructuredOutput on a single array item", async () => {
227
+ const runnable = new ChatAnthropicTools({
228
+ modelName: "claude-3-sonnet-20240229",
229
+ maxRetries: 0,
230
+ }).withStructuredOutput({
231
+ schema: z.object({
232
+ sender: z
233
+ .optional(z.string())
234
+ .describe("The sender's name, if available"),
235
+ sender_phone_number: z
236
+ .optional(z.string())
237
+ .describe("The sender's phone number, if available"),
238
+ sender_address: z
239
+ .optional(z.string())
240
+ .describe("The sender's address, if available"),
241
+ action_items: z
242
+ .array(z.string())
243
+ .describe("A list of action items requested by the email"),
244
+ topic: z
245
+ .string()
246
+ .describe("High level description of what the email is about"),
247
+ tone: z.enum(["positive", "negative"]).describe("The tone of the email."),
248
+ }),
249
+ name: "Email",
250
+ });
251
+ const prompt = ChatPromptTemplate.fromMessages([
252
+ [
253
+ "human",
254
+ "What can you tell me about the following email? Make sure to answer in the correct format: {email}",
255
+ ],
256
+ ]);
257
+ const extractionChain = prompt.pipe(runnable);
258
+ const response = await extractionChain.invoke({
259
+ email: "From: Erick. The email is about the new project. The tone is positive. The action items are to send the report and to schedule a meeting.",
260
+ });
261
+ console.log(JSON.stringify(response, null, 2));
262
+ expect(response).toEqual({
263
+ sender: "Erick",
264
+ action_items: [expect.any(String), expect.any(String)],
265
+ topic: expect.any(String),
266
+ tone: "positive",
267
+ });
268
+ });