Support GPT-4-0613 function calls
This commit is contained in:
parent
6650a5f253
commit
cda646f830
@ -1,9 +1,12 @@
|
||||
import { Action, CustomActions } from './customAction';
|
||||
import fs from 'fs';
|
||||
|
||||
import { Action, CustomActions, getActionInstruction } from './customAction';
|
||||
|
||||
import { CommandResult } from '../util/commonUtil';
|
||||
import { logger } from '../util/logger';
|
||||
|
||||
|
||||
import { SymbolRefAction } from './symbolRefAction';
|
||||
import { SymbolDefAction } from './symbolDefAction';
|
||||
|
||||
|
||||
// extend Action
|
||||
@ -29,7 +32,7 @@ export class CommandRunAction implements Action {
|
||||
async handlerAction(args: {[key: string]: any}): Promise<CommandResult> {
|
||||
try {
|
||||
const commandData = JSON.parse(args.content);
|
||||
const result = await ActionManager.getInstance().applyCommandAction(commandData.command, commandData.args);
|
||||
const result = await ActionManager.getInstance().applyCommandAction(commandData.name, commandData.arguments);
|
||||
return result;
|
||||
} catch (error) {
|
||||
logger.channel()?.error('Failed to parse code file content: ' + error);
|
||||
@ -51,6 +54,9 @@ export default class ActionManager {
|
||||
}
|
||||
|
||||
ActionManager.instance.registerAction(new CommandRunAction());
|
||||
ActionManager.instance.registerAction(new SymbolRefAction());
|
||||
ActionManager.instance.registerAction(new SymbolDefAction());
|
||||
|
||||
return ActionManager.instance;
|
||||
}
|
||||
|
||||
@ -160,4 +166,23 @@ export default class ActionManager {
|
||||
this.registerAction(chatAction);
|
||||
}
|
||||
}
|
||||
|
||||
public actionInstruction(): string {
|
||||
let functionsDefList = []
|
||||
for (const action of this.actions) {
|
||||
functionsDefList.push(getActionInstruction(action));
|
||||
}
|
||||
|
||||
// return as json string
|
||||
return JSON.stringify(functionsDefList, null, 4);
|
||||
}
|
||||
|
||||
public saveActionInstructionFile(tarFile: string): void {
|
||||
try {
|
||||
fs.writeFileSync(tarFile, this.actionInstruction());
|
||||
} catch (error) {
|
||||
logger.channel()?.error(`Failed to save action instruction file: ${error}`);
|
||||
logger.channel()?.show();
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,27 @@ export interface Action {
|
||||
handlerAction: (args: { [key: string]: string }) => Promise<CommandResult>;
|
||||
}
|
||||
|
||||
// generate instruction for action
|
||||
export function getActionInstruction(action: Action): any {
|
||||
const actionSchema = {
|
||||
name: action.name,
|
||||
description: action.description,
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: action.args.reduce((obj: any, arg: any) => {
|
||||
obj[arg.name] = {
|
||||
type: arg.type,
|
||||
description: arg.description
|
||||
};
|
||||
return obj;
|
||||
}, {}),
|
||||
required: action.args.filter((arg: any) => arg.required).map((arg: any) => arg.name)
|
||||
}
|
||||
};
|
||||
|
||||
return actionSchema;
|
||||
}
|
||||
|
||||
export class CustomActions {
|
||||
private static instance: CustomActions | null = null;
|
||||
private actions: Action[] = [];
|
||||
@ -29,44 +50,6 @@ export class CustomActions {
|
||||
return CustomActions.instance;
|
||||
}
|
||||
|
||||
public actionInstruction(): string {
|
||||
let instruction = 'As an AI bot, you replay user with "command" block, don\'t replay any other words.\n' +
|
||||
'"command" block is like this:\n' +
|
||||
'``` command\n' +
|
||||
'{\n' +
|
||||
' "command": "xxx",\n' +
|
||||
' "args" {\n' +
|
||||
' "var name": "xxx"\n' +
|
||||
' }\n' +
|
||||
'}\n' +
|
||||
'```\n' +
|
||||
'You can split task into small sub tasks, after each command I will give you the result of command executed. so, the next command can depend pre command\'s output.\n' +
|
||||
'\n' +
|
||||
'Supported commands are:\n';
|
||||
let index = 1;
|
||||
for (const action of this.actions) {
|
||||
instruction += String(index) + ". " + this.getActionInstruction(action.name) + "\n";
|
||||
index += 1;
|
||||
}
|
||||
|
||||
instruction += 'Restriction for output:\n' +
|
||||
'1. Only reponse "command" block.\n' +
|
||||
'2. Don\'t include any other text exclude command.\n' +
|
||||
'3. Only supported extension commands can be used to complete the response.\n' +
|
||||
'4. When update file, old_content must include at least three lines.';
|
||||
|
||||
return instruction;
|
||||
}
|
||||
|
||||
public saveActionInstructionFile(tarFile: string): void {
|
||||
try {
|
||||
fs.writeFileSync(tarFile, this.actionInstruction());
|
||||
} catch (error) {
|
||||
logger.channel()?.error(`Failed to save action instruction file: ${error}`);
|
||||
logger.channel()?.show();
|
||||
}
|
||||
}
|
||||
|
||||
public parseActions(workflowsDir: string): void {
|
||||
this.actions = [];
|
||||
|
||||
@ -162,23 +145,4 @@ export class CustomActions {
|
||||
public getActions(): Action[] {
|
||||
return this.actions;
|
||||
}
|
||||
|
||||
// generate instruction for action
|
||||
public getActionInstruction(actionName: string): string {
|
||||
const action = this.actions.find(action => action.name.trim() === actionName.trim());
|
||||
if (!action) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let instruction = `${action.name}: ${action.description}\n`;
|
||||
// if args is not undefined and has values, then visit args
|
||||
if (action.args !== undefined && action.args.length > 0) {
|
||||
instruction += `Args:\n`;
|
||||
for (const arg of action.args) {
|
||||
instruction += ` name: ${arg.name} type: (${arg.type}) description: ${arg.description}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
return instruction;
|
||||
}
|
||||
}
|
40
src/action/symbolDefAction.ts
Normal file
40
src/action/symbolDefAction.ts
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
import { Action, CustomActions } from './customAction';
|
||||
|
||||
import { CommandResult } from '../util/commonUtil';
|
||||
import { logger } from '../util/logger';
|
||||
|
||||
|
||||
export class SymbolDefAction implements Action {
|
||||
name: string;
|
||||
description: string;
|
||||
type: string[];
|
||||
action: string;
|
||||
handler: string[];
|
||||
args: { "name": string, "description": string, "type": string, "as"?: string, "from": string }[];
|
||||
|
||||
constructor() {
|
||||
this.name = 'symbol_def';
|
||||
this.description = 'Retrieve the definition information related to the symbol';
|
||||
this.type = ['symbol'];
|
||||
this.action = 'symbol_def';
|
||||
this.handler = [];
|
||||
this.args = [
|
||||
{"name": "symbol", "description": "The symbol variable specifies the symbol for which definition information is to be retrieved.", "type": "string", "from": "content.content.symbol"},
|
||||
];
|
||||
}
|
||||
|
||||
async handlerAction(args: {[key: string]: any}): Promise<CommandResult> {
|
||||
try {
|
||||
const symbolName = args.symbol;
|
||||
|
||||
// get reference information
|
||||
|
||||
return {exitCode: 0, stdout: "", stderr: ""};
|
||||
} catch (error) {
|
||||
logger.channel()?.error(`${this.name} handle error: ${error}`);
|
||||
logger.channel()?.show();
|
||||
return {exitCode: -1, stdout: '', stderr: `${this.name} handle error: ${error}`};
|
||||
}
|
||||
}
|
||||
};
|
40
src/action/symbolRefAction.ts
Normal file
40
src/action/symbolRefAction.ts
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
import { Action, CustomActions } from './customAction';
|
||||
|
||||
import { CommandResult } from '../util/commonUtil';
|
||||
import { logger } from '../util/logger';
|
||||
|
||||
|
||||
export class SymbolRefAction implements Action {
|
||||
name: string;
|
||||
description: string;
|
||||
type: string[];
|
||||
action: string;
|
||||
handler: string[];
|
||||
args: { "name": string, "description": string, "type": string, "as"?: string, "from": string }[];
|
||||
|
||||
constructor() {
|
||||
this.name = 'symbol_ref';
|
||||
this.description = 'Retrieve the reference information related to the symbol';
|
||||
this.type = ['symbol'];
|
||||
this.action = 'symbol_ref';
|
||||
this.handler = [];
|
||||
this.args = [
|
||||
{"name": "symbol", "description": "The symbol variable specifies the symbol for which reference information is to be retrieved.", "type": "string", "from": "content.content.symbol"},
|
||||
];
|
||||
}
|
||||
|
||||
async handlerAction(args: {[key: string]: any}): Promise<CommandResult> {
|
||||
try {
|
||||
const symbolName = args.symbol;
|
||||
|
||||
// get reference information
|
||||
|
||||
return {exitCode: 0, stdout: "", stderr: ""};
|
||||
} catch (error) {
|
||||
logger.channel()?.error(`${this.name} handle error: ${error}`);
|
||||
logger.channel()?.show();
|
||||
return {exitCode: -1, stdout: '', stderr: `${this.name} handle error: ${error}`};
|
||||
}
|
||||
}
|
||||
};
|
@ -1,6 +1,18 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { regInMessage, regOutMessage } from '../util/reg_messages';
|
||||
import ActionManager from '../action/actionManager';
|
||||
import { MessageHandler } from './messageHandler';
|
||||
import { sendMessage } from './sendMessage';
|
||||
import { logger } from '../util/logger';
|
||||
|
||||
function compressText(text: string, maxLength: number): string {
|
||||
if (text.length <= maxLength) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const halfLength = Math.floor(maxLength / 2);
|
||||
return text.slice(0, halfLength) + " ... " + text.slice(-halfLength);
|
||||
}
|
||||
|
||||
async function replaceFileContent(uri: vscode.Uri, newContent: string) {
|
||||
try {
|
||||
@ -61,8 +73,22 @@ export async function applyCodeFile(text: string, fileName: string): Promise<voi
|
||||
|
||||
regInMessage({command: 'code_file_apply', content: '', fileName: ''});
|
||||
export async function codeFileApply(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
|
||||
await applyCodeFile(message.content, message.fileName);
|
||||
return;
|
||||
// await applyCodeFile(message.content, message.fileName);
|
||||
// return;
|
||||
|
||||
try {
|
||||
const result = await ActionManager.getInstance().applyAction("command_run", { "command": "", "fileName": message.fileName, "content": message.content });
|
||||
|
||||
// send error message to devchat
|
||||
const commandObj = JSON.parse(message.content)
|
||||
const newMessage = `{"exit_code": ${result.exitCode}, stdout: ${result.stdout}, stderr: ${compressText(result.stderr, 100)}}`;
|
||||
MessageHandler.sendMessage(panel, { "command": "sendMessageSystem" });
|
||||
sendMessage({command: 'sendMessage', text: newMessage}, panel, commandObj.name);
|
||||
|
||||
} catch (error) {
|
||||
logger.channel()?.error('Failed to parse code file content: ' + error);
|
||||
logger.channel()?.show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -15,12 +15,12 @@ regOutMessage({ command: 'receiveMessagePartial', text: 'xxxx', user: 'xxx', dat
|
||||
// return message:
|
||||
// { command: 'receiveMessage', text: 'xxxx', hash: 'xxx', user: 'xxx', date: 'xxx'}
|
||||
// { command: 'receiveMessagePartial', text: 'xxxx', user: 'xxx', date: 'xxx'}
|
||||
export async function sendMessage(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
|
||||
_lastMessage = message;
|
||||
export async function sendMessage(message: any, panel: vscode.WebviewPanel|vscode.WebviewView, function_name: string|undefined = undefined): Promise<void> {
|
||||
_lastMessage = [message, function_name];
|
||||
|
||||
const responseMessage = await sendMessageBase(message, (data: { command: string, text: string, user: string, date: string}) => {
|
||||
const responseMessage = await sendMessageBase(message, (data: { command: string, text: string, user: string, date: string}) => {
|
||||
MessageHandler.sendMessage(panel, data, false);
|
||||
});
|
||||
}, function_name);
|
||||
if (responseMessage) {
|
||||
MessageHandler.sendMessage(panel, responseMessage);
|
||||
}
|
||||
@ -31,7 +31,7 @@ regInMessage({command: 'regeneration'});
|
||||
export async function regeneration(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
|
||||
// call sendMessage to send last message again
|
||||
if (_lastMessage) {
|
||||
sendMessage(_lastMessage, panel);
|
||||
sendMessage(_lastMessage[0], panel, _lastMessage[1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,6 +102,7 @@ export async function parseMessageAndSetOptions(message: any, chatOptions: any):
|
||||
if (parsedMessage.reference.length > 0) {
|
||||
chatOptions.reference = parsedMessage.reference;
|
||||
}
|
||||
|
||||
return parsedMessage;
|
||||
}
|
||||
|
||||
@ -135,13 +136,16 @@ export async function handlerResponseText(partialDataText: string, chatResponse:
|
||||
}
|
||||
}
|
||||
|
||||
if (chatResponse.finish_reason === "function_call") {
|
||||
return '\n```command\n' + responseText + '\n```\n';
|
||||
}
|
||||
|
||||
return responseText;
|
||||
}
|
||||
|
||||
// 重构后的sendMessage函数
|
||||
export async function sendMessageBase(message: any, handlePartialData: (data: { command: string, text: string, user: string, date: string}) => void): Promise<{ command: string, text: string, hash: string, user: string, date: string, isError: boolean }|undefined> {
|
||||
export async function sendMessageBase(message: any, handlePartialData: (data: { command: string, text: string, user: string, date: string}) => void, function_name: string| undefined = undefined): Promise<{ command: string, text: string, hash: string, user: string, date: string, isError: boolean }|undefined> {
|
||||
userStop = false;
|
||||
|
||||
const chatOptions: any = {};
|
||||
const parsedMessage = await parseMessageAndSetOptions(message, chatOptions);
|
||||
|
||||
@ -150,9 +154,18 @@ export async function sendMessageBase(message: any, handlePartialData: (data: {
|
||||
}
|
||||
logger.channel()?.info(`parent hash: ${chatOptions.parent}`);
|
||||
|
||||
chatOptions.functions = "./.chat/functions.json";
|
||||
if (function_name) {
|
||||
chatOptions.function_name = function_name;
|
||||
chatOptions.role = "function";
|
||||
}
|
||||
|
||||
let partialDataText = '';
|
||||
const onData = (partialResponse: ChatResponse) => {
|
||||
partialDataText = partialResponse.response.replace(/```\ncommitmsg/g, "```commitmsg");
|
||||
if (partialResponse.finish_reason === "function_call") {
|
||||
partialDataText = '\n```command\n' + partialDataText + '\n```\n';
|
||||
}
|
||||
handlePartialData({ command: 'receiveMessagePartial', text: partialDataText!, user: partialResponse.user, date: partialResponse.date });
|
||||
};
|
||||
|
||||
|
@ -39,8 +39,8 @@ export class DevChatViewProvider implements vscode.WebviewViewProvider {
|
||||
ChatContextManager.getInstance().loadCustomContexts(workflowsDir);
|
||||
ActionManager.getInstance().loadCustomActions(workflowsDir);
|
||||
|
||||
const actionInstrucFile = path.join(UiUtilWrapper.workspaceFoldersFirstPath()!, './.chat/action.instr');
|
||||
CustomActions.getInstance().saveActionInstructionFile( actionInstrucFile);
|
||||
const actionInstrucFile = path.join(UiUtilWrapper.workspaceFoldersFirstPath()!, './.chat/functions.json');
|
||||
ActionManager.getInstance().saveActionInstructionFile( actionInstrucFile);
|
||||
}
|
||||
|
||||
this._view = webviewView;
|
||||
|
@ -19,6 +19,9 @@ export interface ChatOptions {
|
||||
parent?: string;
|
||||
reference?: string[];
|
||||
header?: string[];
|
||||
functions?: string;
|
||||
role?: string;
|
||||
function_name?: string;
|
||||
context?: string[];
|
||||
}
|
||||
|
||||
@ -46,6 +49,7 @@ export interface ChatResponse {
|
||||
user: string;
|
||||
date: string;
|
||||
response: string;
|
||||
finish_reason: string;
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
@ -80,6 +84,18 @@ class DevChat {
|
||||
}
|
||||
}
|
||||
|
||||
if (options.functions) {
|
||||
args.push("-f", options.functions);
|
||||
}
|
||||
|
||||
if (options.role) {
|
||||
args.push("-R", options.role);
|
||||
}
|
||||
|
||||
if (options.function_name) {
|
||||
args.push("-n", options.function_name);
|
||||
}
|
||||
|
||||
if (options.parent) {
|
||||
args.push("-p", options.parent);
|
||||
}
|
||||
@ -96,6 +112,7 @@ class DevChat {
|
||||
user: "",
|
||||
date: "",
|
||||
response: "",
|
||||
finish_reason: "",
|
||||
isError: isPartial ? false : true,
|
||||
};
|
||||
}
|
||||
@ -116,16 +133,27 @@ class DevChat {
|
||||
}
|
||||
}
|
||||
|
||||
let finishReasonLine = "";
|
||||
for (let i = responseLines.length - 1; i >= 0; i--) {
|
||||
if (responseLines[i].startsWith("finish_reason:")) {
|
||||
finishReasonLine = responseLines[i];
|
||||
responseLines.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!promptHashLine) {
|
||||
return {
|
||||
"prompt-hash": "",
|
||||
user: user,
|
||||
date: date,
|
||||
response: responseLines.join("\n"),
|
||||
finish_reason: "",
|
||||
isError: isPartial ? false : true,
|
||||
};
|
||||
}
|
||||
|
||||
const finishReason = finishReasonLine.split(" ")[1];
|
||||
const promptHash = promptHashLine.split(" ")[1];
|
||||
const response = responseLines.join("\n");
|
||||
|
||||
@ -134,6 +162,7 @@ class DevChat {
|
||||
user,
|
||||
date,
|
||||
response,
|
||||
finish_reason: finishReason,
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
@ -218,6 +247,7 @@ class DevChat {
|
||||
user: "",
|
||||
date: "",
|
||||
response: stderr,
|
||||
finish_reason: "",
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
@ -230,6 +260,7 @@ class DevChat {
|
||||
user: "",
|
||||
date: "",
|
||||
response: `Error: ${error.stderr}\nExit code: ${error.code}`,
|
||||
finish_reason: "",
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "run_shell",
|
||||
"name": "run_shell_file",
|
||||
"description": "run shell script",
|
||||
"type": [
|
||||
"shell"
|
||||
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"pattern": "auto",
|
||||
"description": "Generate commands for the bot to execute",
|
||||
"message": "",
|
||||
"default": false,
|
||||
"instructions": ["./chat/action.instr"]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user