Skip to content

Commit

Permalink
figured out generics typing almost
Browse files Browse the repository at this point in the history
  • Loading branch information
bracesproul committed Jul 10, 2024
1 parent 27fe0ff commit 63ada8e
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 12 deletions.
24 changes: 12 additions & 12 deletions langchain-core/src/runnables/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1080,12 +1080,12 @@ export abstract class Runnable<
});
}

asTool(fields: {
asTool<T = unknown>(fields: {
name?: string;
description?: string;
schema: z.ZodAny;
}): RunnableToolLike<z.ZodAny, string> {
return convertRunnableToTool(this, fields);
schema: z.ZodType<T>;
}): RunnableToolLike<z.ZodType<T>, string> {
return convertRunnableToTool<T>(this, fields);
}
}

Expand Down Expand Up @@ -2808,7 +2808,7 @@ export interface RunnableToolLikeFields<RunInput, RunOutput> {
export class RunnableToolLike<
RunInput extends z.ZodType = z.ZodType,
RunOutput = string
> extends RunnableLambda<RunInput, RunOutput> {
> extends RunnableLambda<z.infer<RunInput>, RunOutput> {
description?: string;

schema: RunInput;
Expand All @@ -2835,22 +2835,22 @@ const _getDescriptionFromRunnable = (schema: Record<string, any>): string => {
/**
* Given a runnable and a Zod schema, convert the runnable to a tool.
*
* @template RunInput The input schema for the runnable.
* @param {Runnable<RunInput, string>} 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<RunInput> | z.ZodString} [fields.schema] The Zod schema for the input of the tool. Either the schema itself or a ZodString.
* @returns {DynamicTool | DynamicStructuredTool<z.ZodType<RunInput>>} The tool created from the runnable. DynamicTool if the schema is a ZodString, DynamicStructuredTool if the schema is a ZodType.
* @param {z.ZodType<RunInput>} [fields.schema] The Zod schema for the input of the tool.
* @returns {RunnableToolLike<z.ZodType<RunInput>, string>} An instance of `RunnableToolLike` which is a runnable that can be used as a tool.
*/
export function convertRunnableToTool(
export function convertRunnableToTool<RunInput>(
runnable: Runnable,
fields: {
name?: string;
description?: string;
schema: z.ZodAny;
schema: z.ZodType<RunInput>;
}
): RunnableToolLike<z.ZodAny, string> {
): RunnableToolLike<z.ZodType<RunInput>, string> {
const description =
fields.description ??
_getDescriptionFromRunnable(zodToJsonSchema(fields.schema));
Expand Down
73 changes: 73 additions & 0 deletions langchain-core/src/runnables/tests/runnable_tools.test.ts
Original file line number Diff line number Diff line change
@@ -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<z.infer<typeof schema>, 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<z.infer<typeof schema>, 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<z.infer<typeof schema>, 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");
});

0 comments on commit 63ada8e

Please sign in to comment.