fjrdomingues / autopilot

Code Autopilot, a tool that uses GPT to read a codebase, create context and solve tasks.
598 stars 62 forks source link

Cascade coder agent calls #161

Open hatkyinc2 opened 1 year ago

hatkyinc2 commented 1 year ago

Background story explaining the current situation: Due to token limits, we can't contain all the project context at once. That's why a code gets to see one or a few files at a time. After updating code in one file it is likely that other files counting on it would need to be updated.

Desired new situation: After a code is done, pull the list of files that have 'dependenciesList' in the DB including this file. For each of these files call a new coding agent with the task, diff produced so far and the new file context and ask it to update accordingly. A test case we want to see passing is a testing file updated after a code file update, for example asking the name of autopilot dir updated in autopilotConfig.js would update it's test file autopilotConfig.test.js to pass

Implementation details: Make up a new coder agent or adjust the current one to accept the possible diff input as well. Pull the dependent files after a file update. Call the agent with the details mentioned so far above.

re: https://github.com/fjrdomingues/autopilot/pull/158/files https://github.com/fjrdomingues/autopilot/pull/157

codeautopilot[bot] commented 1 year ago

🤖 This is my suggestion:

File: modules/db.js To implement the desired new situation, we need to add a function in modules/db.js that queries the database to get the list of dependent files. We can do this by adding a new function called getDependentFiles that takes the codeBaseDirectory and the filePath as input parameters and returns a list of dependent files.

Here's the plan for this file:

  1. Add a new function getDependentFiles that takes codeBaseDirectory and filePath as input parameters.
  2. Inside the function, get the database instance using the getDB function.
  3. Write a SQL query to fetch the list of dependent files that have the given filePath in their dependenciesLibs column.
  4. Execute the SQL query and return the list of dependent files.

Here's the updated code for modules/db.js:

// ... (existing code)

/**
 * @description Gets the list of dependent files that have the given filePath in their dependenciesLibs column
 * @param {string} codeBaseDirectory - The path to the codebase
 * @param {string} filePath - The file path to search for in the dependenciesLibs column
 * @returns {Array<string>} - An array of dependent file paths
 * @throws {Error} If an error occurs during the database query.
 */
async function getDependentFiles(codeBaseDirectory, filePath) {
  const db = getDB(codeBaseDirectory);
  const sql = `
SELECT path
FROM files
WHERE dependenciesLibs LIKE ?
`;
  const wildcardFilePath = `%${filePath}%`;
  const dependentFiles = await new Promise((resolve, reject) => {
    db.all(sql, [wildcardFilePath], (err, rows) => {
      if (err) {
        reject(err);
      } else {
        resolve(rows.map(row => row.path));
      }
    });
  });
  return dependentFiles;
}

// ... (existing code)

module.exports = {
  createDB,
  createFilesTable,
  insertOrUpdateFile,
  getDB,
  getDBFiles,
  deleteFile,
  getDBFilePath,
  getDependentFiles, // Export the new function
};

Now, you can use the getDependentFiles function in the agents/coder.js file to get the list of dependent files and call the coder agent with the necessary details.

File: agents/coder.js To implement the desired new situation, we need to modify the suggestChanges function in the agents/coder.js file to accept a diff input and call the coder agent for each dependent file after a file update. Here's the plan for this file:

  1. Update the suggestChanges function signature to accept an additional parameter diff which will be the diff input.

  2. After receiving the parsedResponse, check if the updateDependentFiles flag is set to true in the response. If it is, call a function (e.g., updateDependentFiles) that will handle updating the dependent files.

  3. Implement the updateDependentFiles function that will:

    • Call a function from modules/db.js to get the list of dependent files (we will implement this function in the modules/db.js file later).
    • For each dependent file, call the suggestChanges function with the task, diff, and the new file context.

Here's the updated agents/coder.js file with the changes:

const { z } = require('zod');
const { PromptTemplate } = require('langchain/prompts');
const { StructuredOutputParser, OutputFixingParser } = require('langchain/output_parsers');
const { getModel } = require('../modules/model');
const { saveLog } = require('../modules/fsOutput');
const { getDependentFiles } = require('../modules/db'); // Import the function to get dependent files

const promptTemplate = 
` 
# USER INPUT
{task}

# YOUR TASK
As a senior software developer, make the requested changes from the USER INPUT.
Write out new code before deleting old code.

{format_instructions}

# SOURCE CODE
{code}
` 

const parser = StructuredOutputParser.fromZodSchema(
  z.object({
    thoughts: z.object({
      text: z.string().describe('your thoughts'),
      reasoning: z.string().describe('your reasoning'),
      criticism: z.string().describe('constructive self-criticism'),
    }),
    output: z.array(
      z.object({
        fileToUpdate: z.string().describe('File to write. (can be the current file or a new file)'),
        content: z.string().describe('Full content for that file'),
      }),
    ),
  })
);

const formatInstructions = parser.getFormatInstructions();

const prompt = new PromptTemplate({
  template: promptTemplate,
  inputVariables: ['task', 'code'],
  partialVariables: { format_instructions: formatInstructions },
});

async function suggestChanges(task, file, diff) {
  const code = formatCode(file)

  const model = getModel(process.env.INDEXER_MODEL);

  const input = await prompt.format({ task, code });
  const response = await model.call(input);

  let parsedResponse
  try {
    parsedResponse = await parser.parse(response);
  } catch (e){
    const fixParser = OutputFixingParser.fromLLM(
      model,
      parser
    );
    parsedResponse = await fixParser.parse(response);
  }

  saveLog(`coder agent INPUT:\n${input}`)
  saveLog(`coder agent OUTPUT:\n${response}`)

  if (parsedResponse.updateDependentFiles) {
    await updateDependentFiles(task, diff);
  }

  return parsedResponse.output;
}

async function updateDependentFiles(task, diff) {
  const dependentFiles = await getDependentFiles();

  for (const file of dependentFiles) {
    await suggestChanges(task, file, diff);
  }
}

function formatCode(file) {
  // format code for prompt
  let code = '';
  code += `### ${file.path}`;
  code += `\n`;
  code += '```';
  code += `\n`;
  code += `${file.code}`;
  code += `\n`;
  code += '```';
  code += `\n`;
  return code
}

module.exports = { suggestChanges }

Now, you can implement the getDependentFiles function in the modules/db.js file to get the list of dependent files from the database.