Skip to content

Commit

Permalink
Added oai tool support to anthropic
Browse files Browse the repository at this point in the history
  • Loading branch information
bracesproul committed Jun 12, 2024
1 parent f0f6dc8 commit f9e7cd0
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 30 deletions.
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",
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";

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",
"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 @@ -45,15 +46,11 @@ import {
extractToolCalls,
} from "./output_parsers.js";
import { AnthropicToolResponse } from "./types.js";

type AnthropicTool = {
name: string;
description: string;
/**
* JSON schema.
*/
input_schema: Record<string, unknown>;
};
import { isOpenAITool } from "@langchain/core/utils/is_openai_tool";
import type {
MessageCreateParams,
Tool as AnthropicTool,
} from "@anthropic-ai/sdk/resources/index.mjs";

type AnthropicMessage = Anthropic.MessageParam;
type AnthropicMessageCreateParams = Anthropic.MessageCreateParamsNonStreaming;
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,
}));
}

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"
) {
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,
},
];
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");
});
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,9 @@ __metadata:
languageName: node
linkType: hard

"@anthropic-ai/sdk@npm:^0.21.0":
version: 0.21.0
resolution: "@anthropic-ai/sdk@npm:0.21.0"
"@anthropic-ai/sdk@npm:^0.22.0":
version: 0.22.0
resolution: "@anthropic-ai/sdk@npm:0.22.0"
dependencies:
"@types/node": ^18.11.18
"@types/node-fetch": ^2.6.4
Expand All @@ -223,7 +223,7 @@ __metadata:
formdata-node: ^4.3.2
node-fetch: ^2.6.7
web-streams-polyfill: ^3.2.1
checksum: fbed720938487495f1d28822fa6eb3871cf7e7be325c299b69efa78e72e1e0b66d9f564003ae5d7a1e96c7555cc69c817be4b901d1847ae002f782546a4c987d
checksum: f09fc6ea1f5f68483fd2dbdc1ab78a0914d7a3f39fd4a921f5a04b8959ae82aa210372fa2dbff2ee78143d37fb408e7b7d61e0023e7ea31d5fa1f4873f371af8
languageName: node
linkType: hard

Expand Down Expand Up @@ -8926,7 +8926,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@langchain/anthropic@workspace:libs/langchain-anthropic"
dependencies:
"@anthropic-ai/sdk": ^0.21.0
"@anthropic-ai/sdk": ^0.22.0
"@jest/globals": ^29.5.0
"@langchain/community": "workspace:*"
"@langchain/core": ">=0.2.5 <0.3.0"
Expand Down

0 comments on commit f9e7cd0

Please sign in to comment.