Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Support sending any variable to a tool's execute function. #3938

Open
ShervK opened this issue Nov 29, 2024 · 4 comments
Open

Support sending any variable to a tool's execute function. #3938

ShervK opened this issue Nov 29, 2024 · 4 comments
Labels
ai/core enhancement New feature or request

Comments

@ShervK
Copy link

ShervK commented Nov 29, 2024

Feature Description

Similar to #3468

When working on agents or tools that require retrieving, saving, or working with a session, you might need to use dynamic IDs or data that were sent with the original API call but not included as an option or argument on the tool call. It would be great if you can extend ToolExecutionOptions and send in your own variables to be used when the tool.execute function is running.

To keep it type safe, you can define a type for the variables you're going to send and add the type as a generic type parameter. So an example might look like:

// set up type
export type ExtendedOptions = {
  chatId: string;
  userId: string;
}

// define your tools
export const fileSearch = tool<ExtendedOptions>({
  description: 'Semantically search for relevant files the user owns',
  parameters: z.object({
    query: z.string().describe('The search query'),
  }),
  execute: async ({ query }, { userId }) => {
    const result = await searchFiles({
      query,
      userId,
    });
    return {
      files: result.files,
    };
  },
});

export const codeExecution = tool<ExtendedOptions>({
  description:
    "Execute code in a sandboxed Python Notebook environment dedicated to the user's project",
  parameters: z.object({
    code: z.string().describe('The code to execute'),
  }),
  execute: async ({ code }, { chatId, userId, messages }) => {
    const codeCells = getAllCodeToolCalls(messages);
    codeCells.push(code);
    const result = await executeCodeInSandbox({
      codeCells,
      userId,
      chatId,
    });
    return {
      result: result.stdout,
    };
  },
});

Then in the API route, you could send in the dynamic data with the tools

    const { chatId } = await request.json();
    const session = await auth();

    // set up the dynamic option object
    const options: ExtendedOptions = {
      chatId: chatId,
      userId: session.user.id,
    };

    // set up the tools with the dynamic options
    const tools = {
      'file-search': {
        ...fileSearch,
        options,
      },
      'code-execution': {
        ...codeExecution,
        options,
      },
    };

    const { result } = streamText(
      // rest of the parameters
      ...
      tools,
    )

This is a pretty basic example, a feature like this would be more useful for agentic workloads where your agents need access to dynamic data, like chatId or userId for example.

Use Cases

No response

Additional context

There is a workaround where you have a function that takes the dynamic data as arguments which then creates the tool. It's doable but becomes harder to manage as your toolset grows.

@ShervK ShervK added the enhancement New feature or request label Nov 29, 2024
@lgrammel
Copy link
Collaborator

You can solve this with closures and factory methods. Why does this need to be part of the framework?

@matthiasbayer
Copy link

A closure is definitely the most straight forward way to solve this and it works well. I'm also not sure if this should get baked into the framework itself, but it might be a common enough problem worth a mention in the docs at least. Wdyt?

@ShervK
Copy link
Author

ShervK commented Dec 3, 2024

Thanks for the feedback—you guys are absolutely right. Reflecting on it, I see now that my suggestion was more about convenience than necessity, and it wouldn't reduce complexity as much as I initially thought.

For context, my thinking was based on scenarios where closures aren't feasible (e.g., separated codebases). But even then, as you pointed out and the note in the original post, factory methods already covers this.

As for the docs, I’m not sure this is common enough to warrant an update, but I’ll leave that to your judgment. I’m fine with closing this issue as is.

@lgrammel
Copy link
Collaborator

lgrammel commented Dec 3, 2024

@ShervK going to leave this open. if we add generic metadata at some point, this could be a nice part of the feature. not a high priority though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ai/core enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants