Skip to content

Commit

Permalink
feat: add ECL agent chat
Browse files Browse the repository at this point in the history
Signed-off-by: David de Hilster <[email protected]>
  • Loading branch information
dehilsterlexis committed Apr 25, 2024
1 parent 6a0f82d commit 007a684
Show file tree
Hide file tree
Showing 6 changed files with 978 additions and 2 deletions.
30 changes: 29 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@
"theme": "light"
},
"extensionDependencies": [
"GordonSmith.observable-js"
"GordonSmith.observable-js",
"github.copilot-chat"
],
"activationEvents": [
"onLanguage:ecl",
Expand All @@ -148,6 +149,11 @@
"workspaceContains:*.kel",
"workspaceContains:*.dashy"
],
"enabledApiProposals": [
"chatParticipant",
"chatVariableResolver",
"languageModels"
],
"contributes": {
"languages": [
{
Expand Down Expand Up @@ -221,6 +227,10 @@
],
"viewsWelcome": [],
"commands": [
{
"command": "eclagent.doSomething",
"title": "Do Something"
},
{
"command": "ecl.submit",
"category": "ECL",
Expand Down Expand Up @@ -757,6 +767,24 @@
}
}
],
"chatParticipants": [
{
"id": "chat-ecl.eclagent",
"name": "eclagent",
"description": "I'm conversant in ECL! What can I teach you?",
"isSticky": true,
"commands": [
{
"name": "teach",
"description": "Pick at random ECL concept then explain it"
},
{
"name": "play",
"description": "Enter "
}
]
}
],
"submenus": [
{
"id": "setState",
Expand Down
176 changes: 176 additions & 0 deletions src/chat/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import * as vscode from 'vscode';

Check failure on line 1 in src/chat/main.ts

View workflow job for this annotation

GitHub Actions / build

Strings must use doublequote

const ECLAGENT_NAMES_COMMAND_ID = 'eclagent.doSomething';

Check failure on line 3 in src/chat/main.ts

View workflow job for this annotation

GitHub Actions / build

Strings must use doublequote
const ECLAGENT_PARTICIPANT_ID = 'chat-ecl.eclagent';

Check failure on line 4 in src/chat/main.ts

View workflow job for this annotation

GitHub Actions / build

Strings must use doublequote

interface IECLChatResult extends vscode.ChatResult {
metadata: {
command: string;
}
}

const LANGUAGE_MODEL_ID = 'copilot-gpt-3.5-turbo'; // Use faster model. Alternative is 'copilot-gpt-4', which is slower but more powerful

Check failure on line 12 in src/chat/main.ts

View workflow job for this annotation

GitHub Actions / build

Strings must use doublequote

export function activate(context: vscode.ExtensionContext) {

// Define an ECL chat handler.
const handler: vscode.ChatRequestHandler = async (request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken): Promise<IECLChatResult> => {
// To talk to an LLM in your subcommand handler implementation, your
// extension can use VS Code's `requestChatAccess` API to access the Copilot API.
// The GitHub Copilot Chat extension implements this provider.
if (request.command == 'teach') {

Check failure on line 21 in src/chat/main.ts

View workflow job for this annotation

GitHub Actions / build

Strings must use doublequote
stream.progress('Picking the right topic to teach...');

Check failure on line 22 in src/chat/main.ts

View workflow job for this annotation

GitHub Actions / build

Strings must use doublequote
const topic = getTopic(context.history);
const messages = [
new vscode.LanguageModelChatSystemMessage('You are an ECL expert! Your job is to explain ECL concepts. Always start your response by stating what concept you are explaining. Always include code samples.'),

Check failure on line 25 in src/chat/main.ts

View workflow job for this annotation

GitHub Actions / build

Strings must use doublequote
new vscode.LanguageModelChatUserMessage(topic)
];
const chatResponse = await vscode.lm.sendChatRequest(LANGUAGE_MODEL_ID, messages, {}, token);
for await (const fragment of chatResponse.stream) {
stream.markdown(fragment);
}

stream.button({
command: ECLAGENT_NAMES_COMMAND_ID,
title: vscode.l10n.t('Do something'),

Check failure on line 35 in src/chat/main.ts

View workflow job for this annotation

GitHub Actions / build

Strings must use doublequote
});

return { metadata: { command: 'teach' } };

Check failure on line 38 in src/chat/main.ts

View workflow job for this annotation

GitHub Actions / build

Strings must use doublequote
} else if (request.command == 'play') {

Check failure on line 39 in src/chat/main.ts

View workflow job for this annotation

GitHub Actions / build

Strings must use doublequote
stream.progress('Preparing to look at some ECL code...');
const messages = [
new vscode.LanguageModelChatSystemMessage('You are an ECL expert! You are also very knowledgable about HPCC.'),
new vscode.LanguageModelChatUserMessage('Give small random ECL code samples. ' + request.prompt)
];
const chatResponse = await vscode.lm.sendChatRequest(LANGUAGE_MODEL_ID, messages, {}, token);
for await (const fragment of chatResponse.stream) {
stream.markdown(fragment);
}
return { metadata: { command: 'play' } };
} else {
const messages = [
new vscode.LanguageModelChatSystemMessage(`You are an ECL expert! Think carefully and step by step like an expert who is good at explaining something.
Your job is to explain computer science concepts in fun and entertaining way. Always start your response by stating what concept you are explaining. Always include code samples.`),
new vscode.LanguageModelChatUserMessage(request.prompt)
];
const chatResponse = await vscode.lm.sendChatRequest(LANGUAGE_MODEL_ID, messages, {}, token);
for await (const fragment of chatResponse.stream) {
// Process the output from the language model
stream.markdown(fragment);
}

return { metadata: { command: '' } };
}
};

// Chat participants appear as top-level options in the chat input
// when you type `@`, and can contribute sub-commands in the chat input
// that appear when you type `/`.
const eclagent = vscode.chat.createChatParticipant(ECLAGENT_PARTICIPANT_ID, handler);
eclagent.iconPath = vscode.Uri.joinPath(context.extensionUri, 'hpcc-icon.png');
eclagent.followupProvider = {
provideFollowups(result: IECLChatResult, context: vscode.ChatContext, token: vscode.CancellationToken) {
return [{
prompt: 'let us learn',
label: vscode.l10n.t('Play with ECL Code'),
command: 'play'
} satisfies vscode.ChatFollowup];
}
};

vscode.chat.registerChatVariableResolver('ECLAGENT_context', 'Describes the state of mind and version of the ECL agent', {
resolve: (name, context, token) => {
if (name == 'ECLAGENT_context') {
const mood = Math.random() > 0.5 ? 'happy' : 'grumpy';
return [
{
level: vscode.ChatVariableLevel.Short,
value: 'version 1.3 ' + mood
},
{
level: vscode.ChatVariableLevel.Medium,
value: 'I am the ECL agent, version 1.3, and I am ' + mood
},
{
level: vscode.ChatVariableLevel.Full,
value: 'I am the ECL agent, version 1.3, and I am ' + mood
}
]
}
}
});

context.subscriptions.push(
eclagent,
// Register the command handler for the /meow followup
vscode.commands.registerTextEditorCommand(ECLAGENT_NAMES_COMMAND_ID, async (textEditor: vscode.TextEditor) => {
const text = textEditor.document.getText();
const messages = [
new vscode.LanguageModelChatSystemMessage(`You are an ECL expert! Think carefully and step by step.
Your job is to be as clear as possible and be encouraging. Be creative. IMPORTANT respond just with code. Do not use markdown!`),
new vscode.LanguageModelChatUserMessage(text)
];

let chatResponse: vscode.LanguageModelChatResponse | undefined;
try {
chatResponse = await vscode.lm.sendChatRequest(LANGUAGE_MODEL_ID, messages, {}, new vscode.CancellationTokenSource().token);

} catch (err) {
// making the chat request might fail because
// - model does not exist
// - user consent not given
// - quote limits exceeded
if (err instanceof vscode.LanguageModelError) {
console.log(err.message, err.code, err.cause)
}
return
}

// Clear the editor content before inserting new content
await textEditor.edit(edit => {
const start = new vscode.Position(0, 0);
const end = new vscode.Position(textEditor.document.lineCount - 1, textEditor.document.lineAt(textEditor.document.lineCount - 1).text.length);
edit.delete(new vscode.Range(start, end));
});

// Stream the code into the editor as it is coming in from the Language Model
try {
for await (const fragment of chatResponse.stream) {
await textEditor.edit(edit => {
const lastLine = textEditor.document.lineAt(textEditor.document.lineCount - 1);
const position = new vscode.Position(lastLine.lineNumber, lastLine.text.length);
edit.insert(position, fragment);
});
}
} catch (err) {
// async response stream may fail, e.g network interruption or server side error
await textEditor.edit(edit => {
const lastLine = textEditor.document.lineAt(textEditor.document.lineCount - 1);
const position = new vscode.Position(lastLine.lineNumber, lastLine.text.length);
edit.insert(position, (<Error>err).message);
});
}
}),
);
}

// Get a random topic that the ECL agent has not taught in the chat history yet
function getTopic(history: ReadonlyArray<vscode.ChatRequestTurn | vscode.ChatResponseTurn>): string {
const topics = ['import', 'modules', 'functions', 'transforms', 'strings'];
// Filter the chat history to get only the responses from the ecl agent
const previousEclAgentResponses = history.filter(h => {
return h instanceof vscode.ChatResponseTurn && h.participant == ECLAGENT_PARTICIPANT_ID
}) as vscode.ChatResponseTurn[];
// Filter the topics to get only the topics that have not been taught by the ecl agent yet
const topicsNoRepetition = topics.filter(topic => {
return !previousEclAgentResponses.some(eclAgentResponse => {
return eclAgentResponse.response.some(r => {
return r instanceof vscode.ChatResponseMarkdownPart && r.value.value.includes(topic)
});
});
});

return topicsNoRepetition[Math.floor(Math.random() * topicsNoRepetition.length)] || 'I have taught you everything I know.';
}

export function deactivate() { }
Loading

0 comments on commit 007a684

Please sign in to comment.