Skip to content

Commit 0b08580

Browse files
committed
More specific llm tools
1 parent 4367614 commit 0b08580

File tree

8 files changed

+365
-18
lines changed

8 files changed

+365
-18
lines changed

package.json

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,10 +1465,10 @@
14651465
],
14661466
"languageModelTools": [
14671467
{
1468-
"name": "python_environment",
1468+
"name": "get_python_environment_info",
14691469
"displayName": "Get Python Environment Information",
14701470
"userDescription": "%python.languageModelTools.python_environment.userDescription%",
1471-
"modelDescription": "Provides details about the Python environment for a specified file or workspace, including environment type, Python version, run command, and installed packages with their versions. Use this tool to determine the correct command for executing Python code in this workspace.",
1471+
"modelDescription": "This tool will retrieve the details of the Python Environment for the specified file or workspace. The details returned include the 1. Type of Environment (conda, venv, etec), 2. Version of Python, 3. this tool gets details about the Python environment including environment type, Python version, command used to execute Python, and a list of all installed packages with their versions. Use this tool to determine the correct command for executing Python in a terminal.",
14721472
"toolReferenceName": "pythonGetEnvironmentInfo",
14731473
"tags": [
14741474
"ms-python.python"
@@ -1487,6 +1487,56 @@
14871487
"resourcePath"
14881488
]
14891489
},
1490+
"when": "false"
1491+
},
1492+
{
1493+
"name": "get_python_executable",
1494+
"displayName": "Get Python Executable",
1495+
"userDescription": "%python.languageModelTools.get_python_executable.userDescription%",
1496+
"modelDescription": "This tool will retrieve the details of the Python Environment for the specified file or workspace. ALWAYS use this tool before executing any Python command in the terminal. This tool returns the details of how to construct the fully qualified path and or command including details such as arguments required to run Python in a terminal. Note: Instead of executing `python --version` or `python -c 'import sys; print(sys.executable)'`, use this tool to get the Python executable path to replace the `python` command. E.g. instead of using `python -c 'import sys; print(sys.executable)'`, use this tool to build the command `conda run -n <env_name> -c 'import sys; print(sys.executable)'`.",
1497+
"toolReferenceName": "pythonExecutableCommand",
1498+
"tags": [
1499+
"ms-python.python"
1500+
],
1501+
"icon": "$(files)",
1502+
"canBeReferencedInPrompt": true,
1503+
"inputSchema": {
1504+
"type": "object",
1505+
"properties": {
1506+
"resourcePath": {
1507+
"type": "string"
1508+
}
1509+
},
1510+
"description": "The path to the Python file or workspace to get the environment information for.",
1511+
"required": [
1512+
"resourcePath"
1513+
]
1514+
},
1515+
"when": "!pythonEnvExtensionInstalled"
1516+
},
1517+
{
1518+
"name": "list_python_packages",
1519+
"displayName": "List Python Packages",
1520+
"userDescription": "%python.languageModelTools.list_python_packages.userDescription%",
1521+
"modelDescription": "This tool will retrieve the list of all installed packages installed in a Python Environment for the specified file or workspace. ALWAYS use this tool instead of executing Python command in the terminal to fetch the list of installed packages. WARNING: Packages installed can change over time, hence the list of packages returned by this tool may not be accurate. Use this tool to get the list of installed packages in a Python environment.",
1522+
"toolReferenceName": "listPythonPackages",
1523+
"tags": [
1524+
"ms-python.python"
1525+
],
1526+
"icon": "$(files)",
1527+
"canBeReferencedInPrompt": true,
1528+
"inputSchema": {
1529+
"type": "object",
1530+
"properties": {
1531+
"resourcePath": {
1532+
"type": "string"
1533+
}
1534+
},
1535+
"description": "The path to the Python file or workspace to get the environment information for.",
1536+
"required": [
1537+
"resourcePath"
1538+
]
1539+
},
14901540
"when": "!pythonEnvExtensionInstalled"
14911541
},
14921542
{

package.nls.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
{
22
"python.command.python.startTerminalREPL.title": "Start Terminal REPL",
3-
"python.languageModelTools.python_environment.userDescription": "Get Python environment info for a file or path, including version, packages, and the command to run it.",
4-
"python.languageModelTools.python_install_package.userDescription": "Installs Python packages in the given workspace.",
3+
"python.languageModelTools.python_environment.userDescription": "Get Python Environment info for a file or path, including version, packages, and the command to run it.",
4+
"python.languageModelTools.python_install_package.userDescription": "Installs Python packages in a Python Environment.",
5+
"python.languageModelTools.get_python_executable.userDescription": "Get executable info for a Python Environment",
6+
"python.languageModelTools.list_python_packages.userDescription": "Get a list of all installed packages in a Python Environment.",
57
"python.command.python.startNativeREPL.title": "Start Native Python REPL",
68
"python.command.python.createEnvironment.title": "Create Environment...",
79
"python.command.python.createNewFile.title": "New Python File",
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import {
5+
CancellationError,
6+
CancellationToken,
7+
l10n,
8+
LanguageModelTextPart,
9+
LanguageModelTool,
10+
LanguageModelToolInvocationOptions,
11+
LanguageModelToolInvocationPrepareOptions,
12+
LanguageModelToolResult,
13+
PreparedToolInvocation,
14+
Uri,
15+
} from 'vscode';
16+
import { PythonExtension, ResolvedEnvironment } from '../api/types';
17+
import { IServiceContainer } from '../ioc/types';
18+
import { ICodeExecutionService } from '../terminals/types';
19+
import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution';
20+
import { getEnvDisplayName, isCondaEnv, raceCancellationError } from './utils';
21+
import { resolveFilePath } from './utils';
22+
import { traceError } from '../logging';
23+
import { ITerminalHelper, TerminalShellType } from '../common/terminal/types';
24+
import { IDiscoveryAPI } from '../pythonEnvironments/base/locator';
25+
import { Conda } from '../pythonEnvironments/common/environmentManagers/conda';
26+
27+
export interface IResourceReference {
28+
resourcePath: string;
29+
}
30+
31+
export class GetExecutableTool implements LanguageModelTool<IResourceReference> {
32+
private readonly terminalExecutionService: TerminalCodeExecutionProvider;
33+
private readonly terminalHelper: ITerminalHelper;
34+
public static readonly toolName = 'get_python_executable';
35+
constructor(
36+
private readonly api: PythonExtension['environments'],
37+
private readonly serviceContainer: IServiceContainer,
38+
private readonly discovery: IDiscoveryAPI,
39+
) {
40+
this.terminalExecutionService = this.serviceContainer.get<TerminalCodeExecutionProvider>(
41+
ICodeExecutionService,
42+
'standard',
43+
);
44+
this.terminalHelper = this.serviceContainer.get<ITerminalHelper>(ITerminalHelper);
45+
}
46+
async invoke(
47+
options: LanguageModelToolInvocationOptions<IResourceReference>,
48+
token: CancellationToken,
49+
): Promise<LanguageModelToolResult> {
50+
const resourcePath = resolveFilePath(options.input.resourcePath);
51+
52+
try {
53+
// environment
54+
const envPath = this.api.getActiveEnvironmentPath(resourcePath);
55+
const environment = await raceCancellationError(this.api.resolveEnvironment(envPath), token);
56+
if (!environment || !environment.version) {
57+
throw new Error('No environment found for the provided resource path: ' + resourcePath.fsPath);
58+
}
59+
const runCommand = await raceCancellationError(this.getTerminalCommand(environment, resourcePath), token);
60+
61+
const message = [
62+
`Following is the information about the Python environment:`,
63+
`1. Environment Type: ${environment.environment?.type || 'unknown'}`,
64+
`2. Version: ${environment.version.sysVersion || 'unknown'}`,
65+
'',
66+
`3. Command Prefix to run Python in a terminal is: \`${runCommand}\``,
67+
`Instead of running \`Python sample.py\` in the terminal, you will now run: \`${runCommand} sample.py\``,
68+
`Similarly instead of running \`Python -c "import sys;...."\` in the terminal, you will now run: \`${runCommand} -c "import sys;...."\``,
69+
];
70+
return new LanguageModelToolResult([new LanguageModelTextPart(message.join('\n'))]);
71+
} catch (error) {
72+
if (error instanceof CancellationError) {
73+
throw error;
74+
}
75+
traceError('Error while getting environment information', error);
76+
const errorMessage: string = `An error occurred while fetching environment information: ${error}`;
77+
return new LanguageModelToolResult([new LanguageModelTextPart(errorMessage)]);
78+
}
79+
}
80+
81+
private async getTerminalCommand(environment: ResolvedEnvironment, resource: Uri) {
82+
let cmd: { command: string; args: string[] };
83+
if (isCondaEnv(environment)) {
84+
cmd =
85+
(await this.getCondaRunCommand(environment)) ||
86+
(await this.terminalExecutionService.getExecutableInfo(resource));
87+
} else {
88+
cmd = await this.terminalExecutionService.getExecutableInfo(resource);
89+
}
90+
return this.terminalHelper.buildCommandForTerminal(TerminalShellType.other, cmd.command, cmd.args);
91+
}
92+
93+
private async getCondaRunCommand(environment: ResolvedEnvironment) {
94+
if (!environment.executable.uri) {
95+
return;
96+
}
97+
const conda = await Conda.getConda();
98+
if (!conda) {
99+
return;
100+
}
101+
const condaEnv = await conda.getCondaEnvironment(environment.executable.uri?.fsPath);
102+
if (!condaEnv) {
103+
return;
104+
}
105+
const cmd = await conda.getRunPythonArgs(condaEnv, true, false);
106+
if (!cmd) {
107+
return;
108+
}
109+
return { command: cmd[0], args: cmd.slice(1) };
110+
}
111+
112+
async prepareInvocation?(
113+
options: LanguageModelToolInvocationPrepareOptions<IResourceReference>,
114+
token: CancellationToken,
115+
): Promise<PreparedToolInvocation> {
116+
const resourcePath = resolveFilePath(options.input.resourcePath);
117+
const envName = await raceCancellationError(getEnvDisplayName(this.discovery, resourcePath, this.api), token);
118+
return {
119+
invocationMessage: envName
120+
? l10n.t('Fetching Python executable information for {0}', envName)
121+
: l10n.t('Fetching Python executable information'),
122+
};
123+
}
124+
}

src/client/chat/getPythonEnvTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class GetEnvironmentInfoTool implements LanguageModelTool<IResourceRefere
4242
private readonly terminalExecutionService: TerminalCodeExecutionProvider;
4343
private readonly pythonExecFactory: IPythonExecutionFactory;
4444
private readonly processServiceFactory: IProcessServiceFactory;
45-
public static readonly toolName = 'python_environment';
45+
public static readonly toolName = 'get_python_environment_info';
4646
constructor(
4747
private readonly api: PythonExtension['environments'],
4848
private readonly serviceContainer: IServiceContainer,

src/client/chat/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
import { commands, extensions, lm } from 'vscode';
55
import { PythonExtension } from '../api/types';
66
import { IServiceContainer } from '../ioc/types';
7-
import { GetEnvironmentInfoTool } from './getPythonEnvTool';
87
import { InstallPackagesTool } from './installPackagesTool';
98
import { IExtensionContext } from '../common/types';
109
import { DisposableStore } from '../common/utils/resourceLifecycle';
1110
import { ENVS_EXTENSION_ID } from '../envExt/api.internal';
1211
import { IDiscoveryAPI } from '../pythonEnvironments/base/locator';
12+
import { ListPythonPackagesTool } from './listPackagesTool';
13+
import { GetExecutableTool } from './getExecutableTool';
14+
import { GetEnvironmentInfoTool } from './getPythonEnvTool';
1315

1416
export function registerTools(
1517
context: IExtensionContext,
@@ -28,6 +30,18 @@ export function registerTools(
2830
ourTools.add(
2931
lm.registerTool(GetEnvironmentInfoTool.toolName, new GetEnvironmentInfoTool(environmentsApi, serviceContainer)),
3032
);
33+
ourTools.add(
34+
lm.registerTool(
35+
GetExecutableTool.toolName,
36+
new GetExecutableTool(environmentsApi, serviceContainer, discoverApi),
37+
),
38+
);
39+
ourTools.add(
40+
lm.registerTool(
41+
ListPythonPackagesTool.toolName,
42+
new ListPythonPackagesTool(environmentsApi, serviceContainer, discoverApi),
43+
),
44+
);
3145
ourTools.add(
3246
lm.registerTool(
3347
InstallPackagesTool.toolName,

src/client/chat/installPackagesTool.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ import {
1111
LanguageModelToolInvocationPrepareOptions,
1212
LanguageModelToolResult,
1313
PreparedToolInvocation,
14-
Uri,
1514
} from 'vscode';
1615
import { PythonExtension } from '../api/types';
1716
import { IServiceContainer } from '../ioc/types';
18-
import { raceCancellationError } from './utils';
17+
import { getEnvDisplayName, raceCancellationError } from './utils';
1918
import { resolveFilePath } from './utils';
2019
import { IModuleInstaller } from '../common/installer/types';
2120
import { ModuleInstallerType } from '../pythonEnvironments/info';
@@ -120,13 +119,3 @@ export class InstallPackagesTool implements LanguageModelTool<IInstallPackageArg
120119
};
121120
}
122121
}
123-
124-
async function getEnvDisplayName(discovery: IDiscoveryAPI, resource: Uri, api: PythonExtension['environments']) {
125-
try {
126-
const envPath = api.getActiveEnvironmentPath(resource);
127-
const env = await discovery.resolveEnv(envPath.path);
128-
return env?.display || env?.name;
129-
} catch {
130-
return;
131-
}
132-
}

0 commit comments

Comments
 (0)