mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-03 04:33:27 +00:00
Feat: Diagnose error action component (#325)
* init diagnose-error component * rm old diagnose error genui v1 component * diagnose-error schema to shared/tools * CR * Update apps/web/src/components/thread/messages/ai.tsx --------- Co-authored-by: Brace Sproul <braceasproul@gmail.com>
This commit is contained in:
parent
4e52be54f8
commit
942bc5ca1d
7 changed files with 132 additions and 136 deletions
|
|
@ -9,6 +9,7 @@ import {
|
||||||
GraphUpdate,
|
GraphUpdate,
|
||||||
PlanItem,
|
PlanItem,
|
||||||
} from "@open-swe/shared/open-swe/types";
|
} from "@open-swe/shared/open-swe/types";
|
||||||
|
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";
|
||||||
|
|
@ -56,15 +57,7 @@ const userPrompt = `Here is the full conversation history from the steps taken t
|
||||||
|
|
||||||
Please carefully go over all of this information, and provide a helpful explanation of exactly what the issue is, and how you can fix it. When you are ready to provide your diagnosis, call the \`diagnose_error\` tool.`;
|
Please carefully go over all of this information, and provide a helpful explanation of exactly what the issue is, and how you can fix it. When you are ready to provide your diagnosis, call the \`diagnose_error\` tool.`;
|
||||||
|
|
||||||
const diagnoseErrorToolSchema = z.object({
|
const diagnoseErrorTool = createDiagnoseErrorToolFields();
|
||||||
diagnosis: z.string().describe("The diagnosis of the error."),
|
|
||||||
});
|
|
||||||
|
|
||||||
const diagnoseErrorTool = {
|
|
||||||
name: "diagnose_error",
|
|
||||||
description: "Diagnoses an error given a diagnosis.",
|
|
||||||
schema: diagnoseErrorToolSchema,
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatSystemPrompt = (
|
const formatSystemPrompt = (
|
||||||
lastFailedActionContent: string,
|
lastFailedActionContent: string,
|
||||||
|
|
@ -138,7 +131,7 @@ export async function diagnoseError(
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Diagnosed error successfully.", {
|
logger.info("Diagnosed error successfully.", {
|
||||||
diagnosis: (toolCall.args as z.infer<typeof diagnoseErrorToolSchema>)
|
diagnosis: (toolCall.args as z.infer<typeof diagnoseErrorTool.schema>)
|
||||||
.diagnosis,
|
.diagnosis,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import "../app/globals.css";
|
|
||||||
import {
|
|
||||||
AlertTriangle,
|
|
||||||
Loader2,
|
|
||||||
CheckCircle,
|
|
||||||
MessageSquare,
|
|
||||||
FileText,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
type DiagnoseErrorProps = {
|
|
||||||
status: "loading" | "generating" | "done";
|
|
||||||
diagnosis?: string;
|
|
||||||
recommendation?: string;
|
|
||||||
reasoningText?: string;
|
|
||||||
summaryText?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function DiagnoseError({
|
|
||||||
status,
|
|
||||||
diagnosis,
|
|
||||||
recommendation,
|
|
||||||
reasoningText,
|
|
||||||
summaryText,
|
|
||||||
}: DiagnoseErrorProps) {
|
|
||||||
const [showReasoning, setShowReasoning] = useState(false);
|
|
||||||
const [showSummary, setShowSummary] = useState(false);
|
|
||||||
|
|
||||||
const getStatusIcon = () => {
|
|
||||||
switch (status) {
|
|
||||||
case "loading":
|
|
||||||
return (
|
|
||||||
<div className="h-3.5 w-3.5 rounded-full border border-gray-300" />
|
|
||||||
);
|
|
||||||
case "generating":
|
|
||||||
return <Loader2 className="h-3.5 w-3.5 animate-spin text-gray-500" />;
|
|
||||||
case "done":
|
|
||||||
return <CheckCircle className="h-3.5 w-3.5 text-green-500" />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusText = () => {
|
|
||||||
switch (status) {
|
|
||||||
case "loading":
|
|
||||||
return "Preparing error analysis...";
|
|
||||||
case "generating":
|
|
||||||
return "Diagnosing errors...";
|
|
||||||
case "done":
|
|
||||||
return "Error diagnosis complete";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="overflow-hidden rounded-md border border-gray-200">
|
|
||||||
{reasoningText && (
|
|
||||||
<div className="border-b border-blue-100 bg-blue-50 p-2">
|
|
||||||
<button
|
|
||||||
onClick={() => setShowReasoning(!showReasoning)}
|
|
||||||
className="flex items-center gap-1 text-xs font-normal text-blue-700 hover:text-blue-800"
|
|
||||||
>
|
|
||||||
<MessageSquare className="h-3 w-3" />
|
|
||||||
{showReasoning ? "Hide reasoning" : "Show reasoning"}
|
|
||||||
</button>
|
|
||||||
{showReasoning && (
|
|
||||||
<p className="mt-1 text-xs font-normal text-blue-800">
|
|
||||||
{reasoningText}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex items-center border-b border-gray-200 bg-gray-50 p-2">
|
|
||||||
<AlertTriangle className="mr-2 h-3.5 w-3.5 text-amber-500" />
|
|
||||||
<span className="flex-1 text-xs font-normal text-gray-800">
|
|
||||||
{getStatusText()}
|
|
||||||
</span>
|
|
||||||
{getStatusIcon()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{status === "done" && diagnosis && (
|
|
||||||
<div className="p-2">
|
|
||||||
<div className="mb-2">
|
|
||||||
<h3 className="mb-1 text-xs font-normal text-gray-500">
|
|
||||||
Diagnosis
|
|
||||||
</h3>
|
|
||||||
<p className="text-xs font-normal text-gray-800">{diagnosis}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{recommendation && (
|
|
||||||
<div>
|
|
||||||
<h3 className="mb-1 text-xs font-normal text-gray-500">
|
|
||||||
Recommendation
|
|
||||||
</h3>
|
|
||||||
<p className="text-xs font-normal text-gray-800">
|
|
||||||
{recommendation}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{summaryText && status === "done" && (
|
|
||||||
<div className="border-t border-green-100 bg-green-50 p-2">
|
|
||||||
<button
|
|
||||||
onClick={() => setShowSummary(!showSummary)}
|
|
||||||
className="flex items-center gap-1 text-xs font-normal text-green-700 hover:text-green-800"
|
|
||||||
>
|
|
||||||
<FileText className="h-3 w-3" />
|
|
||||||
{showSummary ? "Hide summary" : "Show summary"}
|
|
||||||
</button>
|
|
||||||
{showSummary && (
|
|
||||||
<p className="mt-1 text-xs font-normal text-green-800">
|
|
||||||
{summaryText}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { ActionStep } from "./action-step";
|
import { ActionStep } from "./action-step";
|
||||||
import { DiagnoseError } from "./diagnose-error";
|
|
||||||
import { InitializeStep } from "./initialize-step";
|
import { InitializeStep } from "./initialize-step";
|
||||||
import { PushChanges } from "./push-changes";
|
import { PushChanges } from "./push-changes";
|
||||||
import { ReplanningStep } from "./replanning-step";
|
import { ReplanningStep } from "./replanning-step";
|
||||||
|
|
@ -7,7 +6,6 @@ import { TaskSummary } from "./task-summary";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
"action-step": ActionStep,
|
"action-step": ActionStep,
|
||||||
"diagnose-error": DiagnoseError,
|
|
||||||
"initialize-step": InitializeStep,
|
"initialize-step": InitializeStep,
|
||||||
"push-changes": PushChanges,
|
"push-changes": PushChanges,
|
||||||
"replanning-step": ReplanningStep,
|
"replanning-step": ReplanningStep,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import { Interrupt } from "./interrupt";
|
||||||
import { ActionStep, ActionItemProps } from "@/components/gen-ui/action-step";
|
import { ActionStep, ActionItemProps } from "@/components/gen-ui/action-step";
|
||||||
import { TaskSummary } from "@/components/gen-ui/task-summary";
|
import { TaskSummary } from "@/components/gen-ui/task-summary";
|
||||||
import { PullRequestOpened } from "@/components/gen-ui/pull-request-opened";
|
import { PullRequestOpened } from "@/components/gen-ui/pull-request-opened";
|
||||||
|
import { DiagnoseErrorAction } from "@/components/v2/diagnose-error-action";
|
||||||
import { ToolCall } from "@langchain/core/messages/tool";
|
import { ToolCall } from "@langchain/core/messages/tool";
|
||||||
import {
|
import {
|
||||||
createApplyPatchToolFields,
|
createApplyPatchToolFields,
|
||||||
|
|
@ -30,6 +31,7 @@ import {
|
||||||
createOpenPrToolFields,
|
createOpenPrToolFields,
|
||||||
createInstallDependenciesToolFields,
|
createInstallDependenciesToolFields,
|
||||||
createTakePlannerNotesFields,
|
createTakePlannerNotesFields,
|
||||||
|
createDiagnoseErrorToolFields,
|
||||||
} from "@open-swe/shared/open-swe/tools";
|
} from "@open-swe/shared/open-swe/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { isAIMessageSDK, isToolMessageSDK } from "@/lib/langchain-messages";
|
import { isAIMessageSDK, isToolMessageSDK } from "@/lib/langchain-messages";
|
||||||
|
|
@ -54,6 +56,9 @@ type InstallDependenciesToolArgs = z.infer<
|
||||||
const plannerNotesTool = createTakePlannerNotesFields();
|
const plannerNotesTool = createTakePlannerNotesFields();
|
||||||
type PlannerNotesToolArgs = z.infer<typeof plannerNotesTool.schema>;
|
type PlannerNotesToolArgs = z.infer<typeof plannerNotesTool.schema>;
|
||||||
|
|
||||||
|
const diagnoseErrorTool = createDiagnoseErrorToolFields();
|
||||||
|
type DiagnoseErrorToolArgs = z.infer<typeof diagnoseErrorTool.schema>;
|
||||||
|
|
||||||
function CustomComponent({
|
function CustomComponent({
|
||||||
message,
|
message,
|
||||||
thread,
|
thread,
|
||||||
|
|
@ -256,6 +261,10 @@ export function AssistantMessage({
|
||||||
? aiToolCalls.find((tc) => tc.name === openPrTool.name)
|
? aiToolCalls.find((tc) => tc.name === openPrTool.name)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const diagnoseErrorToolCall = message
|
||||||
|
? aiToolCalls.find((tc) => tc.name === diagnoseErrorTool.name)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
// We can be sure that if the task status tool call is present, it will be the
|
// We can be sure that if the task status tool call is present, it will be the
|
||||||
// only tool call/result we need to render for this message.
|
// only tool call/result we need to render for this message.
|
||||||
if (taskStatusToolCall) {
|
if (taskStatusToolCall) {
|
||||||
|
|
@ -278,7 +287,25 @@ export function AssistantMessage({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same for PR tool. If this is present, it's the only tool call we need to render.
|
if (diagnoseErrorToolCall) {
|
||||||
|
const correspondingToolResult = toolResults.find(
|
||||||
|
(tr) => tr && tr.tool_call_id === diagnoseErrorToolCall.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const args = diagnoseErrorToolCall.args as DiagnoseErrorToolArgs;
|
||||||
|
const reasoningText = getContentString(content);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<DiagnoseErrorAction
|
||||||
|
status={correspondingToolResult ? "done" : "generating"}
|
||||||
|
diagnosis={args.diagnosis}
|
||||||
|
reasoningText={reasoningText}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (openPrToolCall) {
|
if (openPrToolCall) {
|
||||||
let branch: string | undefined;
|
let branch: string | undefined;
|
||||||
let targetBranch: string | undefined = "main";
|
let targetBranch: string | undefined = "main";
|
||||||
|
|
|
||||||
89
apps/web/src/components/v2/diagnose-error-action.tsx
Normal file
89
apps/web/src/components/v2/diagnose-error-action.tsx
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
AlertTriangle,
|
||||||
|
Loader2,
|
||||||
|
CheckCircle,
|
||||||
|
MessageSquare,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
type DiagnoseErrorActionProps = {
|
||||||
|
status: "loading" | "generating" | "done";
|
||||||
|
diagnosis?: string;
|
||||||
|
reasoningText?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DiagnoseErrorAction({
|
||||||
|
status,
|
||||||
|
diagnosis,
|
||||||
|
reasoningText,
|
||||||
|
}: DiagnoseErrorActionProps) {
|
||||||
|
const [showReasoning, setShowReasoning] = useState(false);
|
||||||
|
|
||||||
|
const getStatusIcon = () => {
|
||||||
|
switch (status) {
|
||||||
|
case "loading":
|
||||||
|
return <div className="border-border size-3.5 rounded-full border" />;
|
||||||
|
case "generating":
|
||||||
|
return (
|
||||||
|
<Loader2 className="text-muted-foreground size-3.5 animate-spin" />
|
||||||
|
);
|
||||||
|
case "done":
|
||||||
|
return <CheckCircle className="size-3.5 text-green-500" />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusText = () => {
|
||||||
|
switch (status) {
|
||||||
|
case "loading":
|
||||||
|
return "Preparing error analysis...";
|
||||||
|
case "generating":
|
||||||
|
return "Diagnosing errors...";
|
||||||
|
case "done":
|
||||||
|
return "Error diagnosis complete";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border-border overflow-hidden rounded-md border">
|
||||||
|
{reasoningText && (
|
||||||
|
<div className="border-b border-blue-100 bg-blue-50 p-2 dark:border-blue-800 dark:bg-blue-950">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowReasoning(!showReasoning)}
|
||||||
|
className="flex items-center gap-1 text-xs font-normal text-blue-700 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
||||||
|
>
|
||||||
|
<MessageSquare className="h-3 w-3" />
|
||||||
|
{showReasoning ? "Hide reasoning" : "Show reasoning"}
|
||||||
|
</button>
|
||||||
|
{showReasoning && (
|
||||||
|
<p className="mt-1 text-xs font-normal text-blue-800 dark:text-blue-300">
|
||||||
|
{reasoningText}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="border-border flex items-center border-b bg-gray-50 p-2 dark:bg-gray-800">
|
||||||
|
<AlertTriangle className="mr-2 size-3.5 text-amber-500" />
|
||||||
|
<span className="text-foreground/80 flex-1 text-xs font-normal">
|
||||||
|
{getStatusText()}
|
||||||
|
</span>
|
||||||
|
{getStatusIcon()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{status === "done" && diagnosis && (
|
||||||
|
<div className="p-2">
|
||||||
|
<div className="mb-2">
|
||||||
|
<h3 className="text-muted-foreground mb-1 text-xs font-normal">
|
||||||
|
Diagnosis
|
||||||
|
</h3>
|
||||||
|
<p className="text-foreground/80 text-xs font-normal">
|
||||||
|
{diagnosis}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -127,7 +127,6 @@ export function ThreadView({
|
||||||
setChatInput={setChatInput}
|
setChatInput={setChatInput}
|
||||||
handleSendMessage={handleSendMessage}
|
handleSendMessage={handleSendMessage}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Right Side - Actions & Plan */}
|
{/* Right Side - Actions & Plan */}
|
||||||
<div className="flex h-full flex-1 flex-col">
|
<div className="flex h-full flex-1 flex-col">
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
|
|
|
||||||
|
|
@ -269,3 +269,15 @@ export function createTakePlannerNotesFields() {
|
||||||
"Do not duplicate any information present in the user provided 'custom rules', as we want to avoid duplicating context.",
|
"Do not duplicate any information present in the user provided 'custom rules', as we want to avoid duplicating context.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createDiagnoseErrorToolFields() {
|
||||||
|
const diagnoseErrorToolSchema = z.object({
|
||||||
|
diagnosis: z.string().describe("The diagnosis of the error."),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "diagnose_error",
|
||||||
|
description: "Diagnoses an error given a diagnosis.",
|
||||||
|
schema: diagnoseErrorToolSchema,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue