-
Notifications
You must be signed in to change notification settings - Fork 5
Use langgraph (agent) to call tools #110
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
base: main
Are you sure you want to change the base?
Conversation
b9b329f
to
4686a69
Compare
I open this PR to move forward, and to allow working on useful tools in a separate PR. I also wonder if should allow disabling some tools from the settings (in future PR maybe), to avoid displaying them in the list, if a lot are provided by extensions. |
Thanks! I'll have a look locally.
After looking at the screencast, could the list of tools allow for multiple selection? That way users could more easily choose which tools to enable and disable (all could be enabled by default for convenience). |
Currently no, I take a look at it. |
Also wondering what the granularity of tools should be. Maybe it would result in too many entries if each command can become a tool. Instead higher level tools like |
As an alternative to the test tool, maybe we could add a first tool to generate a new notebook? (similar to what Jupyter AI currently provides). Although this tool may be replaced later with a more generic one (for example The tool would make use of existing commands to create the notebook with cells. Although not sure how much work this would require, I think Jupyter AI uses a special prompt to make sure the notebook is well-formed. Otherwise maybe we can have another simpler default tool, that would still perform some useful action. |
That's what I thought too, tools handling several scopes of actions. Indeed it would not be usable if you have to allow each command. |
I updated the default tool. output.webmCurrently it can only use one command:
To make it work properly, we should probably have a subset of commands that do not open popups and select default values instead. |
Nice, this is already looking really good!
Normally there is a set of args we should be able to pass to avoid the dialogs (for example using
Maybe this could also be a good opportunity to improve some of the core commands (the rename command could take an optional argument). |
export const createNotebook = ( | ||
commands: CommandRegistry | ||
): StructuredToolInterface => { | ||
return tool( | ||
async ({ command, args }) => { | ||
let result: any = 'No command called'; | ||
if (command === 'notebook:create-new') { | ||
result = await commands.execute( | ||
command, | ||
args as ReadonlyPartialJSONObject | ||
); | ||
} | ||
const output = ` | ||
The test tool has been called, with the following query: "${command}" | ||
The args for the commands where ${JSON.stringify(args)} | ||
The result of the command (if called) is "${result}" | ||
`; | ||
return output; | ||
}, | ||
{ | ||
name: 'createNotebook', | ||
description: 'Run jupyterlab command to create a notebook', | ||
schema: z.object({ | ||
command: z.string().describe('The Jupyterlab command id to execute'), | ||
args: z | ||
.object({}) | ||
.passthrough() | ||
.describe('The argument for the command') | ||
}) | ||
} | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could pass the kernelName
with something like the following:
export const createNotebook = ( | |
commands: CommandRegistry | |
): StructuredToolInterface => { | |
return tool( | |
async ({ command, args }) => { | |
let result: any = 'No command called'; | |
if (command === 'notebook:create-new') { | |
result = await commands.execute( | |
command, | |
args as ReadonlyPartialJSONObject | |
); | |
} | |
const output = ` | |
The test tool has been called, with the following query: "${command}" | |
The args for the commands where ${JSON.stringify(args)} | |
The result of the command (if called) is "${result}" | |
`; | |
return output; | |
}, | |
{ | |
name: 'createNotebook', | |
description: 'Run jupyterlab command to create a notebook', | |
schema: z.object({ | |
command: z.string().describe('The Jupyterlab command id to execute'), | |
args: z | |
.object({}) | |
.passthrough() | |
.describe('The argument for the command') | |
}) | |
} | |
); | |
}; | |
export const createNotebook = ( | |
commands: CommandRegistry | |
): StructuredToolInterface => { | |
return tool( | |
async ({ command, args, kernelName }) => { | |
let result: any = 'No command called'; | |
if (command === 'notebook:create-new') { | |
// Create args object with kernelName if provided | |
const commandArgs: ReadonlyPartialJSONObject = { | |
...args, | |
...(kernelName && { kernelName }) | |
}; | |
result = await commands.execute(command, commandArgs); | |
} | |
const output = ` | |
The test tool has been called, with the following query: "${command}" | |
The args for the commands where ${JSON.stringify(args)} | |
${kernelName ? `The kernel name specified: "${kernelName}"` : ''} | |
The result of the command (if called) is "${result}" | |
`; | |
return output; | |
}, | |
{ | |
name: 'createNotebook', | |
description: 'Run jupyterlab command to create a notebook', | |
schema: z.object({ | |
command: z.string().describe('The Jupyterlab command id to execute'), | |
args: z | |
.object({}) | |
.passthrough() | |
.describe('The argument for the command'), | |
kernelName: z | |
.string() | |
.optional() | |
.default('python3') | |
.describe( | |
'The name of the kernel to use for the notebook. If not specified, will use the default kernel.' | |
) | |
}) | |
} | |
); | |
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although instead of hardcoding python3
we could also get the default kernel name from app.serviceManager.kernelspecs
The extension seems to be building fine locally, although it leaves some kind of error / warnings in the logs:
|
src/provider.ts
Outdated
Private.setAgent(null); | ||
return; | ||
} | ||
chatModel.bindTools?.(tools); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I'm looking into this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the PR.
- the
allowTools
setting is now in the provider registry settings instead of chat settings, for consistency (the agent is built there). - the set tools button is now disabled if the provider cannot handle tools, or if the provider is not set (e.g. no APIkey).
Unfortunately, it seems that it does not work at the model level. The tools are still available for example with Claude 2.1. When sending a message, we receive an error message ('claude-2.1' does not support tool use via API fields.
)
Yes, I saw it, it seems to be a issue with the packaging of Maybe we can handle these warnings with a webpack setting... |
Yeah providing a custom webpack config to ignore these warnings seem to do the trick for now: brichet#2 |
Thanks @brichet! Just tried quickly again with the latest state and it looks like a good start 👍 Spotted a few small things. The tool greyed out icon looks like the feature is not enabled and would not allow selecting tools, although it does. Maybe it should have the same color as the send button? jupyterlite-ai-tool-icon.mp4Ideally we should be able to see the list of arguments instead of And there is likely something to do with the message history too, as the model seems to be taking into account previous requests: But I guess it would be ok to fix that separately too. |
The idea was to easily catch if a tool is set or not (blue if tools are selected, grey otherwise).
I wonder if it would help to update the prompt with something like: |
Trying to rebase to resolve the conflicts, I can't build anymore: node_modules/@langchain/langgraph/dist/graph/messages_annotation.d.ts:82:15 - error TS2589: Type instantiation is excessively deep and possibly infinite.
82 messages: import("./zod/meta.js").ReducedZodChannel<z.ZodType<BaseMessage[], z.ZodTypeDef, BaseMessage[]>, import("@langchain/core/utils/types").InteropZodType<Messages>>;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/tools/create-notebook.ts:9:3 - error TS2589: Type instantiation is excessively deep and possibly infinite.
9 return tool(
~~~~~~
src/tools/create-notebook.ts:9:10 - error TS2589: Type instantiation is excessively deep and possibly infinite.
9 return tool(
~~~~~
10 async ({ command, args }) => {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...
35 }
~~~~~
36 );
~~~
Found 3 errors in 2 files.
Errors Files
1 node_modules/@langchain/langgraph/dist/graph/messages_annotation.d.ts:82
2 src/tools/create-notebook.ts:9 Restoring |
This PR adds
langgraph
, which allows to call tools that can interact with Jupyterlab API.As an example, the PR includes a default tools, that only shows a Jupyterlab modal with the response from the model.
UPDATED RECORDING
output.webm
record-2025-06-26_17.23.44.webmCurrent state
langgraph
, and call it instead of only the LLMJupyternaut
) and from the tool (user is the tool name)To do, To discuss