mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 12:43:16 +00:00
feat: Improved reviewer prompt and tools (#513)
* feat: Improved reviewer prompt and tools * consolidate around scratchpad * fix build * cr * cr
This commit is contained in:
parent
248957ae62
commit
ff5af2b93b
18 changed files with 215 additions and 148 deletions
|
|
@ -25,10 +25,10 @@ import { getMissingMessages } from "../../../../utils/github/issue-messages.js";
|
|||
import { getPlansFromIssue } from "../../../../utils/github/issue-task.js";
|
||||
import { createSearchTool } from "../../../../tools/search.js";
|
||||
import { formatCustomRulesPrompt } from "../../../../utils/custom-rules.js";
|
||||
import { createPlannerNotesTool } from "../../../../tools/planner-notes.js";
|
||||
import { createScratchpadTool } from "../../../../tools/scratchpad.js";
|
||||
import { getMcpTools } from "../../../../utils/mcp-client.js";
|
||||
import { filterMessagesWithoutContent } from "../../../../utils/message/content.js";
|
||||
import { getPlannerNotes } from "../../utils/get-notes.js";
|
||||
import { getScratchpad } from "../../utils/scratchpad-notes.js";
|
||||
import { formatUserRequestPrompt } from "../../../../utils/user-request.js";
|
||||
import {
|
||||
convertMessagesToCacheControlledMessages,
|
||||
|
|
@ -40,7 +40,7 @@ const logger = createLogger(LogLevel.INFO, "GeneratePlanningMessageNode");
|
|||
function formatSystemPrompt(state: PlannerGraphState): string {
|
||||
// It's a followup if there's more than one human message.
|
||||
const isFollowup = isFollowupRequest(state.taskPlan, state.proposedPlan);
|
||||
const plannerNotes = getPlannerNotes(state.messages)
|
||||
const scratchpad = getScratchpad(state.messages)
|
||||
.map((n) => `- ${n}`)
|
||||
.join("\n");
|
||||
return SYSTEM_PROMPT.replace(
|
||||
|
|
@ -49,7 +49,7 @@ function formatSystemPrompt(state: PlannerGraphState): string {
|
|||
? formatFollowupMessagePrompt(
|
||||
state.taskPlan,
|
||||
state.proposedPlan,
|
||||
plannerNotes,
|
||||
scratchpad,
|
||||
)
|
||||
: "",
|
||||
)
|
||||
|
|
@ -79,7 +79,9 @@ export async function generateAction(
|
|||
const tools = [
|
||||
createSearchTool(state),
|
||||
createShellTool(state),
|
||||
createPlannerNotesTool(),
|
||||
createScratchpadTool(
|
||||
"when generating a final plan, after all context gathering is complete",
|
||||
),
|
||||
createGetURLContentTool(state),
|
||||
createSearchDocumentForTool(state, config),
|
||||
...mcpTools,
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import {
|
|||
import { stopSandbox } from "../../../../utils/sandbox.js";
|
||||
import { z } from "zod";
|
||||
import { formatCustomRulesPrompt } from "../../../../utils/custom-rules.js";
|
||||
import { getPlannerNotes } from "../../utils/get-notes.js";
|
||||
import { PLANNER_NOTES_PROMPT, SYSTEM_PROMPT } from "./prompt.js";
|
||||
import { getScratchpad } from "../../utils/scratchpad-notes.js";
|
||||
import { SCRATCHPAD_PROMPT, SYSTEM_PROMPT } from "./prompt.js";
|
||||
import { DO_NOT_RENDER_ID_PREFIX } from "@open-swe/shared/constants";
|
||||
import { filterMessagesWithoutContent } from "../../../../utils/message/content.js";
|
||||
import { trackCachePerformance } from "../../../../utils/caching.js";
|
||||
|
|
@ -28,7 +28,7 @@ import { trackCachePerformance } from "../../../../utils/caching.js";
|
|||
function formatSystemPrompt(state: PlannerGraphState): string {
|
||||
// It's a followup if there's more than one human message.
|
||||
const isFollowup = isFollowupRequest(state.taskPlan, state.proposedPlan);
|
||||
const plannerNotes = getPlannerNotes(state.messages)
|
||||
const scratchpad = getScratchpad(state.messages)
|
||||
.map((n) => `- ${n}`)
|
||||
.join("\n");
|
||||
return SYSTEM_PROMPT.replace(
|
||||
|
|
@ -42,9 +42,9 @@ function formatSystemPrompt(state: PlannerGraphState): string {
|
|||
.replace("{USER_REQUEST_PROMPT}", formatUserRequestPrompt(state.messages))
|
||||
.replaceAll("{CUSTOM_RULES}", formatCustomRulesPrompt(state.customRules))
|
||||
.replaceAll(
|
||||
"{PLANNER_NOTES}",
|
||||
plannerNotes.length
|
||||
? PLANNER_NOTES_PROMPT.replace("{PLANNER_NOTES}", plannerNotes)
|
||||
"{SCRATCHPAD}",
|
||||
scratchpad.length
|
||||
? SCRATCHPAD_PROMPT.replace("{SCRATCHPAD}", scratchpad)
|
||||
: "",
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
export const PLANNER_NOTES_PROMPT = `Here is a collection of technical notes you took while gathering context for the plan. Ensure you take these into account when writing your plan.
|
||||
export const SCRATCHPAD_PROMPT = `Here is a collection of technical notes you wrote to a scratchpad while gathering context for the plan. Ensure you take these into account when writing your plan.
|
||||
|
||||
<planner_notes>
|
||||
{PLANNER_NOTES}
|
||||
</planner_notes>`;
|
||||
<scratchpad>
|
||||
{SCRATCHPAD}
|
||||
</scratchpad>`;
|
||||
|
||||
export const SYSTEM_PROMPT = `You are a terminal-based agentic coding assistant built by LangChain, designed to enable natural language interaction with local codebases through wrapped LLM models.
|
||||
|
||||
|
|
@ -63,6 +63,6 @@ Structure your plan items as clear directives, for example:
|
|||
|
||||
{CUSTOM_RULES}
|
||||
|
||||
{PLANNER_NOTES}
|
||||
{SCRATCHPAD}
|
||||
|
||||
Remember: Your goal is to create a focused, executable plan that efficiently accomplishes the user's request using the context you've already gathered.`;
|
||||
|
|
|
|||
|
|
@ -13,17 +13,17 @@ import {
|
|||
import { getMessageString } from "../../../utils/message/content.js";
|
||||
import { formatUserRequestPrompt } from "../../../utils/user-request.js";
|
||||
import { formatCustomRulesPrompt } from "../../../utils/custom-rules.js";
|
||||
import { getPlannerNotes } from "../utils/get-notes.js";
|
||||
import { getScratchpad } from "../utils/scratchpad-notes.js";
|
||||
import { ToolMessage } from "@langchain/core/messages";
|
||||
import { DO_NOT_RENDER_ID_PREFIX } from "@open-swe/shared/constants";
|
||||
import { createWriteTechnicalNotesToolFields } from "@open-swe/shared/open-swe/tools";
|
||||
import { trackCachePerformance } from "../../../utils/caching.js";
|
||||
|
||||
const PLANNER_NOTES_PROMPT = `You've also taken technical notes throughout the context gathering process. Ensure you include/incorporate these notes, or the highest quality parts of these notes in your conclusion notes.
|
||||
const SCRATCHPAD_PROMPT = `You've also wrote technical notes to a scratchpad throughout the context gathering process. Ensure you include/incorporate these notes, or the highest quality parts of these notes in your conclusion notes.
|
||||
|
||||
<planner_notes>
|
||||
{PLANNER_NOTES}
|
||||
</planner_notes>`;
|
||||
<scratchpad>
|
||||
{SCRATCHPAD}
|
||||
</scratchpad>`;
|
||||
const CUSTOM_RULES_EXTRA_CONTEXT =
|
||||
"- Carefully read over the user's custom rules to ensure you don't duplicate or repeat information found in that section, as you will always have access to it (even after the planning step!).";
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ Your goal is to generate notes on all of the low-hanging fruit from the conversa
|
|||
|
||||
{CUSTOM_RULES}
|
||||
|
||||
{PLANNER_NOTES}
|
||||
{SCRATCHPAD}
|
||||
|
||||
You MUST adhere to the following criteria when generating your notes:
|
||||
- Do not retain any full code snippets.
|
||||
|
|
@ -62,7 +62,7 @@ With all of this in mind, please carefully inspect the conversation history, and
|
|||
`;
|
||||
|
||||
const formatPrompt = (state: PlannerGraphState): string => {
|
||||
const plannerNotes = getPlannerNotes(state.messages)
|
||||
const scratchpad = getScratchpad(state.messages)
|
||||
.map((n) => ` - ${n}`)
|
||||
.join("\n");
|
||||
|
||||
|
|
@ -84,9 +84,9 @@ const formatPrompt = (state: PlannerGraphState): string => {
|
|||
),
|
||||
)
|
||||
.replaceAll(
|
||||
"{PLANNER_NOTES}",
|
||||
plannerNotes.length
|
||||
? PLANNER_NOTES_PROMPT.replace("{PLANNER_NOTES}", plannerNotes)
|
||||
"{SCRATCHPAD}",
|
||||
scratchpad.length
|
||||
? SCRATCHPAD_PROMPT.replace("{SCRATCHPAD}", scratchpad)
|
||||
: "",
|
||||
)
|
||||
.replaceAll(
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import {
|
|||
stashAndClearChanges,
|
||||
} from "../../../utils/github/git.js";
|
||||
import { getRepoAbsolutePath } from "@open-swe/shared/git";
|
||||
import { createPlannerNotesTool } from "../../../tools/planner-notes.js";
|
||||
import { createScratchpadTool } from "../../../tools/scratchpad.js";
|
||||
import { getMcpTools } from "../../../utils/mcp-client.js";
|
||||
import { getSandboxWithErrorHandling } from "../../../utils/sandbox.js";
|
||||
import { shouldDiagnoseError } from "../../../utils/tool-message-error.js";
|
||||
|
|
@ -50,7 +50,7 @@ export async function takeActions(
|
|||
|
||||
const shellTool = createShellTool(state);
|
||||
const searchTool = createSearchTool(state);
|
||||
const plannerNotesTool = createPlannerNotesTool();
|
||||
const scratchpadTool = createScratchpadTool("");
|
||||
const getURLContentTool = createGetURLContentTool(state);
|
||||
const searchDocumentForTool = createSearchDocumentForTool(state, config);
|
||||
const mcpTools = await getMcpTools(config);
|
||||
|
|
@ -64,7 +64,7 @@ export async function takeActions(
|
|||
const allTools = [
|
||||
shellTool,
|
||||
searchTool,
|
||||
plannerNotesTool,
|
||||
scratchpadTool,
|
||||
getURLContentTool,
|
||||
searchDocumentForTool,
|
||||
...mcpTools,
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ import { TaskPlan } from "@open-swe/shared/open-swe/types";
|
|||
const previousCompletedPlanPrompt = `Here is the list of tasks from the previous session. You've already completed all of these tasks. Use the tasks, and task summaries as context when generating a new plan:
|
||||
{PREVIOUS_PLAN}
|
||||
|
||||
Here are the notes you took while gathering context for these tasks:
|
||||
{PLANNER_NOTES}`;
|
||||
Here are the notes you wrote to a scratchpad while gathering context for these tasks:
|
||||
{SCRATCHPAD}`;
|
||||
|
||||
const previousProposedPlanPrompt = `Here is the complete list of the proposed plan you generated before the user sent their followup request:
|
||||
{PREVIOUS_PROPOSED_PLAN}
|
||||
|
||||
Here are the notes you took while gathering context for these tasks:
|
||||
{PLANNER_NOTES}`;
|
||||
Here are the notes you wrote to a scratchpad while gathering context for these tasks:
|
||||
{SCRATCHPAD}`;
|
||||
|
||||
const followupMessagePrompt = `<followup_message_instructions>
|
||||
The user is sending a followup request for you to generate a plan for. You are provided with the following context to aid in your new plan context gathering steps:
|
||||
|
|
@ -23,10 +23,7 @@ The user is sending a followup request for you to generate a plan for. You are p
|
|||
{PREVIOUS_PLAN}
|
||||
</followup_message_instructions>`;
|
||||
|
||||
const formatPreviousPlans = (
|
||||
tasks: TaskPlan,
|
||||
plannerNotes?: string,
|
||||
): string => {
|
||||
const formatPreviousPlans = (tasks: TaskPlan, scratchpad?: string): string => {
|
||||
const formattedTasksAndRequests = tasks.tasks
|
||||
.map((task) => {
|
||||
const activePlanItems =
|
||||
|
|
@ -53,25 +50,25 @@ ${activePlanItems
|
|||
|
||||
return previousCompletedPlanPrompt
|
||||
.replace("{PREVIOUS_PLAN}", formattedTasksAndRequests)
|
||||
.replace("{PLANNER_NOTES}", plannerNotes || "");
|
||||
.replace("{SCRATCHPAD}", scratchpad || "");
|
||||
};
|
||||
|
||||
const formatPreviousProposedPlan = (
|
||||
proposedPlan: string[],
|
||||
plannerNotes?: string,
|
||||
scratchpad?: string,
|
||||
): string => {
|
||||
const formattedProposedPlan = proposedPlan
|
||||
.map((p) => `<proposed-plan-item>${p}</proposed-plan-item>`)
|
||||
.join("\n");
|
||||
return previousProposedPlanPrompt
|
||||
.replace("{PREVIOUS_PROPOSED_PLAN}", formattedProposedPlan)
|
||||
.replace("{PLANNER_NOTES}", plannerNotes || "");
|
||||
.replace("{SCRATCHPAD}", scratchpad || "");
|
||||
};
|
||||
|
||||
export function formatFollowupMessagePrompt(
|
||||
tasks: TaskPlan,
|
||||
proposedPlan: string[],
|
||||
plannerNotes?: string,
|
||||
scratchpad?: string,
|
||||
): string {
|
||||
let isGeneratingNewPlan = false;
|
||||
if (tasks && tasks.tasks?.length) {
|
||||
|
|
@ -86,8 +83,8 @@ export function formatFollowupMessagePrompt(
|
|||
return followupMessagePrompt.replace(
|
||||
"{PREVIOUS_PLAN}",
|
||||
isGeneratingNewPlan
|
||||
? formatPreviousPlans(tasks, plannerNotes)
|
||||
: formatPreviousProposedPlan(proposedPlan, plannerNotes),
|
||||
? formatPreviousPlans(tasks, scratchpad)
|
||||
: formatPreviousProposedPlan(proposedPlan, scratchpad),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
import { BaseMessage, isAIMessage } from "@langchain/core/messages";
|
||||
import { createTakePlannerNotesFields } from "@open-swe/shared/open-swe/tools";
|
||||
import z from "zod";
|
||||
|
||||
export function getPlannerNotes(messages: BaseMessage[]): string[] {
|
||||
const plannerNotesFields = createTakePlannerNotesFields();
|
||||
const plannerNotes = messages.flatMap((m) => {
|
||||
if (!isAIMessage(m)) {
|
||||
return [];
|
||||
}
|
||||
const notesToolCalls = m.tool_calls?.filter(
|
||||
(tc) => tc.name === plannerNotesFields.name,
|
||||
);
|
||||
if (!notesToolCalls?.length) {
|
||||
return [];
|
||||
}
|
||||
return notesToolCalls.map(
|
||||
(tc) => (tc.args as z.infer<typeof plannerNotesFields.schema>).notes,
|
||||
);
|
||||
});
|
||||
return plannerNotes.flat();
|
||||
}
|
||||
22
apps/open-swe/src/graphs/planner/utils/scratchpad-notes.ts
Normal file
22
apps/open-swe/src/graphs/planner/utils/scratchpad-notes.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { BaseMessage, isAIMessage } from "@langchain/core/messages";
|
||||
import { createScratchpadFields } from "@open-swe/shared/open-swe/tools";
|
||||
import z from "zod";
|
||||
|
||||
export function getScratchpad(messages: BaseMessage[]): string[] {
|
||||
const scratchpadFields = createScratchpadFields("");
|
||||
const scratchpad = messages.flatMap((m) => {
|
||||
if (!isAIMessage(m)) {
|
||||
return [];
|
||||
}
|
||||
const scratchpadToolCalls = m.tool_calls?.filter(
|
||||
(tc) => tc.name === scratchpadFields.name,
|
||||
);
|
||||
if (!scratchpadToolCalls?.length) {
|
||||
return [];
|
||||
}
|
||||
return scratchpadToolCalls.map(
|
||||
(tc) => (tc.args as z.infer<typeof scratchpadFields.schema>).scratchpad,
|
||||
);
|
||||
});
|
||||
return scratchpad.flat();
|
||||
}
|
||||
|
|
@ -23,8 +23,14 @@ import { GraphConfig, PlanItem } from "@open-swe/shared/open-swe/types";
|
|||
import { z } from "zod";
|
||||
import { addTaskPlanToIssue } from "../../../utils/github/issue-task.js";
|
||||
import { getMessageString } from "../../../utils/message/content.js";
|
||||
import { ToolMessage } from "@langchain/core/messages";
|
||||
import {
|
||||
AIMessage,
|
||||
BaseMessage,
|
||||
isAIMessage,
|
||||
ToolMessage,
|
||||
} from "@langchain/core/messages";
|
||||
import { trackCachePerformance } from "../../../utils/caching.js";
|
||||
import { createScratchpadTool } from "../../../tools/scratchpad.js";
|
||||
|
||||
const SYSTEM_PROMPT = `You are a code reviewer for a software engineer working on a large codebase.
|
||||
|
||||
|
|
@ -46,6 +52,9 @@ Here is the full list of actions you took during your review:
|
|||
|
||||
And here are the tasks which were outlined in the plan, and completed by the Programmer Assistant:
|
||||
{PLANNED_TASKS}
|
||||
|
||||
Here are all of the notes you wrote to your scratchpad during the review:
|
||||
{SCRATCHPAD_NOTES}
|
||||
</context>
|
||||
|
||||
<review-guidelines>
|
||||
|
|
@ -56,6 +65,26 @@ Carefully read over all of the provided context above, and if you determine that
|
|||
Otherwise, if you determine that the task has been successfully completed, call the \`{COMPLETE_TOOL_NAME}\` tool.
|
||||
</review-guidelines>`;
|
||||
|
||||
const getScratchpadNotesString = (messages: BaseMessage[]) => {
|
||||
return messages
|
||||
.filter(
|
||||
(m) =>
|
||||
isAIMessage(m) &&
|
||||
m.tool_calls?.length &&
|
||||
m.tool_calls?.some((tc) => tc.name === createScratchpadTool("").name),
|
||||
)
|
||||
.map((m) => {
|
||||
const scratchpadTool = (m as AIMessage).tool_calls?.find(
|
||||
(tc) => tc.name === createScratchpadTool("").name,
|
||||
);
|
||||
if (!scratchpadTool) {
|
||||
return "";
|
||||
}
|
||||
return `<scratchpad_entry>\n${scratchpadTool.args.scratchpad}\n</scratchpad_entry>`;
|
||||
})
|
||||
.join("\n");
|
||||
};
|
||||
|
||||
const formatSystemPrompt = (state: ReviewerGraphState) => {
|
||||
const markCompletedToolName = createCodeReviewMarkTaskCompletedFields().name;
|
||||
const markNotCompleteToolName =
|
||||
|
|
@ -65,6 +94,9 @@ const formatSystemPrompt = (state: ReviewerGraphState) => {
|
|||
const messagesString = state.reviewerMessages
|
||||
.map(getMessageString)
|
||||
.join("\n");
|
||||
const scratchpadNotesString = getScratchpadNotesString(
|
||||
state.reviewerMessages,
|
||||
);
|
||||
|
||||
return SYSTEM_PROMPT.replaceAll("{REVIEW_ACTIONS}", messagesString)
|
||||
.replaceAll(
|
||||
|
|
@ -73,7 +105,8 @@ const formatSystemPrompt = (state: ReviewerGraphState) => {
|
|||
)
|
||||
.replaceAll("{PLANNED_TASKS}", tasksString)
|
||||
.replaceAll("{COMPLETE_TOOL_NAME}", markCompletedToolName)
|
||||
.replaceAll("{NOT_COMPLETE_TOOL_NAME}", markNotCompleteToolName);
|
||||
.replaceAll("{NOT_COMPLETE_TOOL_NAME}", markNotCompleteToolName)
|
||||
.replaceAll("{SCRATCHPAD_NOTES}", scratchpadNotesString);
|
||||
};
|
||||
|
||||
export async function finalReview(
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import {
|
|||
convertMessagesToCacheControlledMessages,
|
||||
trackCachePerformance,
|
||||
} from "../../../../utils/caching.js";
|
||||
import { createScratchpadTool } from "../../../../tools/scratchpad.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "GenerateReviewActionsNode");
|
||||
|
||||
|
|
@ -119,6 +120,9 @@ export async function generateReviewActions(
|
|||
createSearchTool(state),
|
||||
createShellTool(state),
|
||||
createInstallDependenciesTool(state),
|
||||
createScratchpadTool(
|
||||
"when generating a final review, after all context gathering and reviewing is complete",
|
||||
),
|
||||
];
|
||||
tools[tools.length - 1] = {
|
||||
...tools[tools.length - 1],
|
||||
|
|
|
|||
|
|
@ -52,26 +52,44 @@ By reviewing these actions, and comparing them to the plan and original user req
|
|||
</reviewing_guidelines>
|
||||
|
||||
<instructions>
|
||||
You should inspect each of the files modified by the programmer (see the <changed_files> section below), and confirm they properly implement the plan (see the <completed_tasks_and_summaries> section below), and that the user's request has been fully implemented.
|
||||
You should be reviewing them from the perspective of a quality assurance engineer, ensuring the code written is of the highest quality, fully implements the user's request, and all actions have been taken for the PR to be accepted.
|
||||
|
||||
You're also provided with the conversation history of the actions the programmer has taken, and any user input they've received. The first user message below contains this information.
|
||||
Ensure you carefully read over all of these messages to ensure you have the proper context and do not duplicate actions the programmer has already taken.
|
||||
|
||||
Common tasks you should always confirm were executed:
|
||||
- Linter/formatter scripts were executed
|
||||
- Unit tests were executed
|
||||
- If no tests for the code written/updated exists, confirm whether or not tests should be written
|
||||
- Documentation was updated, if applicable
|
||||
When reviewing the changes, you should perform these actions in order:
|
||||
|
||||
<required_scripts>
|
||||
Search for any scripts which are required for the pull request to pass CI. This may include unit tests (you do not have access to environment variables, and thus can not run integration tests), linters, formatters, etc.
|
||||
Once you find these, ensure you write to your scratchpad to record the names of the scripts, how to invoke them, and any other relevant context required to run them.
|
||||
</required_scripts>
|
||||
|
||||
<changed_files>
|
||||
You should carefully review each of the following changed files. For each changed file, ask yourself:
|
||||
- Should this file be committed? You should only include files which are required for the pull request with the changes to be merged. This means backup files, scripts you wrote during development, etc. should be identified, and deleted.
|
||||
You should write to your scratchpad to record the names of the files which should be deleted.
|
||||
|
||||
- Is this file in the correct location? You should ensure that the file is in the correct location for the pull request with the changes to be merged. This means that if the file is in the wrong location, you should identify it, and move it to the correct location.
|
||||
You should write to your scratchpad to record the names of the files which should be moved, and the new location for each file.
|
||||
|
||||
- Do the changes in the file make sense in relation to the user's request?
|
||||
You should inspect the diff (run \`git diff\` via the shell tool) to ensure all of the changes made are:
|
||||
1. Complete, and accurate
|
||||
2. Required for the user's request to be successfully completed
|
||||
3. Are there extraneous comments, or code which is no longer needed?
|
||||
|
||||
Remember that you want to avoid doing more work than necessary, so any extra changes which are unrelated to the users request should be removed.
|
||||
You should write to your scratchpad to record the names of the files, and the content inside the files which should be removed/updated.
|
||||
</changed_files>
|
||||
|
||||
You MUST perform the above actions. You should write your findings to the scratchpad, as you do not need to take action on your findings right now.
|
||||
Once you've completed your review you'll be given the chance to say whether or not the task has been successfully completed, and if not, you'll be able to provide a list of new actions to take.
|
||||
|
||||
**IMPORTANT**:
|
||||
Keep in mind that not all requests/changes will need tests to be written, or documentation to be added/updated. Ensure you consider whether or not the standard engineering organization would write tests, or documentation for the changes you're reviewing.
|
||||
After considering this, you may not need to check if tests should be written, or documentation should be added/updated.
|
||||
|
||||
Based on the generated plan, the actions taken and files changed, you should review the modified code and determine if it properly completes the overall task, or if more changes need to be made/existing changes should be modified.
|
||||
On top of inspecting the changed files, you should also look to see if the programmer missed anything, made changes which do not respect the custom rules, or if the changes are otherwise insufficient to complete the task.
|
||||
|
||||
You do not want to do more work than required, but you always should complete tasks which you believe are necessary to complete the user's request, and merge the pull request without further action.
|
||||
|
||||
After you're satisfied with the context you've gathered, and are ready to provide a final review, respond with exactly 'done' without any tool calls.
|
||||
This will redirect you to a final review step where you'll submit your final review, and optionally provide a list of additional actions to take.
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import { Command } from "@langchain/langgraph";
|
|||
import { shouldDiagnoseError } from "../../../utils/tool-message-error.js";
|
||||
import { filterHiddenMessages } from "../../../utils/message/filter-hidden.js";
|
||||
import { getGitHubTokensFromConfig } from "../../../utils/github-tokens.js";
|
||||
import { createScratchpadTool } from "../../../tools/scratchpad.js";
|
||||
import { getActiveTask } from "@open-swe/shared/open-swe/tasks";
|
||||
import { createPullRequestToolCallMessage } from "../../../utils/message/create-pr-message.js";
|
||||
|
||||
|
|
@ -47,7 +48,13 @@ export async function takeReviewerActions(
|
|||
const shellTool = createShellTool(state);
|
||||
const searchTool = createSearchTool(state);
|
||||
const installDependenciesTool = createInstallDependenciesTool(state);
|
||||
const allTools = [shellTool, searchTool, installDependenciesTool];
|
||||
const scratchpadTool = createScratchpadTool("");
|
||||
const allTools = [
|
||||
shellTool,
|
||||
searchTool,
|
||||
installDependenciesTool,
|
||||
scratchpadTool,
|
||||
];
|
||||
const toolsMap = Object.fromEntries(
|
||||
allTools.map((tool) => [tool.name, tool]),
|
||||
);
|
||||
|
|
@ -98,11 +105,13 @@ export async function takeReviewerActions(
|
|||
result: string;
|
||||
status: "success" | "error";
|
||||
};
|
||||
|
||||
result = toolResult.result;
|
||||
if (!result) {
|
||||
result = toolResult.status;
|
||||
}
|
||||
toolCallStatus = toolResult.status;
|
||||
|
||||
if (!result) {
|
||||
result = toolCallStatus;
|
||||
}
|
||||
} catch (e) {
|
||||
toolCallStatus = "error";
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
import { tool } from "@langchain/core/tools";
|
||||
import { createTakePlannerNotesFields } from "@open-swe/shared/open-swe/tools";
|
||||
|
||||
export function createPlannerNotesTool() {
|
||||
const plannerNotesTool = tool(
|
||||
async (
|
||||
_input,
|
||||
): Promise<{ result: string; status: "success" | "error" }> => {
|
||||
// TODO: This should write to saved state once that feature is released in LangGraph.
|
||||
return {
|
||||
result: "Successfully saved notes. Thank you!",
|
||||
status: "success",
|
||||
};
|
||||
},
|
||||
createTakePlannerNotesFields(),
|
||||
);
|
||||
|
||||
return plannerNotesTool;
|
||||
}
|
||||
19
apps/open-swe/src/tools/scratchpad.ts
Normal file
19
apps/open-swe/src/tools/scratchpad.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { tool } from "@langchain/core/tools";
|
||||
import { createScratchpadFields } from "@open-swe/shared/open-swe/tools";
|
||||
|
||||
export function createScratchpadTool(whenMessage: string) {
|
||||
const scratchpadTool = tool(
|
||||
async (
|
||||
_input,
|
||||
): Promise<{ result: string; status: "success" | "error" }> => {
|
||||
// TODO: This should write to saved state once that feature is released in LangGraph.
|
||||
return {
|
||||
result: "Successfully wrote to scratchpad. Thank you!",
|
||||
status: "success",
|
||||
};
|
||||
},
|
||||
createScratchpadFields(whenMessage),
|
||||
);
|
||||
|
||||
return scratchpadTool;
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ import {
|
|||
createApplyPatchToolFields,
|
||||
createShellToolFields,
|
||||
createInstallDependenciesToolFields,
|
||||
createTakePlannerNotesFields,
|
||||
createScratchpadFields,
|
||||
createGetURLContentToolFields,
|
||||
createSearchToolFields,
|
||||
createSearchDocumentForToolFields,
|
||||
|
|
@ -46,8 +46,8 @@ const installDependenciesTool = createInstallDependenciesToolFields(dummyRepo);
|
|||
type InstallDependenciesToolArgs = z.infer<
|
||||
typeof installDependenciesTool.schema
|
||||
>;
|
||||
const plannerNotesTool = createTakePlannerNotesFields();
|
||||
type PlannerNotesToolArgs = z.infer<typeof plannerNotesTool.schema>;
|
||||
const scratchpadTool = createScratchpadFields("");
|
||||
type ScratchpadToolArgs = z.infer<typeof scratchpadTool.schema>;
|
||||
const getURLContentTool = createGetURLContentToolFields();
|
||||
type GetURLContentToolArgs = z.infer<typeof getURLContentTool.schema>;
|
||||
const searchTool = createSearchToolFields(dummyRepo);
|
||||
|
|
@ -87,9 +87,9 @@ type InstallDependenciesActionProps = BaseActionProps &
|
|||
errorCode?: number;
|
||||
};
|
||||
|
||||
type PlannerNotesActionProps = BaseActionProps &
|
||||
Partial<PlannerNotesToolArgs> & {
|
||||
actionType: "planner_notes";
|
||||
type ScratchpadActionProps = BaseActionProps &
|
||||
Partial<ScratchpadToolArgs> & {
|
||||
actionType: "scratchpad";
|
||||
};
|
||||
|
||||
type GetURLContentActionProps = BaseActionProps &
|
||||
|
|
@ -123,7 +123,7 @@ export type ActionItemProps =
|
|||
| ShellActionProps
|
||||
| PatchActionProps
|
||||
| InstallDependenciesActionProps
|
||||
| PlannerNotesActionProps
|
||||
| ScratchpadActionProps
|
||||
| GetURLContentActionProps
|
||||
| McpActionProps
|
||||
| SearchActionProps
|
||||
|
|
@ -139,7 +139,7 @@ const ACTION_GENERATING_TEXT_MAP = {
|
|||
[shellTool.name]: "Executing...",
|
||||
[applyPatchTool.name]: "Applying patch...",
|
||||
[installDependenciesTool.name]: "Installing dependencies...",
|
||||
[plannerNotesTool.name]: "Saving notes...",
|
||||
[scratchpadTool.name]: "Saving notes...",
|
||||
[getURLContentTool.name]: "Fetching URL content...",
|
||||
[searchDocumentForTool.name]: "Searching document...",
|
||||
[searchTool.name]: "Searching...",
|
||||
|
|
@ -212,8 +212,10 @@ function ActionItem(props: ActionItemProps) {
|
|||
return props.success ? "Patch applied" : "Patch failed";
|
||||
} else if (props.actionType === "install_dependencies") {
|
||||
return props.success ? "Dependencies installed" : "Installation failed";
|
||||
} else if (props.actionType === "planner_notes") {
|
||||
return props.success ? "Notes saved" : "Failed to save notes";
|
||||
} else if (props.actionType === "scratchpad") {
|
||||
return props.success
|
||||
? "Scratchpad updated"
|
||||
: "Failed to update scratchpad";
|
||||
} else if (props.actionType === "get_url_content") {
|
||||
return props.success
|
||||
? "URL content fetched"
|
||||
|
|
@ -248,8 +250,8 @@ function ActionItem(props: ActionItemProps) {
|
|||
return !!props.output;
|
||||
} else if (props.actionType === "apply-patch") {
|
||||
return !!props.diff;
|
||||
} else if (props.actionType === "planner_notes") {
|
||||
return !!(props.notes && props.notes.length > 0);
|
||||
} else if (props.actionType === "scratchpad") {
|
||||
return !!(props.scratchpad && props.scratchpad.length > 0);
|
||||
} else if (props.actionType === "mcp") {
|
||||
const hasArgs = props.args && Object.keys(props.args).length > 0;
|
||||
const hasOutput = !!props.output;
|
||||
|
|
@ -272,10 +274,10 @@ function ActionItem(props: ActionItemProps) {
|
|||
);
|
||||
}
|
||||
|
||||
if (props.actionType === "planner_notes") {
|
||||
if (props.actionType === "scratchpad") {
|
||||
return (
|
||||
<ToolIconWithTooltip
|
||||
toolNamePretty="Planner Notes"
|
||||
toolNamePretty="Scratchpad"
|
||||
icon={<FileText className={cn(defaultIconStyling)} />}
|
||||
/>
|
||||
);
|
||||
|
|
@ -341,11 +343,11 @@ function ActionItem(props: ActionItemProps) {
|
|||
);
|
||||
}
|
||||
|
||||
if (props.actionType === "planner_notes") {
|
||||
if (props.actionType === "scratchpad") {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<span className="text-foreground/80 text-xs font-normal">
|
||||
Planner Notes
|
||||
Scratchpad
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -584,14 +586,14 @@ function ActionItem(props: ActionItemProps) {
|
|||
</div>
|
||||
);
|
||||
} else if (
|
||||
props.actionType === "planner_notes" &&
|
||||
props.notes &&
|
||||
props.notes.length > 0
|
||||
props.actionType === "scratchpad" &&
|
||||
props.scratchpad &&
|
||||
props.scratchpad.length > 0
|
||||
) {
|
||||
return (
|
||||
<div className="bg-muted overflow-x-auto p-2 dark:bg-gray-900">
|
||||
<ul className="list-disc pl-5 text-xs font-normal">
|
||||
{props.notes.map((note, i) => (
|
||||
{props.scratchpad.map((note, i) => (
|
||||
<li
|
||||
key={i}
|
||||
className="text-foreground/90 mb-1 whitespace-pre-wrap"
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ import {
|
|||
createSearchToolFields,
|
||||
createOpenPrToolFields,
|
||||
createInstallDependenciesToolFields,
|
||||
createTakePlannerNotesFields,
|
||||
createCodeReviewMarkTaskCompletedFields,
|
||||
createCodeReviewMarkTaskNotCompleteFields,
|
||||
createDiagnoseErrorToolFields,
|
||||
|
|
@ -46,6 +45,7 @@ import {
|
|||
createWriteTechnicalNotesToolFields,
|
||||
createConversationHistorySummaryToolFields,
|
||||
createReviewStartedToolFields,
|
||||
createScratchpadFields,
|
||||
} from "@open-swe/shared/open-swe/tools";
|
||||
import { z } from "zod";
|
||||
import { isAIMessageSDK, isToolMessageSDK } from "@/lib/langchain-messages";
|
||||
|
|
@ -75,8 +75,8 @@ const installDependenciesTool = createInstallDependenciesToolFields(dummyRepo);
|
|||
type InstallDependenciesToolArgs = z.infer<
|
||||
typeof installDependenciesTool.schema
|
||||
>;
|
||||
const plannerNotesTool = createTakePlannerNotesFields();
|
||||
type PlannerNotesToolArgs = z.infer<typeof plannerNotesTool.schema>;
|
||||
const scratchpadTool = createScratchpadFields("");
|
||||
type ScratchpadToolArgs = z.infer<typeof scratchpadTool.schema>;
|
||||
const markFinalReviewTaskCompletedTool =
|
||||
createCodeReviewMarkTaskCompletedFields();
|
||||
type MarkFinalReviewTaskCompletedToolArgs = z.infer<
|
||||
|
|
@ -113,7 +113,7 @@ function isMcpTool(toolName: string): boolean {
|
|||
shellTool.name,
|
||||
applyPatchTool.name,
|
||||
installDependenciesTool.name,
|
||||
plannerNotesTool.name,
|
||||
scratchpadTool.name,
|
||||
getURLContentTool.name,
|
||||
openPrTool.name,
|
||||
diagnoseErrorTool.name,
|
||||
|
|
@ -194,6 +194,9 @@ export function mapToolMessageToActionStepProps(
|
|||
const status: ActionItemProps["status"] = "done";
|
||||
const success = message.status === "success";
|
||||
|
||||
const msgContent = getContentString(message.content);
|
||||
const output = msgContent === "" ? "Empty string" : msgContent;
|
||||
|
||||
if (toolCall?.name === shellTool.name) {
|
||||
const args = toolCall.args as ShellToolArgs;
|
||||
return {
|
||||
|
|
@ -202,7 +205,7 @@ export function mapToolMessageToActionStepProps(
|
|||
success,
|
||||
command: args.command || [],
|
||||
workdir: args.workdir,
|
||||
output: getContentString(message.content),
|
||||
output,
|
||||
reasoningText,
|
||||
};
|
||||
} else if (toolCall?.name === applyPatchTool.name) {
|
||||
|
|
@ -231,7 +234,7 @@ export function mapToolMessageToActionStepProps(
|
|||
exclude_files: args.exclude_files || "",
|
||||
include_files: args.include_files || "",
|
||||
file_types: args.file_types || [],
|
||||
output: getContentString(message.content),
|
||||
output,
|
||||
reasoningText,
|
||||
};
|
||||
} else if (toolCall?.name === installDependenciesTool.name) {
|
||||
|
|
@ -242,16 +245,16 @@ export function mapToolMessageToActionStepProps(
|
|||
success,
|
||||
command: args.command || "",
|
||||
workdir: args.workdir || "",
|
||||
output: getContentString(message.content),
|
||||
output,
|
||||
reasoningText,
|
||||
};
|
||||
} else if (toolCall?.name === plannerNotesTool.name) {
|
||||
const args = toolCall.args as PlannerNotesToolArgs;
|
||||
} else if (toolCall?.name === scratchpadTool.name) {
|
||||
const args = toolCall.args as ScratchpadToolArgs;
|
||||
return {
|
||||
actionType: "planner_notes",
|
||||
actionType: "scratchpad",
|
||||
status,
|
||||
success,
|
||||
notes: args.notes || [],
|
||||
scratchpad: args.scratchpad || [],
|
||||
reasoningText,
|
||||
};
|
||||
} else if (toolCall?.name === getURLContentTool.name) {
|
||||
|
|
@ -261,7 +264,7 @@ export function mapToolMessageToActionStepProps(
|
|||
status,
|
||||
success,
|
||||
url: args.url || "",
|
||||
output: getContentString(message.content),
|
||||
output,
|
||||
reasoningText,
|
||||
};
|
||||
} else if (toolCall?.name === searchDocumentForTool.name) {
|
||||
|
|
@ -272,7 +275,7 @@ export function mapToolMessageToActionStepProps(
|
|||
success,
|
||||
url: args.url || "",
|
||||
query: args.query || "",
|
||||
output: getContentString(message.content),
|
||||
output,
|
||||
reasoningText,
|
||||
};
|
||||
} else if (toolCall && isMcpTool(toolCall.name)) {
|
||||
|
|
@ -282,7 +285,7 @@ export function mapToolMessageToActionStepProps(
|
|||
success,
|
||||
toolName: toolCall.name,
|
||||
args: toolCall.args as Record<string, any>,
|
||||
output: getContentString(message.content),
|
||||
output,
|
||||
reasoningText,
|
||||
};
|
||||
}
|
||||
|
|
@ -351,7 +354,7 @@ export function AssistantMessage({
|
|||
tc.name === applyPatchTool.name ||
|
||||
tc.name === searchTool.name ||
|
||||
tc.name === installDependenciesTool.name ||
|
||||
tc.name === plannerNotesTool.name ||
|
||||
tc.name === scratchpadTool.name ||
|
||||
tc.name === getURLContentTool.name ||
|
||||
isMcpTool(tc.name),
|
||||
)
|
||||
|
|
@ -638,12 +641,12 @@ export function AssistantMessage({
|
|||
workdir: args?.workdir || "",
|
||||
output: "",
|
||||
} as ActionItemProps;
|
||||
} else if (toolCall.name === plannerNotesTool.name) {
|
||||
const args = toolCall.args as PlannerNotesToolArgs;
|
||||
} else if (toolCall.name === scratchpadTool.name) {
|
||||
const args = toolCall.args as ScratchpadToolArgs;
|
||||
return {
|
||||
actionType: "planner_notes",
|
||||
actionType: "scratchpad",
|
||||
status: "generating",
|
||||
notes: args?.notes || [],
|
||||
scratchpad: args?.scratchpad || [],
|
||||
} as ActionItemProps;
|
||||
} else if (toolCall.name === getURLContentTool.name) {
|
||||
const args = toolCall.args as GetURLContentToolArgs;
|
||||
|
|
|
|||
|
|
@ -344,22 +344,21 @@ export function createOpenPrToolFields() {
|
|||
};
|
||||
}
|
||||
|
||||
export function createTakePlannerNotesFields() {
|
||||
const plannerContextNotesSchema = z.object({
|
||||
notes: z
|
||||
export function createScratchpadFields(whenMessage: string) {
|
||||
const scratchpadSchema = z.object({
|
||||
scratchpad: z
|
||||
.array(z.string())
|
||||
.describe(
|
||||
"The high quality, concise and technical notes you deem important to save for the programmer to use when implementing the plan.",
|
||||
`Write concise, technical, and useful notes to your scratchpad. These notes will be saved for you to use ${whenMessage}.`,
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
name: "take_notes",
|
||||
schema: plannerContextNotesSchema,
|
||||
name: "scratchpad",
|
||||
schema: scratchpadSchema,
|
||||
description:
|
||||
"Use this tool to write & save technical notes on the planner context.\n" +
|
||||
"This should be called if you come across context you think will be highly useful to the programmer when they are actually implementing the plan, and you want to ensure it's not lost.\n" +
|
||||
"Do not duplicate any information present in the user provided 'custom rules', as we want to avoid duplicating context.",
|
||||
`Use this tool to write & save technical notes on the actions you take, and observations you make, and any notes you believe will be useful ${whenMessage}.` +
|
||||
" This should be called if you come across context which you believe will be useful to you during later steps.",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ export const GraphAnnotation = MessagesZodState.extend({
|
|||
},
|
||||
}),
|
||||
/**
|
||||
* Notes taken based on the actions preformed by the planning agent.
|
||||
* Notes taken based on the actions performed by the planning agent.
|
||||
*/
|
||||
contextGatheringNotes: withLangGraph(z.custom<string>(), {
|
||||
reducer: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue