@langchain/anthropic 0.1.6 → 0.1.8

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.
@@ -262,3 +262,17 @@ test("Test ChatAnthropic withStructuredOutput on a single array item", async ()
262
262
  tone: "positive",
263
263
  });
264
264
  });
265
+ test("Test ChatAnthropicTools", async () => {
266
+ const chat = new ChatAnthropicTools({
267
+ modelName: "claude-3-sonnet-20240229",
268
+ maxRetries: 0,
269
+ });
270
+ const structured = chat.withStructuredOutput(z.object({
271
+ nested: z.array(z.number()),
272
+ }), { force: false });
273
+ const res = await structured.invoke("What are the first five natural numbers?");
274
+ console.log(res);
275
+ expect(res).toEqual({
276
+ nested: [1, 2, 3, 4, 5],
277
+ });
278
+ });
@@ -18,6 +18,9 @@ class ChatAnthropicTools extends chat_models_1.BaseChatModel {
18
18
  return "ChatAnthropicTools";
19
19
  }
20
20
  constructor(fields) {
21
+ if (fields?.cache !== undefined) {
22
+ throw new Error("Caching is not supported for this model.");
23
+ }
21
24
  super(fields ?? {});
22
25
  Object.defineProperty(this, "llm", {
23
26
  enumerable: true,
@@ -59,7 +62,7 @@ class ChatAnthropicTools extends chat_models_1.BaseChatModel {
59
62
  async *_streamResponseChunks(messages, options, runManager) {
60
63
  yield* this.llm._streamResponseChunks(messages, options, runManager);
61
64
  }
62
- async _prepareAndParseToolCall({ messages, options, runManager, systemPromptTemplate = tool_calling_js_1.DEFAULT_TOOL_SYSTEM_PROMPT, stopSequences, }) {
65
+ async _prepareAndParseToolCall({ messages, options, systemPromptTemplate = tool_calling_js_1.DEFAULT_TOOL_SYSTEM_PROMPT, stopSequences, }) {
63
66
  let promptMessages = messages;
64
67
  let forced = false;
65
68
  let toolCall;
@@ -105,8 +108,10 @@ class ChatAnthropicTools extends chat_models_1.BaseChatModel {
105
108
  else if (options.tool_choice !== undefined) {
106
109
  throw new Error(`If "tool_choice" is provided, "tools" must also be.`);
107
110
  }
108
- const chatResult = await this.llm._generate(promptMessages, options, runManager);
109
- const chatGenerationContent = chatResult.generations[0].message.content;
111
+ const chatResult = await this.llm
112
+ .withConfig({ runName: "ChatAnthropicTools" })
113
+ .invoke(promptMessages, options);
114
+ const chatGenerationContent = chatResult.content;
110
115
  if (typeof chatGenerationContent !== "string") {
111
116
  throw new Error("AnthropicFunctions does not support non-string output.");
112
117
  }
@@ -171,15 +176,25 @@ class ChatAnthropicTools extends chat_models_1.BaseChatModel {
171
176
  generations: [{ message: responseMessageWithFunctions, text: "" }],
172
177
  };
173
178
  }
174
- return chatResult;
179
+ return { generations: [{ message: chatResult, text: "" }] };
175
180
  }
176
- async _generate(messages, options, _runManager) {
177
- return this._prepareAndParseToolCall({
178
- messages,
179
- options,
181
+ async generate(messages, parsedOptions, callbacks) {
182
+ const baseMessages = messages.map((messageList) => messageList.map(messages_1.coerceMessageLikeToMessage));
183
+ // generate results
184
+ const chatResults = await Promise.all(baseMessages.map((messageList) => this._prepareAndParseToolCall({
185
+ messages: messageList,
186
+ options: { callbacks, ...parsedOptions },
180
187
  systemPromptTemplate: this.systemPromptTemplate,
181
188
  stopSequences: this.stopSequences ?? [],
182
- });
189
+ })));
190
+ // create combined output
191
+ const output = {
192
+ generations: chatResults.map((chatResult) => chatResult.generations),
193
+ };
194
+ return output;
195
+ }
196
+ async _generate(_messages, _options, _runManager) {
197
+ throw new Error("Unused.");
183
198
  }
184
199
  _llmType() {
185
200
  return "anthropic_tool_calling";
@@ -190,6 +205,7 @@ class ChatAnthropicTools extends chat_models_1.BaseChatModel {
190
205
  let name;
191
206
  let method;
192
207
  let includeRaw;
208
+ let force;
193
209
  if (isStructuredOutputMethodParams(outputSchema)) {
194
210
  schema = outputSchema.schema;
195
211
  name = outputSchema.name;
@@ -201,11 +217,12 @@ class ChatAnthropicTools extends chat_models_1.BaseChatModel {
201
217
  name = config?.name;
202
218
  method = config?.method;
203
219
  includeRaw = config?.includeRaw;
220
+ force = config?.force ?? false;
204
221
  }
205
222
  if (method === "jsonMode") {
206
223
  throw new Error(`Anthropic only supports "functionCalling" as a method.`);
207
224
  }
208
- const functionName = name ?? "extract";
225
+ let functionName = name ?? "extract";
209
226
  let outputParser;
210
227
  let tools;
211
228
  if (isZodSchema(schema)) {
@@ -227,14 +244,24 @@ class ChatAnthropicTools extends chat_models_1.BaseChatModel {
227
244
  });
228
245
  }
229
246
  else {
247
+ let openAIFunctionDefinition;
248
+ if (typeof schema.name === "string" &&
249
+ typeof schema.parameters === "object" &&
250
+ schema.parameters != null) {
251
+ openAIFunctionDefinition = schema;
252
+ functionName = schema.name;
253
+ }
254
+ else {
255
+ openAIFunctionDefinition = {
256
+ name: functionName,
257
+ description: schema.description ?? "",
258
+ parameters: schema,
259
+ };
260
+ }
230
261
  tools = [
231
262
  {
232
263
  type: "function",
233
- function: {
234
- name: functionName,
235
- description: schema.description,
236
- parameters: schema,
237
- },
264
+ function: openAIFunctionDefinition,
238
265
  },
239
266
  ];
240
267
  outputParser = new openai_tools_1.JsonOutputKeyToolsParser({
@@ -244,12 +271,14 @@ class ChatAnthropicTools extends chat_models_1.BaseChatModel {
244
271
  }
245
272
  const llm = this.bind({
246
273
  tools,
247
- tool_choice: {
248
- type: "function",
249
- function: {
250
- name: functionName,
251
- },
252
- },
274
+ tool_choice: force
275
+ ? {
276
+ type: "function",
277
+ function: {
278
+ name: functionName,
279
+ },
280
+ }
281
+ : "auto",
253
282
  });
254
283
  if (!includeRaw) {
255
284
  return llm.pipe(outputParser).withConfig({
@@ -1,9 +1,9 @@
1
- import { BaseMessage } from "@langchain/core/messages";
2
- import { ChatGenerationChunk, ChatResult } from "@langchain/core/outputs";
1
+ import { BaseMessage, BaseMessageLike } from "@langchain/core/messages";
2
+ import type { ChatGenerationChunk, ChatResult, LLMResult } from "@langchain/core/outputs";
3
3
  import { BaseChatModel, BaseChatModelParams } from "@langchain/core/language_models/chat_models";
4
- import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager";
4
+ import { CallbackManagerForLLMRun, Callbacks } from "@langchain/core/callbacks/manager";
5
5
  import { BasePromptTemplate } from "@langchain/core/prompts";
6
- import { BaseLanguageModelCallOptions, BaseLanguageModelInput, StructuredOutputMethodParams, StructuredOutputMethodOptions, ToolDefinition } from "@langchain/core/language_models/base";
6
+ import type { BaseLanguageModelCallOptions, BaseLanguageModelInput, StructuredOutputMethodParams, StructuredOutputMethodOptions, ToolDefinition } from "@langchain/core/language_models/base";
7
7
  import { Runnable } from "@langchain/core/runnables";
8
8
  import { z } from "zod";
9
9
  import { type AnthropicInput } from "../chat_models.js";
@@ -35,17 +35,21 @@ export declare class ChatAnthropicTools extends BaseChatModel<ChatAnthropicTools
35
35
  /** @ignore */
36
36
  _identifyingParams(): Record<string, any>;
37
37
  _streamResponseChunks(messages: BaseMessage[], options: this["ParsedCallOptions"], runManager?: CallbackManagerForLLMRun): AsyncGenerator<ChatGenerationChunk>;
38
- _prepareAndParseToolCall({ messages, options, runManager, systemPromptTemplate, stopSequences, }: {
38
+ _prepareAndParseToolCall({ messages, options, systemPromptTemplate, stopSequences, }: {
39
39
  messages: BaseMessage[];
40
40
  options: ChatAnthropicToolsCallOptions;
41
- runManager?: CallbackManagerForLLMRun;
42
41
  systemPromptTemplate?: BasePromptTemplate;
43
42
  stopSequences: string[];
44
43
  }): Promise<ChatResult>;
45
- _generate(messages: BaseMessage[], options: this["ParsedCallOptions"], _runManager?: CallbackManagerForLLMRun | undefined): Promise<ChatResult>;
44
+ generate(messages: BaseMessageLike[][], parsedOptions?: ChatAnthropicToolsCallOptions, callbacks?: Callbacks): Promise<LLMResult>;
45
+ _generate(_messages: BaseMessage[], _options: this["ParsedCallOptions"], _runManager?: CallbackManagerForLLMRun | undefined): Promise<ChatResult>;
46
46
  _llmType(): string;
47
- withStructuredOutput<RunOutput extends Record<string, any> = Record<string, any>>(outputSchema: StructuredOutputMethodParams<RunOutput, false> | z.ZodType<RunOutput> | Record<string, any>, config?: StructuredOutputMethodOptions<false>): Runnable<BaseLanguageModelInput, RunOutput>;
48
- withStructuredOutput<RunOutput extends Record<string, any> = Record<string, any>>(outputSchema: StructuredOutputMethodParams<RunOutput, true> | z.ZodType<RunOutput> | Record<string, any>, config?: StructuredOutputMethodOptions<true>): Runnable<BaseLanguageModelInput, {
47
+ withStructuredOutput<RunOutput extends Record<string, any> = Record<string, any>>(outputSchema: StructuredOutputMethodParams<RunOutput, false> | z.ZodType<RunOutput> | Record<string, any>, config?: StructuredOutputMethodOptions<false> & {
48
+ force?: boolean;
49
+ }): Runnable<BaseLanguageModelInput, RunOutput>;
50
+ withStructuredOutput<RunOutput extends Record<string, any> = Record<string, any>>(outputSchema: StructuredOutputMethodParams<RunOutput, true> | z.ZodType<RunOutput> | Record<string, any>, config?: StructuredOutputMethodOptions<true> & {
51
+ force?: boolean;
52
+ }): Runnable<BaseLanguageModelInput, {
49
53
  raw: BaseMessage;
50
54
  parsed: RunOutput;
51
55
  }>;
@@ -1,5 +1,5 @@
1
1
  import { XMLParser } from "fast-xml-parser";
2
- import { AIMessage, SystemMessage, } from "@langchain/core/messages";
2
+ import { AIMessage, SystemMessage, coerceMessageLikeToMessage, } from "@langchain/core/messages";
3
3
  import { BaseChatModel, } from "@langchain/core/language_models/chat_models";
4
4
  import { RunnablePassthrough, RunnableSequence, } from "@langchain/core/runnables";
5
5
  import { JsonOutputKeyToolsParser } from "@langchain/core/output_parsers/openai_tools";
@@ -15,6 +15,9 @@ export class ChatAnthropicTools extends BaseChatModel {
15
15
  return "ChatAnthropicTools";
16
16
  }
17
17
  constructor(fields) {
18
+ if (fields?.cache !== undefined) {
19
+ throw new Error("Caching is not supported for this model.");
20
+ }
18
21
  super(fields ?? {});
19
22
  Object.defineProperty(this, "llm", {
20
23
  enumerable: true,
@@ -56,7 +59,7 @@ export class ChatAnthropicTools extends BaseChatModel {
56
59
  async *_streamResponseChunks(messages, options, runManager) {
57
60
  yield* this.llm._streamResponseChunks(messages, options, runManager);
58
61
  }
59
- async _prepareAndParseToolCall({ messages, options, runManager, systemPromptTemplate = DEFAULT_TOOL_SYSTEM_PROMPT, stopSequences, }) {
62
+ async _prepareAndParseToolCall({ messages, options, systemPromptTemplate = DEFAULT_TOOL_SYSTEM_PROMPT, stopSequences, }) {
60
63
  let promptMessages = messages;
61
64
  let forced = false;
62
65
  let toolCall;
@@ -102,8 +105,10 @@ export class ChatAnthropicTools extends BaseChatModel {
102
105
  else if (options.tool_choice !== undefined) {
103
106
  throw new Error(`If "tool_choice" is provided, "tools" must also be.`);
104
107
  }
105
- const chatResult = await this.llm._generate(promptMessages, options, runManager);
106
- const chatGenerationContent = chatResult.generations[0].message.content;
108
+ const chatResult = await this.llm
109
+ .withConfig({ runName: "ChatAnthropicTools" })
110
+ .invoke(promptMessages, options);
111
+ const chatGenerationContent = chatResult.content;
107
112
  if (typeof chatGenerationContent !== "string") {
108
113
  throw new Error("AnthropicFunctions does not support non-string output.");
109
114
  }
@@ -168,15 +173,25 @@ export class ChatAnthropicTools extends BaseChatModel {
168
173
  generations: [{ message: responseMessageWithFunctions, text: "" }],
169
174
  };
170
175
  }
171
- return chatResult;
176
+ return { generations: [{ message: chatResult, text: "" }] };
172
177
  }
173
- async _generate(messages, options, _runManager) {
174
- return this._prepareAndParseToolCall({
175
- messages,
176
- options,
178
+ async generate(messages, parsedOptions, callbacks) {
179
+ const baseMessages = messages.map((messageList) => messageList.map(coerceMessageLikeToMessage));
180
+ // generate results
181
+ const chatResults = await Promise.all(baseMessages.map((messageList) => this._prepareAndParseToolCall({
182
+ messages: messageList,
183
+ options: { callbacks, ...parsedOptions },
177
184
  systemPromptTemplate: this.systemPromptTemplate,
178
185
  stopSequences: this.stopSequences ?? [],
179
- });
186
+ })));
187
+ // create combined output
188
+ const output = {
189
+ generations: chatResults.map((chatResult) => chatResult.generations),
190
+ };
191
+ return output;
192
+ }
193
+ async _generate(_messages, _options, _runManager) {
194
+ throw new Error("Unused.");
180
195
  }
181
196
  _llmType() {
182
197
  return "anthropic_tool_calling";
@@ -187,6 +202,7 @@ export class ChatAnthropicTools extends BaseChatModel {
187
202
  let name;
188
203
  let method;
189
204
  let includeRaw;
205
+ let force;
190
206
  if (isStructuredOutputMethodParams(outputSchema)) {
191
207
  schema = outputSchema.schema;
192
208
  name = outputSchema.name;
@@ -198,11 +214,12 @@ export class ChatAnthropicTools extends BaseChatModel {
198
214
  name = config?.name;
199
215
  method = config?.method;
200
216
  includeRaw = config?.includeRaw;
217
+ force = config?.force ?? false;
201
218
  }
202
219
  if (method === "jsonMode") {
203
220
  throw new Error(`Anthropic only supports "functionCalling" as a method.`);
204
221
  }
205
- const functionName = name ?? "extract";
222
+ let functionName = name ?? "extract";
206
223
  let outputParser;
207
224
  let tools;
208
225
  if (isZodSchema(schema)) {
@@ -224,14 +241,24 @@ export class ChatAnthropicTools extends BaseChatModel {
224
241
  });
225
242
  }
226
243
  else {
244
+ let openAIFunctionDefinition;
245
+ if (typeof schema.name === "string" &&
246
+ typeof schema.parameters === "object" &&
247
+ schema.parameters != null) {
248
+ openAIFunctionDefinition = schema;
249
+ functionName = schema.name;
250
+ }
251
+ else {
252
+ openAIFunctionDefinition = {
253
+ name: functionName,
254
+ description: schema.description ?? "",
255
+ parameters: schema,
256
+ };
257
+ }
227
258
  tools = [
228
259
  {
229
260
  type: "function",
230
- function: {
231
- name: functionName,
232
- description: schema.description,
233
- parameters: schema,
234
- },
261
+ function: openAIFunctionDefinition,
235
262
  },
236
263
  ];
237
264
  outputParser = new JsonOutputKeyToolsParser({
@@ -241,12 +268,14 @@ export class ChatAnthropicTools extends BaseChatModel {
241
268
  }
242
269
  const llm = this.bind({
243
270
  tools,
244
- tool_choice: {
245
- type: "function",
246
- function: {
247
- name: functionName,
248
- },
249
- },
271
+ tool_choice: force
272
+ ? {
273
+ type: "function",
274
+ function: {
275
+ name: functionName,
276
+ },
277
+ }
278
+ : "auto",
250
279
  });
251
280
  if (!includeRaw) {
252
281
  return llm.pipe(outputParser).withConfig({
@@ -18,7 +18,9 @@ You may call them like this:
18
18
  </function_calls>
19
19
 
20
20
  Here are the tools available:
21
- {tools}`);
21
+ {tools}
22
+
23
+ If the schema above contains a property typed as an enum, you must only return values matching an allowed value for that enum.`);
22
24
  function formatAsXMLRepresentation(tool) {
23
25
  const builder = new fast_xml_parser_1.XMLBuilder();
24
26
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -62,9 +64,21 @@ xmlParameters
62
64
  const schemaType = schema.properties[key].type;
63
65
  // Crawl for lists indistinguishable from single items
64
66
  if (schema.properties && schema.properties[key] && schemaType === "array") {
65
- fixedParameters[key] = Array.isArray(xmlParameters[key])
66
- ? xmlParameters[key]
67
- : [xmlParameters[key]];
67
+ const value = xmlParameters[key];
68
+ if (Array.isArray(value)) {
69
+ fixedParameters[key] = value;
70
+ }
71
+ else if (typeof value === "string") {
72
+ if (value.startsWith("[") && value.endsWith("]")) {
73
+ fixedParameters[key] = JSON.parse(value);
74
+ }
75
+ else {
76
+ fixedParameters[key] = value.split(",");
77
+ }
78
+ }
79
+ else {
80
+ fixedParameters[key] = [value];
81
+ }
68
82
  // Crawl for objects like {"item": "my string"} that should really just be "my string"
69
83
  if (schemaType !== "object" &&
70
84
  typeof xmlParameters[key] === "object" &&
@@ -76,7 +90,12 @@ xmlParameters
76
90
  }
77
91
  else if (typeof xmlParameters[key] === "object" &&
78
92
  xmlParameters[key] !== null) {
79
- fixedParameters[key] = fixArrayXMLParameters(schema, xmlParameters[key]);
93
+ fixedParameters[key] = fixArrayXMLParameters({
94
+ ...schema.properties[key],
95
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
96
+ definitions: schema.definitions,
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ }, xmlParameters[key]);
80
99
  }
81
100
  else {
82
101
  fixedParameters[key] = xmlParameters[key];
@@ -1,7 +1,7 @@
1
1
  import { JsonSchema7ObjectType } from "zod-to-json-schema";
2
2
  import { PromptTemplate } from "@langchain/core/prompts";
3
3
  import { ToolDefinition } from "@langchain/core/language_models/base";
4
- export declare const DEFAULT_TOOL_SYSTEM_PROMPT: PromptTemplate<import("@langchain/core/prompts").ParamsFromFString<"In this environment you have access to a set of tools you can use to answer the user's question.\n\nYou may call them like this:\n<function_calls>\n<invoke>\n<tool_name>$TOOL_NAME</tool_name>\n<parameters>\n<$PARAMETER_NAME>$PARAMETER_VALUE</$PARAMETER_NAME>\n...\n</parameters>\n</invoke>\n</function_calls>\n\nHere are the tools available:\n{tools}">, any>;
4
+ export declare const DEFAULT_TOOL_SYSTEM_PROMPT: PromptTemplate<import("@langchain/core/prompts").ParamsFromFString<"In this environment you have access to a set of tools you can use to answer the user's question.\n\nYou may call them like this:\n<function_calls>\n<invoke>\n<tool_name>$TOOL_NAME</tool_name>\n<parameters>\n<$PARAMETER_NAME>$PARAMETER_VALUE</$PARAMETER_NAME>\n...\n</parameters>\n</invoke>\n</function_calls>\n\nHere are the tools available:\n{tools}\n\nIf the schema above contains a property typed as an enum, you must only return values matching an allowed value for that enum.">, any>;
5
5
  export type ToolInvocation = {
6
6
  tool_name: string;
7
7
  parameters: Record<string, unknown>;
@@ -15,7 +15,9 @@ You may call them like this:
15
15
  </function_calls>
16
16
 
17
17
  Here are the tools available:
18
- {tools}`);
18
+ {tools}
19
+
20
+ If the schema above contains a property typed as an enum, you must only return values matching an allowed value for that enum.`);
19
21
  export function formatAsXMLRepresentation(tool) {
20
22
  const builder = new XMLBuilder();
21
23
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -58,9 +60,21 @@ xmlParameters
58
60
  const schemaType = schema.properties[key].type;
59
61
  // Crawl for lists indistinguishable from single items
60
62
  if (schema.properties && schema.properties[key] && schemaType === "array") {
61
- fixedParameters[key] = Array.isArray(xmlParameters[key])
62
- ? xmlParameters[key]
63
- : [xmlParameters[key]];
63
+ const value = xmlParameters[key];
64
+ if (Array.isArray(value)) {
65
+ fixedParameters[key] = value;
66
+ }
67
+ else if (typeof value === "string") {
68
+ if (value.startsWith("[") && value.endsWith("]")) {
69
+ fixedParameters[key] = JSON.parse(value);
70
+ }
71
+ else {
72
+ fixedParameters[key] = value.split(",");
73
+ }
74
+ }
75
+ else {
76
+ fixedParameters[key] = [value];
77
+ }
64
78
  // Crawl for objects like {"item": "my string"} that should really just be "my string"
65
79
  if (schemaType !== "object" &&
66
80
  typeof xmlParameters[key] === "object" &&
@@ -72,7 +86,12 @@ xmlParameters
72
86
  }
73
87
  else if (typeof xmlParameters[key] === "object" &&
74
88
  xmlParameters[key] !== null) {
75
- fixedParameters[key] = fixArrayXMLParameters(schema, xmlParameters[key]);
89
+ fixedParameters[key] = fixArrayXMLParameters({
90
+ ...schema.properties[key],
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ definitions: schema.definitions,
93
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
+ }, xmlParameters[key]);
76
95
  }
77
96
  else {
78
97
  fixedParameters[key] = xmlParameters[key];
@@ -95,7 +95,7 @@ test("Test ChatAnthropic in streaming mode", async () => {
95
95
  }),
96
96
  });
97
97
  const message = new HumanMessage("Hello!");
98
- const res = await model.call([message]);
98
+ const res = await model.invoke([message]);
99
99
  console.log({ res });
100
100
  expect(nrNewTokens > 0).toBe(true);
101
101
  expect(res.content).toBe(streamedCompletion);
@@ -117,7 +117,7 @@ test("Test ChatAnthropic in streaming mode with a signal", async () => {
117
117
  const controller = new AbortController();
118
118
  const message = new HumanMessage("Hello! Give me an extremely verbose response");
119
119
  await expect(() => {
120
- const res = model.call([message], {
120
+ const res = model.invoke([message], {
121
121
  signal: controller.signal,
122
122
  });
123
123
  setTimeout(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/anthropic",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Anthropic integrations for LangChain.js",
5
5
  "type": "module",
6
6
  "engines": {