mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 11:33:14 +00:00
fix: Agent and web fixes (#40)
* fix: Agent and web fixes * cr * cr * cr * cr * cr
This commit is contained in:
parent
9b825de25d
commit
d079c63424
10 changed files with 106 additions and 20 deletions
|
|
@ -5,6 +5,8 @@ import { getRepoAbsolutePath } from "../utils/git/index.js";
|
|||
import { formatPlanPrompt } from "../utils/plan-prompt.js";
|
||||
import { pauseSandbox } from "../utils/sandbox.js";
|
||||
import { createLogger, LogLevel } from "../utils/logger.js";
|
||||
import { getCurrentTask } from "../utils/current-task.js";
|
||||
import { getMessageContentString } from "../utils/message/content.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "GenerateMessageNode");
|
||||
|
||||
|
|
@ -81,6 +83,7 @@ You MUST adhere to the following criteria when executing the task:
|
|||
- Always use \`rg\` instead of \`grep/ls -R\` because it is much faster and respects gitignore.
|
||||
- Always use glob patterns when searching with \`rg\` for specific file types. For example, to search for all TSX files, use \`rg -i star -g **/*.tsx project-directory/\`. This is because \`rg\` does not have built in file types for every language.
|
||||
- Only make changes to the existing Git repo ({REPO_DIRECTORY}). Any changes outside this repo will not be detected, so do not attempt to create new files or directories outside of this repo.
|
||||
- You do NOT have access to the \`set_task_status\` or \`diagnose_error\` tools. NEVER attempt to call them.
|
||||
|
||||
Below, is a collection of useful context about the codebase. It is updated after each completed task, and is provided to you to help you make decisions, and avoid duplicate work:
|
||||
{CODEBASE_CONTEXT}
|
||||
|
|
@ -133,9 +136,16 @@ export async function generateAction(
|
|||
}
|
||||
|
||||
logger.info("Generated action", {
|
||||
name: response.tool_calls?.[0].name,
|
||||
args: response.tool_calls?.[0].args,
|
||||
currentTask: getCurrentTask(state.plan).plan,
|
||||
...(response.tool_calls?.[0] && {
|
||||
name: response.tool_calls?.[0].name,
|
||||
args: response.tool_calls?.[0].args,
|
||||
}),
|
||||
...(getMessageContentString(response.content) && {
|
||||
content: getMessageContentString(response.content),
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
messages: [response],
|
||||
...(newSandboxSessionId && { sandboxSessionId: newSandboxSessionId }),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
zodSchemaToString,
|
||||
} from "../utils/zod-to-string.js";
|
||||
import { Command } from "@langchain/langgraph";
|
||||
import { truncateOutput } from "../utils/truncate-outputs.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "TakeAction");
|
||||
|
||||
|
|
@ -105,7 +106,7 @@ export async function takeAction(
|
|||
|
||||
const toolMessage = new ToolMessage({
|
||||
tool_call_id: toolCall.id ?? "",
|
||||
content: result,
|
||||
content: truncateOutput(result),
|
||||
name: toolCall.name,
|
||||
status: toolCallStatus,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import { loadModel, Task } from "../../../utils/load-model.js";
|
||||
import { shellTool, applyPatchTool } from "../../../tools/index.js";
|
||||
import { shellTool } from "../../../tools/index.js";
|
||||
import { PlannerGraphState, PlannerGraphUpdate } from "../types.js";
|
||||
import { GraphConfig } from "../../../types.js";
|
||||
import { isHumanMessage } from "@langchain/core/messages";
|
||||
import { createLogger, LogLevel } from "../../../utils/logger.js";
|
||||
import { getMessageContentString } from "../../../utils/message/content.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "GeneratePlanningMessageNode");
|
||||
|
||||
const systemPrompt = `You are operating as a terminal-based agentic coding assistant built by LangChain. It wraps LLM models to enable natural language interaction with a local codebase. You are expected to be precise, safe, and helpful.
|
||||
|
||||
|
|
@ -23,7 +27,7 @@ export async function generateAction(
|
|||
config: GraphConfig,
|
||||
): Promise<PlannerGraphUpdate> {
|
||||
const model = await loadModel(config, Task.ACTION_GENERATOR);
|
||||
const tools = [shellTool, applyPatchTool];
|
||||
const tools = [shellTool];
|
||||
const modelWithTools = model.bindTools(tools, { tool_choice: "auto" });
|
||||
|
||||
const firstUserMessage = state.messages.find(isHumanMessage);
|
||||
|
|
@ -39,6 +43,16 @@ export async function generateAction(
|
|||
...state.plannerMessages,
|
||||
]);
|
||||
|
||||
logger.info("Generated planning message", {
|
||||
...(response.tool_calls?.[0] && {
|
||||
name: response.tool_calls?.[0].name,
|
||||
args: response.tool_calls?.[0].args,
|
||||
}),
|
||||
...(getMessageContentString(response.content) && {
|
||||
content: getMessageContentString(response.content),
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
plannerMessages: [response],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { isAIMessage, ToolMessage } from "@langchain/core/messages";
|
||||
import { applyPatchTool, shellTool } from "../../../tools/index.js";
|
||||
import { shellTool } from "../../../tools/index.js";
|
||||
import { GraphConfig } from "../../../types.js";
|
||||
import { PlannerGraphState, PlannerGraphUpdate } from "../types.js";
|
||||
import { createLogger, LogLevel } from "../../../utils/logger.js";
|
||||
import { zodSchemaToString } from "../../../utils/zod-to-string.js";
|
||||
import { formatBadArgsError } from "../../../utils/zod-to-string.js";
|
||||
import { truncateOutput } from "../../../utils/truncate-outputs.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "TakeAction");
|
||||
|
||||
|
|
@ -20,7 +21,6 @@ export async function takeAction(
|
|||
}
|
||||
|
||||
const toolsMap = {
|
||||
[applyPatchTool.name]: applyPatchTool,
|
||||
[shellTool.name]: shellTool,
|
||||
};
|
||||
|
||||
|
|
@ -39,9 +39,12 @@ export async function takeAction(
|
|||
let result = "";
|
||||
let toolCallStatus: "success" | "error" = "success";
|
||||
try {
|
||||
const toolResult: { result: string; status: "success" | "error" } =
|
||||
const toolResult =
|
||||
// @ts-expect-error tool.invoke types are weird here...
|
||||
await tool.invoke(toolCall.args);
|
||||
(await tool.invoke(toolCall.args)) as {
|
||||
result: string;
|
||||
status: "success" | "error";
|
||||
};
|
||||
result = toolResult.result;
|
||||
toolCallStatus = toolResult.status;
|
||||
} catch (e) {
|
||||
|
|
@ -68,7 +71,7 @@ export async function takeAction(
|
|||
|
||||
const toolMessage = new ToolMessage({
|
||||
tool_call_id: toolCall.id ?? "",
|
||||
content: result,
|
||||
content: truncateOutput(result),
|
||||
name: toolCall.name,
|
||||
status: toolCallStatus,
|
||||
});
|
||||
|
|
|
|||
12
apps/open-swe/src/utils/current-task.ts
Normal file
12
apps/open-swe/src/utils/current-task.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { PlanItem } from "../types.js";
|
||||
|
||||
export function getCurrentTask(plan: PlanItem[]) {
|
||||
return (
|
||||
plan.filter((p) => !p.completed).sort((a, b) => a.index - b.index)?.[0] || {
|
||||
plan: "No current task found.",
|
||||
index: -1,
|
||||
completed: true,
|
||||
summary: "",
|
||||
}
|
||||
);
|
||||
}
|
||||
36
apps/open-swe/src/utils/truncate-outputs.ts
Normal file
36
apps/open-swe/src/utils/truncate-outputs.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
export function truncateOutput(
|
||||
output: string,
|
||||
options?: {
|
||||
/**
|
||||
* @default 10000
|
||||
*/
|
||||
numStartCharacters?: number;
|
||||
|
||||
/**
|
||||
* @default 10000
|
||||
*/
|
||||
numEndCharacters?: number;
|
||||
},
|
||||
) {
|
||||
const { numStartCharacters = 10000, numEndCharacters = 10000 } =
|
||||
options ?? {};
|
||||
|
||||
if (numStartCharacters < 0 || numEndCharacters < 0) {
|
||||
throw new Error("numStartCharacters and numEndCharacters must be >= 0");
|
||||
}
|
||||
if (!numStartCharacters && !numEndCharacters) {
|
||||
throw new Error(
|
||||
"At least one of numStartCharacters or numEndCharacters must be > 0",
|
||||
);
|
||||
}
|
||||
|
||||
if (output.length <= numStartCharacters + numEndCharacters) {
|
||||
return output;
|
||||
}
|
||||
|
||||
return (
|
||||
output.slice(0, numStartCharacters) +
|
||||
`\n... Output too long. Truncated the middle ${output.length - numStartCharacters - numEndCharacters} characters of the output ...\n` +
|
||||
output.slice(-numEndCharacters)
|
||||
);
|
||||
}
|
||||
|
|
@ -254,6 +254,16 @@ export function Thread() {
|
|||
stream.submit(undefined, {
|
||||
checkpoint: parentCheckpoint,
|
||||
streamMode: ["values"],
|
||||
config: {
|
||||
// TODO: Make configurable (just that target_repository object) & recursion limit in the UI.
|
||||
configurable: {
|
||||
target_repository: {
|
||||
owner: "langchain-ai",
|
||||
repo: "open-swe",
|
||||
},
|
||||
},
|
||||
recursion_limit: 400,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -141,19 +141,19 @@ export function AssistantMessage({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="group mr-auto flex items-start gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="group mr-auto flex w-full max-w-3xl items-start gap-2">
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
{isToolResult ? (
|
||||
<>
|
||||
<span>
|
||||
<ToolResult message={message} />
|
||||
<Interrupt
|
||||
interruptValue={threadInterrupt?.value}
|
||||
isLastMessage={isLastMessage}
|
||||
hasNoAIOrToolMessages={hasNoAIOrToolMessages}
|
||||
/>
|
||||
</>
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span>
|
||||
{contentString.length > 0 && (
|
||||
<div className="py-1">
|
||||
<MarkdownText>{contentString}</MarkdownText>
|
||||
|
|
@ -161,7 +161,7 @@ export function AssistantMessage({
|
|||
)}
|
||||
|
||||
{!hideToolCalls && (
|
||||
<>
|
||||
<span>
|
||||
{(hasToolCalls && toolCallsHaveContents && (
|
||||
<ToolCalls toolCalls={message.tool_calls} />
|
||||
)) ||
|
||||
|
|
@ -171,7 +171,7 @@ export function AssistantMessage({
|
|||
(hasToolCalls && (
|
||||
<ToolCalls toolCalls={message.tool_calls} />
|
||||
))}
|
||||
</>
|
||||
</span>
|
||||
)}
|
||||
|
||||
{message && (
|
||||
|
|
@ -204,7 +204,7 @@ export function AssistantMessage({
|
|||
handleRegenerate={() => handleRegenerate(parentCheckpoint)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ export function HumanMessage({
|
|||
)}
|
||||
{/* Render text if present, otherwise fallback to file/image name */}
|
||||
{contentString ? (
|
||||
<p className="bg-muted ml-auto w-fit rounded-3xl px-4 py-2 text-right whitespace-pre-wrap">
|
||||
<p className="bg-muted ml-auto w-fit rounded-3xl px-4 py-2 whitespace-pre-wrap">
|
||||
{contentString}
|
||||
</p>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export function ToolCalls({
|
|||
if (!toolCalls || toolCalls.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="mx-auto grid max-w-3xl grid-rows-[1fr_auto] gap-2">
|
||||
<div className="grid w-full max-w-3xl grid-rows-[1fr_auto] gap-2">
|
||||
{toolCalls.map((tc, idx) => {
|
||||
const args = tc.args as Record<string, any>;
|
||||
const hasArgs = Object.keys(args).length > 0;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue