Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code capability enhancement & Bot crash fix #272

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
90df61d
Sort docs by relevance to !newAction("task")
Ninot1Quyi Oct 31, 2024
f264b23
Add select_num exception range judgment
Ninot1Quyi Nov 1, 2024
17fa2b6
Add select_num exception range judgment
Ninot1Quyi Nov 1, 2024
ecaf5e8
Code capability enhancement & bot crash fix
Ninot1Quyi Nov 2, 2024
80d0c25
Merge remote-tracking branch 'origin/Tasks-more-relevant-docs-and-cod…
Ninot1Quyi Nov 2, 2024
5e84d69
Merger conflict resolution
Ninot1Quyi Nov 2, 2024
e1dfad9
Change note to English
Ninot1Quyi Nov 2, 2024
615af11
Merge remote-tracking branch 'origin/Tasks-more-relevant-docs-and-cod…
Ninot1Quyi Nov 4, 2024
82b37e0
Resolving merge conflicts with Task Manager
Ninot1Quyi Nov 4, 2024
f6e309a
Fix spelling mistakes
Ninot1Quyi Nov 6, 2024
043fc78
Merge remote-tracking branch 'origin/Tasks-more-relevant-docs-and-cod…
Ninot1Quyi Nov 8, 2024
e15c516
Resolving conflicts created by adding new annotations
Ninot1Quyi Nov 8, 2024
c8302c2
Improve the relevance of docs to !newAction("task")
Ninot1Quyi Nov 8, 2024
a368451
Fix Qwen api concurrency limit issue
Ninot1Quyi Nov 8, 2024
2322f78
code_timeout_mins is set to 3
Ninot1Quyi Nov 10, 2024
dd176af
set default profiles to andy.json
Ninot1Quyi Nov 10, 2024
69c0bd1
Default settings except code_timeout_mins
Ninot1Quyi Nov 10, 2024
cba7f7b
Default settings except code_timeout_mins
Ninot1Quyi Nov 10, 2024
11c63cb
Merge remote-tracking branch 'refs/remotes/upstream/main' into Tasks-…
Ninot1Quyi Dec 13, 2024
1835d5e
Resolve merge conflicts with latest code
Ninot1Quyi Dec 13, 2024
c5b6cd5
Preliminary code separation
Ninot1Quyi Dec 14, 2024
37aecb0
Merge remote-tracking branch 'upstream/main' into Tasks-more-relevant…
Ninot1Quyi Dec 16, 2024
4c8c61b
Modify the url of qwen.json to default to the international version '…
Ninot1Quyi Dec 16, 2024
b1dad6b
Code Separation: Related Skill Selection
Ninot1Quyi Dec 16, 2024
72397c4
Add setting for number of "relevant_docs_count"
Ninot1Quyi Dec 16, 2024
a7000ea
Merge code templates into codeTemplate.json
Ninot1Quyi Jan 3, 2025
2127e5b
Merge remote-tracking branch 'refs/remotes/upstream/main' into Tasks-…
Ninot1Quyi Jan 4, 2025
a458a66
Resolve merge conflicts in deepseek
Ninot1Quyi Jan 4, 2025
485d4a6
Rollback two code template.js
Ninot1Quyi Jan 12, 2025
8590366
Rollback two code template.js
Ninot1Quyi Jan 12, 2025
9be83fe
Rename two code template
Ninot1Quyi Jan 12, 2025
4782da1
Rename func 'check' to 'lint'
Ninot1Quyi Jan 12, 2025
5dd57dd
Merge remote-tracking branch 'refs/remotes/upstream/main' into Tasks-…
Ninot1Quyi Jan 19, 2025
1a86c3a
Fix Qwen.js to be compatible with OpenAI and add random backoff for r…
Ninot1Quyi Jan 19, 2025
1d54af2
add the lost `|| new_resume`
Ninot1Quyi Jan 19, 2025
f0396df
Remove the relevant_skill_doc prompts from coder.js and action_manage…
Ninot1Quyi Jan 19, 2025
2019dff
Merge remote-tracking branch 'refs/remotes/upstream/main' into Tasks-…
Ninot1Quyi Jan 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
10 changes: 10 additions & 0 deletions bots/lintTemplate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as skills from '../../../src/agent/library/skills.js';
import * as world from '../../../src/agent/library/world.js';
import Vec3 from 'vec3';

const log = skills.log;

export async function main(bot) {
/* CODE HERE */
log(bot, 'Code finished.');
}
25 changes: 25 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// eslint.config.js
import globals from "globals";
import pluginJs from "@eslint/js";

/** @type {import('eslint').Linter.Config[]} */
export default [
// First, import the recommended configuration
pluginJs.configs.recommended,

// Then override or customize specific rules
{
languageOptions: {
globals: globals.browser,
ecmaVersion: 2021,
sourceType: "module",
},
rules: {
"no-undef": "error", // Disallow the use of undeclared variables or functions.
"semi": ["error", "always"], // Require the use of semicolons at the end of statements.
"curly": "warn", // Enforce the use of curly braces around blocks of code.
"no-unused-vars": "off", // Disable warnings for unused variables.
"no-unreachable": "off", // Disable warnings for unreachable code.
},
},
];
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,10 @@
"scripts": {
"postinstall": "patch-package",
"start": "node main.js"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"eslint": "^9.13.0",
"globals": "^15.11.0"
}
}
8 changes: 6 additions & 2 deletions profiles/qwen.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@

"model": {
"api": "qwen",
"url": "https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/text-generation/generation",
"url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
"model": "qwen-max"
},

"embedding": "openai"
"embedding": {
"api": "qwen",
"url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
"model": "text-embedding-v3"
}
}
1 change: 1 addition & 0 deletions settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default

"allow_insecure_coding": false, // allows newAction command and model can write/run code on your computer. enable at own risk
"code_timeout_mins": -1, // minutes code is allowed to run. -1 for no timeout
"relevant_docs_count": 5, // Parameter: -1 = all, 0 = no references, 5 = five references. If exceeding the maximum, all reference documents are returned.

"max_messages": 15, // max number of messages to keep in context
"num_examples": 2, // number of examples to give to the model
Expand Down
11 changes: 6 additions & 5 deletions src/agent/action_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,13 @@ export class ActionManager {
// Log the full stack trace
console.error(err.stack);
await this.stop();
err = err.toString();

let message = this._getBotOutputSummary() +
'!!Code threw exception!!\n' +
let message = this._getBotOutputSummary() +
'!!Code threw exception!!\n' +
'Error: ' + err + '\n' +
'Stack trace:\n' + err.stack;
'Stack trace:\n' + err.stack+'\n';

let interrupted = this.agent.bot.interrupt_code;
this.agent.clearBotLogs();
if (!interrupted && !this.agent.coder.generating) {
Expand All @@ -137,7 +138,7 @@ export class ActionManager {
First outputs:\n${output.substring(0, MAX_OUT / 2)}\n...skipping many lines.\nFinal outputs:\n ${output.substring(output.length - MAX_OUT / 2)}`;
}
else {
output = 'Code output:\n' + output;
output = 'Code output:\n' + output.toString();
}
return output;
}
Expand Down
74 changes: 64 additions & 10 deletions src/agent/coder.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { makeCompartment } from './library/lockdown.js';
import * as skills from './library/skills.js';
import * as world from './library/world.js';
import { Vec3 } from 'vec3';
import {ESLint} from "eslint";

export class Coder {
constructor(agent) {
Expand All @@ -12,15 +13,62 @@ export class Coder {
this.fp = '/bots/'+agent.name+'/action-code/';
this.generating = false;
this.code_template = '';
this.code_lint_template = '';

readFile('./bots/template.js', 'utf8', (err, data) => {
readFile('./bots/execTemplate.js', 'utf8', (err, data) => {
if (err) throw err;
this.code_template = data;
});

readFile('./bots/lintTemplate.js', 'utf8', (err, data) => {
if (err) throw err;
this.code_lint_template = data;
});
mkdirSync('.' + this.fp, { recursive: true });
}

async lintCode(code) {
let result = '#### CODE ERROR INFO ###\n';
// Extract everything in the code between the beginning of 'skills./world.' and the '('
const skillRegex = /(?:skills|world)\.(.*?)\(/g;
const skills = [];
let match;
while ((match = skillRegex.exec(code)) !== null) {
skills.push(match[1]);
}
const allDocs = await this.agent.prompter.skill_libary.getRelevantSkillDocs();
//lint if the function exists
const missingSkills = skills.filter(skill => !allDocs.includes(skill));
if (missingSkills.length > 0) {
result += 'These functions do not exist. Please modify the correct function name and try again.\n';
result += '### FUNCTIONS NOT FOUND ###\n';
result += missingSkills.join('\n');
console.log(result)
return result;
}

const eslint = new ESLint();
const results = await eslint.lintText(code);
const codeLines = code.split('\n');
const exceptions = results.map(r => r.messages).flat();

if (exceptions.length > 0) {
exceptions.forEach((exc, index) => {
if (exc.line && exc.column ) {
const errorLine = codeLines[exc.line - 1]?.trim() || 'Unable to retrieve error line content';
result += `#ERROR ${index + 1}\n`;
result += `Message: ${exc.message}\n`;
result += `Location: Line ${exc.line}, Column ${exc.column}\n`;
result += `Related Code Line: ${errorLine}\n`;
}
});
result += 'The code contains exceptions and cannot continue execution.';
} else {
return null;//no error
}

return result ;
}
// write custom code to file and import it
// write custom code to file and prepare for evaluation
async stageCode(code) {
code = this.sanitizeCode(code);
Expand All @@ -35,6 +83,7 @@ export class Coder {
for (let line of code.split('\n')) {
src += ` ${line}\n`;
}
let src_lint_copy = this.code_lint_template.replace('/* CODE HERE */', src);
src = this.code_template.replace('/* CODE HERE */', src);

let filename = this.file_counter + '.js';
Expand All @@ -46,7 +95,7 @@ export class Coder {
// });
// } commented for now, useful to keep files for debugging
this.file_counter++;

let write_result = await this.writeFilePromise('.' + this.fp + filename, src);
// This is where we determine the environment the agent's code should be exposed to.
// It will only have access to these things, (in addition to basic javascript objects like Array, Object, etc.)
Expand All @@ -63,8 +112,7 @@ export class Coder {
console.error('Error writing code execution file: ' + result);
return null;
}

return { main: mainFn };
return { func:{main: mainFn}, src_lint_copy: src_lint_copy };
}

sanitizeCode(code) {
Expand Down Expand Up @@ -140,8 +188,15 @@ export class Coder {
continue;
}
code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```'));

const executionModuleExports = await this.stageCode(code);
const result = await this.stageCode(code);
const executionModuleExports = result.func;
let src_lint_copy = result.src_lint_copy;
const analysisResult = await this.lintCode(src_lint_copy);
if (analysisResult) {
const message = 'Error: Code syntax error. Please try again:'+'\n'+analysisResult+'\n';
messages.push({ role: 'system', content: message });
continue;
}
if (!executionModuleExports) {
agent_history.add('system', 'Failed to stage code, something is wrong.');
return {success: false, message: null, interrupted: false, timedout: false};
Expand All @@ -152,10 +207,10 @@ export class Coder {
}, { timeout: settings.code_timeout_mins });
if (code_return.interrupted && !code_return.timedout)
return { success: false, message: null, interrupted: true, timedout: false };
console.log("Code generation result:", code_return.success, code_return.message);
console.log("Code generation result:", code_return.success, code_return.message.toString());

if (code_return.success) {
const summary = "Summary of newAction\nAgent wrote this code: \n```" + this.sanitizeCode(code) + "```\nCode Output:\n" + code_return.message;
const summary = "Summary of newAction\nAgent wrote this code: \n```" + this.sanitizeCode(code) + "```\nCode Output:\n" + code_return.message.toString();
return { success: true, message: summary, interrupted: false, timedout: false };
}

Expand All @@ -170,5 +225,4 @@ export class Coder {
}
return { success: false, message: null, interrupted: false, timedout: true };
}

}
19 changes: 10 additions & 9 deletions src/agent/library/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ import * as world from './world.js';


export function docHelper(functions, module_name) {
let docstring = '';
let docArray = [];
for (let skillFunc of functions) {
let str = skillFunc.toString();
if (str.includes('/**')){
docstring += module_name+'.'+skillFunc.name;
docstring += str.substring(str.indexOf('/**')+3, str.indexOf('**/')) + '\n';
if (str.includes('/**')) {
let docEntry = `${module_name}.${skillFunc.name}\n`;
docEntry += str.substring(str.indexOf('/**') + 3, str.indexOf('**/')).trim();
docArray.push(docEntry);
}
}
return docstring;
return docArray;
}

export function getSkillDocs() {
let docstring = "\n*SKILL DOCS\nThese skills are javascript functions that can be called when writing actions and skills.\n";
docstring += docHelper(Object.values(skills), 'skills');
docstring += docHelper(Object.values(world), 'world');
return docstring + '*\n';
let docArray = [];
docArray = docArray.concat(docHelper(Object.values(skills), 'skills'));
docArray = docArray.concat(docHelper(Object.values(world), 'world'));
return docArray;
}
47 changes: 47 additions & 0 deletions src/agent/library/skill_library.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { cosineSimilarity } from '../../utils/math.js';
import { getSkillDocs } from './index.js';

export class SkillLibrary {
constructor(agent,embedding_model) {
this.agent = agent;
this.embedding_model = embedding_model;
this.skill_docs_embeddings = {};
}
async initSkillLibrary() {
const skillDocs = getSkillDocs();
const embeddingPromises = skillDocs.map((doc) => {
return (async () => {
let func_name_desc = doc.split('\n').slice(0, 2).join('');
this.skill_docs_embeddings[doc] = await this.embedding_model.embed(func_name_desc);
})();
});
await Promise.all(embeddingPromises);
}

async getRelevantSkillDocs(message, select_num) {
let latest_message_embedding = '';
if(message) //message is not empty, get the relevant skill docs, else return all skill docs
latest_message_embedding = await this.embedding_model.embed(message);

let skill_doc_similarities = Object.keys(this.skill_docs_embeddings)
.map(doc_key => ({
doc_key,
similarity_score: cosineSimilarity(latest_message_embedding, this.skill_docs_embeddings[doc_key])
}))
.sort((a, b) => b.similarity_score - a.similarity_score);

let length = skill_doc_similarities.length;
if (typeof select_num !== 'number' || isNaN(select_num) || select_num < 0) {
select_num = length;
} else {
select_num = Math.min(Math.floor(select_num), length);
}
let selected_docs = skill_doc_similarities.slice(0, select_num);
let relevant_skill_docs = '#### RELEVENT DOCS INFO ###\nThe following functions are listed in descending order of relevance.\n';
relevant_skill_docs += 'SkillDocs:\n'
relevant_skill_docs += selected_docs.map(doc => `${doc.doc_key}`).join('\n### ');
return relevant_skill_docs;
}


}
18 changes: 15 additions & 3 deletions src/agent/prompter.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { readFileSync, mkdirSync, writeFileSync} from 'fs';
import { Examples } from '../utils/examples.js';
import { getCommandDocs } from './commands/index.js';
import { getSkillDocs } from './library/index.js';
import { stringifyTurns } from '../utils/text.js';
import { getCommand } from './commands/index.js';
import settings from '../../settings.js';
Expand All @@ -17,6 +16,7 @@ import { GroqCloudAPI } from '../models/groq.js';
import { HuggingFace } from '../models/huggingface.js';
import { Qwen } from "../models/qwen.js";
import { Grok } from "../models/grok.js";
import {SkillLibrary} from "./library/skill_library.js";
import { DeepSeek } from '../models/deepseek.js';

export class Prompter {
Expand Down Expand Up @@ -136,7 +136,7 @@ export class Prompter {
console.log('Continuing anyway, using word overlap instead.');
this.embedding_model = null;
}

this.skill_libary = new SkillLibrary(agent, this.embedding_model);
mkdirSync(`./bots/${name}`, { recursive: true });
writeFileSync(`./bots/${name}/last_profile.json`, JSON.stringify(this.profile, null, 4), (err) => {
if (err) {
Expand All @@ -162,7 +162,8 @@ export class Prompter {
// Wait for both examples to load before proceeding
await Promise.all([
this.convo_examples.load(this.profile.conversation_examples),
this.coding_examples.load(this.profile.coding_examples)
this.coding_examples.load(this.profile.coding_examples),
this.skill_libary.initSkillLibrary()
]);

console.log('Examples initialized.');
Expand All @@ -188,6 +189,17 @@ export class Prompter {
}
if (prompt.includes('$COMMAND_DOCS'))
prompt = prompt.replaceAll('$COMMAND_DOCS', getCommandDocs());
if (prompt.includes('$CODE_DOCS')) {
const code_task_content = messages.slice().reverse().find(msg =>
msg.role !== 'system' && msg.content.includes('!newAction(')
)?.content?.match(/!newAction\((.*?)\)/)?.[1] || '';

prompt = prompt.replaceAll(
'$CODE_DOCS',
await this.skill_libary.getRelevantSkillDocs(code_task_content, settings.relevant_docs_count)
);
}
prompt = prompt.replaceAll('$COMMAND_DOCS', getCommandDocs());
if (prompt.includes('$CODE_DOCS'))
prompt = prompt.replaceAll('$CODE_DOCS', getSkillDocs());
if (prompt.includes('$EXAMPLES') && examples !== null)
Expand Down
Loading