From 5c7629aeef7be85167ee8f99edcef16361b873e2 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Wed, 10 Jul 2024 14:52:38 -0700 Subject: [PATCH 01/18] init --- langchain-core/src/runnables/tool.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 langchain-core/src/runnables/tool.ts diff --git a/langchain-core/src/runnables/tool.ts b/langchain-core/src/runnables/tool.ts new file mode 100644 index 000000000000..b1a990019f7f --- /dev/null +++ b/langchain-core/src/runnables/tool.ts @@ -0,0 +1 @@ +// todo implement \ No newline at end of file From af5b8ad18b3fed04e94790cabe4fa26da0b9919e Mon Sep 17 00:00:00 2001 From: bracesproul Date: Wed, 10 Jul 2024 15:31:09 -0700 Subject: [PATCH 02/18] RunnableToolLike --- langchain-core/src/runnables/tool.ts | 35 +++++++++++++++++++++++++++- langchain-core/src/tools.ts | 4 +--- langchain-core/src/types/zod.ts | 4 ++++ 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 langchain-core/src/types/zod.ts diff --git a/langchain-core/src/runnables/tool.ts b/langchain-core/src/runnables/tool.ts index b1a990019f7f..eec6be2a61a6 100644 --- a/langchain-core/src/runnables/tool.ts +++ b/langchain-core/src/runnables/tool.ts @@ -1 +1,34 @@ -// todo implement \ No newline at end of file +import { ZodAny } from "../types/zod.js"; +import { RunnableLambda } from "./base.js"; +import { RunnableConfig } from "./config.js"; + +export interface RunnableToolLikeFields { + name?: string; + + description?: string; + + schema: RunInput; + + func: + | ((input: RunInput, config?: RunnableConfig) => RunOutput) + | ((input: RunInput, config?: RunnableConfig) => Promise); +} + +export class RunnableToolLike< + RunInput extends ZodAny, + RunOutput = string +> extends RunnableLambda { + description?: string; + + schema: RunInput; + + constructor(fields: RunnableToolLikeFields) { + super({ + func: fields.func, + }); + + this.name = fields.name; + this.description = fields.description; + this.schema = fields.schema; + } +} diff --git a/langchain-core/src/tools.ts b/langchain-core/src/tools.ts index 252571ca084a..5adc4d27b4e4 100644 --- a/langchain-core/src/tools.ts +++ b/langchain-core/src/tools.ts @@ -11,9 +11,7 @@ import { } from "./language_models/base.js"; import { ensureConfig, type RunnableConfig } from "./runnables/config.js"; import type { RunnableFunc, RunnableInterface } from "./runnables/base.js"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type ZodAny = z.ZodObject; +import { ZodAny } from "./types/zod.js"; /** * Parameters for the Tool classes. diff --git a/langchain-core/src/types/zod.ts b/langchain-core/src/types/zod.ts new file mode 100644 index 000000000000..d864170ddafa --- /dev/null +++ b/langchain-core/src/types/zod.ts @@ -0,0 +1,4 @@ +import type { z } from "zod"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type ZodAny = z.ZodObject; From 27fe0ffae1f4b32f89c1affdf92fbcb371d2aa1a Mon Sep 17 00:00:00 2001 From: bracesproul Date: Wed, 10 Jul 2024 15:45:57 -0700 Subject: [PATCH 03/18] implemented without generics --- langchain-core/src/runnables/base.ts | 80 ++++++++++++++++++++++++++++ langchain-core/src/runnables/tool.ts | 34 ------------ 2 files changed, 80 insertions(+), 34 deletions(-) delete mode 100644 langchain-core/src/runnables/tool.ts diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index 446ce87839dd..2d371e3746d5 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -6,6 +6,7 @@ import { type TraceableFunction, isTraceableFunction, } from "langsmith/singletons/traceable"; +import { zodToJsonSchema } from "zod-to-json-schema"; import type { RunnableInterface, RunnableBatchOptions } from "./types.js"; import { CallbackManagerForChainRun } from "../callbacks/manager.js"; import { @@ -1078,6 +1079,14 @@ export abstract class Runnable< ], }); } + + asTool(fields: { + name?: string; + description?: string; + schema: z.ZodAny; + }): RunnableToolLike { + return convertRunnableToTool(this, fields); + } } export type RunnableBindingArgs< @@ -2783,3 +2792,74 @@ export class RunnablePick< return IterableReadableStream.fromAsyncGenerator(wrappedGenerator); } } + +export interface RunnableToolLikeFields { + name?: string; + + description?: string; + + schema: RunInput; + + func: + | ((input: RunInput, config?: RunnableConfig) => RunOutput) + | ((input: RunInput, config?: RunnableConfig) => Promise); +} + +export class RunnableToolLike< + RunInput extends z.ZodType = z.ZodType, + RunOutput = string +> extends RunnableLambda { + description?: string; + + schema: RunInput; + + constructor(fields: RunnableToolLikeFields) { + super({ + func: fields.func, + }); + + this.name = fields.name; + this.description = fields.description; + this.schema = fields.schema; + } +} + +/** + * Generate a placeholder description of a runnable + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const _getDescriptionFromRunnable = (schema: Record): string => { + return `Takes ${JSON.stringify(schema, null, 2)}`; +}; + +/** + * Given a runnable and a Zod schema, convert the runnable to a tool. + * + * @template RunInput The input schema for the runnable. + * @param {Runnable} runnable The runnable to convert to a tool. + * @param fields + * @param {string | undefined} [fields.name] The name of the tool. If not provided, it will default to the name of the runnable. + * @param {string | undefined} [fields.description] The description of the tool. If not provided, it will default to `Takes {schema}` where `schema` is a JSON string representation of the input schema. + * @param {z.ZodType | z.ZodString} [fields.schema] The Zod schema for the input of the tool. Either the schema itself or a ZodString. + * @returns {DynamicTool | DynamicStructuredTool>} The tool created from the runnable. DynamicTool if the schema is a ZodString, DynamicStructuredTool if the schema is a ZodType. + */ +export function convertRunnableToTool( + runnable: Runnable, + fields: { + name?: string; + description?: string; + schema: z.ZodAny; + } +): RunnableToolLike { + const description = + fields.description ?? + _getDescriptionFromRunnable(zodToJsonSchema(fields.schema)); + const name = fields.name ?? runnable.getName(); + + return new RunnableToolLike({ + name, + description, + schema: fields.schema, + func: runnable.invoke, + }); +} diff --git a/langchain-core/src/runnables/tool.ts b/langchain-core/src/runnables/tool.ts deleted file mode 100644 index eec6be2a61a6..000000000000 --- a/langchain-core/src/runnables/tool.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ZodAny } from "../types/zod.js"; -import { RunnableLambda } from "./base.js"; -import { RunnableConfig } from "./config.js"; - -export interface RunnableToolLikeFields { - name?: string; - - description?: string; - - schema: RunInput; - - func: - | ((input: RunInput, config?: RunnableConfig) => RunOutput) - | ((input: RunInput, config?: RunnableConfig) => Promise); -} - -export class RunnableToolLike< - RunInput extends ZodAny, - RunOutput = string -> extends RunnableLambda { - description?: string; - - schema: RunInput; - - constructor(fields: RunnableToolLikeFields) { - super({ - func: fields.func, - }); - - this.name = fields.name; - this.description = fields.description; - this.schema = fields.schema; - } -} From 63ada8e958ea1ee4f600eb470eec23db1686849d Mon Sep 17 00:00:00 2001 From: bracesproul Date: Wed, 10 Jul 2024 15:57:13 -0700 Subject: [PATCH 04/18] figured out generics typing almost --- langchain-core/src/runnables/base.ts | 24 +++--- .../runnables/tests/runnable_tools.test.ts | 73 +++++++++++++++++++ 2 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 langchain-core/src/runnables/tests/runnable_tools.test.ts diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index 2d371e3746d5..4843ec0dbdce 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -1080,12 +1080,12 @@ export abstract class Runnable< }); } - asTool(fields: { + asTool(fields: { name?: string; description?: string; - schema: z.ZodAny; - }): RunnableToolLike { - return convertRunnableToTool(this, fields); + schema: z.ZodType; + }): RunnableToolLike, string> { + return convertRunnableToTool(this, fields); } } @@ -2808,7 +2808,7 @@ export interface RunnableToolLikeFields { export class RunnableToolLike< RunInput extends z.ZodType = z.ZodType, RunOutput = string -> extends RunnableLambda { +> extends RunnableLambda, RunOutput> { description?: string; schema: RunInput; @@ -2835,22 +2835,22 @@ const _getDescriptionFromRunnable = (schema: Record): string => { /** * Given a runnable and a Zod schema, convert the runnable to a tool. * - * @template RunInput The input schema for the runnable. - * @param {Runnable} runnable The runnable to convert to a tool. + * @template RunInput The input type for the runnable. + * @param {Runnable} runnable The runnable to convert to a tool. * @param fields * @param {string | undefined} [fields.name] The name of the tool. If not provided, it will default to the name of the runnable. * @param {string | undefined} [fields.description] The description of the tool. If not provided, it will default to `Takes {schema}` where `schema` is a JSON string representation of the input schema. - * @param {z.ZodType | z.ZodString} [fields.schema] The Zod schema for the input of the tool. Either the schema itself or a ZodString. - * @returns {DynamicTool | DynamicStructuredTool>} The tool created from the runnable. DynamicTool if the schema is a ZodString, DynamicStructuredTool if the schema is a ZodType. + * @param {z.ZodType} [fields.schema] The Zod schema for the input of the tool. + * @returns {RunnableToolLike, string>} An instance of `RunnableToolLike` which is a runnable that can be used as a tool. */ -export function convertRunnableToTool( +export function convertRunnableToTool( runnable: Runnable, fields: { name?: string; description?: string; - schema: z.ZodAny; + schema: z.ZodType; } -): RunnableToolLike { +): RunnableToolLike, string> { const description = fields.description ?? _getDescriptionFromRunnable(zodToJsonSchema(fields.schema)); diff --git a/langchain-core/src/runnables/tests/runnable_tools.test.ts b/langchain-core/src/runnables/tests/runnable_tools.test.ts new file mode 100644 index 000000000000..30d188cb2ea2 --- /dev/null +++ b/langchain-core/src/runnables/tests/runnable_tools.test.ts @@ -0,0 +1,73 @@ +import { z } from "zod"; +import { zodToJsonSchema } from "zod-to-json-schema"; +import { RunnableLambda, RunnableToolLike } from "../base.js"; + +test("Runnable asTool works", async () => { + const schema = z.object({ + foo: z.string(), + }); + const runnable = RunnableLambda.from, string>( + (input, config) => { + return `${input.foo}${config?.configurable.foo}`; + } + ); + const tool = runnable.asTool({ + schema, + }); + + expect(tool).toBeInstanceOf(RunnableToolLike); + expect(tool.schema).toBe(schema); + expect(tool.description).toBe( + `Takes ${JSON.stringify(zodToJsonSchema(schema), null, 2)}` + ); + expect(tool.name).toBe(runnable.getName()); +}); + +test("Runnable asTool works with all populated fields", async () => { + const schema = z.object({ + foo: z.string(), + }); + const runnable = RunnableLambda.from, string>( + (input, config) => { + return `${input.foo}${config?.configurable.foo}`; + } + ); + const tool = runnable.asTool({ + schema, + name: "test", + description: "test", + }); + + expect(tool).toBeInstanceOf(RunnableToolLike); + expect(tool.schema).toBe(schema); + expect(tool.description).toBe("test"); + expect(tool.name).toBe("test"); +}); + +test("Runnable asTool can invoke", async () => { + const schema = z.object({ + foo: z.string(), + }); + const runnable = RunnableLambda.from, string>( + (input, config) => { + console.log("I am invoked."); + return `${input.foo}${config?.configurable.foo}`; + } + ); + const tool = runnable.asTool({ + schema, + }); + + const toolResponse = await tool.invoke( + { + foo: "bar", + }, + { + configurable: { + foo: "bar", + }, + } + ); + + expect(toolResponse).toBe("barbar"); +}); From 3946489ba5114227fe71161771d03f0d4cd49f55 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Wed, 10 Jul 2024 16:02:01 -0700 Subject: [PATCH 05/18] use runnable binding instead of lambda --- langchain-core/src/runnables/base.ts | 13 +++++++------ .../src/runnables/tests/runnable_tools.test.ts | 1 - 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index 4843ec0dbdce..e755f4d31d6b 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -2800,22 +2800,23 @@ export interface RunnableToolLikeFields { schema: RunInput; - func: - | ((input: RunInput, config?: RunnableConfig) => RunOutput) - | ((input: RunInput, config?: RunnableConfig) => Promise); + runnable: Runnable; + + config?: RunnableConfig; } export class RunnableToolLike< RunInput extends z.ZodType = z.ZodType, RunOutput = string -> extends RunnableLambda, RunOutput> { +> extends RunnableBinding, RunOutput> { description?: string; schema: RunInput; constructor(fields: RunnableToolLikeFields) { super({ - func: fields.func, + bound: fields.runnable, + config: fields.config ?? {}, }); this.name = fields.name; @@ -2860,6 +2861,6 @@ export function convertRunnableToTool( name, description, schema: fields.schema, - func: runnable.invoke, + runnable, }); } diff --git a/langchain-core/src/runnables/tests/runnable_tools.test.ts b/langchain-core/src/runnables/tests/runnable_tools.test.ts index 30d188cb2ea2..49a60eb2d5cf 100644 --- a/langchain-core/src/runnables/tests/runnable_tools.test.ts +++ b/langchain-core/src/runnables/tests/runnable_tools.test.ts @@ -50,7 +50,6 @@ test("Runnable asTool can invoke", async () => { }); const runnable = RunnableLambda.from, string>( (input, config) => { - console.log("I am invoked."); return `${input.foo}${config?.configurable.foo}`; } ); From bdbac9551e690ab3fdfeb7c4bd496fd37d0ada6b Mon Sep 17 00:00:00 2001 From: bracesproul Date: Wed, 10 Jul 2024 16:35:36 -0700 Subject: [PATCH 06/18] use RunInput generic in a way... --- langchain-core/src/runnables/base.ts | 38 +++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index e755f4d31d6b..3333ce3be365 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -1080,12 +1080,12 @@ export abstract class Runnable< }); } - asTool(fields: { + asTool(fields: { name?: string; description?: string; schema: z.ZodType; - }): RunnableToolLike, string> { - return convertRunnableToTool(this, fields); + }): RunnableToolLike, RunOutput> { + return convertRunnableToTool(this, fields); } } @@ -2793,21 +2793,22 @@ export class RunnablePick< } } -export interface RunnableToolLikeFields { +export interface RunnableToolLikeFields< + RunInput extends z.ZodType = z.ZodType, + RunOutput = unknown +> extends Omit, RunOutput>, "config"> { name?: string; description?: string; schema: RunInput; - runnable: Runnable; - config?: RunnableConfig; } export class RunnableToolLike< RunInput extends z.ZodType = z.ZodType, - RunOutput = string + RunOutput = unknown > extends RunnableBinding, RunOutput> { description?: string; @@ -2815,7 +2816,7 @@ export class RunnableToolLike< constructor(fields: RunnableToolLikeFields) { super({ - bound: fields.runnable, + bound: fields.bound, config: fields.config ?? {}, }); @@ -2828,8 +2829,9 @@ export class RunnableToolLike< /** * Generate a placeholder description of a runnable */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const _getDescriptionFromRunnable = (schema: Record): string => { +const _getDescriptionFromRunnable = ( + schema: RunInput +): string => { return `Takes ${JSON.stringify(schema, null, 2)}`; }; @@ -2837,30 +2839,32 @@ const _getDescriptionFromRunnable = (schema: Record): string => { * Given a runnable and a Zod schema, convert the runnable to a tool. * * @template RunInput The input type for the runnable. - * @param {Runnable} runnable The runnable to convert to a tool. + * @template RunOutput The output type for the runnable. + * + * @param {Runnable} runnable The runnable to convert to a tool. * @param fields * @param {string | undefined} [fields.name] The name of the tool. If not provided, it will default to the name of the runnable. * @param {string | undefined} [fields.description] The description of the tool. If not provided, it will default to `Takes {schema}` where `schema` is a JSON string representation of the input schema. * @param {z.ZodType} [fields.schema] The Zod schema for the input of the tool. - * @returns {RunnableToolLike, string>} An instance of `RunnableToolLike` which is a runnable that can be used as a tool. + * @returns {RunnableToolLike, RunOutput>} An instance of `RunnableToolLike` which is a runnable that can be used as a tool. */ -export function convertRunnableToTool( - runnable: Runnable, +export function convertRunnableToTool( + runnable: Runnable, fields: { name?: string; description?: string; schema: z.ZodType; } -): RunnableToolLike, string> { +): RunnableToolLike, RunOutput> { const description = fields.description ?? _getDescriptionFromRunnable(zodToJsonSchema(fields.schema)); const name = fields.name ?? runnable.getName(); - return new RunnableToolLike({ + return new RunnableToolLike, RunOutput>({ name, description, schema: fields.schema, - runnable, + bound: runnable, }); } From 60c05682219308e352ed2293bd3c186b5327dec9 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Wed, 10 Jul 2024 16:58:10 -0700 Subject: [PATCH 07/18] cr --- langchain-core/package.json | 1 + langchain-core/src/runnables/base.ts | 14 +++++++++++++- .../src/runnables/tests/runnable_tools.test.ts | 17 +++++++++++++++++ yarn.lock | 1 + 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/langchain-core/package.json b/langchain-core/package.json index ed1fce530149..efbb5bf2bfb8 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -74,6 +74,7 @@ "prettier": "^2.8.3", "release-it": "^15.10.1", "rimraf": "^5.0.1", + "ts-jest": "^29.1.0", "typescript": "~5.1.6", "web-streams-polyfill": "^3.3.3" }, diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index 3333ce3be365..dd9808b6eb92 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -1080,6 +1080,18 @@ export abstract class Runnable< }); } + /** + * Convert a runnable to a tool. Return a new instance of `RunnableToolLike` + * which contains the runnable, name, description and schema. + * + * @template {T extends RunInput = RunInput} RunInput - The input type of the runnable. Should be the same as the `RunInput` type of the runnable. + * + * @param fields + * @param {string | undefined} [fields.name] The name of the tool. If not provided, it will default to the name of the runnable. + * @param {string | undefined} [fields.description] The description of the tool. If not provided, it will default to `Takes {schema}` where `schema` is a JSON string representation of the input schema. + * @param {z.ZodType} [fields.schema] The Zod schema for the input of the tool. Infers the Zod type from the input type of the runnable. + * @returns {RunnableToolLike, RunOutput>} An instance of `RunnableToolLike` which is a runnable that can be used as a tool. + */ asTool(fields: { name?: string; description?: string; @@ -2845,7 +2857,7 @@ const _getDescriptionFromRunnable = ( * @param fields * @param {string | undefined} [fields.name] The name of the tool. If not provided, it will default to the name of the runnable. * @param {string | undefined} [fields.description] The description of the tool. If not provided, it will default to `Takes {schema}` where `schema` is a JSON string representation of the input schema. - * @param {z.ZodType} [fields.schema] The Zod schema for the input of the tool. + * @param {z.ZodType} [fields.schema] The Zod schema for the input of the tool. Infers the Zod type from the input type of the runnable. * @returns {RunnableToolLike, RunOutput>} An instance of `RunnableToolLike` which is a runnable that can be used as a tool. */ export function convertRunnableToTool( diff --git a/langchain-core/src/runnables/tests/runnable_tools.test.ts b/langchain-core/src/runnables/tests/runnable_tools.test.ts index 49a60eb2d5cf..9692a039b2a9 100644 --- a/langchain-core/src/runnables/tests/runnable_tools.test.ts +++ b/langchain-core/src/runnables/tests/runnable_tools.test.ts @@ -70,3 +70,20 @@ test("Runnable asTool can invoke", async () => { expect(toolResponse).toBe("barbar"); }); + +test("asTool should type error with mismatched schema", async () => { + // asTool infers the type of the Zod schema from the existing runnable's RunInput generic. + // If the Zod schema does not match the RunInput, it should throw a type error. + const schema = z.object({ + foo: z.string(), + }); + const runnable = RunnableLambda.from<{ bar: string }, string>( + (input, config) => { + return `${input.bar}${config?.configurable.foo}`; + } + ); + runnable.asTool({ + // @ts-expect-error - Should error. If this does not give a type error, the generics/typing of `asTool` is broken. + schema, + }); +}); diff --git a/yarn.lock b/yarn.lock index fed5231ea68f..fd8f162bba33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11032,6 +11032,7 @@ __metadata: prettier: ^2.8.3 release-it: ^15.10.1 rimraf: ^5.0.1 + ts-jest: ^29.1.0 typescript: ~5.1.6 uuid: ^10.0.0 web-streams-polyfill: ^3.3.3 From b621e2ab61a623f1a855af0fe09ad65d47a4259f Mon Sep 17 00:00:00 2001 From: bracesproul Date: Wed, 10 Jul 2024 17:00:48 -0700 Subject: [PATCH 08/18] more test --- langchain-core/src/runnables/base.ts | 4 ++-- .../src/runnables/tests/runnable_tools.test.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index dd9808b6eb92..9ce3027562c2 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -2805,7 +2805,7 @@ export class RunnablePick< } } -export interface RunnableToolLikeFields< +export interface RunnableToolLikeArgs< RunInput extends z.ZodType = z.ZodType, RunOutput = unknown > extends Omit, RunOutput>, "config"> { @@ -2826,7 +2826,7 @@ export class RunnableToolLike< schema: RunInput; - constructor(fields: RunnableToolLikeFields) { + constructor(fields: RunnableToolLikeArgs) { super({ bound: fields.bound, config: fields.config ?? {}, diff --git a/langchain-core/src/runnables/tests/runnable_tools.test.ts b/langchain-core/src/runnables/tests/runnable_tools.test.ts index 9692a039b2a9..77187eb7cae6 100644 --- a/langchain-core/src/runnables/tests/runnable_tools.test.ts +++ b/langchain-core/src/runnables/tests/runnable_tools.test.ts @@ -87,3 +87,21 @@ test("asTool should type error with mismatched schema", async () => { schema, }); }); + +test("Create a runnable tool directly from RunnableToolLike", async () => { + const schema = z.object({ + foo: z.string(), + }); + const adderFunc = (_: z.infer): Promise => { + return Promise.resolve(true); + }; + const tool = new RunnableToolLike({ + schema, + name: "test", + description: "test", + bound: RunnableLambda.from(adderFunc), + }); + + const result = await tool.invoke({ foo: "bar" }); + expect(result).toBe(true); +}); From 467537f2aa3b43227eab068e8ddb3cc6585088f5 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Wed, 10 Jul 2024 17:37:02 -0700 Subject: [PATCH 09/18] more tests and start docs --- .../how_to/convert_runnable_to_tool.ipynb | 476 ++++++++++++++++++ docs/core_docs/docs/how_to/index.mdx | 1 + docs/core_docs/package.json | 2 +- .../runnables/tests/runnable_tools.test.ts | 17 + yarn.lock | 19 +- 5 files changed, 497 insertions(+), 18 deletions(-) create mode 100644 docs/core_docs/docs/how_to/convert_runnable_to_tool.ipynb diff --git a/docs/core_docs/docs/how_to/convert_runnable_to_tool.ipynb b/docs/core_docs/docs/how_to/convert_runnable_to_tool.ipynb new file mode 100644 index 000000000000..4a6cb6a0ce9e --- /dev/null +++ b/docs/core_docs/docs/how_to/convert_runnable_to_tool.ipynb @@ -0,0 +1,476 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9a8bceb3-95bd-4496-bb9e-57655136e070", + "metadata": {}, + "source": [ + "# How to use Runnables as Tools\n", + "\n", + "```{=mdx}\n", + ":::info Prerequisites\n", + "\n", + "This guide assumes familiarity with the following concepts:\n", + "\n", + "- [Runnables](/docs/concepts#runnable-interface)\n", + "- [Tools](/docs/concepts#tools)\n", + "- [Agents](/docs/tutorials/agents)\n", + "\n", + ":::\n", + "\n", + "```\n", + "\n", + "Here we will demonstrate how to convert a LangChain `Runnable` into a tool that can be used by agents, chains, or chat models.\n", + "\n", + "## Dependencies\n", + "\n", + "**Note**: this guide requires `@langchain/core` >= 0.2.16. We will also use [OpenAI](/docs/integrations/platforms/openai/) for embeddings, but any LangChain embeddings should suffice. We will use a simple [LangGraph](https://langchain-ai.github.io/langgraphjs/) agent for demonstration purposes.\n", + "\n", + "```{=mdx}\n", + "\n", + "```npm2ayrn\n", + "@langchain/core @langchain/langgraph @langchain/openai zod\n", + "```\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "2b0dcc1a-48e8-4a81-b920-3563192ce076", + "metadata": {}, + "source": [ + "LangChain [tools](/docs/concepts#tools) are interfaces that an agent, chain, or chat model can use to interact with the world. See [here](/docs/how_to/#tools) for how-to guides covering tool-calling, built-in tools, custom tools, and more information.\n", + "\n", + "LangChain tools-- instances of [BaseTool](https://api.python.langchain.com/en/latest/tools/langchain_core.tools.BaseTool.html)-- are [Runnables](/docs/concepts/#runnable-interface) with additional constraints that enable them to be invoked effectively by language models:\n", + "\n", + "- Their inputs are constrained to be serializable, specifically strings and objects;\n", + "- They contain names and descriptions indicating how and when they should be used;\n", + "- They contain a detailed `schema` property for their arguments. That is, while a tool (as a `Runnable`) might accept a single object input, the specific keys and type information needed to populate an object should be specified in the `schema` field.\n", + "\n", + "Runnables that accept string or object inputs can be converted to tools using the [`asTool`](https://api.js.langchain.com/classes/langchain_core_runnables.Runnable.html#asTool) method, which allows for the specification of names, descriptions, and additional schema information for arguments." + ] + }, + { + "cell_type": "markdown", + "id": "b4d76680-1b6b-4862-8c4f-22766a1d41f2", + "metadata": {}, + "source": [ + "## Basic usage\n", + "\n", + "With object input:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "b2cc4231-64a3-4733-a284-932dcbf2fcc3", + "metadata": {}, + "outputs": [], + "source": [ + "import { RunnableLambda } from \"@langchain/core/runnables\";\n", + "import { z } from \"zod\";\n", + "\n", + "const schema = z.object({\n", + " a: z.number(),\n", + " b: z.array(z.number()),\n", + "});\n", + "\n", + "\n", + "const runnable = RunnableLambda.from, number>((input) => {\n", + " return input.a * Math.max(...input.b);\n", + "})\n", + "\n", + "const asTool = runnable.asTool({\n", + " name: \"My tool\",\n", + " description: \"Explanation of when to use tool.\",\n", + " schema,\n", + "})" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "57f2d435-624d-459a-903d-8509fbbde610", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Explanation of when to use tool.\n" + ] + } + ], + "source": [ + "console.log(asTool.description)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "54ae7384-a03d-4fa4-8cdf-9604a4bc39ee", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6\n" + ] + } + ], + "source": [ + "await asTool.invoke({ a: 3, b: [1, 2] })" + ] + }, + { + "cell_type": "markdown", + "id": "7c474d85-4e01-4fae-9bba-0c6c8c26475c", + "metadata": {}, + "source": [ + "String input is also supported:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c475282a-58d6-4c2b-af7d-99b73b7d8a13", + "metadata": {}, + "outputs": [], + "source": [ + "const firstRunnable = RunnableLambda.from((input) => {\n", + " return input + \"a\";\n", + "})\n", + "\n", + "const secondRunnable = RunnableLambda.from((input) => {\n", + " return input + \"z\";\n", + "})\n", + "\n", + "const runnable = firstRunnable.pipe(secondRunnable)\n", + "const asTool = runnable.asTool({\n", + " schema: z.string(),\n", + "})" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ad6d8d96-3a87-40bd-a2ac-44a8acde0a8e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "baz\n" + ] + } + ], + "source": [ + "await asTool.invoke(\"b\")" + ] + }, + { + "cell_type": "markdown", + "id": "89fdb3a7-d228-48f0-8f73-262af4febb58", + "metadata": {}, + "source": [ + "## In agents\n", + "\n", + "Below we will incorporate LangChain Runnables as tools in an [agent](/docs/concepts/#agents) application. We will demonstrate with:\n", + "\n", + "- a document [retriever](/docs/concepts/#retrievers);\n", + "- a simple [RAG](/docs/tutorials/rag/) chain, allowing an agent to delegate relevant queries to it.\n", + "\n", + "We first instantiate a chat model that supports [tool calling](/docs/how_to/tool_calling/):\n", + "\n", + "```{=mdx}\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d06c9f2a-4475-450f-9106-54db1d99623b", + "metadata": {}, + "outputs": [], + "source": [ + "import { ChatOpenAI } from \"@langchain/openai\";\n", + "\n", + "const llm = new ChatOpenAI({ model: \"gpt-3.5-turbo-0125\", temperature: 0})" + ] + }, + { + "cell_type": "markdown", + "id": "e8a2038a-d762-4196-b5e3-fdb89c11e71d", + "metadata": {}, + "source": [ + "Following the [RAG tutorial](/docs/tutorials/rag/), let's first construct a retriever:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "23d2a47e-6712-4294-81c8-2c1d76b4bb81", + "metadata": {}, + "outputs": [], + "source": [ + "import { Document } from \"@langchain/core/documents\"\n", + "import { MemoryVectorStore } from \"langchain/vectorstores/memory\";\n", + "import { OpenAIEmbeddings } from \"@langchain/openai\";\n", + "\n", + "const documents = [\n", + " new Document({\n", + " pageContent: \"Dogs are great companions, known for their loyalty and friendliness.\",\n", + " }),\n", + " new Document({\n", + " pageContent: \"Cats are independent pets that often enjoy their own space.\",\n", + " }),\n", + "]\n", + "\n", + "const vectorstore = await MemoryVectorStore.fromDocuments(\n", + " documents, new OpenAIEmbeddings()\n", + ")\n", + "\n", + "const retriever = vectorstore.asRetriever({\n", + " k: 1,\n", + " searchType: \"similarity\",\n", + "})" + ] + }, + { + "cell_type": "markdown", + "id": "9ba737ac-43a2-4a6f-b855-5bd0305017f1", + "metadata": {}, + "source": [ + "We next create use a simple pre-built [LangGraph agent](/docs/how_to/migrate_agent/) and provide it the tool:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c939cf2a-60e9-4afd-8b47-84d76ccb13f5", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "11:39 - Type 'RunnableToolLike, DocumentInterface>[]>[]' is not assignable to type 'ToolNode | StructuredTool[]'.\n", + "11:39 - Type 'RunnableToolLike, DocumentInterface>[]>[]' is not assignable to type 'StructuredTool[]'.\n", + "11:39 - Type 'RunnableToolLike, DocumentInterface>[]>' is missing the following properties from type 'StructuredTool': _call, call, returnDirect, verbose\n" + ] + } + ], + "source": [ + "import { createReactAgent } from \"@langchain/langgraph/prebuilt\";\n", + "\n", + "const tools = [\n", + " retriever.asTool({\n", + " name: \"pet_info_retriever\",\n", + " description: \"Get information about pets.\",\n", + " schema: z.string(),\n", + " })\n", + "]\n", + "\n", + "const agent = createReactAgent({ llm, tools });\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be29437b-a187-4a0a-9a5d-419c56f2434e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_W8cnfOjwqEn4cFcg19LN9mYD', 'function': {'arguments': '{\"__arg1\":\"dogs\"}', 'name': 'pet_info_retriever'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 60, 'total_tokens': 79}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-d7f81de9-1fb7-4caf-81ed-16dcdb0b2ab4-0', tool_calls=[{'name': 'pet_info_retriever', 'args': {'__arg1': 'dogs'}, 'id': 'call_W8cnfOjwqEn4cFcg19LN9mYD'}], usage_metadata={'input_tokens': 60, 'output_tokens': 19, 'total_tokens': 79})]}}\n", + "----\n", + "{'tools': {'messages': [ToolMessage(content=\"[Document(id='86f835fe-4bbe-4ec6-aeb4-489a8b541707', page_content='Dogs are great companions, known for their loyalty and friendliness.')]\", name='pet_info_retriever', tool_call_id='call_W8cnfOjwqEn4cFcg19LN9mYD')]}}\n", + "----\n", + "{'agent': {'messages': [AIMessage(content='Dogs are known for being great companions, known for their loyalty and friendliness.', response_metadata={'token_usage': {'completion_tokens': 18, 'prompt_tokens': 134, 'total_tokens': 152}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-9ca5847a-a5eb-44c0-a774-84cc2c5bbc5b-0', usage_metadata={'input_tokens': 134, 'output_tokens': 18, 'total_tokens': 152})]}}\n", + "----\n" + ] + } + ], + "source": [ + "for chunk in agent.stream({\"messages\": [(\"human\", \"What are dogs known for?\")]}):\n", + " print(chunk)\n", + " print(\"----\")" + ] + }, + { + "cell_type": "markdown", + "id": "96f2ac9c-36f4-4b7a-ae33-f517734c86aa", + "metadata": {}, + "source": [ + "See [LangSmith trace](https://smith.langchain.com/public/44e438e3-2faf-45bd-b397-5510fc145eb9/r) for the above run." + ] + }, + { + "cell_type": "markdown", + "id": "a722fd8a-b957-4ba7-b408-35596b76835f", + "metadata": {}, + "source": [ + "Going further, we can create a simple [RAG](/docs/tutorials/rag/) chain that takes an additional parameter-- here, the \"style\" of the answer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bea518c9-c711-47c2-b8cc-dbd102f71f09", + "metadata": {}, + "outputs": [], + "source": [ + "from operator import itemgetter\n", + "\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "system_prompt = \"\"\"\n", + "You are an assistant for question-answering tasks.\n", + "Use the below context to answer the question. If\n", + "you don't know the answer, say you don't know.\n", + "Use three sentences maximum and keep the answer\n", + "concise.\n", + "\n", + "Answer in the style of {answer_style}.\n", + "\n", + "Question: {question}\n", + "\n", + "Context: {context}\n", + "\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages([(\"system\", system_prompt)])\n", + "\n", + "rag_chain = (\n", + " {\n", + " \"context\": itemgetter(\"question\") | retriever,\n", + " \"question\": itemgetter(\"question\"),\n", + " \"answer_style\": itemgetter(\"answer_style\"),\n", + " }\n", + " | prompt\n", + " | llm\n", + " | StrOutputParser()\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "955a23db-5218-4c34-8486-450a2ddb3443", + "metadata": {}, + "source": [ + "Note that the input schema for our chain contains the required arguments, so it converts to a tool without further specification:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c9f6e61-80ed-4abb-8e77-84de3ccbc891", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'title': 'RunnableParallelInput',\n", + " 'type': 'object',\n", + " 'properties': {'question': {'title': 'Question'},\n", + " 'answer_style': {'title': 'Answer Style'}}}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rag_chain.input_schema.schema()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3f9cf5b-8c71-4b0f-902b-f92e028780c9", + "metadata": {}, + "outputs": [], + "source": [ + "rag_tool = rag_chain.as_tool(\n", + " name=\"pet_expert\",\n", + " description=\"Get information about pets.\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "4570615b-8f96-4d97-ae01-1c08b14be584", + "metadata": {}, + "source": [ + "Below we again invoke the agent. Note that the agent populates the required parameters in its `tool_calls`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "06409913-a2ad-400f-a202-7b8dd2ef483a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_17iLPWvOD23zqwd1QVQ00Y63', 'function': {'arguments': '{\"question\":\"What are dogs known for according to pirates?\",\"answer_style\":\"quote\"}', 'name': 'pet_expert'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 28, 'prompt_tokens': 59, 'total_tokens': 87}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-7fef44f3-7bba-4e63-8c51-2ad9c5e65e2e-0', tool_calls=[{'name': 'pet_expert', 'args': {'question': 'What are dogs known for according to pirates?', 'answer_style': 'quote'}, 'id': 'call_17iLPWvOD23zqwd1QVQ00Y63'}], usage_metadata={'input_tokens': 59, 'output_tokens': 28, 'total_tokens': 87})]}}\n", + "----\n", + "{'tools': {'messages': [ToolMessage(content='\"Dogs are known for their loyalty and friendliness, making them great companions for pirates on long sea voyages.\"', name='pet_expert', tool_call_id='call_17iLPWvOD23zqwd1QVQ00Y63')]}}\n", + "----\n", + "{'agent': {'messages': [AIMessage(content='According to pirates, dogs are known for their loyalty and friendliness, making them great companions for pirates on long sea voyages.', response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 119, 'total_tokens': 146}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-5a30edc3-7be0-4743-b980-ca2f8cad9b8d-0', usage_metadata={'input_tokens': 119, 'output_tokens': 27, 'total_tokens': 146})]}}\n", + "----\n" + ] + } + ], + "source": [ + "agent = create_react_agent(llm, [rag_tool])\n", + "\n", + "for chunk in agent.stream(\n", + " {\"messages\": [(\"human\", \"What would a pirate say dogs are known for?\")]}\n", + "):\n", + " print(chunk)\n", + " print(\"----\")" + ] + }, + { + "cell_type": "markdown", + "id": "96cc9bc3-e79e-49a8-9915-428ea225358b", + "metadata": {}, + "source": [ + "See [LangSmith trace](https://smith.langchain.com/public/147ae4e6-4dfb-4dd9-8ca0-5c5b954f08ac/r) for the above run." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "TypeScript", + "language": "typescript", + "name": "tslab" + }, + "language_info": { + "codemirror_mode": { + "mode": "typescript", + "name": "javascript", + "typescript": true + }, + "file_extension": ".ts", + "mimetype": "text/typescript", + "name": "typescript", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/core_docs/docs/how_to/index.mdx b/docs/core_docs/docs/how_to/index.mdx index fba45f0ce5dc..93a664458476 100644 --- a/docs/core_docs/docs/how_to/index.mdx +++ b/docs/core_docs/docs/how_to/index.mdx @@ -160,6 +160,7 @@ LangChain [Tools](/docs/concepts/#tools) contain a description of the tool (to p - [How to: create custom tools](/docs/how_to/custom_tools) - [How to: use built-in tools and built-in toolkits](/docs/how_to/tools_builtin) +- [How to: convert Runnables to tools](/docs/how_to/convert_runnable_to_tool) - [How to: use a chat model to call tools](/docs/how_to/tool_calling/) - [How to: add ad-hoc tool calling capability to LLMs and Chat Models](/docs/how_to/tools_prompting) diff --git a/docs/core_docs/package.json b/docs/core_docs/package.json index a356cacd1ebe..cd5b972d904f 100644 --- a/docs/core_docs/package.json +++ b/docs/core_docs/package.json @@ -50,7 +50,7 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.18.2", - "@langchain/langgraph": "latest", + "@langchain/langgraph": "0.0.26", "@langchain/scripts": "workspace:*", "@swc/core": "^1.3.62", "@types/cookie": "^0", diff --git a/langchain-core/src/runnables/tests/runnable_tools.test.ts b/langchain-core/src/runnables/tests/runnable_tools.test.ts index 77187eb7cae6..69ce7fba066e 100644 --- a/langchain-core/src/runnables/tests/runnable_tools.test.ts +++ b/langchain-core/src/runnables/tests/runnable_tools.test.ts @@ -105,3 +105,20 @@ test("Create a runnable tool directly from RunnableToolLike", async () => { const result = await tool.invoke({ foo: "bar" }); expect(result).toBe(true); }); + +test("asTool can take a single string input", async () => { + const firstRunnable = RunnableLambda.from((input) => { + return `${input }a`; + }); + const secondRunnable = RunnableLambda.from((input) => { + return `${input }z`; + }); + + const runnable = firstRunnable.pipe(secondRunnable); + const asTool = runnable.asTool({ + schema: z.string(), + }); + + const result = await asTool.invoke("b"); + expect(result).toBe("baz"); +}); diff --git a/yarn.lock b/yarn.lock index fd8f162bba33..61e83a835572 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11319,22 +11319,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/langgraph@npm:latest": - version: 0.0.18 - resolution: "@langchain/langgraph@npm:0.0.18" - dependencies: - "@langchain/core": ^0.1.61 - uuid: ^9.0.1 - peerDependencies: - better-sqlite3: ^9.5.0 - peerDependenciesMeta: - better-sqlite3: - optional: true - checksum: dc35aaafc3489d8b30863e31c6564fc10be0bc1cd216d5877cc248a46e3ad508f7faf120e7ead675e0ac7f0ea1deae23bbd239ac0020e6a0c46a129a691c62b5 - languageName: node - linkType: hard - -"@langchain/langgraph@npm:~0.0.26": +"@langchain/langgraph@npm:0.0.26, @langchain/langgraph@npm:~0.0.26": version: 0.0.26 resolution: "@langchain/langgraph@npm:0.0.26" dependencies: @@ -21658,7 +21643,7 @@ __metadata: "@docusaurus/preset-classic": 2.4.3 "@docusaurus/remark-plugin-npm2yarn": 2.4.3 "@docusaurus/theme-mermaid": 2.4.3 - "@langchain/langgraph": latest + "@langchain/langgraph": 0.0.26 "@langchain/scripts": "workspace:*" "@mdx-js/react": ^1.6.22 "@supabase/supabase-js": ^2.39.7 From e338a7542342b9c74bc5ed4dd741fe554d80ab9b Mon Sep 17 00:00:00 2001 From: bracesproul Date: Wed, 10 Jul 2024 17:41:35 -0700 Subject: [PATCH 10/18] drop docs --- .../how_to/convert_runnable_to_tool.ipynb | 476 ------------------ docs/core_docs/docs/how_to/index.mdx | 1 - 2 files changed, 477 deletions(-) delete mode 100644 docs/core_docs/docs/how_to/convert_runnable_to_tool.ipynb diff --git a/docs/core_docs/docs/how_to/convert_runnable_to_tool.ipynb b/docs/core_docs/docs/how_to/convert_runnable_to_tool.ipynb deleted file mode 100644 index 4a6cb6a0ce9e..000000000000 --- a/docs/core_docs/docs/how_to/convert_runnable_to_tool.ipynb +++ /dev/null @@ -1,476 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "9a8bceb3-95bd-4496-bb9e-57655136e070", - "metadata": {}, - "source": [ - "# How to use Runnables as Tools\n", - "\n", - "```{=mdx}\n", - ":::info Prerequisites\n", - "\n", - "This guide assumes familiarity with the following concepts:\n", - "\n", - "- [Runnables](/docs/concepts#runnable-interface)\n", - "- [Tools](/docs/concepts#tools)\n", - "- [Agents](/docs/tutorials/agents)\n", - "\n", - ":::\n", - "\n", - "```\n", - "\n", - "Here we will demonstrate how to convert a LangChain `Runnable` into a tool that can be used by agents, chains, or chat models.\n", - "\n", - "## Dependencies\n", - "\n", - "**Note**: this guide requires `@langchain/core` >= 0.2.16. We will also use [OpenAI](/docs/integrations/platforms/openai/) for embeddings, but any LangChain embeddings should suffice. We will use a simple [LangGraph](https://langchain-ai.github.io/langgraphjs/) agent for demonstration purposes.\n", - "\n", - "```{=mdx}\n", - "\n", - "```npm2ayrn\n", - "@langchain/core @langchain/langgraph @langchain/openai zod\n", - "```\n", - "\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "2b0dcc1a-48e8-4a81-b920-3563192ce076", - "metadata": {}, - "source": [ - "LangChain [tools](/docs/concepts#tools) are interfaces that an agent, chain, or chat model can use to interact with the world. See [here](/docs/how_to/#tools) for how-to guides covering tool-calling, built-in tools, custom tools, and more information.\n", - "\n", - "LangChain tools-- instances of [BaseTool](https://api.python.langchain.com/en/latest/tools/langchain_core.tools.BaseTool.html)-- are [Runnables](/docs/concepts/#runnable-interface) with additional constraints that enable them to be invoked effectively by language models:\n", - "\n", - "- Their inputs are constrained to be serializable, specifically strings and objects;\n", - "- They contain names and descriptions indicating how and when they should be used;\n", - "- They contain a detailed `schema` property for their arguments. That is, while a tool (as a `Runnable`) might accept a single object input, the specific keys and type information needed to populate an object should be specified in the `schema` field.\n", - "\n", - "Runnables that accept string or object inputs can be converted to tools using the [`asTool`](https://api.js.langchain.com/classes/langchain_core_runnables.Runnable.html#asTool) method, which allows for the specification of names, descriptions, and additional schema information for arguments." - ] - }, - { - "cell_type": "markdown", - "id": "b4d76680-1b6b-4862-8c4f-22766a1d41f2", - "metadata": {}, - "source": [ - "## Basic usage\n", - "\n", - "With object input:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "b2cc4231-64a3-4733-a284-932dcbf2fcc3", - "metadata": {}, - "outputs": [], - "source": [ - "import { RunnableLambda } from \"@langchain/core/runnables\";\n", - "import { z } from \"zod\";\n", - "\n", - "const schema = z.object({\n", - " a: z.number(),\n", - " b: z.array(z.number()),\n", - "});\n", - "\n", - "\n", - "const runnable = RunnableLambda.from, number>((input) => {\n", - " return input.a * Math.max(...input.b);\n", - "})\n", - "\n", - "const asTool = runnable.asTool({\n", - " name: \"My tool\",\n", - " description: \"Explanation of when to use tool.\",\n", - " schema,\n", - "})" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "57f2d435-624d-459a-903d-8509fbbde610", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Explanation of when to use tool.\n" - ] - } - ], - "source": [ - "console.log(asTool.description)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "54ae7384-a03d-4fa4-8cdf-9604a4bc39ee", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "6\n" - ] - } - ], - "source": [ - "await asTool.invoke({ a: 3, b: [1, 2] })" - ] - }, - { - "cell_type": "markdown", - "id": "7c474d85-4e01-4fae-9bba-0c6c8c26475c", - "metadata": {}, - "source": [ - "String input is also supported:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "c475282a-58d6-4c2b-af7d-99b73b7d8a13", - "metadata": {}, - "outputs": [], - "source": [ - "const firstRunnable = RunnableLambda.from((input) => {\n", - " return input + \"a\";\n", - "})\n", - "\n", - "const secondRunnable = RunnableLambda.from((input) => {\n", - " return input + \"z\";\n", - "})\n", - "\n", - "const runnable = firstRunnable.pipe(secondRunnable)\n", - "const asTool = runnable.asTool({\n", - " schema: z.string(),\n", - "})" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "ad6d8d96-3a87-40bd-a2ac-44a8acde0a8e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "baz\n" - ] - } - ], - "source": [ - "await asTool.invoke(\"b\")" - ] - }, - { - "cell_type": "markdown", - "id": "89fdb3a7-d228-48f0-8f73-262af4febb58", - "metadata": {}, - "source": [ - "## In agents\n", - "\n", - "Below we will incorporate LangChain Runnables as tools in an [agent](/docs/concepts/#agents) application. We will demonstrate with:\n", - "\n", - "- a document [retriever](/docs/concepts/#retrievers);\n", - "- a simple [RAG](/docs/tutorials/rag/) chain, allowing an agent to delegate relevant queries to it.\n", - "\n", - "We first instantiate a chat model that supports [tool calling](/docs/how_to/tool_calling/):\n", - "\n", - "```{=mdx}\n", - "\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "d06c9f2a-4475-450f-9106-54db1d99623b", - "metadata": {}, - "outputs": [], - "source": [ - "import { ChatOpenAI } from \"@langchain/openai\";\n", - "\n", - "const llm = new ChatOpenAI({ model: \"gpt-3.5-turbo-0125\", temperature: 0})" - ] - }, - { - "cell_type": "markdown", - "id": "e8a2038a-d762-4196-b5e3-fdb89c11e71d", - "metadata": {}, - "source": [ - "Following the [RAG tutorial](/docs/tutorials/rag/), let's first construct a retriever:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "23d2a47e-6712-4294-81c8-2c1d76b4bb81", - "metadata": {}, - "outputs": [], - "source": [ - "import { Document } from \"@langchain/core/documents\"\n", - "import { MemoryVectorStore } from \"langchain/vectorstores/memory\";\n", - "import { OpenAIEmbeddings } from \"@langchain/openai\";\n", - "\n", - "const documents = [\n", - " new Document({\n", - " pageContent: \"Dogs are great companions, known for their loyalty and friendliness.\",\n", - " }),\n", - " new Document({\n", - " pageContent: \"Cats are independent pets that often enjoy their own space.\",\n", - " }),\n", - "]\n", - "\n", - "const vectorstore = await MemoryVectorStore.fromDocuments(\n", - " documents, new OpenAIEmbeddings()\n", - ")\n", - "\n", - "const retriever = vectorstore.asRetriever({\n", - " k: 1,\n", - " searchType: \"similarity\",\n", - "})" - ] - }, - { - "cell_type": "markdown", - "id": "9ba737ac-43a2-4a6f-b855-5bd0305017f1", - "metadata": {}, - "source": [ - "We next create use a simple pre-built [LangGraph agent](/docs/how_to/migrate_agent/) and provide it the tool:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "c939cf2a-60e9-4afd-8b47-84d76ccb13f5", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "11:39 - Type 'RunnableToolLike, DocumentInterface>[]>[]' is not assignable to type 'ToolNode | StructuredTool[]'.\n", - "11:39 - Type 'RunnableToolLike, DocumentInterface>[]>[]' is not assignable to type 'StructuredTool[]'.\n", - "11:39 - Type 'RunnableToolLike, DocumentInterface>[]>' is missing the following properties from type 'StructuredTool': _call, call, returnDirect, verbose\n" - ] - } - ], - "source": [ - "import { createReactAgent } from \"@langchain/langgraph/prebuilt\";\n", - "\n", - "const tools = [\n", - " retriever.asTool({\n", - " name: \"pet_info_retriever\",\n", - " description: \"Get information about pets.\",\n", - " schema: z.string(),\n", - " })\n", - "]\n", - "\n", - "const agent = createReactAgent({ llm, tools });\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "be29437b-a187-4a0a-9a5d-419c56f2434e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_W8cnfOjwqEn4cFcg19LN9mYD', 'function': {'arguments': '{\"__arg1\":\"dogs\"}', 'name': 'pet_info_retriever'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 60, 'total_tokens': 79}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-d7f81de9-1fb7-4caf-81ed-16dcdb0b2ab4-0', tool_calls=[{'name': 'pet_info_retriever', 'args': {'__arg1': 'dogs'}, 'id': 'call_W8cnfOjwqEn4cFcg19LN9mYD'}], usage_metadata={'input_tokens': 60, 'output_tokens': 19, 'total_tokens': 79})]}}\n", - "----\n", - "{'tools': {'messages': [ToolMessage(content=\"[Document(id='86f835fe-4bbe-4ec6-aeb4-489a8b541707', page_content='Dogs are great companions, known for their loyalty and friendliness.')]\", name='pet_info_retriever', tool_call_id='call_W8cnfOjwqEn4cFcg19LN9mYD')]}}\n", - "----\n", - "{'agent': {'messages': [AIMessage(content='Dogs are known for being great companions, known for their loyalty and friendliness.', response_metadata={'token_usage': {'completion_tokens': 18, 'prompt_tokens': 134, 'total_tokens': 152}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-9ca5847a-a5eb-44c0-a774-84cc2c5bbc5b-0', usage_metadata={'input_tokens': 134, 'output_tokens': 18, 'total_tokens': 152})]}}\n", - "----\n" - ] - } - ], - "source": [ - "for chunk in agent.stream({\"messages\": [(\"human\", \"What are dogs known for?\")]}):\n", - " print(chunk)\n", - " print(\"----\")" - ] - }, - { - "cell_type": "markdown", - "id": "96f2ac9c-36f4-4b7a-ae33-f517734c86aa", - "metadata": {}, - "source": [ - "See [LangSmith trace](https://smith.langchain.com/public/44e438e3-2faf-45bd-b397-5510fc145eb9/r) for the above run." - ] - }, - { - "cell_type": "markdown", - "id": "a722fd8a-b957-4ba7-b408-35596b76835f", - "metadata": {}, - "source": [ - "Going further, we can create a simple [RAG](/docs/tutorials/rag/) chain that takes an additional parameter-- here, the \"style\" of the answer." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bea518c9-c711-47c2-b8cc-dbd102f71f09", - "metadata": {}, - "outputs": [], - "source": [ - "from operator import itemgetter\n", - "\n", - "from langchain_core.output_parsers import StrOutputParser\n", - "from langchain_core.prompts import ChatPromptTemplate\n", - "from langchain_core.runnables import RunnablePassthrough\n", - "\n", - "system_prompt = \"\"\"\n", - "You are an assistant for question-answering tasks.\n", - "Use the below context to answer the question. If\n", - "you don't know the answer, say you don't know.\n", - "Use three sentences maximum and keep the answer\n", - "concise.\n", - "\n", - "Answer in the style of {answer_style}.\n", - "\n", - "Question: {question}\n", - "\n", - "Context: {context}\n", - "\"\"\"\n", - "\n", - "prompt = ChatPromptTemplate.from_messages([(\"system\", system_prompt)])\n", - "\n", - "rag_chain = (\n", - " {\n", - " \"context\": itemgetter(\"question\") | retriever,\n", - " \"question\": itemgetter(\"question\"),\n", - " \"answer_style\": itemgetter(\"answer_style\"),\n", - " }\n", - " | prompt\n", - " | llm\n", - " | StrOutputParser()\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "955a23db-5218-4c34-8486-450a2ddb3443", - "metadata": {}, - "source": [ - "Note that the input schema for our chain contains the required arguments, so it converts to a tool without further specification:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2c9f6e61-80ed-4abb-8e77-84de3ccbc891", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'title': 'RunnableParallelInput',\n", - " 'type': 'object',\n", - " 'properties': {'question': {'title': 'Question'},\n", - " 'answer_style': {'title': 'Answer Style'}}}" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rag_chain.input_schema.schema()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a3f9cf5b-8c71-4b0f-902b-f92e028780c9", - "metadata": {}, - "outputs": [], - "source": [ - "rag_tool = rag_chain.as_tool(\n", - " name=\"pet_expert\",\n", - " description=\"Get information about pets.\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "4570615b-8f96-4d97-ae01-1c08b14be584", - "metadata": {}, - "source": [ - "Below we again invoke the agent. Note that the agent populates the required parameters in its `tool_calls`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "06409913-a2ad-400f-a202-7b8dd2ef483a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_17iLPWvOD23zqwd1QVQ00Y63', 'function': {'arguments': '{\"question\":\"What are dogs known for according to pirates?\",\"answer_style\":\"quote\"}', 'name': 'pet_expert'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 28, 'prompt_tokens': 59, 'total_tokens': 87}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-7fef44f3-7bba-4e63-8c51-2ad9c5e65e2e-0', tool_calls=[{'name': 'pet_expert', 'args': {'question': 'What are dogs known for according to pirates?', 'answer_style': 'quote'}, 'id': 'call_17iLPWvOD23zqwd1QVQ00Y63'}], usage_metadata={'input_tokens': 59, 'output_tokens': 28, 'total_tokens': 87})]}}\n", - "----\n", - "{'tools': {'messages': [ToolMessage(content='\"Dogs are known for their loyalty and friendliness, making them great companions for pirates on long sea voyages.\"', name='pet_expert', tool_call_id='call_17iLPWvOD23zqwd1QVQ00Y63')]}}\n", - "----\n", - "{'agent': {'messages': [AIMessage(content='According to pirates, dogs are known for their loyalty and friendliness, making them great companions for pirates on long sea voyages.', response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 119, 'total_tokens': 146}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-5a30edc3-7be0-4743-b980-ca2f8cad9b8d-0', usage_metadata={'input_tokens': 119, 'output_tokens': 27, 'total_tokens': 146})]}}\n", - "----\n" - ] - } - ], - "source": [ - "agent = create_react_agent(llm, [rag_tool])\n", - "\n", - "for chunk in agent.stream(\n", - " {\"messages\": [(\"human\", \"What would a pirate say dogs are known for?\")]}\n", - "):\n", - " print(chunk)\n", - " print(\"----\")" - ] - }, - { - "cell_type": "markdown", - "id": "96cc9bc3-e79e-49a8-9915-428ea225358b", - "metadata": {}, - "source": [ - "See [LangSmith trace](https://smith.langchain.com/public/147ae4e6-4dfb-4dd9-8ca0-5c5b954f08ac/r) for the above run." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "TypeScript", - "language": "typescript", - "name": "tslab" - }, - "language_info": { - "codemirror_mode": { - "mode": "typescript", - "name": "javascript", - "typescript": true - }, - "file_extension": ".ts", - "mimetype": "text/typescript", - "name": "typescript", - "version": "3.7.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/core_docs/docs/how_to/index.mdx b/docs/core_docs/docs/how_to/index.mdx index 93a664458476..fba45f0ce5dc 100644 --- a/docs/core_docs/docs/how_to/index.mdx +++ b/docs/core_docs/docs/how_to/index.mdx @@ -160,7 +160,6 @@ LangChain [Tools](/docs/concepts/#tools) contain a description of the tool (to p - [How to: create custom tools](/docs/how_to/custom_tools) - [How to: use built-in tools and built-in toolkits](/docs/how_to/tools_builtin) -- [How to: convert Runnables to tools](/docs/how_to/convert_runnable_to_tool) - [How to: use a chat model to call tools](/docs/how_to/tool_calling/) - [How to: add ad-hoc tool calling capability to LLMs and Chat Models](/docs/how_to/tools_prompting) From ab67fbbc9adccb8184e11cd256a27c5ea5687821 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Wed, 10 Jul 2024 17:59:24 -0700 Subject: [PATCH 11/18] allow runnabletoollike in bindTools --- .../src/language_models/chat_models.ts | 2 ++ langchain-core/src/runnables/base.ts | 6 +++++- langchain-core/src/runnables/index.ts | 2 ++ .../src/runnables/tests/runnable_tools.test.ts | 4 ++-- langchain-core/src/utils/function_calling.ts | 16 +++++++++++++--- libs/langchain-anthropic/src/chat_models.ts | 3 +++ libs/langchain-aws/src/chat_models.ts | 3 ++- libs/langchain-aws/src/common.ts | 2 ++ libs/langchain-cohere/src/chat_models.ts | 4 +++- .../src/chat_models/bedrock/web.ts | 16 +++++++++++++--- libs/langchain-google-common/src/chat_models.ts | 3 +++ libs/langchain-google-genai/src/chat_models.ts | 2 ++ libs/langchain-google-genai/src/utils/common.ts | 2 ++ libs/langchain-groq/src/chat_models.ts | 2 ++ libs/langchain-mistralai/src/chat_models.ts | 7 ++++++- libs/langchain-openai/src/chat_models.ts | 7 ++++++- 16 files changed, 68 insertions(+), 13 deletions(-) diff --git a/langchain-core/src/language_models/chat_models.ts b/langchain-core/src/language_models/chat_models.ts index f3b900427ba0..5522b5b316e1 100644 --- a/langchain-core/src/language_models/chat_models.ts +++ b/langchain-core/src/language_models/chat_models.ts @@ -38,6 +38,7 @@ import { Runnable, RunnableLambda, RunnableSequence, + RunnableToolLike, } from "../runnables/base.js"; import { isStreamEventsHandler } from "../tracers/event_stream.js"; import { isLogStreamHandler } from "../tracers/log_stream.js"; @@ -163,6 +164,7 @@ export abstract class BaseChatModel< | StructuredToolInterface | Record | ToolDefinition + | RunnableToolLike )[], kwargs?: Partial ): Runnable; diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index 9ce3027562c2..f5cba453fb27 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -2809,7 +2809,7 @@ export interface RunnableToolLikeArgs< RunInput extends z.ZodType = z.ZodType, RunOutput = unknown > extends Omit, RunOutput>, "config"> { - name?: string; + name: string; description?: string; @@ -2822,6 +2822,10 @@ export class RunnableToolLike< RunInput extends z.ZodType = z.ZodType, RunOutput = unknown > extends RunnableBinding, RunOutput> { + lc_runnable_tool_like: true; + + name: string; + description?: string; schema: RunInput; diff --git a/langchain-core/src/runnables/index.ts b/langchain-core/src/runnables/index.ts index 65a95c22aa7a..dca9673bdec7 100644 --- a/langchain-core/src/runnables/index.ts +++ b/langchain-core/src/runnables/index.ts @@ -15,6 +15,8 @@ export { RunnableAssign, RunnablePick, _coerceToRunnable, + RunnableToolLike, + RunnableToolLikeArgs, } from "./base.js"; export { type RunnableBatchOptions, diff --git a/langchain-core/src/runnables/tests/runnable_tools.test.ts b/langchain-core/src/runnables/tests/runnable_tools.test.ts index 69ce7fba066e..f97ecb4c3514 100644 --- a/langchain-core/src/runnables/tests/runnable_tools.test.ts +++ b/langchain-core/src/runnables/tests/runnable_tools.test.ts @@ -108,10 +108,10 @@ test("Create a runnable tool directly from RunnableToolLike", async () => { test("asTool can take a single string input", async () => { const firstRunnable = RunnableLambda.from((input) => { - return `${input }a`; + return `${input}a`; }); const secondRunnable = RunnableLambda.from((input) => { - return `${input }z`; + return `${input}z`; }); const runnable = firstRunnable.pipe(secondRunnable); diff --git a/langchain-core/src/utils/function_calling.ts b/langchain-core/src/utils/function_calling.ts index aa68de52253b..97c3372a3bdf 100644 --- a/langchain-core/src/utils/function_calling.ts +++ b/langchain-core/src/utils/function_calling.ts @@ -1,6 +1,7 @@ import { zodToJsonSchema } from "zod-to-json-schema"; import { StructuredToolInterface } from "../tools.js"; import { FunctionDefinition, ToolDefinition } from "../language_models/base.js"; +import { Runnable, RunnableToolLike } from "../runnables/base.js"; /** * Formats a `StructuredTool` instance into a format that is compatible @@ -9,7 +10,7 @@ import { FunctionDefinition, ToolDefinition } from "../language_models/base.js"; * schema, which is then used as the parameters for the OpenAI function. */ export function convertToOpenAIFunction( - tool: StructuredToolInterface + tool: StructuredToolInterface | RunnableToolLike ): FunctionDefinition { return { name: tool.name, @@ -26,9 +27,9 @@ export function convertToOpenAIFunction( */ export function convertToOpenAITool( // eslint-disable-next-line @typescript-eslint/no-explicit-any - tool: StructuredToolInterface | Record + tool: StructuredToolInterface | Record | RunnableToolLike ): ToolDefinition { - if (isStructuredTool(tool)) { + if (isStructuredTool(tool) || isRunnableToolLike(tool)) { return { type: "function", function: convertToOpenAIFunction(tool), @@ -46,3 +47,12 @@ export function isStructuredTool( Array.isArray((tool as StructuredToolInterface).lc_namespace) ); } + +export function isRunnableToolLike(tool?: unknown): tool is RunnableToolLike { + return ( + tool !== undefined && + Runnable.isRunnable(tool) && + "lc_runnable_tool_like" in tool && + tool.lc_runnable_tool_like === true + ); +} diff --git a/libs/langchain-anthropic/src/chat_models.ts b/libs/langchain-anthropic/src/chat_models.ts index a4943e6da029..54e1ee4a0e4a 100644 --- a/libs/langchain-anthropic/src/chat_models.ts +++ b/libs/langchain-anthropic/src/chat_models.ts @@ -38,6 +38,7 @@ import { Runnable, RunnablePassthrough, RunnableSequence, + RunnableToolLike, } from "@langchain/core/runnables"; import { isZodSchema } from "@langchain/core/utils/types"; import { ToolCall } from "@langchain/core/messages/tool"; @@ -73,6 +74,7 @@ export interface ChatAnthropicCallOptions | AnthropicTool | Record | ToolDefinition + | RunnableToolLike )[]; /** * Whether or not to specify what tool the model should use @@ -592,6 +594,7 @@ export class ChatAnthropicMessages< | Record | StructuredToolInterface | ToolDefinition + | RunnableToolLike )[], kwargs?: Partial ): Runnable { diff --git a/libs/langchain-aws/src/chat_models.ts b/libs/langchain-aws/src/chat_models.ts index 6d565fc10130..8d5ae1abf00d 100644 --- a/libs/langchain-aws/src/chat_models.ts +++ b/libs/langchain-aws/src/chat_models.ts @@ -29,7 +29,7 @@ import { } from "@aws-sdk/credential-provider-node"; import type { DocumentType as __DocumentType } from "@smithy/types"; import { StructuredToolInterface } from "@langchain/core/tools"; -import { Runnable } from "@langchain/core/runnables"; +import { Runnable, RunnableToolLike } from "@langchain/core/runnables"; import { BedrockToolChoice, ConverseCommandParams, @@ -289,6 +289,7 @@ export class ChatBedrockConverse | ToolDefinition // eslint-disable-next-line @typescript-eslint/no-explicit-any | Record + | RunnableToolLike )[], kwargs?: Partial ): Runnable< diff --git a/libs/langchain-aws/src/common.ts b/libs/langchain-aws/src/common.ts index 945fa932f43f..2c5bab0b9472 100644 --- a/libs/langchain-aws/src/common.ts +++ b/libs/langchain-aws/src/common.ts @@ -27,6 +27,7 @@ import { StructuredToolInterface } from "@langchain/core/tools"; import { isStructuredTool } from "@langchain/core/utils/function_calling"; import { zodToJsonSchema } from "zod-to-json-schema"; import { ChatGenerationChunk } from "@langchain/core/outputs"; +import { RunnableToolLike } from "@langchain/core/runnables"; import { BedrockToolChoice } from "./types.js"; export function extractImageInfo(base64: string): ContentBlock.ImageMember { @@ -207,6 +208,7 @@ export function convertToConverseTools( | BedrockTool // eslint-disable-next-line @typescript-eslint/no-explicit-any | Record + | RunnableToolLike )[] ): BedrockTool[] { if (tools.every(isOpenAITool)) { diff --git a/libs/langchain-cohere/src/chat_models.ts b/libs/langchain-cohere/src/chat_models.ts index bd9d7001979b..ab49603f755a 100644 --- a/libs/langchain-cohere/src/chat_models.ts +++ b/libs/langchain-cohere/src/chat_models.ts @@ -38,7 +38,7 @@ import { } from "@langchain/core/messages/tool"; import * as uuid from "uuid"; import { StructuredToolInterface } from "@langchain/core/tools"; -import { Runnable } from "@langchain/core/runnables"; +import { Runnable, RunnableToolLike } from "@langchain/core/runnables"; /** * Input interface for ChatCohere @@ -93,6 +93,7 @@ export interface ChatCohereCallOptions | Cohere.Tool | Record | ToolDefinition + | RunnableToolLike )[]; } @@ -369,6 +370,7 @@ export class ChatCohere< | Record | StructuredToolInterface | ToolDefinition + | RunnableToolLike )[], kwargs?: Partial ): Runnable { diff --git a/libs/langchain-community/src/chat_models/bedrock/web.ts b/libs/langchain-community/src/chat_models/bedrock/web.ts index 86e64d5859a2..a15dc949412a 100644 --- a/libs/langchain-community/src/chat_models/bedrock/web.ts +++ b/libs/langchain-community/src/chat_models/bedrock/web.ts @@ -16,7 +16,7 @@ import { ToolDefinition, isOpenAITool, } from "@langchain/core/language_models/base"; -import { Runnable } from "@langchain/core/runnables"; +import { Runnable, RunnableToolLike } from "@langchain/core/runnables"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { AIMessageChunk, @@ -142,7 +142,12 @@ function formatTools(tools: BedrockChatCallOptions["tools"]): AnthropicTool[] { } export interface BedrockChatCallOptions extends BaseChatModelCallOptions { - tools?: (StructuredToolInterface | AnthropicTool | ToolDefinition)[]; + tools?: ( + | StructuredToolInterface + | AnthropicTool + | ToolDefinition + | RunnableToolLike + )[]; } export interface BedrockChatFields @@ -744,7 +749,12 @@ export class BedrockChat } override bindTools( - tools: (StructuredToolInterface | AnthropicTool | ToolDefinition)[], + tools: ( + | StructuredToolInterface + | AnthropicTool + | ToolDefinition + | RunnableToolLike + )[], _kwargs?: Partial ): Runnable< BaseLanguageModelInput, diff --git a/libs/langchain-google-common/src/chat_models.ts b/libs/langchain-google-common/src/chat_models.ts index 6a736157d004..52a7748f2028 100644 --- a/libs/langchain-google-common/src/chat_models.ts +++ b/libs/langchain-google-common/src/chat_models.ts @@ -20,6 +20,7 @@ import { Runnable, RunnablePassthrough, RunnableSequence, + RunnableToolLike, } from "@langchain/core/runnables"; import { JsonOutputKeyToolsParser } from "@langchain/core/output_parsers/openai_tools"; import { BaseLLMOutputParser } from "@langchain/core/output_parsers"; @@ -163,6 +164,7 @@ function convertToGeminiTools( | StructuredToolInterface | Record | ToolDefinition + | RunnableToolLike )[] ): GeminiTool[] { return [ @@ -317,6 +319,7 @@ export abstract class ChatGoogleBase | StructuredToolInterface | Record | ToolDefinition + | RunnableToolLike )[], kwargs?: Partial ): Runnable< diff --git a/libs/langchain-google-genai/src/chat_models.ts b/libs/langchain-google-genai/src/chat_models.ts index 4de417dc3e9c..e9dab61acd86 100644 --- a/libs/langchain-google-genai/src/chat_models.ts +++ b/libs/langchain-google-genai/src/chat_models.ts @@ -33,6 +33,7 @@ import { Runnable, RunnablePassthrough, RunnableSequence, + RunnableToolLike, } from "@langchain/core/runnables"; import type { z } from "zod"; import { isZodSchema } from "@langchain/core/utils/types"; @@ -349,6 +350,7 @@ export class ChatGoogleGenerativeAI | StructuredToolInterface | Record | ToolDefinition + | RunnableToolLike )[], kwargs?: Partial ): Runnable< diff --git a/libs/langchain-google-genai/src/utils/common.ts b/libs/langchain-google-genai/src/utils/common.ts index 5835eb5b1524..afb60eb69c8d 100644 --- a/libs/langchain-google-genai/src/utils/common.ts +++ b/libs/langchain-google-genai/src/utils/common.ts @@ -29,6 +29,7 @@ import { isOpenAITool, } from "@langchain/core/language_models/base"; import { ToolCallChunk } from "@langchain/core/messages/tool"; +import { RunnableToolLike } from "@langchain/core/runnables"; import { jsonSchemaToGeminiParameters, zodToGenerativeAIParameters, @@ -322,6 +323,7 @@ export function convertToGenerativeAITools( | StructuredToolInterface | Record | ToolDefinition + | RunnableToolLike )[] ): GoogleGenerativeAIFunctionDeclarationsTool[] { if ( diff --git a/libs/langchain-groq/src/chat_models.ts b/libs/langchain-groq/src/chat_models.ts index 07dae53ff556..1abe7b3db765 100644 --- a/libs/langchain-groq/src/chat_models.ts +++ b/libs/langchain-groq/src/chat_models.ts @@ -43,6 +43,7 @@ import { Runnable, RunnablePassthrough, RunnableSequence, + RunnableToolLike, } from "@langchain/core/runnables"; import { BaseLanguageModelInput, @@ -372,6 +373,7 @@ export class ChatGroq extends BaseChatModel< | Record | StructuredToolInterface | ToolDefinition + | RunnableToolLike )[], kwargs?: Partial ): Runnable { diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index 0ddaa9e1389e..ea0317f34d84 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -62,6 +62,7 @@ import { Runnable, RunnablePassthrough, RunnableSequence, + RunnableToolLike, } from "@langchain/core/runnables"; import { zodToJsonSchema } from "zod-to-json-schema"; @@ -508,7 +509,11 @@ export class ChatMistralAI< } override bindTools( - tools: (Record | StructuredToolInterface)[], + tools: ( + | Record + | StructuredToolInterface + | RunnableToolLike + )[], kwargs?: Partial ): Runnable { const mistralAITools = tools diff --git a/libs/langchain-openai/src/chat_models.ts b/libs/langchain-openai/src/chat_models.ts index aeaf3682a2b3..601d70d33fff 100644 --- a/libs/langchain-openai/src/chat_models.ts +++ b/libs/langchain-openai/src/chat_models.ts @@ -41,6 +41,7 @@ import { Runnable, RunnablePassthrough, RunnableSequence, + RunnableToolLike, } from "@langchain/core/runnables"; import { JsonOutputParser, @@ -530,7 +531,11 @@ export class ChatOpenAI< } override bindTools( - tools: (Record | StructuredToolInterface)[], + tools: ( + | Record + | StructuredToolInterface + | RunnableToolLike + )[], kwargs?: Partial ): Runnable { return this.bind({ From 9aa961c5c51b50b7b99a94d35a22826aa6e84291 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Wed, 10 Jul 2024 18:17:13 -0700 Subject: [PATCH 12/18] mark as type in re-export --- langchain-core/src/runnables/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langchain-core/src/runnables/index.ts b/langchain-core/src/runnables/index.ts index dca9673bdec7..2a34d91c8164 100644 --- a/langchain-core/src/runnables/index.ts +++ b/langchain-core/src/runnables/index.ts @@ -16,7 +16,7 @@ export { RunnablePick, _coerceToRunnable, RunnableToolLike, - RunnableToolLikeArgs, + type RunnableToolLikeArgs, } from "./base.js"; export { type RunnableBatchOptions, From a226063f2d413a377de43cbef94c3f18446e2158 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Thu, 11 Jul 2024 09:07:34 -0700 Subject: [PATCH 13/18] drop default description --- langchain-core/src/runnables/base.ts | 20 ++++--------------- .../runnables/tests/runnable_tools.test.ts | 3 --- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index f5cba453fb27..4423c0c72132 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -6,7 +6,6 @@ import { type TraceableFunction, isTraceableFunction, } from "langsmith/singletons/traceable"; -import { zodToJsonSchema } from "zod-to-json-schema"; import type { RunnableInterface, RunnableBatchOptions } from "./types.js"; import { CallbackManagerForChainRun } from "../callbacks/manager.js"; import { @@ -1088,7 +1087,7 @@ export abstract class Runnable< * * @param fields * @param {string | undefined} [fields.name] The name of the tool. If not provided, it will default to the name of the runnable. - * @param {string | undefined} [fields.description] The description of the tool. If not provided, it will default to `Takes {schema}` where `schema` is a JSON string representation of the input schema. + * @param {string | undefined} [fields.description] The description of the tool. * @param {z.ZodType} [fields.schema] The Zod schema for the input of the tool. Infers the Zod type from the input type of the runnable. * @returns {RunnableToolLike, RunOutput>} An instance of `RunnableToolLike` which is a runnable that can be used as a tool. */ @@ -2842,15 +2841,6 @@ export class RunnableToolLike< } } -/** - * Generate a placeholder description of a runnable - */ -const _getDescriptionFromRunnable = ( - schema: RunInput -): string => { - return `Takes ${JSON.stringify(schema, null, 2)}`; -}; - /** * Given a runnable and a Zod schema, convert the runnable to a tool. * @@ -2860,7 +2850,7 @@ const _getDescriptionFromRunnable = ( * @param {Runnable} runnable The runnable to convert to a tool. * @param fields * @param {string | undefined} [fields.name] The name of the tool. If not provided, it will default to the name of the runnable. - * @param {string | undefined} [fields.description] The description of the tool. If not provided, it will default to `Takes {schema}` where `schema` is a JSON string representation of the input schema. + * @param {string | undefined} [fields.description] The description of the tool. * @param {z.ZodType} [fields.schema] The Zod schema for the input of the tool. Infers the Zod type from the input type of the runnable. * @returns {RunnableToolLike, RunOutput>} An instance of `RunnableToolLike` which is a runnable that can be used as a tool. */ @@ -2872,15 +2862,13 @@ export function convertRunnableToTool( schema: z.ZodType; } ): RunnableToolLike, RunOutput> { - const description = - fields.description ?? - _getDescriptionFromRunnable(zodToJsonSchema(fields.schema)); + const { description, schema } = fields; const name = fields.name ?? runnable.getName(); return new RunnableToolLike, RunOutput>({ name, description, - schema: fields.schema, + schema, bound: runnable, }); } diff --git a/langchain-core/src/runnables/tests/runnable_tools.test.ts b/langchain-core/src/runnables/tests/runnable_tools.test.ts index f97ecb4c3514..938fde9b8c30 100644 --- a/langchain-core/src/runnables/tests/runnable_tools.test.ts +++ b/langchain-core/src/runnables/tests/runnable_tools.test.ts @@ -17,9 +17,6 @@ test("Runnable asTool works", async () => { expect(tool).toBeInstanceOf(RunnableToolLike); expect(tool.schema).toBe(schema); - expect(tool.description).toBe( - `Takes ${JSON.stringify(zodToJsonSchema(schema), null, 2)}` - ); expect(tool.name).toBe(runnable.getName()); }); From 573dcbf59be8084954b550c70d8d617addad8dcf Mon Sep 17 00:00:00 2001 From: bracesproul Date: Thu, 11 Jul 2024 09:13:15 -0700 Subject: [PATCH 14/18] update isRunnableToolLike to check lc_name --- langchain-core/src/runnables/base.ts | 6 ++++-- langchain-core/src/utils/function_calling.ts | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index 4423c0c72132..f68b93b743a2 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -2821,8 +2821,6 @@ export class RunnableToolLike< RunInput extends z.ZodType = z.ZodType, RunOutput = unknown > extends RunnableBinding, RunOutput> { - lc_runnable_tool_like: true; - name: string; description?: string; @@ -2839,6 +2837,10 @@ export class RunnableToolLike< this.description = fields.description; this.schema = fields.schema; } + + static lc_name() { + return "RunnableToolLike"; + } } /** diff --git a/langchain-core/src/utils/function_calling.ts b/langchain-core/src/utils/function_calling.ts index 97c3372a3bdf..311bad9c81d4 100644 --- a/langchain-core/src/utils/function_calling.ts +++ b/langchain-core/src/utils/function_calling.ts @@ -52,7 +52,8 @@ export function isRunnableToolLike(tool?: unknown): tool is RunnableToolLike { return ( tool !== undefined && Runnable.isRunnable(tool) && - "lc_runnable_tool_like" in tool && - tool.lc_runnable_tool_like === true + "lc_name" in tool.constructor && + typeof tool.constructor.lc_name === "function" && + tool.constructor.lc_name() === "RunnableToolLike" ); } From 052fb4f669d29762c8ed738bdb16bcdb639b3447 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Thu, 11 Jul 2024 09:17:51 -0700 Subject: [PATCH 15/18] added standard test --- .../runnables/tests/runnable_tools.test.ts | 1 - .../src/integration_tests/chat_models.ts | 44 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/langchain-core/src/runnables/tests/runnable_tools.test.ts b/langchain-core/src/runnables/tests/runnable_tools.test.ts index 938fde9b8c30..bd9edbfecb86 100644 --- a/langchain-core/src/runnables/tests/runnable_tools.test.ts +++ b/langchain-core/src/runnables/tests/runnable_tools.test.ts @@ -1,5 +1,4 @@ import { z } from "zod"; -import { zodToJsonSchema } from "zod-to-json-schema"; import { RunnableLambda, RunnableToolLike } from "../base.js"; test("Runnable asTool works", async () => { diff --git a/libs/langchain-standard-tests/src/integration_tests/chat_models.ts b/libs/langchain-standard-tests/src/integration_tests/chat_models.ts index ce8ff4c3a96b..d80741d2fed0 100644 --- a/libs/langchain-standard-tests/src/integration_tests/chat_models.ts +++ b/libs/langchain-standard-tests/src/integration_tests/chat_models.ts @@ -12,6 +12,7 @@ import { z } from "zod"; import { StructuredTool } from "@langchain/core/tools"; import { zodToJsonSchema } from "zod-to-json-schema"; import { ChatPromptTemplate } from "@langchain/core/prompts"; +import { RunnableLambda } from "@langchain/core/runnables"; import { BaseChatModelsTests, BaseChatModelsTestsFields, @@ -438,6 +439,42 @@ export abstract class ChatModelIntegrationTests< expect(tool_calls[0].name).toBe("math_addition"); } + async testBindToolsWithRunnableToolLike() { + if (!this.chatModelHasToolCalling) { + console.log("Test requires tool calling. Skipping..."); + return; + } + + const model = new this.Cls(this.constructorArgs); + if (!model.bindTools) { + throw new Error( + "bindTools undefined. Cannot test OpenAI formatted tool calls." + ); + } + + const runnableLike = RunnableLambda.from((_) => { + // no-op + }).asTool({ + name: "math_addition", + description: adderSchema.description, + schema: adderSchema, + }); + + const modelWithTools = model.bindTools([runnableLike]); + + const result: AIMessage = await MATH_ADDITION_PROMPT.pipe( + modelWithTools + ).invoke({ + toolName: "math_addition", + }); + expect(result.tool_calls).toHaveLength(1); + if (!result.tool_calls) { + throw new Error("result.tool_calls is undefined"); + } + const { tool_calls } = result; + expect(tool_calls[0].name).toBe("math_addition"); + } + /** * Run all unit tests for the chat model. * Each test is wrapped in a try/catch block to prevent the entire test suite from failing. @@ -531,6 +568,13 @@ export abstract class ChatModelIntegrationTests< console.error("testBindToolsWithOpenAIFormattedTools failed", e); } + try { + await this.testBindToolsWithRunnableToolLike(); + } catch (e: any) { + allTestsPassed = false; + console.error("testBindToolsWithRunnableToolLike failed", e); + } + return allTestsPassed; } } From 6c36bd7cddcde073b95226e89aa3488ac939c9f1 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Thu, 11 Jul 2024 10:02:31 -0700 Subject: [PATCH 16/18] cr --- langchain-core/src/utils/function_calling.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/langchain-core/src/utils/function_calling.ts b/langchain-core/src/utils/function_calling.ts index 311bad9c81d4..7cdf016be8a0 100644 --- a/langchain-core/src/utils/function_calling.ts +++ b/langchain-core/src/utils/function_calling.ts @@ -4,8 +4,8 @@ import { FunctionDefinition, ToolDefinition } from "../language_models/base.js"; import { Runnable, RunnableToolLike } from "../runnables/base.js"; /** - * Formats a `StructuredTool` instance into a format that is compatible - * with OpenAI function calling. It uses the `zodToJsonSchema` + * Formats a `StructuredTool` or `RunnableToolLike` instance into a format + * that is compatible with OpenAI function calling. It uses the `zodToJsonSchema` * function to convert the schema of the `StructuredTool` into a JSON * schema, which is then used as the parameters for the OpenAI function. */ @@ -20,10 +20,10 @@ export function convertToOpenAIFunction( } /** - * Formats a `StructuredTool` instance into a format that is compatible - * with OpenAI tool calling. It uses the `zodToJsonSchema` - * function to convert the schema of the `StructuredTool` into a JSON - * schema, which is then used as the parameters for the OpenAI tool. + * Formats a `StructuredTool` or `RunnableToolLike` instance into a + * format that is compatible with OpenAI tool calling. It uses the + * `zodToJsonSchema` function to convert the schema of the `StructuredTool` + * into a JSON schema, which is then used as the parameters for the OpenAI tool. */ export function convertToOpenAITool( // eslint-disable-next-line @typescript-eslint/no-explicit-any From 4f09f43285ffc1a5ed87a4648f95756c835c4392 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Thu, 11 Jul 2024 10:13:54 -0700 Subject: [PATCH 17/18] default to zod description --- langchain-core/src/runnables/base.ts | 8 ++++---- .../runnables/tests/runnable_tools.test.ts | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index f68b93b743a2..ac099057ffc3 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -1087,7 +1087,7 @@ export abstract class Runnable< * * @param fields * @param {string | undefined} [fields.name] The name of the tool. If not provided, it will default to the name of the runnable. - * @param {string | undefined} [fields.description] The description of the tool. + * @param {string | undefined} [fields.description] The description of the tool. Falls back to the description on the Zod schema if not provided, or undefined if neither are provided. * @param {z.ZodType} [fields.schema] The Zod schema for the input of the tool. Infers the Zod type from the input type of the runnable. * @returns {RunnableToolLike, RunOutput>} An instance of `RunnableToolLike` which is a runnable that can be used as a tool. */ @@ -2852,7 +2852,7 @@ export class RunnableToolLike< * @param {Runnable} runnable The runnable to convert to a tool. * @param fields * @param {string | undefined} [fields.name] The name of the tool. If not provided, it will default to the name of the runnable. - * @param {string | undefined} [fields.description] The description of the tool. + * @param {string | undefined} [fields.description] The description of the tool. Falls back to the description on the Zod schema if not provided, or undefined if neither are provided. * @param {z.ZodType} [fields.schema] The Zod schema for the input of the tool. Infers the Zod type from the input type of the runnable. * @returns {RunnableToolLike, RunOutput>} An instance of `RunnableToolLike` which is a runnable that can be used as a tool. */ @@ -2864,13 +2864,13 @@ export function convertRunnableToTool( schema: z.ZodType; } ): RunnableToolLike, RunOutput> { - const { description, schema } = fields; const name = fields.name ?? runnable.getName(); + const description = fields.description ?? fields.schema.description; return new RunnableToolLike, RunOutput>({ name, description, - schema, + schema: fields.schema, bound: runnable, }); } diff --git a/langchain-core/src/runnables/tests/runnable_tools.test.ts b/langchain-core/src/runnables/tests/runnable_tools.test.ts index bd9edbfecb86..4c16aea077c7 100644 --- a/langchain-core/src/runnables/tests/runnable_tools.test.ts +++ b/langchain-core/src/runnables/tests/runnable_tools.test.ts @@ -118,3 +118,22 @@ test("asTool can take a single string input", async () => { const result = await asTool.invoke("b"); expect(result).toBe("baz"); }); + +test("Runnable asTool uses Zod schema description if not provided", async () => { + const description = "Test schema"; + const schema = z + .object({ + foo: z.string(), + }) + .describe(description); + const runnable = RunnableLambda.from, string>( + (input, config) => { + return `${input.foo}${config?.configurable.foo}`; + } + ); + const tool = runnable.asTool({ + schema, + }); + + expect(tool.description).toBe(description); +}); From 9188bb59e09ad9a3abc53831023feeb916325e46 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Thu, 11 Jul 2024 12:40:23 -0700 Subject: [PATCH 18/18] fix some jsdoc --- langchain-core/src/utils/function_calling.ts | 25 +++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/langchain-core/src/utils/function_calling.ts b/langchain-core/src/utils/function_calling.ts index 7cdf016be8a0..cafc4c268e24 100644 --- a/langchain-core/src/utils/function_calling.ts +++ b/langchain-core/src/utils/function_calling.ts @@ -6,8 +6,11 @@ import { Runnable, RunnableToolLike } from "../runnables/base.js"; /** * Formats a `StructuredTool` or `RunnableToolLike` instance into a format * that is compatible with OpenAI function calling. It uses the `zodToJsonSchema` - * function to convert the schema of the `StructuredTool` into a JSON - * schema, which is then used as the parameters for the OpenAI function. + * function to convert the schema of the `StructuredTool` or `RunnableToolLike` + * into a JSON schema, which is then used as the parameters for the OpenAI function. + * + * @param {StructuredToolInterface | RunnableToolLike} tool The tool to convert to an OpenAI function. + * @returns {FunctionDefinition} The inputted tool in OpenAI function format. */ export function convertToOpenAIFunction( tool: StructuredToolInterface | RunnableToolLike @@ -23,7 +26,11 @@ export function convertToOpenAIFunction( * Formats a `StructuredTool` or `RunnableToolLike` instance into a * format that is compatible with OpenAI tool calling. It uses the * `zodToJsonSchema` function to convert the schema of the `StructuredTool` - * into a JSON schema, which is then used as the parameters for the OpenAI tool. + * or `RunnableToolLike` into a JSON schema, which is then used as the + * parameters for the OpenAI tool. + * + * @param {StructuredToolInterface | Record | RunnableToolLike} tool The tool to convert to an OpenAI tool. + * @returns {ToolDefinition} The inputted tool in OpenAI tool format. */ export function convertToOpenAITool( // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -38,6 +45,12 @@ export function convertToOpenAITool( return tool as ToolDefinition; } +/** + * Confirm whether the inputted tool is an instance of `StructuredToolInterface`. + * + * @param {StructuredToolInterface | Record | undefined} tool The tool to check if it is an instance of `StructuredToolInterface`. + * @returns {tool is StructuredToolInterface} Whether the inputted tool is an instance of `StructuredToolInterface`. + */ export function isStructuredTool( // eslint-disable-next-line @typescript-eslint/no-explicit-any tool?: StructuredToolInterface | Record @@ -48,6 +61,12 @@ export function isStructuredTool( ); } +/** + * Confirm whether the inputted tool is an instance of `RunnableToolLike`. + * + * @param {unknown | undefined} tool The tool to check if it is an instance of `RunnableToolLike`. + * @returns {tool is RunnableToolLike} Whether the inputted tool is an instance of `RunnableToolLike`. + */ export function isRunnableToolLike(tool?: unknown): tool is RunnableToolLike { return ( tool !== undefined &&