Brace/07 16/fixes (#431)

* fix: General fixes

* cr

* bump langchain core langsmith deps
This commit is contained in:
Brace Sproul 2025-07-16 13:17:35 -07:00 • committed by GitHub
parent d4ca73089c
commit b128e4ae45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 278 additions and 56 deletions

View file

@ -1,5 +1,6 @@
{ {
"cSpell.words": [ "cSpell.words": [
"DAYTONA",
"helicunate" "helicunate"
] ]
} }

View file

@ -1,14 +1,9 @@
import { SNAPSHOT_NAME } from "@open-swe/shared/constants"; import { DAYTONA_SNAPSHOT_NAME } from "@open-swe/shared/constants";
import { CreateSandboxFromImageParams } from "@daytonaio/sdk"; import { CreateSandboxFromSnapshotParams } from "@daytonaio/sdk";
export const DEFAULT_SANDBOX_CREATE_PARAMS: CreateSandboxFromImageParams = { export const DEFAULT_SANDBOX_CREATE_PARAMS: CreateSandboxFromSnapshotParams = {
resources: {
cpu: 2,
memory: 4,
disk: 5,
},
user: "daytona", user: "daytona",
image: SNAPSHOT_NAME, snapshot: DAYTONA_SNAPSHOT_NAME,
autoDeleteInterval: 15, // delete after 15 minutes autoDeleteInterval: 15, // delete after 15 minutes
}; };

View file

@ -11,7 +11,11 @@ import {
RemoveMessage, RemoveMessage,
} from "@langchain/core/messages"; } from "@langchain/core/messages";
import { z } from "zod"; 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 { Command, END } from "@langchain/langgraph";
import { getMessageContentString } from "@open-swe/shared/messages"; import { getMessageContentString } from "@open-swe/shared/messages";
import { import {
@ -88,9 +92,17 @@ export async function classifyMessage(
schema, schema,
}; };
const model = await loadModel(config, Task.ROUTER); const model = await loadModel(config, Task.ROUTER);
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
config,
Task.ROUTER,
);
const modelWithTools = model.bindTools([respondAndRouteTool], { const modelWithTools = model.bindTools([respondAndRouteTool], {
tool_choice: respondAndRouteTool.name, tool_choice: respondAndRouteTool.name,
parallel_tool_calls: false, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: false,
}
: {}),
}); });
const response = await modelWithTools.invoke([ const response = await modelWithTools.invoke([

View file

@ -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_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 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`; 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. 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: 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. - 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 # 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. 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.

View file

@ -28,6 +28,7 @@ import {
RESUME_AND_UPDATE_PLANNER_ROUTING_OPTION, RESUME_AND_UPDATE_PLANNER_ROUTING_OPTION,
START_PLANNER_ROUTING_OPTION, START_PLANNER_ROUTING_OPTION,
TASK_PLAN_PROMPT, TASK_PLAN_PROMPT,
START_PLANNER_FOR_FOLLOWUP_ROUTING_OPTION,
} from "./prompts.js"; } from "./prompts.js";
import { createClassificationSchema } from "./schemas.js"; import { createClassificationSchema } from "./schemas.js";
@ -107,6 +108,9 @@ export function createClassificationPromptAndToolSchema(inputs: {
const plannerRunning = inputs.plannerStatus === "busy"; const plannerRunning = inputs.plannerStatus === "busy";
const plannerInterrupted = inputs.plannerStatus === "interrupted"; const plannerInterrupted = inputs.plannerStatus === "interrupted";
const plannerNotStarted = inputs.plannerStatus === "not_started"; 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 = const showCreateIssueOption =
inputs.programmerStatus !== "not_started" || inputs.programmerStatus !== "not_started" ||
@ -115,7 +119,9 @@ export function createClassificationPromptAndToolSchema(inputs: {
const routingOptions: [string, ...string[]] = [ const routingOptions: [string, ...string[]] = [
"no_op", "no_op",
...(programmerRunning ? ["update_programmer"] : []), ...(programmerRunning ? ["update_programmer"] : []),
...(plannerNotStarted ? ["start_planner"] : []), ...((plannerNotStarted ?? plannerAndProgrammerIdle)
? ["start_planner"]
: []),
...(plannerRunning ? ["update_planner"] : []), ...(plannerRunning ? ["update_planner"] : []),
...(plannerInterrupted ? ["resume_and_update_planner"] : []), ...(plannerInterrupted ? ["resume_and_update_planner"] : []),
...(showCreateIssueOption ? ["create_new_issue"] : []), ...(showCreateIssueOption ? ["create_new_issue"] : []),
@ -138,13 +144,17 @@ export function createClassificationPromptAndToolSchema(inputs: {
"{START_PLANNER_ROUTING_OPTION}", "{START_PLANNER_ROUTING_OPTION}",
plannerNotStarted ? START_PLANNER_ROUTING_OPTION : "", plannerNotStarted ? START_PLANNER_ROUTING_OPTION : "",
) )
.replaceAll(
"{START_PLANNER_FOR_FOLLOWUP_ROUTING_OPTION}",
plannerAndProgrammerIdle ? START_PLANNER_FOR_FOLLOWUP_ROUTING_OPTION : "",
)
.replaceAll( .replaceAll(
"{UPDATE_PLANNER_ROUTING_OPTION}", "{UPDATE_PLANNER_ROUTING_OPTION}",
plannerRunning ? UPDATE_PLANNER_ROUTING_OPTION : "", plannerRunning ? UPDATE_PLANNER_ROUTING_OPTION : "",
) )
.replaceAll( .replaceAll(
"{RESUME_AND_UPDATE_PLANNER_ROUTING_OPTION}", "{RESUME_AND_UPDATE_PLANNER_ROUTING_OPTION}",
plannerNotStarted ? RESUME_AND_UPDATE_PLANNER_ROUTING_OPTION : "", plannerInterrupted ? RESUME_AND_UPDATE_PLANNER_ROUTING_OPTION : "",
) )
.replaceAll( .replaceAll(
"{CREATE_NEW_ISSUE_ROUTING_OPTION}", "{CREATE_NEW_ISSUE_ROUTING_OPTION}",

View file

@ -1,7 +1,11 @@
import { BaseMessage } from "@langchain/core/messages"; import { BaseMessage } from "@langchain/core/messages";
import { GraphConfig } from "@open-swe/shared/open-swe/types"; import { GraphConfig } from "@open-swe/shared/open-swe/types";
import { z } from "zod"; 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"; import { getMessageString } from "../../../utils/message/content.js";
export async function createIssueFieldsFromMessages( export async function createIssueFieldsFromMessages(
@ -25,10 +29,18 @@ export async function createIssueFieldsFromMessages(
), ),
}), }),
}; };
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
{ configurable },
Task.ROUTER,
);
const modelWithTools = model const modelWithTools = model
.bindTools([githubIssueTool], { .bindTools([githubIssueTool], {
tool_choice: githubIssueTool.name, tool_choice: githubIssueTool.name,
parallel_tool_calls: false, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: false,
}
: {}),
}) })
.withConfig({ tags: ["nostream"], runName: "create-issue-fields" }); .withConfig({ tags: ["nostream"], runName: "create-issue-fields" });

View file

@ -5,7 +5,11 @@ import {
} from "@open-swe/shared/open-swe/planner/types"; } from "@open-swe/shared/open-swe/planner/types";
import { GraphConfig } from "@open-swe/shared/open-swe/types"; import { GraphConfig } from "@open-swe/shared/open-swe/types";
import { z } from "zod"; 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 { getMissingMessages } from "../../../utils/github/issue-messages.js";
import { getMessageString } from "../../../utils/message/content.js"; import { getMessageString } from "../../../utils/message/content.js";
import { isHumanMessage } from "@langchain/core/messages"; 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.", "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], { const modelWithTools = model.bindTools([determineContextTool], {
tool_choice: determineContextTool.name, tool_choice: determineContextTool.name,
parallel_tool_calls: false, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: false,
}
: {}),
}); });
const response = await modelWithTools.invoke([ const response = await modelWithTools.invoke([

View file

@ -1,4 +1,8 @@
import { loadModel, Task } from "../../../../utils/load-model.js"; import {
loadModel,
supportsParallelToolCallsParam,
Task,
} from "../../../../utils/load-model.js";
import { import {
createGetURLContentTool, createGetURLContentTool,
createShellTool, createShellTool,
@ -51,6 +55,10 @@ export async function generateAction(
config: GraphConfig, config: GraphConfig,
): Promise<PlannerGraphUpdate> { ): Promise<PlannerGraphUpdate> {
const model = await loadModel(config, Task.PROGRAMMER); const model = await loadModel(config, Task.PROGRAMMER);
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
config,
Task.PROGRAMMER,
);
const mcpTools = await getMcpTools(config); const mcpTools = await getMcpTools(config);
const tools = [ const tools = [
@ -66,7 +74,11 @@ export async function generateAction(
const modelWithTools = model.bindTools(tools, { const modelWithTools = model.bindTools(tools, {
tool_choice: "auto", tool_choice: "auto",
parallel_tool_calls: true, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: true,
}
: {}),
}); });
const [missingMessages, { taskPlan: latestTaskPlan }] = await Promise.all([ const [missingMessages, { taskPlan: latestTaskPlan }] = await Promise.all([

View file

@ -2,7 +2,11 @@ import { v4 as uuidv4 } from "uuid";
import { isAIMessage, ToolMessage } from "@langchain/core/messages"; import { isAIMessage, ToolMessage } from "@langchain/core/messages";
import { createSessionPlanToolFields } from "../../../../tools/index.js"; import { createSessionPlanToolFields } from "../../../../tools/index.js";
import { GraphConfig } from "@open-swe/shared/open-swe/types"; 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 { import {
PlannerGraphState, PlannerGraphState,
PlannerGraphUpdate, PlannerGraphUpdate,
@ -50,10 +54,18 @@ export async function generatePlan(
config: GraphConfig, config: GraphConfig,
): Promise<PlannerGraphUpdate> { ): Promise<PlannerGraphUpdate> {
const model = await loadModel(config, Task.PROGRAMMER); const model = await loadModel(config, Task.PROGRAMMER);
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
config,
Task.SUMMARIZER,
);
const sessionPlanTool = createSessionPlanToolFields(); const sessionPlanTool = createSessionPlanToolFields();
const modelWithTools = model.bindTools([sessionPlanTool], { const modelWithTools = model.bindTools([sessionPlanTool], {
tool_choice: sessionPlanTool.name, tool_choice: sessionPlanTool.name,
parallel_tool_calls: false, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: false,
}
: {}),
}); });
let optionalToolMessage: ToolMessage | undefined; let optionalToolMessage: ToolMessage | undefined;

View file

@ -5,7 +5,11 @@ import {
PlannerGraphState, PlannerGraphState,
PlannerGraphUpdate, PlannerGraphUpdate,
} from "@open-swe/shared/open-swe/planner/types"; } 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 { getMessageString } from "../../../utils/message/content.js";
import { getUserRequest } from "../../../utils/user-request.js"; import { getUserRequest } from "../../../utils/user-request.js";
import { formatCustomRulesPrompt } from "../../../utils/custom-rules.js"; import { formatCustomRulesPrompt } from "../../../utils/custom-rules.js";
@ -101,9 +105,17 @@ export async function notetaker(
config: GraphConfig, config: GraphConfig,
): Promise<PlannerGraphUpdate> { ): Promise<PlannerGraphUpdate> {
const model = await loadModel(config, Task.SUMMARIZER); const model = await loadModel(config, Task.SUMMARIZER);
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
config,
Task.SUMMARIZER,
);
const modelWithTools = model.bindTools([condenseContextTool], { const modelWithTools = model.bindTools([condenseContextTool], {
tool_choice: condenseContextTool.name, tool_choice: condenseContextTool.name,
parallel_tool_calls: false, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: false,
}
: {}),
}); });
const conversationHistoryStr = `Here is the full conversation history: const conversationHistoryStr = `Here is the full conversation history:

View file

@ -11,7 +11,11 @@ import {
PlannerGraphUpdate, PlannerGraphUpdate,
} from "@open-swe/shared/open-swe/planner/types"; } from "@open-swe/shared/open-swe/planner/types";
import { getUserRequest } from "../../../utils/user-request.js"; 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. 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( async function identifyTasksToModifyFunc(
state: PlannerGraphState, state: PlannerGraphState,
model: ConfigurableModel, model: ConfigurableModel,
supportsParallelToolCallsParam: boolean,
): Promise<PlanItem[]> { ): Promise<PlanItem[]> {
if (!state.planChangeRequest) { if (!state.planChangeRequest) {
throw new Error("No plan change request found."); 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. // The model should always call the tool when identifying plan changes.
tool_choice: identifyPlanChangesTool.name, tool_choice: identifyPlanChangesTool.name,
parallel_tool_calls: false, ...(supportsParallelToolCallsParam
? {
parallel_tool_calls: false,
}
: {}),
}, },
); );
@ -173,6 +182,7 @@ async function updatePlanTasksFunc(
state: PlannerGraphState, state: PlannerGraphState,
tasksToModify: PlanItem[], tasksToModify: PlanItem[],
model: ConfigurableModel, model: ConfigurableModel,
supportsParallelToolCallsParam: boolean,
): Promise<string[]> { ): Promise<string[]> {
if (!state.planChangeRequest) { if (!state.planChangeRequest) {
throw new Error("No plan change request found."); throw new Error("No plan change request found.");
@ -200,7 +210,11 @@ async function updatePlanTasksFunc(
const modelWithUpdatePlanTasksTool = model.bindTools([updatePlanTasksTool], { const modelWithUpdatePlanTasksTool = model.bindTools([updatePlanTasksTool], {
// The model should always call the tool when identifying plan changes. // The model should always call the tool when identifying plan changes.
tool_choice: updatePlanTasksTool.name, tool_choice: updatePlanTasksTool.name,
parallel_tool_calls: false, ...(supportsParallelToolCallsParam
? {
parallel_tool_calls: false,
}
: {}),
}); });
const userRequest = getUserRequest(state.messages); const userRequest = getUserRequest(state.messages);
@ -241,8 +255,21 @@ export async function rewritePlan(
} }
const model = await loadModel(config, Task.PROGRAMMER); const model = await loadModel(config, Task.PROGRAMMER);
const tasksToModify = await identifyTasksToModify(state, model); const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
const updatedPlanTasks = await updatePlanTasks(state, tasksToModify, model); config,
Task.PROGRAMMER,
);
const tasksToModify = await identifyTasksToModify(
state,
model,
modelSupportsParallelToolCallsParam,
);
const updatedPlanTasks = await updatePlanTasks(
state,
tasksToModify,
model,
modelSupportsParallelToolCallsParam,
);
return { return {
proposedPlan: updatedPlanTasks, proposedPlan: updatedPlanTasks,

View file

@ -14,7 +14,11 @@ import { createDiagnoseErrorToolFields } from "@open-swe/shared/open-swe/tools";
import { formatPlanPromptWithSummaries } from "../../../utils/plan-prompt.js"; import { formatPlanPromptWithSummaries } from "../../../utils/plan-prompt.js";
import { getMessageString } from "../../../utils/message/content.js"; import { getMessageString } from "../../../utils/message/content.js";
import { getMessageContentString } from "@open-swe/shared/messages"; 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 { z } from "zod";
import { createLogger, LogLevel } from "../../../utils/logger.js"; import { createLogger, LogLevel } from "../../../utils/logger.js";
import { import {
@ -105,9 +109,17 @@ export async function diagnoseError(
logger.info("The last two tool calls resulted in errors. Diagnosing error."); logger.info("The last two tool calls resulted in errors. Diagnosing error.");
const model = await loadModel(config, Task.SUMMARIZER); const model = await loadModel(config, Task.SUMMARIZER);
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
config,
Task.SUMMARIZER,
);
const modelWithTools = model.bindTools([diagnoseErrorTool], { const modelWithTools = model.bindTools([diagnoseErrorTool], {
tool_choice: diagnoseErrorTool.name, tool_choice: diagnoseErrorTool.name,
parallel_tool_calls: false, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: false,
}
: {}),
}); });
const response = await modelWithTools.invoke([ const response = await modelWithTools.invoke([

View file

@ -3,7 +3,11 @@ import {
GraphConfig, GraphConfig,
GraphUpdate, GraphUpdate,
} from "@open-swe/shared/open-swe/types"; } 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 { import {
createShellTool, createShellTool,
createApplyPatchTool, createApplyPatchTool,
@ -89,6 +93,10 @@ export async function generateAction(
config: GraphConfig, config: GraphConfig,
): Promise<GraphUpdate> { ): Promise<GraphUpdate> {
const model = await loadModel(config, Task.PROGRAMMER); const model = await loadModel(config, Task.PROGRAMMER);
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
config,
Task.PROGRAMMER,
);
const mcpTools = await getMcpTools(config); const mcpTools = await getMcpTools(config);
const tools = [ const tools = [
@ -110,7 +118,11 @@ export async function generateAction(
const modelWithTools = model.bindTools(tools, { const modelWithTools = model.bindTools(tools, {
tool_choice: "auto", tool_choice: "auto",
parallel_tool_calls: true, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: true,
}
: {}),
}); });
const [missingMessages, { taskPlan: latestTaskPlan }] = await Promise.all([ const [missingMessages, { taskPlan: latestTaskPlan }] = await Promise.all([

View file

@ -12,7 +12,11 @@ import {
import { createPullRequest } from "../../../utils/github/api.js"; import { createPullRequest } from "../../../utils/github/api.js";
import { createLogger, LogLevel } from "../../../utils/logger.js"; import { createLogger, LogLevel } from "../../../utils/logger.js";
import { z } from "zod"; 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 { formatPlanPromptWithSummaries } from "../../../utils/plan-prompt.js";
import { getUserRequest } from "../../../utils/user-request.js"; import { getUserRequest } from "../../../utils/user-request.js";
import { AIMessage, ToolMessage } from "@langchain/core/messages"; import { AIMessage, ToolMessage } from "@langchain/core/messages";
@ -91,9 +95,17 @@ export async function openPullRequest(
const openPrTool = createOpenPrToolFields(); const openPrTool = createOpenPrToolFields();
// use the router model since this is a simple task that doesn't need an advanced model // 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 model = await loadModel(config, Task.ROUTER);
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
config,
Task.ROUTER,
);
const modelWithTool = model.bindTools([openPrTool], { const modelWithTool = model.bindTools([openPrTool], {
tool_choice: openPrTool.name, tool_choice: openPrTool.name,
parallel_tool_calls: false, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: false,
}
: {}),
}); });
const userRequest = getUserRequest(state.internalMessages); const userRequest = getUserRequest(state.internalMessages);

View file

@ -6,7 +6,11 @@ import {
GraphUpdate, GraphUpdate,
PlanItem, PlanItem,
} from "@open-swe/shared/open-swe/types"; } 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 { formatPlanPrompt } from "../../../utils/plan-prompt.js";
import { Command } from "@langchain/langgraph"; import { Command } from "@langchain/langgraph";
import { getMessageString } from "../../../utils/message/content.js"; import { getMessageString } from "../../../utils/message/content.js";
@ -68,11 +72,19 @@ export async function progressPlanStep(
const markNotCompletedTool = createMarkTaskNotCompletedToolFields(); const markNotCompletedTool = createMarkTaskNotCompletedToolFields();
const markCompletedTool = createMarkTaskCompletedToolFields(); const markCompletedTool = createMarkTaskCompletedToolFields();
const model = await loadModel(config, Task.SUMMARIZER); const model = await loadModel(config, Task.SUMMARIZER);
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
config,
Task.SUMMARIZER,
);
const modelWithTools = model.bindTools( const modelWithTools = model.bindTools(
[markNotCompletedTool, markCompletedTool], [markNotCompletedTool, markCompletedTool],
{ {
tool_choice: "any", tool_choice: "any",
parallel_tool_calls: false, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: false,
}
: {}),
}, },
); );

View file

@ -6,7 +6,11 @@ import {
GraphUpdate, GraphUpdate,
CustomRules, CustomRules,
} from "@open-swe/shared/open-swe/types"; } 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 { z } from "zod";
import { import {
getActiveTask, getActiveTask,
@ -119,9 +123,17 @@ export async function updatePlan(
}); });
const model = await loadModel(config, Task.PROGRAMMER); const model = await loadModel(config, Task.PROGRAMMER);
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
config,
Task.PROGRAMMER,
);
const modelWithTools = model.bindTools([updatePlanTool], { const modelWithTools = model.bindTools([updatePlanTool], {
tool_choice: updatePlanTool.name, tool_choice: updatePlanTool.name,
parallel_tool_calls: false, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: false,
}
: {}),
}); });
const activeTask = getActiveTask(state.taskPlan); const activeTask = getActiveTask(state.taskPlan);

View file

@ -14,7 +14,11 @@ import {
createCodeReviewMarkTaskCompletedFields, createCodeReviewMarkTaskCompletedFields,
createCodeReviewMarkTaskNotCompleteFields, createCodeReviewMarkTaskNotCompleteFields,
} from "@open-swe/shared/open-swe/tools"; } 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 { GraphConfig, PlanItem } from "@open-swe/shared/open-swe/types";
import { z } from "zod"; import { z } from "zod";
import { addTaskPlanToIssue } from "../../../utils/github/issue-task.js"; import { addTaskPlanToIssue } from "../../../utils/github/issue-task.js";
@ -77,9 +81,17 @@ export async function finalReview(
const incompleteTool = createCodeReviewMarkTaskNotCompleteFields(); const incompleteTool = createCodeReviewMarkTaskNotCompleteFields();
const tools = [completedTool, incompleteTool]; const tools = [completedTool, incompleteTool];
const model = await loadModel(config, Task.PROGRAMMER); const model = await loadModel(config, Task.PROGRAMMER);
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
config,
Task.PROGRAMMER,
);
const modelWithTools = model.bindTools(tools, { const modelWithTools = model.bindTools(tools, {
tool_choice: "any", tool_choice: "any",
parallel_tool_calls: false, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: false,
}
: {}),
}); });
const response = await modelWithTools.invoke([ const response = await modelWithTools.invoke([

View file

@ -1,4 +1,8 @@
import { loadModel, Task } from "../../../../utils/load-model.js"; import {
loadModel,
supportsParallelToolCallsParam,
Task,
} from "../../../../utils/load-model.js";
import { import {
ReviewerGraphState, ReviewerGraphState,
ReviewerGraphUpdate, ReviewerGraphUpdate,
@ -74,6 +78,10 @@ export async function generateReviewActions(
config: GraphConfig, config: GraphConfig,
): Promise<ReviewerGraphUpdate> { ): Promise<ReviewerGraphUpdate> {
const model = await loadModel(config, Task.PROGRAMMER); const model = await loadModel(config, Task.PROGRAMMER);
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
config,
Task.PROGRAMMER,
);
const tools = [ const tools = [
createSearchTool(state), createSearchTool(state),
createShellTool(state), createShellTool(state),
@ -81,7 +89,11 @@ export async function generateReviewActions(
]; ];
const modelWithTools = model.bindTools(tools, { const modelWithTools = model.bindTools(tools, {
tool_choice: "auto", tool_choice: "auto",
parallel_tool_calls: true, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: true,
}
: {}),
}); });
const response = await modelWithTools.invoke([ const response = await modelWithTools.invoke([

View file

@ -11,7 +11,11 @@ import { GraphConfig } from "@open-swe/shared/open-swe/types";
import { createLogger, LogLevel } from "../../utils/logger.js"; import { createLogger, LogLevel } from "../../utils/logger.js";
import { getAllLastFailedActions } from "../../utils/tool-message-error.js"; import { getAllLastFailedActions } from "../../utils/tool-message-error.js";
import { getMessageString } from "../../utils/message/content.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"); 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."); logger.info("The last few tool calls resulted in errors. Diagnosing error.");
const model = await loadModel(config, Task.SUMMARIZER); const model = await loadModel(config, Task.SUMMARIZER);
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
config,
Task.SUMMARIZER,
);
const modelWithTools = model.bindTools([diagnoseErrorTool], { const modelWithTools = model.bindTools([diagnoseErrorTool], {
tool_choice: diagnoseErrorTool.name, tool_choice: diagnoseErrorTool.name,
parallel_tool_calls: false, ...(modelSupportsParallelToolCallsParam
? {
parallel_tool_calls: false,
}
: {}),
}); });
const response = await modelWithTools.invoke([ const response = await modelWithTools.invoke([

View file

@ -79,3 +79,16 @@ export async function loadModel(config: GraphConfig, task: Task) {
return model; 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);
}

View file

@ -243,6 +243,10 @@ export function ActionsRenderer<State extends PlannerGraphState | GraphState>({
} }
}, [stream.values, graphId]); }, [stream.values, graphId]);
useEffect(() => {
console.log(stream.messages);
}, [stream.messages]);
if (streamLoading) { if (streamLoading) {
return <LoadingActionsCardContent />; return <LoadingActionsCardContent />;
} }

View file

@ -21,7 +21,8 @@
}, },
"resolutions": { "resolutions": {
"@langchain/langgraph-sdk": "^0.0.95", "@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" "packageManager": "yarn@3.5.1"
} }

View file

@ -1,6 +1,7 @@
export const TIMEOUT_SEC = 60; // 1 minute export const TIMEOUT_SEC = 60; // 1 minute
export const SANDBOX_ROOT_DIR = "/home/daytona"; 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_DELIMITER = ":::";
export const PLAN_INTERRUPT_ACTION_TITLE = "Approve/Edit Plan"; export const PLAN_INTERRUPT_ACTION_TITLE = "Approve/Edit Plan";

View file

@ -3098,23 +3098,23 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@langchain/core@npm:^0.3.58": "@langchain/core@npm:^0.3.64":
version: 0.3.62 version: 0.3.64
resolution: "@langchain/core@npm:0.3.62" resolution: "@langchain/core@npm:0.3.64"
dependencies: dependencies:
"@cfworker/json-schema": ^4.0.2 "@cfworker/json-schema": ^4.0.2
ansi-styles: ^5.0.0 ansi-styles: ^5.0.0
camelcase: 6 camelcase: 6
decamelize: 1.2.0 decamelize: 1.2.0
js-tiktoken: ^1.0.12 js-tiktoken: ^1.0.12
langsmith: ^0.3.33 langsmith: ^0.3.46
mustache: ^4.2.0 mustache: ^4.2.0
p-queue: ^6.6.2 p-queue: ^6.6.2
p-retry: 4 p-retry: 4
uuid: ^10.0.0 uuid: ^10.0.0
zod: ^3.25.32 zod: ^3.25.32
zod-to-json-schema: ^3.22.3 zod-to-json-schema: ^3.22.3
checksum: 7168404d80b8bcc1ff7fbdd91fbf64f172da6731a584e56e0add7ecc301fe002eba10eeb21415187c29ce5b7df96722d9b561a6cb5d54a20c66344def5f58d48 checksum: 0c24b92f9b68cf39e57f499b2b9ea5489c7f5449467ffc3fe2aad674776ab611fa451221ac4b5f94e956acb63d9c6e87b1a265aae99f3d0f467e77e5a501d41c
languageName: node languageName: node
linkType: hard linkType: hard
@ -13768,9 +13768,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"langsmith@npm:^0.3.29, langsmith@npm:^0.3.33": "langsmith@npm:^0.3.46":
version: 0.3.43 version: 0.3.46
resolution: "langsmith@npm:0.3.43" resolution: "langsmith@npm:0.3.46"
dependencies: dependencies:
"@types/uuid": ^10.0.0 "@types/uuid": ^10.0.0
chalk: ^4.1.2 chalk: ^4.1.2
@ -13793,7 +13793,7 @@ __metadata:
optional: true optional: true
openai: openai:
optional: true optional: true
checksum: 23a1234f82b9100a2f23d9aebae3556f9b296653313e9efff25aa676f008c6712c4014d825dd85e03d50e4a60f2c03b1832e8b5c0053d3dc434d5d91963e1280 checksum: b9388c3a5dc0179e2d85fdc430e965301c941428639f177748101e354436ffccafb4f89fc8006f88a73db0304fa473d780c87e33582bd27fc2a8ca5add5bc1f3
languageName: node languageName: node
linkType: hard linkType: hard