Skip to content

Commit fffb8f1

Browse files
committed
tool calling loop
1 parent f960f9f commit fffb8f1

3 files changed

Lines changed: 187 additions & 73 deletions

File tree

src/features/chat/chatParticipant.tsx

Lines changed: 140 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,153 @@
1-
2-
import { sendChatParticipantRequest } from "@vscode/chat-extension-utils";
3-
import { traceInfo, traceWarn } from "../../common/logging";
4-
import * as vscode from "vscode";
5-
import { PythonEnvsPrompt } from "./prompts";
6-
import { PromptElementAndProps } from "@vscode/chat-extension-utils/dist/toolsPrompt";
7-
import { ChatEnvironmentErrorInfo, ERROR_CHAT_CONTEXT_QUEUE } from "./send_prompt";
8-
import { PythonHelperChatParticipant } from "./pythonHelperChatParticipant";
1+
import { traceInfo, traceWarn } from '../../common/logging';
2+
import * as vscode from 'vscode';
3+
import { PythonEnvsPrompt, PythonEnvsPromptProps } from './prompts';
4+
import { PythonHelperChatParticipant } from './pythonHelperChatParticipant';
5+
import { PromptElement, renderPrompt, UserMessage } from '@vscode/prompt-tsx';
6+
import { LanguageModelTextPart, LanguageModelToolCallPart } from 'vscode';
7+
import { ToolCallRound, ToolResultMetadata } from '@vscode/chat-extension-utils/dist/toolsPrompt';
98

109
export const CHAT_PARTICIPANT_ID = 'python-helper';
1110
export const CHAT_PARTICIPANT_AT_MENTION = `@${CHAT_PARTICIPANT_ID}`;
1211

12+
export function registerChatParticipant(_context: vscode.ExtensionContext) {
13+
traceInfo('Registering python helper chat participant'); // Log registration start
1314

15+
// Define the request handler for the chat participant
16+
const requestHandler: vscode.ChatRequestHandler = async (
17+
request: vscode.ChatRequest, // prompt made in pip file
18+
chatContext: vscode.ChatContext, // history
19+
stream: vscode.ChatResponseStream,
20+
token: vscode.CancellationToken,
21+
) => {
22+
traceWarn('Python helper chat participant invoked with request:', request); // Log request details
1423

15-
export function registerChatParticipant(
16-
_context: vscode.ExtensionContext,
17-
) {
18-
traceInfo('Registering python helper chat participant');
19-
const participant = vscode.chat.createChatParticipant(CHAT_PARTICIPANT_ID,
20-
async (
21-
request: vscode.ChatRequest,
22-
chatContext: vscode.ChatContext,
23-
stream: vscode.ChatResponseStream,
24-
token: vscode.CancellationToken
25-
) => {
26-
traceWarn('Python helper chat participant invoked with request:', request);
27-
const userPrompt = request.prompt;
28-
29-
30-
const prompt: PromptElementAndProps<PythonEnvsPrompt> = {
31-
promptElement: PythonEnvsPrompt,
32-
props: {
33-
title: 'Python Environment',
34-
description: 'Provide information about the Python environment.',
35-
request: {
36-
prompt: '' + userPrompt + ' What is the current Python environment? What packages are installed?',
37-
},
38-
},
39-
};
40-
41-
const { result } = sendChatParticipantRequest(
42-
request,
43-
chatContext,
44-
{
45-
prompt,
46-
requestJustification: vscode.l10n.t('Tell me about my environment.'),
47-
responseStreamOptions: {
48-
stream,
49-
references: false,
50-
responseText: true,
51-
},
24+
// gather the available tools
25+
const first100tools = vscode.lm.tools.slice(0, 100);
26+
const tools: vscode.LanguageModelChatTool[] = first100tools.map((tool): vscode.LanguageModelChatTool => {
27+
return {
28+
name: tool.name,
29+
description: tool.description,
30+
inputSchema: tool.inputSchema ?? {},
31+
};
32+
});
33+
traceInfo('Tools prepared:', tools); // Log tools
34+
35+
const userPrompt = request.prompt;
36+
traceInfo('User prompt received:', userPrompt); // Log user prompt
37+
38+
const refTools = request.toolReferences;
39+
const refToolInvToken = request.toolInvocationToken;
40+
const refRef = request.references;
41+
const refCom = request.command;
42+
const refPrompt = request.prompt;
43+
let model = request.model;
44+
traceInfo('References received:', refTools, refToolInvToken, refRef, refCom, refPrompt, model); // Log references
45+
46+
// takes the info and creates a prompt using the PythonEnvsPrompt
47+
const result = await renderPrompt<PythonEnvsPromptProps>(
48+
PythonEnvsPrompt, // Extract the constructor PythonEnvsPromptProps
49+
{
50+
title: 'Python Environment',
51+
description: 'Provide information about the Python environment.',
52+
request: {
53+
prompt: '' + userPrompt + ' What is the current Python environment? What packages are installed?',
5254
},
53-
token,
54-
);
55+
},
56+
{ modelMaxPromptTokens: model.maxInputTokens },
57+
model,
58+
);
59+
60+
// result of building the prompt
61+
let messages = result.messages;
62+
63+
const options: vscode.LanguageModelChatRequestOptions = {
64+
justification: 'To make a request to @toolsTSX',
65+
tools: tools,
66+
};
67+
68+
const toolReferences = [...request.toolReferences];
69+
const accumulatedToolResults: Record<string, vscode.LanguageModelToolResult> = {};
70+
const toolCallRounds: ToolCallRound[] = [];
71+
const runWithTools = async (): Promise<void> => {
72+
const requestedTool = toolReferences.shift();
73+
74+
if (requestedTool) {
75+
// NOT WORKING::: If a toolReference is present, force the model to call that tool
76+
options.toolMode = vscode.LanguageModelChatToolMode.Required;
77+
options.tools = vscode.lm.tools.filter((tool) => tool.name === requestedTool.name);
78+
} else {
79+
options.toolMode = undefined;
80+
options.tools = [...tools];
81+
}
82+
console.log('Requested tool:', requestedTool); // Log requested tool
83+
84+
// Send the request to the model
85+
const response = await model.sendRequest(messages, options, token);
86+
traceInfo('Chat participant response sent:', response); // Log response
87+
88+
// Stream the response back to VS Code
89+
let responseStr = '';
90+
const toolCalls: vscode.LanguageModelToolCallPart[] = [];
91+
92+
for await (const chunk of response.stream) {
93+
if (chunk instanceof LanguageModelTextPart) {
94+
stream.markdown(chunk.value);
95+
responseStr += chunk.value; // Accumulate the response string
96+
} else if (chunk instanceof LanguageModelToolCallPart) {
97+
// If the response contains vscode.LanguageModelToolCallPart, then you should re-send the prompt with a ToolCall element for each of those.
98+
console.log('TOOL CALL', chunk);
99+
toolCalls.push(chunk);
100+
}
101+
}
55102

56-
return await result;
57-
}
58-
);
103+
if (toolCalls.length) {
104+
traceInfo('Tool calls detected:', toolCalls); // Log tool calls
105+
106+
// If the model called any tools, then we do another round- render the prompt with those tool calls (rendering the PromptElements will invoke the tools)
107+
// and include the tool results in the prompt for the next request.
108+
toolCallRounds.push({
109+
response: responseStr,
110+
toolCalls,
111+
});
112+
113+
const result = await renderPrompt<PythonEnvsPromptProps>(
114+
PythonEnvsPrompt, // Extract the constructor PythonEnvsPromptProps
115+
{
116+
title: 'Python Environment',
117+
description: 'Provide information about the Python environment.',
118+
request: {
119+
prompt:
120+
'' +
121+
userPrompt +
122+
' What is the current Python environment? What packages are installed?',
123+
},
124+
},
125+
{ modelMaxPromptTokens: model.maxInputTokens },
126+
model,
127+
);
128+
129+
// result of building the prompt
130+
let messages = result.messages;
131+
const toolResultMetadata = result.metadatas.getAll(ToolResultMetadata);
132+
if (toolResultMetadata?.length) {
133+
// Cache tool results for later, so they can be incorporated into later prompts without calling the tool again
134+
toolResultMetadata.forEach((meta) => (accumulatedToolResults[meta.toolCallId] = meta.result));
135+
}
136+
137+
// This loops until the model doesn't want to call any more tools, then the request is done.
138+
return runWithTools();
139+
}
140+
};
141+
await runWithTools(); // Ensure tools are run before proceeding
142+
143+
// end of request handler
144+
};
145+
146+
const participant = vscode.chat.createChatParticipant(CHAT_PARTICIPANT_ID, requestHandler);
59147
participant.iconPath = new vscode.ThemeIcon('python');
60148

149+
traceInfo('Chat participant created and registered'); // Log participant creation
150+
61151
// Register using our singleton manager
62152
return PythonHelperChatParticipant.register(participant, _context);
63-
64-
65153
}
66-

src/features/chat/prompts.tsx

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,50 @@ export interface PythonEnvsPromptProps extends BasePromptElementProps {
1717

1818
export class PythonEnvsPrompt extends PromptElement<PythonEnvsPromptProps, void> {
1919
render(_state: void, _sizing: PromptSizing) {
20+
traceWarn('Render function called'); // Log when render is invoked
2021

22+
// this.props is PythonEnvsPromptProps
2123
const isContext = !ERROR_CHAT_CONTEXT_QUEUE.isEmpty();
22-
if (!isContext) {
23-
traceWarn('No context found for python helper chat participant');
24-
return;
25-
}
24+
traceWarn('Context queue is empty:', !isContext); // Log the state of the context queue
25+
26+
if (!isContext) {
27+
traceWarn('No context found for python helper chat participant');
28+
return;
29+
}
30+
2631
const contextErr: ChatEnvironmentErrorInfo | undefined = ERROR_CHAT_CONTEXT_QUEUE.pop();
32+
traceWarn('Popped context error:', contextErr); // Log the popped context error
33+
2734
if (!contextErr) {
2835
traceWarn('No context error found for python helper chat participant');
2936
return;
3037
}
38+
3139
const attemptedPackages = contextErr.attemptedPackages.join(', ');
3240
const packagesBeforeInstall = contextErr.packagesBeforeInstall.join(', ');
33-
console.log(packagesBeforeInstall)
41+
traceWarn('Attempted packages:', attemptedPackages); // Log attempted packages
42+
traceWarn('Packages before install:', packagesBeforeInstall); // Log packages before install
43+
3444
const envString = contextErr.environment.displayName + ' (' + contextErr.environment.environmentPath + ') ' + contextErr.environment.version;
35-
console.log(envString);
45+
traceWarn('Environment string:', envString); // Log environment string
3646

3747
const rawPrompt = this.props.request.prompt .replace(/^@pythonHelper\s*/, '');
38-
let _errorInfo: ChatEnvironmentErrorInfo | undefined;
39-
try {
40-
_errorInfo = JSON.parse(rawPrompt);
41-
} catch (e) {
42-
// Handle parse error
43-
}
44-
return (
45-
<>
48+
traceWarn('Raw prompt:', rawPrompt); // Log the raw prompt
49+
50+
// let errorInfo: ChatEnvironmentErrorInfo | undefined;
51+
// try {
52+
// errorInfo = JSON.stringify(rawPrompt);
53+
// traceWarn('Parsed error info:', errorInfo); // Log parsed error info
54+
// } catch (e) {
55+
// console.error('Error parsing raw prompt the prompt:', rawPrompt);
56+
// traceWarn('Error parsing raw prompt:', e); // Log parsing error
57+
// }
58+
const msg = <>
4659
<UserMessage priority={100}>🚨 **Package Management Error**##<br /> ❗ Error Details<br />``` <br /> {contextErr.errorMessage}<br />```<br />Stack Trace<br />```<br />{contextErr.stackTrace}<br />```<br />## 📝 **Context**<br />**Attempted Packages:**{attemptedPackages}<br /><br /> **Package Manager:** {contextErr.packageManager}<br /> **Environment:** {envString}<br /> **Packages Before Install:** {packagesBeforeInstall}<br />## 🛠️ **How to Diagnose & Fix**<br />1. **Diagnose the error above.**<br />2. **Suggest a fix.**<br />3. Use the following tools where applicable:<br />- `installPythonPackage`: Install a package with a version specification, etc.<br /> -`configurePythonEnvironment`: Create an environment with the given attributes.<br /> If you see one best path forward, start doing that WITHOUT asking the user again. If you see 2 to 3 paths to solve this problem, reply with the two paths like so: `button1: prompt solution` `button2: prompt solution</UserMessage>
4760
</>
48-
)
61+
62+
traceWarn('Returning message final:', msg); // Log the final message to be returned
63+
return msg;
4964

5065
// <UserMessage priority={69}><br /> **Attempted Packages:**{attemptedPackages}<br /> **Package Manager:** {contextErr.packageManager}<br /> **Environment:** {contextErr.environment}<br /> **Packages Before Install:** {contextErr.packagesBeforeInstall}</UserMessage>
5166
// <UserMessage priority={67}><br /> **Environment:** {envString}<br /></UserMessage>

src/features/chat/send_prompt.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// // const token: CancellationToken = new CancellationTokenSource().token;
33

44
import { commands, extensions, l10n, ProgressLocation, ProgressOptions, window } from 'vscode';
5+
import { traceInfo, traceWarn } from '../../common/logging';
56

67
// import {
78
// CancellationToken,
@@ -95,30 +96,41 @@ function isCopilotChatInstalled(): boolean {
9596
return !!extensions.getExtension(COPILOT_CHAT_EXTENSION_ID);
9697
}
9798
export async function sendPromptIfCopilotChatInstalled(prompt: string): Promise<void> {
99+
traceInfo('Checking if Copilot Chat is installed'); // Log check start
100+
98101
const sendPrompt = async () => {
99-
// Artificial delay to work around
100-
// https://github.com/microsoft/vscode-copilot/issues/16541
102+
traceInfo('Preparing to send prompt to Copilot Chat'); // Log prompt preparation
103+
101104
const progressOptions: ProgressOptions = {
102105
location: ProgressLocation.Notification,
103106
title: l10n.t('Analyzing your python environment extension logs...'),
104107
cancellable: false,
105108
};
106109
await window.withProgress(progressOptions, async () => {
110+
traceInfo('Showing progress notification'); // Log progress notification
107111
await new Promise((resolve) => setTimeout(resolve, 5));
108112
});
113+
114+
traceInfo('Executing chat open command with prompt:', prompt); // Log command execution
115+
109116
const abc = await commands.executeCommand('workbench.action.chat.open', {
110117
query: prompt,
111118
mode: 'agent',
112119
});
120+
113121
if (abc) {
114-
console.log('Chat opened successfully');
122+
traceInfo('Chat opened successfully'); // Log success
123+
} else {
124+
traceWarn('Failed to open chat'); // Log failure
115125
}
116126
};
117-
// If the user has Copilot Chat installed, assume they are
118-
// logged in and can receive the 'sendToNewChat' command
127+
119128
if (isCopilotChatInstalled()) {
129+
traceInfo('Copilot Chat is installed, sending prompt'); // Log installation check
120130
await sendPrompt();
121131
return;
132+
} else {
133+
traceWarn('Copilot Chat is not installed'); // Log absence
122134
}
123135
}
124136

0 commit comments

Comments
 (0)