Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

all[minor]: Add support for openai tools in all tool calling models #5737

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/standard-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jobs:
package: [anthropic, cohere, google-genai, groq, mistralai]
steps:
- uses: actions/checkout@v4
if: contains(needs.get-changed-files.outputs.changed_files, 'langchain-core/') || contains(needs.get-changed-files.outputs.changed_files, 'libs/langchain-${{ matrix.package }}/')
bracesproul marked this conversation as resolved.
Show resolved Hide resolved
- name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
Expand Down
4 changes: 4 additions & 0 deletions langchain-core/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,10 @@ utils/types.cjs
utils/types.js
utils/types.d.ts
utils/types.d.cts
utils/is_openai_tool.cjs
utils/is_openai_tool.js
utils/is_openai_tool.d.ts
utils/is_openai_tool.d.cts
vectorstores.cjs
vectorstores.js
vectorstores.d.ts
Expand Down
1 change: 1 addition & 0 deletions langchain-core/langchain.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const config = {
"utils/testing": "utils/testing/index",
"utils/tiktoken": "utils/tiktoken",
"utils/types": "utils/types/index",
"utils/is_openai_tool": "utils/is_openai_tool",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put this in an existing entrypoint? types maybe?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Want to avoid fragmentation

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to @langchain/core/language_models/base so it's exported from the same place as ToolDefinition

vectorstores: "vectorstores",
},
tsConfigPath: resolve("./tsconfig.json"),
Expand Down
13 changes: 13 additions & 0 deletions langchain-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,15 @@
"import": "./utils/types.js",
"require": "./utils/types.cjs"
},
"./utils/is_openai_tool": {
"types": {
"import": "./utils/is_openai_tool.d.ts",
"require": "./utils/is_openai_tool.d.cts",
"default": "./utils/is_openai_tool.d.ts"
},
"import": "./utils/is_openai_tool.js",
"require": "./utils/is_openai_tool.cjs"
},
"./vectorstores": {
"types": {
"import": "./vectorstores.d.ts",
Expand Down Expand Up @@ -810,6 +819,10 @@
"utils/types.js",
"utils/types.d.ts",
"utils/types.d.cts",
"utils/is_openai_tool.cjs",
"utils/is_openai_tool.js",
"utils/is_openai_tool.d.ts",
"utils/is_openai_tool.d.cts",
"vectorstores.cjs",
"vectorstores.js",
"vectorstores.d.ts",
Expand Down
17 changes: 17 additions & 0 deletions langchain-core/src/utils/is_openai_tool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ToolDefinition } from "../language_models/base.js";
bracesproul marked this conversation as resolved.
Show resolved Hide resolved

export function isOpenAITool(tool: unknown): tool is ToolDefinition {
if (typeof tool !== "object" || !tool) return false;
if (
"type" in tool &&
tool.type === "function" &&
"function" in tool &&
typeof tool.function === "object" &&
tool.function &&
"name" in tool.function &&
"parameters" in tool.function
) {
return true;
}
return false;
}
2 changes: 1 addition & 1 deletion libs/langchain-anthropic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"author": "LangChain",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there! I noticed the dependency change for "@anthropic-ai/sdk" from "^0.21.0" to "^0.22.0" in the package.json file. This comment is to flag the change for maintainers to review. Great work on the PR!

"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": "^0.21.0",
"@anthropic-ai/sdk": "^0.22.0",
"@langchain/core": ">=0.2.5 <0.3.0",
"fast-xml-parser": "^4.3.5",
"zod": "^3.22.4",
Expand Down
65 changes: 41 additions & 24 deletions libs/langchain-anthropic/src/chat_models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ import {
type BaseChatModelParams,
} from "@langchain/core/language_models/chat_models";
import {
StructuredOutputMethodOptions,
type StructuredOutputMethodOptions,
type BaseLanguageModelCallOptions,
BaseLanguageModelInput,
type BaseLanguageModelInput,
type ToolDefinition,
} from "@langchain/core/language_models/base";
import { StructuredToolInterface } from "@langchain/core/tools";
import { zodToJsonSchema } from "zod-to-json-schema";
Expand All @@ -40,21 +41,17 @@ import {
import { isZodSchema } from "@langchain/core/utils/types";
import { ToolCall } from "@langchain/core/messages/tool";
import { z } from "zod";
import { isOpenAITool } from "@langchain/core/utils/is_openai_tool";
import type {
MessageCreateParams,
Tool as AnthropicTool,
} from "@anthropic-ai/sdk/resources/index.mjs";
import {
AnthropicToolsOutputParser,
extractToolCalls,
} from "./output_parsers.js";
import { AnthropicToolResponse } from "./types.js";

type AnthropicTool = {
name: string;
description: string;
/**
* JSON schema.
*/
input_schema: Record<string, unknown>;
};

type AnthropicMessage = Anthropic.MessageParam;
type AnthropicMessageCreateParams = Anthropic.MessageCreateParamsNonStreaming;
type AnthropicStreamingMessageCreateParams =
Expand All @@ -71,7 +68,12 @@ type AnthropicToolChoice =
export interface ChatAnthropicCallOptions
extends BaseLanguageModelCallOptions,
Pick<AnthropicInput, "streamUsage"> {
tools?: (StructuredToolInterface | AnthropicTool)[];
tools?: (
| StructuredToolInterface
| AnthropicTool
| Record<string, unknown>
| ToolDefinition
)[];
/**
* Whether or not to specify what tool the model should use
* @default "auto"
Expand Down Expand Up @@ -562,22 +564,35 @@ export class ChatAnthropicMessages<
return tools as AnthropicTool[];
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((tools as any[]).every((tool) => isOpenAITool(tool))) {
// Formatted as OpenAI tool, convert to Anthropic tool
return (tools as ToolDefinition[]).map((tc) => ({
name: tc.function.name,
description: tc.function.description,
input_schema: tc.function.parameters as AnthropicTool.InputSchema,
}));
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((tools as any[]).some((tool) => isAnthropicTool(tool))) {
throw new Error(
`Can not pass in a mix of AnthropicTools and StructuredTools`
);
throw new Error(`Can not pass in a mix of tool schemas to ChatAnthropic`);
}

return (tools as StructuredToolInterface[]).map((tool) => ({
name: tool.name,
description: tool.description,
input_schema: zodToJsonSchema(tool.schema),
input_schema: zodToJsonSchema(tool.schema) as AnthropicTool.InputSchema,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why necessary now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're technically using a different type since we import from the SDK now. Anthropic enforces type: string in their json schemas which are always there, but they aren't typed as explicitly including it.

}));
}

override bindTools(
tools: (AnthropicTool | StructuredToolInterface)[],
tools: (
| AnthropicTool
| Record<string, unknown>
| StructuredToolInterface
| ToolDefinition
)[],
kwargs?: Partial<CallOptions>
): Runnable<BaseLanguageModelInput, AIMessageChunk, CallOptions> {
return this.bind({
Expand All @@ -597,10 +612,9 @@ export class ChatAnthropicMessages<
> &
Kwargs {
let tool_choice:
| {
type: string;
name?: string;
}
| MessageCreateParams.ToolChoiceAuto
| MessageCreateParams.ToolChoiceAny
| MessageCreateParams.ToolChoiceTool
| undefined;
if (options?.tool_choice) {
if (options?.tool_choice === "any") {
Expand Down Expand Up @@ -739,7 +753,10 @@ export class ChatAnthropicMessages<
if (data?.usage !== undefined) {
usageData.output_tokens += data.usage.output_tokens;
}
} else if (data.type === "content_block_delta") {
} else if (
data.type === "content_block_delta" &&
data.delta.type === "text_delta"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why necessary now? We don't support streaming tool calls yet.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SDK types bump req it to access text below

) {
const content = data.delta?.text;
if (content !== undefined) {
yield new ChatGenerationChunk({
Expand Down Expand Up @@ -976,7 +993,7 @@ export class ChatAnthropicMessages<
name: functionName,
description:
jsonSchema.description ?? "A function available to call.",
input_schema: jsonSchema,
input_schema: jsonSchema as AnthropicTool.InputSchema,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer as few changes as possible

},
];
outputParser = new AnthropicToolsOutputParser({
Expand All @@ -998,7 +1015,7 @@ export class ChatAnthropicMessages<
anthropicTools = {
name: functionName,
description: schema.description ?? "",
input_schema: schema,
input_schema: schema as AnthropicTool.InputSchema,
};
}
tools = [anthropicTools];
Expand Down
22 changes: 22 additions & 0 deletions libs/langchain-anthropic/src/tests/chat_models-tools.int.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,25 @@ test("Can pass tool_choice", async () => {
expect(input).toBeTruthy();
expect(input.location).toBeTruthy();
});

test("bindTools accepts openai formatted tool", async () => {
const openaiTool = {
type: "function",
function: {
name: "get_weather",
description:
"Get the weather of a specific location and return the temperature in Celsius.",
parameters: zodToJsonSchema(zodSchema),
},
};
const modelWithTools = model.bindTools([openaiTool]);
const response = await modelWithTools.invoke(
"Whats the weather like in san francisco?"
);
expect(response.tool_calls).toHaveLength(1);
const { tool_calls } = response;
if (!tool_calls) {
return;
}
expect(tool_calls[0].name).toBe("get_weather");
});
Loading
Loading