From b128e4ae45546f45c124c8ef4929029578104dd3 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Wed, 16 Jul 2025 13:17:35 -0700 Subject: [PATCH] Brace/07 16/fixes (#431) * fix: General fixes * cr * bump langchain core langsmith deps --- .vscode/settings.json | 1 + apps/open-swe/src/constants.ts | 13 ++----- .../manager/nodes/classify-message/index.ts | 16 +++++++- .../manager/nodes/classify-message/prompts.ts | 4 +- .../manager/nodes/classify-message/utils.ts | 14 ++++++- .../manager/utils/generate-issue-fields.ts | 16 +++++++- .../planner/nodes/determine-needs-context.ts | 16 +++++++- .../planner/nodes/generate-message/index.ts | 16 +++++++- .../planner/nodes/generate-plan/index.ts | 16 +++++++- .../src/graphs/planner/nodes/notetaker.ts | 16 +++++++- .../src/graphs/planner/nodes/rewrite-plan.ts | 37 ++++++++++++++++--- .../graphs/programmer/nodes/diagnose-error.ts | 16 +++++++- .../nodes/generate-message/index.ts | 16 +++++++- .../src/graphs/programmer/nodes/open-pr.ts | 16 +++++++- .../programmer/nodes/progress-plan-step.ts | 16 +++++++- .../graphs/programmer/nodes/update-plan.ts | 16 +++++++- .../src/graphs/reviewer/nodes/final-review.ts | 16 +++++++- .../nodes/generate-review-actions/index.ts | 16 +++++++- .../src/graphs/shared/diagnose-error.ts | 16 +++++++- apps/open-swe/src/utils/load-model.ts | 13 +++++++ .../src/components/v2/actions-renderer.tsx | 4 ++ package.json | 3 +- packages/shared/src/constants.ts | 3 +- yarn.lock | 18 ++++----- 24 files changed, 278 insertions(+), 56 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 26ae434b..63383fc2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "cSpell.words": [ + "DAYTONA", "helicunate" ] } \ No newline at end of file diff --git a/apps/open-swe/src/constants.ts b/apps/open-swe/src/constants.ts index efde049a..b1121d6d 100644 --- a/apps/open-swe/src/constants.ts +++ b/apps/open-swe/src/constants.ts @@ -1,14 +1,9 @@ -import { SNAPSHOT_NAME } from "@open-swe/shared/constants"; -import { CreateSandboxFromImageParams } from "@daytonaio/sdk"; +import { DAYTONA_SNAPSHOT_NAME } from "@open-swe/shared/constants"; +import { CreateSandboxFromSnapshotParams } from "@daytonaio/sdk"; -export const DEFAULT_SANDBOX_CREATE_PARAMS: CreateSandboxFromImageParams = { - resources: { - cpu: 2, - memory: 4, - disk: 5, - }, +export const DEFAULT_SANDBOX_CREATE_PARAMS: CreateSandboxFromSnapshotParams = { user: "daytona", - image: SNAPSHOT_NAME, + snapshot: DAYTONA_SNAPSHOT_NAME, autoDeleteInterval: 15, // delete after 15 minutes }; diff --git a/apps/open-swe/src/graphs/manager/nodes/classify-message/index.ts b/apps/open-swe/src/graphs/manager/nodes/classify-message/index.ts index 45a94a79..57e690cd 100644 --- a/apps/open-swe/src/graphs/manager/nodes/classify-message/index.ts +++ b/apps/open-swe/src/graphs/manager/nodes/classify-message/index.ts @@ -11,7 +11,11 @@ import { RemoveMessage, } from "@langchain/core/messages"; import { z } from "zod"; -import { loadModel, Task } from "../../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../../utils/load-model.js"; import { Command, END } from "@langchain/langgraph"; import { getMessageContentString } from "@open-swe/shared/messages"; import { @@ -88,9 +92,17 @@ export async function classifyMessage( schema, }; const model = await loadModel(config, Task.ROUTER); + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.ROUTER, + ); const modelWithTools = model.bindTools([respondAndRouteTool], { tool_choice: respondAndRouteTool.name, - parallel_tool_calls: false, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: false, + } + : {}), }); const response = await modelWithTools.invoke([ diff --git a/apps/open-swe/src/graphs/manager/nodes/classify-message/prompts.ts b/apps/open-swe/src/graphs/manager/nodes/classify-message/prompts.ts index aa0f88e2..7c470055 100644 --- a/apps/open-swe/src/graphs/manager/nodes/classify-message/prompts.ts +++ b/apps/open-swe/src/graphs/manager/nodes/classify-message/prompts.ts @@ -2,6 +2,8 @@ export const UPDATE_PROGRAMMER_ROUTING_OPTION = `- update_programmer: You should export const START_PLANNER_ROUTING_OPTION = `- start_planner: You should call this route if the user's message is a complete request you can send to the planner, which it can use to generate a plan. This route may be called when the planner has not started yet.\n`; +export const START_PLANNER_FOR_FOLLOWUP_ROUTING_OPTION = `- start_planner: You should call this route if the user's message is a complete followup request you can send to the planner, which it can use to generate a plan new plan to address the user's feedback/followup request. This route may be called when the planner and programmer are no longer running (e.g. after the user's initial request has been completed).\n`; + export const UPDATE_PLANNER_ROUTING_OPTION = `- update_planner: You should call this route if the user sends a new message containing anything from a related request that the planner should plan for, additional context about their previous request/the codebase, or something which the planner should be aware of.\n`; export const RESUME_AND_UPDATE_PLANNER_ROUTING_OPTION = `- resume_and_update_planner: You should call this route if the planner is currently interrupted, and the user's message includes additional context/related requests the which require updates to the plan. This will resume the planner so that it can handle the user's new request.\n`; @@ -53,7 +55,7 @@ The programmer's current status is: {PROGRAMMER_STATUS} Based on all of the context provided above, generate a response to send to the user, including messaging about the route you'll select from the below options in your next step. Your routing options are: - no_op: This should be called when the user's message is not a new request, additional context, or a new issue to create. This should only be called when none of the routing options are appropriate. -{UPDATE_PROGRAMMER_ROUTING_OPTION}{START_PLANNER_ROUTING_OPTION}{UPDATE_PLANNER_ROUTING_OPTION}{RESUME_AND_UPDATE_PLANNER_ROUTING_OPTION}{CREATE_NEW_ISSUE_ROUTING_OPTION} +{UPDATE_PROGRAMMER_ROUTING_OPTION}{START_PLANNER_ROUTING_OPTION}{UPDATE_PLANNER_ROUTING_OPTION}{RESUME_AND_UPDATE_PLANNER_ROUTING_OPTION}{CREATE_NEW_ISSUE_ROUTING_OPTION}{START_PLANNER_FOR_FOLLOWUP_ROUTING_OPTION} # Response Your response should be clear, concise and straight to the point. Do NOT include any additional context, such as an idea for how to implement their request. diff --git a/apps/open-swe/src/graphs/manager/nodes/classify-message/utils.ts b/apps/open-swe/src/graphs/manager/nodes/classify-message/utils.ts index 575ad5d9..584f30d1 100644 --- a/apps/open-swe/src/graphs/manager/nodes/classify-message/utils.ts +++ b/apps/open-swe/src/graphs/manager/nodes/classify-message/utils.ts @@ -28,6 +28,7 @@ import { RESUME_AND_UPDATE_PLANNER_ROUTING_OPTION, START_PLANNER_ROUTING_OPTION, TASK_PLAN_PROMPT, + START_PLANNER_FOR_FOLLOWUP_ROUTING_OPTION, } from "./prompts.js"; import { createClassificationSchema } from "./schemas.js"; @@ -107,6 +108,9 @@ export function createClassificationPromptAndToolSchema(inputs: { const plannerRunning = inputs.plannerStatus === "busy"; const plannerInterrupted = inputs.plannerStatus === "interrupted"; const plannerNotStarted = inputs.plannerStatus === "not_started"; + // If both are idle, we should allow 'start_planner' to start a new planning run on the same request. + const plannerAndProgrammerIdle = + inputs.programmerStatus === "idle" && inputs.plannerStatus === "idle"; const showCreateIssueOption = inputs.programmerStatus !== "not_started" || @@ -115,7 +119,9 @@ export function createClassificationPromptAndToolSchema(inputs: { const routingOptions: [string, ...string[]] = [ "no_op", ...(programmerRunning ? ["update_programmer"] : []), - ...(plannerNotStarted ? ["start_planner"] : []), + ...((plannerNotStarted ?? plannerAndProgrammerIdle) + ? ["start_planner"] + : []), ...(plannerRunning ? ["update_planner"] : []), ...(plannerInterrupted ? ["resume_and_update_planner"] : []), ...(showCreateIssueOption ? ["create_new_issue"] : []), @@ -138,13 +144,17 @@ export function createClassificationPromptAndToolSchema(inputs: { "{START_PLANNER_ROUTING_OPTION}", plannerNotStarted ? START_PLANNER_ROUTING_OPTION : "", ) + .replaceAll( + "{START_PLANNER_FOR_FOLLOWUP_ROUTING_OPTION}", + plannerAndProgrammerIdle ? START_PLANNER_FOR_FOLLOWUP_ROUTING_OPTION : "", + ) .replaceAll( "{UPDATE_PLANNER_ROUTING_OPTION}", plannerRunning ? UPDATE_PLANNER_ROUTING_OPTION : "", ) .replaceAll( "{RESUME_AND_UPDATE_PLANNER_ROUTING_OPTION}", - plannerNotStarted ? RESUME_AND_UPDATE_PLANNER_ROUTING_OPTION : "", + plannerInterrupted ? RESUME_AND_UPDATE_PLANNER_ROUTING_OPTION : "", ) .replaceAll( "{CREATE_NEW_ISSUE_ROUTING_OPTION}", diff --git a/apps/open-swe/src/graphs/manager/utils/generate-issue-fields.ts b/apps/open-swe/src/graphs/manager/utils/generate-issue-fields.ts index 2040261f..74a9f5e9 100644 --- a/apps/open-swe/src/graphs/manager/utils/generate-issue-fields.ts +++ b/apps/open-swe/src/graphs/manager/utils/generate-issue-fields.ts @@ -1,7 +1,11 @@ import { BaseMessage } from "@langchain/core/messages"; import { GraphConfig } from "@open-swe/shared/open-swe/types"; import { z } from "zod"; -import { loadModel, Task } from "../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../utils/load-model.js"; import { getMessageString } from "../../../utils/message/content.js"; export async function createIssueFieldsFromMessages( @@ -25,10 +29,18 @@ export async function createIssueFieldsFromMessages( ), }), }; + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + { configurable }, + Task.ROUTER, + ); const modelWithTools = model .bindTools([githubIssueTool], { tool_choice: githubIssueTool.name, - parallel_tool_calls: false, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: false, + } + : {}), }) .withConfig({ tags: ["nostream"], runName: "create-issue-fields" }); diff --git a/apps/open-swe/src/graphs/planner/nodes/determine-needs-context.ts b/apps/open-swe/src/graphs/planner/nodes/determine-needs-context.ts index 51e7b0a9..2b8d170f 100644 --- a/apps/open-swe/src/graphs/planner/nodes/determine-needs-context.ts +++ b/apps/open-swe/src/graphs/planner/nodes/determine-needs-context.ts @@ -5,7 +5,11 @@ import { } from "@open-swe/shared/open-swe/planner/types"; import { GraphConfig } from "@open-swe/shared/open-swe/types"; import { z } from "zod"; -import { loadModel, Task } from "../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../utils/load-model.js"; import { getMissingMessages } from "../../../utils/github/issue-messages.js"; import { getMessageString } from "../../../utils/message/content.js"; import { isHumanMessage } from "@langchain/core/messages"; @@ -120,9 +124,17 @@ export async function determineNeedsContext( "Can not determine if more context is needed if there are no missing messages.", ); } + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.ROUTER, + ); const modelWithTools = model.bindTools([determineContextTool], { tool_choice: determineContextTool.name, - parallel_tool_calls: false, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: false, + } + : {}), }); const response = await modelWithTools.invoke([ diff --git a/apps/open-swe/src/graphs/planner/nodes/generate-message/index.ts b/apps/open-swe/src/graphs/planner/nodes/generate-message/index.ts index 7856ff26..48d611d8 100644 --- a/apps/open-swe/src/graphs/planner/nodes/generate-message/index.ts +++ b/apps/open-swe/src/graphs/planner/nodes/generate-message/index.ts @@ -1,4 +1,8 @@ -import { loadModel, Task } from "../../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../../utils/load-model.js"; import { createGetURLContentTool, createShellTool, @@ -51,6 +55,10 @@ export async function generateAction( config: GraphConfig, ): Promise { const model = await loadModel(config, Task.PROGRAMMER); + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.PROGRAMMER, + ); const mcpTools = await getMcpTools(config); const tools = [ @@ -66,7 +74,11 @@ export async function generateAction( const modelWithTools = model.bindTools(tools, { tool_choice: "auto", - parallel_tool_calls: true, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: true, + } + : {}), }); const [missingMessages, { taskPlan: latestTaskPlan }] = await Promise.all([ diff --git a/apps/open-swe/src/graphs/planner/nodes/generate-plan/index.ts b/apps/open-swe/src/graphs/planner/nodes/generate-plan/index.ts index a79ef25b..2da0aa50 100644 --- a/apps/open-swe/src/graphs/planner/nodes/generate-plan/index.ts +++ b/apps/open-swe/src/graphs/planner/nodes/generate-plan/index.ts @@ -2,7 +2,11 @@ import { v4 as uuidv4 } from "uuid"; import { isAIMessage, ToolMessage } from "@langchain/core/messages"; import { createSessionPlanToolFields } from "../../../../tools/index.js"; import { GraphConfig } from "@open-swe/shared/open-swe/types"; -import { loadModel, Task } from "../../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../../utils/load-model.js"; import { PlannerGraphState, PlannerGraphUpdate, @@ -50,10 +54,18 @@ export async function generatePlan( config: GraphConfig, ): Promise { const model = await loadModel(config, Task.PROGRAMMER); + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.SUMMARIZER, + ); const sessionPlanTool = createSessionPlanToolFields(); const modelWithTools = model.bindTools([sessionPlanTool], { tool_choice: sessionPlanTool.name, - parallel_tool_calls: false, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: false, + } + : {}), }); let optionalToolMessage: ToolMessage | undefined; diff --git a/apps/open-swe/src/graphs/planner/nodes/notetaker.ts b/apps/open-swe/src/graphs/planner/nodes/notetaker.ts index e8c35e59..07f0c56b 100644 --- a/apps/open-swe/src/graphs/planner/nodes/notetaker.ts +++ b/apps/open-swe/src/graphs/planner/nodes/notetaker.ts @@ -5,7 +5,11 @@ import { PlannerGraphState, PlannerGraphUpdate, } from "@open-swe/shared/open-swe/planner/types"; -import { loadModel, Task } from "../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../utils/load-model.js"; import { getMessageString } from "../../../utils/message/content.js"; import { getUserRequest } from "../../../utils/user-request.js"; import { formatCustomRulesPrompt } from "../../../utils/custom-rules.js"; @@ -101,9 +105,17 @@ export async function notetaker( config: GraphConfig, ): Promise { const model = await loadModel(config, Task.SUMMARIZER); + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.SUMMARIZER, + ); const modelWithTools = model.bindTools([condenseContextTool], { tool_choice: condenseContextTool.name, - parallel_tool_calls: false, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: false, + } + : {}), }); const conversationHistoryStr = `Here is the full conversation history: diff --git a/apps/open-swe/src/graphs/planner/nodes/rewrite-plan.ts b/apps/open-swe/src/graphs/planner/nodes/rewrite-plan.ts index 012bc220..95eb22b3 100644 --- a/apps/open-swe/src/graphs/planner/nodes/rewrite-plan.ts +++ b/apps/open-swe/src/graphs/planner/nodes/rewrite-plan.ts @@ -11,7 +11,11 @@ import { PlannerGraphUpdate, } from "@open-swe/shared/open-swe/planner/types"; import { getUserRequest } from "../../../utils/user-request.js"; -import { loadModel, Task } from "../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../utils/load-model.js"; const systemPromptIdentifyChanges = `You are operating as an agentic coding assistant built by LangChain. You've previously been given a task to generate a plan of action for, to address the user's initial request. @@ -91,6 +95,7 @@ const formatSysPromptRewritePlan = ( async function identifyTasksToModifyFunc( state: PlannerGraphState, model: ConfigurableModel, + supportsParallelToolCallsParam: boolean, ): Promise { if (!state.planChangeRequest) { throw new Error("No plan change request found."); @@ -136,7 +141,11 @@ async function identifyTasksToModifyFunc( { // The model should always call the tool when identifying plan changes. tool_choice: identifyPlanChangesTool.name, - parallel_tool_calls: false, + ...(supportsParallelToolCallsParam + ? { + parallel_tool_calls: false, + } + : {}), }, ); @@ -173,6 +182,7 @@ async function updatePlanTasksFunc( state: PlannerGraphState, tasksToModify: PlanItem[], model: ConfigurableModel, + supportsParallelToolCallsParam: boolean, ): Promise { if (!state.planChangeRequest) { throw new Error("No plan change request found."); @@ -200,7 +210,11 @@ async function updatePlanTasksFunc( const modelWithUpdatePlanTasksTool = model.bindTools([updatePlanTasksTool], { // The model should always call the tool when identifying plan changes. tool_choice: updatePlanTasksTool.name, - parallel_tool_calls: false, + ...(supportsParallelToolCallsParam + ? { + parallel_tool_calls: false, + } + : {}), }); const userRequest = getUserRequest(state.messages); @@ -241,8 +255,21 @@ export async function rewritePlan( } const model = await loadModel(config, Task.PROGRAMMER); - const tasksToModify = await identifyTasksToModify(state, model); - const updatedPlanTasks = await updatePlanTasks(state, tasksToModify, model); + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.PROGRAMMER, + ); + const tasksToModify = await identifyTasksToModify( + state, + model, + modelSupportsParallelToolCallsParam, + ); + const updatedPlanTasks = await updatePlanTasks( + state, + tasksToModify, + model, + modelSupportsParallelToolCallsParam, + ); return { proposedPlan: updatedPlanTasks, diff --git a/apps/open-swe/src/graphs/programmer/nodes/diagnose-error.ts b/apps/open-swe/src/graphs/programmer/nodes/diagnose-error.ts index 7507221a..545d456e 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/diagnose-error.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/diagnose-error.ts @@ -14,7 +14,11 @@ import { createDiagnoseErrorToolFields } from "@open-swe/shared/open-swe/tools"; import { formatPlanPromptWithSummaries } from "../../../utils/plan-prompt.js"; import { getMessageString } from "../../../utils/message/content.js"; import { getMessageContentString } from "@open-swe/shared/messages"; -import { loadModel, Task } from "../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../utils/load-model.js"; import { z } from "zod"; import { createLogger, LogLevel } from "../../../utils/logger.js"; import { @@ -105,9 +109,17 @@ export async function diagnoseError( logger.info("The last two tool calls resulted in errors. Diagnosing error."); const model = await loadModel(config, Task.SUMMARIZER); + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.SUMMARIZER, + ); const modelWithTools = model.bindTools([diagnoseErrorTool], { tool_choice: diagnoseErrorTool.name, - parallel_tool_calls: false, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: false, + } + : {}), }); const response = await modelWithTools.invoke([ diff --git a/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts b/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts index d32ef320..cfab894b 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts @@ -3,7 +3,11 @@ import { GraphConfig, GraphUpdate, } from "@open-swe/shared/open-swe/types"; -import { loadModel, Task } from "../../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../../utils/load-model.js"; import { createShellTool, createApplyPatchTool, @@ -89,6 +93,10 @@ export async function generateAction( config: GraphConfig, ): Promise { const model = await loadModel(config, Task.PROGRAMMER); + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.PROGRAMMER, + ); const mcpTools = await getMcpTools(config); const tools = [ @@ -110,7 +118,11 @@ export async function generateAction( const modelWithTools = model.bindTools(tools, { tool_choice: "auto", - parallel_tool_calls: true, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: true, + } + : {}), }); const [missingMessages, { taskPlan: latestTaskPlan }] = await Promise.all([ diff --git a/apps/open-swe/src/graphs/programmer/nodes/open-pr.ts b/apps/open-swe/src/graphs/programmer/nodes/open-pr.ts index 8a606e0e..3900172a 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/open-pr.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/open-pr.ts @@ -12,7 +12,11 @@ import { import { createPullRequest } from "../../../utils/github/api.js"; import { createLogger, LogLevel } from "../../../utils/logger.js"; import { z } from "zod"; -import { loadModel, Task } from "../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../utils/load-model.js"; import { formatPlanPromptWithSummaries } from "../../../utils/plan-prompt.js"; import { getUserRequest } from "../../../utils/user-request.js"; import { AIMessage, ToolMessage } from "@langchain/core/messages"; @@ -91,9 +95,17 @@ export async function openPullRequest( const openPrTool = createOpenPrToolFields(); // use the router model since this is a simple task that doesn't need an advanced model const model = await loadModel(config, Task.ROUTER); + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.ROUTER, + ); const modelWithTool = model.bindTools([openPrTool], { tool_choice: openPrTool.name, - parallel_tool_calls: false, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: false, + } + : {}), }); const userRequest = getUserRequest(state.internalMessages); diff --git a/apps/open-swe/src/graphs/programmer/nodes/progress-plan-step.ts b/apps/open-swe/src/graphs/programmer/nodes/progress-plan-step.ts index 818c6286..a483421a 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/progress-plan-step.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/progress-plan-step.ts @@ -6,7 +6,11 @@ import { GraphUpdate, PlanItem, } from "@open-swe/shared/open-swe/types"; -import { loadModel, Task } from "../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../utils/load-model.js"; import { formatPlanPrompt } from "../../../utils/plan-prompt.js"; import { Command } from "@langchain/langgraph"; import { getMessageString } from "../../../utils/message/content.js"; @@ -68,11 +72,19 @@ export async function progressPlanStep( const markNotCompletedTool = createMarkTaskNotCompletedToolFields(); const markCompletedTool = createMarkTaskCompletedToolFields(); const model = await loadModel(config, Task.SUMMARIZER); + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.SUMMARIZER, + ); const modelWithTools = model.bindTools( [markNotCompletedTool, markCompletedTool], { tool_choice: "any", - parallel_tool_calls: false, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: false, + } + : {}), }, ); diff --git a/apps/open-swe/src/graphs/programmer/nodes/update-plan.ts b/apps/open-swe/src/graphs/programmer/nodes/update-plan.ts index 7f6bcf20..598d7b37 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/update-plan.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/update-plan.ts @@ -6,7 +6,11 @@ import { GraphUpdate, CustomRules, } from "@open-swe/shared/open-swe/types"; -import { loadModel, Task } from "../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../utils/load-model.js"; import { z } from "zod"; import { getActiveTask, @@ -119,9 +123,17 @@ export async function updatePlan( }); const model = await loadModel(config, Task.PROGRAMMER); + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.PROGRAMMER, + ); const modelWithTools = model.bindTools([updatePlanTool], { tool_choice: updatePlanTool.name, - parallel_tool_calls: false, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: false, + } + : {}), }); const activeTask = getActiveTask(state.taskPlan); diff --git a/apps/open-swe/src/graphs/reviewer/nodes/final-review.ts b/apps/open-swe/src/graphs/reviewer/nodes/final-review.ts index 6d8b6bc9..3f67eb16 100644 --- a/apps/open-swe/src/graphs/reviewer/nodes/final-review.ts +++ b/apps/open-swe/src/graphs/reviewer/nodes/final-review.ts @@ -14,7 +14,11 @@ import { createCodeReviewMarkTaskCompletedFields, createCodeReviewMarkTaskNotCompleteFields, } from "@open-swe/shared/open-swe/tools"; -import { loadModel, Task } from "../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../utils/load-model.js"; import { GraphConfig, PlanItem } from "@open-swe/shared/open-swe/types"; import { z } from "zod"; import { addTaskPlanToIssue } from "../../../utils/github/issue-task.js"; @@ -77,9 +81,17 @@ export async function finalReview( const incompleteTool = createCodeReviewMarkTaskNotCompleteFields(); const tools = [completedTool, incompleteTool]; const model = await loadModel(config, Task.PROGRAMMER); + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.PROGRAMMER, + ); const modelWithTools = model.bindTools(tools, { tool_choice: "any", - parallel_tool_calls: false, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: false, + } + : {}), }); const response = await modelWithTools.invoke([ diff --git a/apps/open-swe/src/graphs/reviewer/nodes/generate-review-actions/index.ts b/apps/open-swe/src/graphs/reviewer/nodes/generate-review-actions/index.ts index 8878c807..8ee09409 100644 --- a/apps/open-swe/src/graphs/reviewer/nodes/generate-review-actions/index.ts +++ b/apps/open-swe/src/graphs/reviewer/nodes/generate-review-actions/index.ts @@ -1,4 +1,8 @@ -import { loadModel, Task } from "../../../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../../../utils/load-model.js"; import { ReviewerGraphState, ReviewerGraphUpdate, @@ -74,6 +78,10 @@ export async function generateReviewActions( config: GraphConfig, ): Promise { const model = await loadModel(config, Task.PROGRAMMER); + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.PROGRAMMER, + ); const tools = [ createSearchTool(state), createShellTool(state), @@ -81,7 +89,11 @@ export async function generateReviewActions( ]; const modelWithTools = model.bindTools(tools, { tool_choice: "auto", - parallel_tool_calls: true, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: true, + } + : {}), }); const response = await modelWithTools.invoke([ diff --git a/apps/open-swe/src/graphs/shared/diagnose-error.ts b/apps/open-swe/src/graphs/shared/diagnose-error.ts index fa59488d..3e27d1b8 100644 --- a/apps/open-swe/src/graphs/shared/diagnose-error.ts +++ b/apps/open-swe/src/graphs/shared/diagnose-error.ts @@ -11,7 +11,11 @@ import { GraphConfig } from "@open-swe/shared/open-swe/types"; import { createLogger, LogLevel } from "../../utils/logger.js"; import { getAllLastFailedActions } from "../../utils/tool-message-error.js"; import { getMessageString } from "../../utils/message/content.js"; -import { loadModel, Task } from "../../utils/load-model.js"; +import { + loadModel, + supportsParallelToolCallsParam, + Task, +} from "../../utils/load-model.js"; const logger = createLogger(LogLevel.INFO, "SharedDiagnoseError"); @@ -89,9 +93,17 @@ export async function diagnoseError( logger.info("The last few tool calls resulted in errors. Diagnosing error."); const model = await loadModel(config, Task.SUMMARIZER); + const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam( + config, + Task.SUMMARIZER, + ); const modelWithTools = model.bindTools([diagnoseErrorTool], { tool_choice: diagnoseErrorTool.name, - parallel_tool_calls: false, + ...(modelSupportsParallelToolCallsParam + ? { + parallel_tool_calls: false, + } + : {}), }); const response = await modelWithTools.invoke([ diff --git a/apps/open-swe/src/utils/load-model.ts b/apps/open-swe/src/utils/load-model.ts index d1451f7e..9bc09b2b 100644 --- a/apps/open-swe/src/utils/load-model.ts +++ b/apps/open-swe/src/utils/load-model.ts @@ -79,3 +79,16 @@ export async function loadModel(config: GraphConfig, task: Task) { return model; } + +const MODELS_NO_PARALLEL_TOOL_CALLING = ["openai:o3", "openai:o3-mini"]; + +export function supportsParallelToolCallsParam( + config: GraphConfig, + task: Task, +): boolean { + const modelStr = + config.configurable?.[`${task}ModelName`] ?? + TASK_TO_CONFIG_DEFAULTS_MAP[task].modelName; + + return !MODELS_NO_PARALLEL_TOOL_CALLING.some((model) => modelStr === model); +} diff --git a/apps/web/src/components/v2/actions-renderer.tsx b/apps/web/src/components/v2/actions-renderer.tsx index 32adf96f..1d7bcbfc 100644 --- a/apps/web/src/components/v2/actions-renderer.tsx +++ b/apps/web/src/components/v2/actions-renderer.tsx @@ -243,6 +243,10 @@ export function ActionsRenderer({ } }, [stream.values, graphId]); + useEffect(() => { + console.log(stream.messages); + }, [stream.messages]); + if (streamLoading) { return ; } diff --git a/package.json b/package.json index c55d7350..b3d5f5c7 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ }, "resolutions": { "@langchain/langgraph-sdk": "^0.0.95", - "@langchain/core": "^0.3.58" + "@langchain/core": "^0.3.64", + "langsmith": "^0.3.46" }, "packageManager": "yarn@3.5.1" } diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts index 7fb9f40f..166ce2f1 100644 --- a/packages/shared/src/constants.ts +++ b/packages/shared/src/constants.ts @@ -1,6 +1,7 @@ export const TIMEOUT_SEC = 60; // 1 minute export const SANDBOX_ROOT_DIR = "/home/daytona"; -export const SNAPSHOT_NAME = "daytonaio/langchain-open-swe:0.1.0"; +export const DAYTONA_IMAGE_NAME = "daytonaio/langchain-open-swe:0.1.0"; +export const DAYTONA_SNAPSHOT_NAME = "open-swe-vcpu2-mem4-disk5"; export const PLAN_INTERRUPT_DELIMITER = ":::"; export const PLAN_INTERRUPT_ACTION_TITLE = "Approve/Edit Plan"; diff --git a/yarn.lock b/yarn.lock index a32ca773..0743d3dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3098,23 +3098,23 @@ __metadata: languageName: node linkType: hard -"@langchain/core@npm:^0.3.58": - version: 0.3.62 - resolution: "@langchain/core@npm:0.3.62" +"@langchain/core@npm:^0.3.64": + version: 0.3.64 + resolution: "@langchain/core@npm:0.3.64" dependencies: "@cfworker/json-schema": ^4.0.2 ansi-styles: ^5.0.0 camelcase: 6 decamelize: 1.2.0 js-tiktoken: ^1.0.12 - langsmith: ^0.3.33 + langsmith: ^0.3.46 mustache: ^4.2.0 p-queue: ^6.6.2 p-retry: 4 uuid: ^10.0.0 zod: ^3.25.32 zod-to-json-schema: ^3.22.3 - checksum: 7168404d80b8bcc1ff7fbdd91fbf64f172da6731a584e56e0add7ecc301fe002eba10eeb21415187c29ce5b7df96722d9b561a6cb5d54a20c66344def5f58d48 + checksum: 0c24b92f9b68cf39e57f499b2b9ea5489c7f5449467ffc3fe2aad674776ab611fa451221ac4b5f94e956acb63d9c6e87b1a265aae99f3d0f467e77e5a501d41c languageName: node linkType: hard @@ -13768,9 +13768,9 @@ __metadata: languageName: node linkType: hard -"langsmith@npm:^0.3.29, langsmith@npm:^0.3.33": - version: 0.3.43 - resolution: "langsmith@npm:0.3.43" +"langsmith@npm:^0.3.46": + version: 0.3.46 + resolution: "langsmith@npm:0.3.46" dependencies: "@types/uuid": ^10.0.0 chalk: ^4.1.2 @@ -13793,7 +13793,7 @@ __metadata: optional: true openai: optional: true - checksum: 23a1234f82b9100a2f23d9aebae3556f9b296653313e9efff25aa676f008c6712c4014d825dd85e03d50e4a60f2c03b1832e8b5c0053d3dc434d5d91963e1280 + checksum: b9388c3a5dc0179e2d85fdc430e965301c941428639f177748101e354436ffccafb4f89fc8006f88a73db0304fa473d780c87e33582bd27fc2a8ca5add5bc1f3 languageName: node linkType: hard