Comments, cleanup

This commit is contained in:
Rob Lourens
2024-10-28 17:40:13 -07:00
parent fa7a0d8d9d
commit f61c9032a6
3 changed files with 42 additions and 30 deletions

View File

@ -1,6 +1,6 @@
import * as vscode from 'vscode';
import { FindFilesTool, RunInTerminalTool, TabCountTool } from './tools';
import { registerToolUserChatParticipant } from './tsxParticipant';
import { registerToolUserChatParticipant } from './participant';
export function activate(context: vscode.ExtensionContext) {
registerChatTools(context);

View File

@ -35,18 +35,12 @@ export function registerToolUserChatParticipant(context: vscode.ExtensionContext
model = models[0];
}
const allTools = vscode.lm.tools.map((tool): vscode.LanguageModelChatTool => {
return {
name: tool.name,
description: tool.description,
parametersSchema: tool.parametersSchema ?? {}
};
});
const allTools = vscode.lm.tools;
const options: vscode.LanguageModelChatRequestOptions = {
justification: 'To make a request to @toolsTSX',
};
// Render the initial prompt
let { messages, references } = await renderPrompt(
ToolUserPrompt,
{
@ -66,19 +60,22 @@ export function registerToolUserChatParticipant(context: vscode.ExtensionContext
const toolReferences = [...request.toolReferences];
const accumulatedToolResults: Record<string, vscode.LanguageModelToolResult> = {};
const toolCallRounds: ToolCallRound[] = [];
const runWithFunctions = async (): Promise<void> => {
const runWithTools = async (): Promise<void> => {
// If a toolReference is present, force the model to call that tool
const requestedTool = toolReferences.shift();
if (requestedTool) {
options.toolMode = vscode.LanguageModelChatToolMode.Required;
options.tools = allTools.filter(tool => tool.name === requestedTool.name);
} else {
options.toolMode = undefined;
options.tools = allTools;
options.tools = [...allTools];
}
const toolCalls: vscode.LanguageModelToolCallPart[] = [];
// Send the request to the LanguageModelChat
const response = await model.sendRequest(messages, options, token);
// Stream text output and collect tool calls from the response
const toolCalls: vscode.LanguageModelToolCallPart[] = [];
let responseStr = '';
for await (const part of response.stream) {
if (part instanceof vscode.LanguageModelTextPart) {
@ -90,6 +87,8 @@ export function registerToolUserChatParticipant(context: vscode.ExtensionContext
}
if (toolCalls.length) {
// 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)
// and include the tool results in the prompt for the next request.
toolCallRounds.push({
response: responseStr,
toolCalls
@ -107,17 +106,20 @@ export function registerToolUserChatParticipant(context: vscode.ExtensionContext
messages = result.messages;
const toolResultMetadata = result.metadatas.getAll(ToolResultMetadata)
if (toolResultMetadata?.length) {
// Cache tool results for later, so they can be incorporated into later prompts without calling the tool again
toolResultMetadata.forEach(meta => accumulatedToolResults[meta.toolCallId] = meta.result);
}
return runWithFunctions();
// This loops until the model doesn't want to call any more tools, then the request is done.
return runWithTools();
}
};
await runWithFunctions();
await runWithTools();
return {
metadata: {
// Return tool call metadata so it can be used in prompt history on the next request
toolCallsMetadata: {
toolCallResults: accumulatedToolResults,
toolCallRounds
@ -126,7 +128,7 @@ export function registerToolUserChatParticipant(context: vscode.ExtensionContext
}
};
const toolUser = vscode.chat.createChatParticipant('chat-tools-sample.tools2', handler);
const toolUser = vscode.chat.createChatParticipant('chat-tools-sample.tools', handler);
toolUser.iconPath = new vscode.ThemeIcon('tools');
context.subscriptions.push(toolUser);
}

View File

@ -16,7 +16,7 @@ import {
TextChunk,
} from '@vscode/prompt-tsx';
import * as vscode from 'vscode';
import { isTsxToolUserMetadata } from './tsxParticipant';
import { isTsxToolUserMetadata } from './participant';
import { PromptElementJSON } from '@vscode/prompt-tsx/dist/base/jsonTypes';
import { ToolResult } from '@vscode/prompt-tsx/dist/base/promptElements';
@ -50,10 +50,8 @@ export class ToolUserPrompt extends PromptElement<ToolUserProps, void> {
- Don't make assumptions about the situation- gather context first, then
perform the task or answer the question. <br />
- Don't ask the user for confirmation to use tools, just use them.
<br />- After editing a file, DO NOT show the user a codeblock with the
edit or new file contents. Assume that the user can see the result.
</UserMessage>
<History context={this.props.context} priority={10}></History>
<History context={this.props.context} priority={10} />
<PromptReferences
references={this.props.request.references}
priority={20}
@ -62,8 +60,7 @@ export class ToolUserPrompt extends PromptElement<ToolUserProps, void> {
<ToolCalls
toolCallRounds={this.props.toolCallRounds}
toolInvocationToken={this.props.request.toolInvocationToken}
toolCallResults={this.props.toolCallResults}>
</ToolCalls>
toolCallResults={this.props.toolCallResults}/>
</>
);
}
@ -77,13 +74,16 @@ interface ToolCallsProps extends BasePromptElementProps {
const dummyCancellationToken: vscode.CancellationToken = new vscode.CancellationTokenSource().token;
/**
* Render a set of tool calls, which look like an AssistantMessage with a set of tool calls followed by the associated UserMessages containing results.
*/
class ToolCalls extends PromptElement<ToolCallsProps, void> {
async render(state: void, sizing: PromptSizing) {
if (!this.props.toolCallRounds.length) {
return undefined;
}
// Note- the final prompt must end with a UserMessage
// Note- for the copilot models, the final prompt must end with a non-tool-result UserMessage
return <>
{this.props.toolCallRounds.map(round => this.renderOneToolCallRound(round))}
<UserMessage>Above is the result of calling one or more tools. The user cannot see the results, so you should explain them to the user if referencing them in your answer.</UserMessage>
@ -92,23 +92,25 @@ class ToolCalls extends PromptElement<ToolCallsProps, void> {
private renderOneToolCallRound(round: ToolCallRound) {
const assistantToolCalls: ToolCall[] = round.toolCalls.map(tc => ({ type: 'function', function: { name: tc.name, arguments: JSON.stringify(tc.parameters) }, id: tc.callId }));
// TODO- just need to adopt prompt-tsx update in vscode-copilot
return (
<Chunk>
<AssistantMessage toolCalls={assistantToolCalls}>{round.response}</AssistantMessage>
{round.toolCalls.map(toolCall =>
<ToolCallElement toolCall={toolCall} toolInvocationToken={this.props.toolInvocationToken} toolCallResult={this.props.toolCallResults[toolCall.callId]}></ToolCallElement>)}
<ToolResultElement toolCall={toolCall} toolInvocationToken={this.props.toolInvocationToken} toolCallResult={this.props.toolCallResults[toolCall.callId]} />)}
</Chunk>);
}
}
interface ToolCallElementProps extends BasePromptElementProps {
interface ToolResultElementProps extends BasePromptElementProps {
toolCall: vscode.LanguageModelToolCallPart;
toolInvocationToken: vscode.ChatParticipantToolToken | undefined;
toolCallResult: vscode.LanguageModelToolResult | undefined;
}
class ToolCallElement extends PromptElement<ToolCallElementProps, void> {
/**
* One tool call result, which either comes from the cache or from invoking the tool.
*/
class ToolResultElement extends PromptElement<ToolResultElementProps, void> {
async render(state: void, sizing: PromptSizing): Promise<PromptPiece | undefined> {
const tool = vscode.lm.tools.find(t => t.name === this.props.toolCall.name);
if (!tool) {
@ -147,6 +149,9 @@ interface HistoryProps extends BasePromptElementProps {
context: vscode.ChatContext;
}
/**
* Render the chat history, including previous tool call/results.
*/
class History extends PromptElement<HistoryProps, void> {
render(state: void, sizing: PromptSizing) {
return (
@ -173,6 +178,9 @@ class History extends PromptElement<HistoryProps, void> {
}
}
/**
* Convert the stream of chat response parts into something that can be rendered in the prompt.
*/
function chatResponseToString(response: vscode.ChatResponseTurn): string {
return response.response
.map((r) => {
@ -196,6 +204,9 @@ interface PromptReferencesProps extends BasePromptElementProps {
excludeReferences?: boolean;
}
/**
* Render references that were included in the user's request, eg files and selections.
*/
class PromptReferences extends PromptElement<PromptReferencesProps, void> {
render(state: void, sizing: PromptSizing): PromptPiece {
return (
@ -216,7 +227,6 @@ interface PromptReferenceProps extends BasePromptElementProps {
class PromptReferenceElement extends PromptElement<PromptReferenceProps> {
async render(state: void, sizing: PromptSizing): Promise<PromptPiece | undefined> {
const value = this.props.ref.value;
// TODO make context a list of TextChunks so that it can be trimmed
if (value instanceof vscode.Uri) {
const fileContents = (await vscode.workspace.fs.readFile(value)).toString();
return (
@ -246,11 +256,11 @@ class PromptReferenceElement extends PromptElement<PromptReferenceProps> {
}
}
export type TagProps = PromptElementProps<{
type TagProps = PromptElementProps<{
name: string;
}>;
export class Tag extends PromptElement<TagProps> {
class Tag extends PromptElement<TagProps> {
private static readonly _regex = /^[a-zA-Z_][\w\.\-]*$/;
render() {