mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 09:13:14 +00:00
feat: Allow manager to support followups (#248)
* feat: Allow manager to support followups * cr * cr
This commit is contained in:
parent
d3f6045636
commit
3d43abd4a0
5 changed files with 40 additions and 22 deletions
|
|
@ -14,6 +14,7 @@ import {
|
|||
import { SYSTEM_PROMPT } from "./prompt.js";
|
||||
import { getRepoAbsolutePath } from "@open-swe/shared/git";
|
||||
import { getMissingMessages } from "../../../../utils/github/issue-messages.js";
|
||||
import { getTaskPlanFromIssue } from "../../../../utils/github/issue-task.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "GeneratePlanningMessageNode");
|
||||
|
||||
|
|
@ -47,13 +48,19 @@ export async function generateAction(
|
|||
parallel_tool_calls: false,
|
||||
});
|
||||
|
||||
const missingMessages = await getMissingMessages(state, config);
|
||||
const [missingMessages, latestTaskPlan] = await Promise.all([
|
||||
getMissingMessages(state, config),
|
||||
getTaskPlanFromIssue(state, config),
|
||||
]);
|
||||
const response = await modelWithTools
|
||||
.withConfig({ tags: ["nostream"] })
|
||||
.invoke([
|
||||
{
|
||||
role: "system",
|
||||
content: formatSystemPrompt(state),
|
||||
content: formatSystemPrompt({
|
||||
...state,
|
||||
taskPlan: latestTaskPlan ?? state.taskPlan,
|
||||
}),
|
||||
},
|
||||
...state.messages,
|
||||
...missingMessages,
|
||||
|
|
@ -71,5 +78,6 @@ export async function generateAction(
|
|||
|
||||
return {
|
||||
messages: [...missingMessages, response],
|
||||
...(latestTaskPlan && { taskPlan: latestTaskPlan }),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { getActivePlanItems } from "@open-swe/shared/open-swe/tasks";
|
||||
import { TaskPlan } from "@open-swe/shared/open-swe/types";
|
||||
|
||||
const previousCompletedPlanPrompt = `Here is the complete list of previous requests made by the user, along with the tasks you generated to complete these requests, and the task summaries of each task you completed previously:
|
||||
const previousCompletedPlanPrompt = `Here is the list of tasks from the previous session. You've already completed all of these tasks. Use the tasks, and task summaries as context when generating a new plan:
|
||||
{PREVIOUS_PLAN}`;
|
||||
|
||||
const previousProposedPlanPrompt = `Here is the complete list of the proposed plan you generated before the user sent their followup request:
|
||||
|
|
|
|||
|
|
@ -165,7 +165,6 @@ export async function summarizeTaskSteps(
|
|||
const allTasksCompleted = activePlanItems.every((p) => p.completed);
|
||||
if (allTasksCompleted) {
|
||||
const commandUpdate: GraphUpdate = {
|
||||
messages: [condensedTaskMessage],
|
||||
internalMessages: newMessagesStateUpdate,
|
||||
taskPlan: updatedTaskPlan,
|
||||
};
|
||||
|
|
@ -176,7 +175,6 @@ export async function summarizeTaskSteps(
|
|||
}
|
||||
|
||||
const commandUpdate: GraphUpdate = {
|
||||
messages: [condensedTaskMessage],
|
||||
internalMessages: newMessagesStateUpdate,
|
||||
taskPlan: updatedTaskPlan,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { isHumanMessageSDK } from "@/lib/langchain-messages";
|
||||
import { UseStream, useStream } from "@langchain/langgraph-sdk/react";
|
||||
import { AssistantMessage } from "../thread/messages/ai";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Dispatch, SetStateAction, useEffect, useRef } from "react";
|
||||
import { ManagerGraphState } from "@open-swe/shared/open-swe/manager/types";
|
||||
import { PlannerGraphState } from "@open-swe/shared/open-swe/planner/types";
|
||||
import { GraphState } from "@open-swe/shared/open-swe/types";
|
||||
|
||||
interface ActionsRendererProps {
|
||||
graphId: string;
|
||||
|
|
@ -12,14 +14,16 @@ interface ActionsRendererProps {
|
|||
session: ManagerGraphState["programmerSession"],
|
||||
) => void;
|
||||
programmerSession?: ManagerGraphState["programmerSession"];
|
||||
setSelectedTab?: Dispatch<SetStateAction<"planner" | "programmer">>;
|
||||
}
|
||||
|
||||
export function ActionsRenderer<State extends Record<string, unknown>>({
|
||||
export function ActionsRenderer<State extends PlannerGraphState | GraphState>({
|
||||
graphId,
|
||||
threadId,
|
||||
runId,
|
||||
setProgrammerSession,
|
||||
programmerSession,
|
||||
setSelectedTab,
|
||||
}: ActionsRendererProps) {
|
||||
const stream = useStream<State>({
|
||||
apiUrl: process.env.NEXT_PUBLIC_API_URL,
|
||||
|
|
@ -43,22 +47,14 @@ export function ActionsRenderer<State extends Record<string, unknown>>({
|
|||
// TODO: Need a better way to handle this. Not great like this...
|
||||
useEffect(() => {
|
||||
if (
|
||||
stream.values?.programmerSession &&
|
||||
typeof stream.values.programmerSession === "object" &&
|
||||
"programmerSession" in stream.values &&
|
||||
stream.values.programmerSession &&
|
||||
(
|
||||
stream.values
|
||||
.programmerSession as ManagerGraphState["programmerSession"]
|
||||
)?.runId &&
|
||||
(
|
||||
stream.values
|
||||
.programmerSession as ManagerGraphState["programmerSession"]
|
||||
)?.threadId &&
|
||||
!programmerSession
|
||||
(stream.values.programmerSession.runId !== programmerSession?.runId ||
|
||||
stream.values.programmerSession.threadId !==
|
||||
programmerSession?.threadId)
|
||||
) {
|
||||
const programmerSession = stream.values
|
||||
.programmerSession as ManagerGraphState["programmerSession"];
|
||||
setProgrammerSession?.(programmerSession);
|
||||
setProgrammerSession?.(stream.values.programmerSession);
|
||||
setSelectedTab?.("programmer");
|
||||
}
|
||||
}, [stream.values]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
|
@ -14,6 +15,7 @@ import { ManagerGraphState } from "@open-swe/shared/open-swe/manager/types";
|
|||
import { PlannerGraphState } from "@open-swe/shared/open-swe/planner/types";
|
||||
import { ActionsRenderer } from "./actions-renderer";
|
||||
import { ThemeToggle } from "../theme-toggle";
|
||||
import { HumanMessage } from "@langchain/core/messages";
|
||||
|
||||
const PROGRAMMER_ASSISTANT_ID = process.env.NEXT_PUBLIC_PROGRAMMER_ASSISTANT_ID;
|
||||
const PLANNER_ASSISTANT_ID = process.env.NEXT_PUBLIC_PLANNER_ASSISTANT_ID;
|
||||
|
|
@ -32,6 +34,9 @@ export function ThreadView({
|
|||
onBackToHome,
|
||||
}: ThreadViewProps) {
|
||||
const [chatInput, setChatInput] = useState("");
|
||||
const [selectedTab, setSelectedTab] = useState<"planner" | "programmer">(
|
||||
"planner",
|
||||
);
|
||||
const plannerThreadId = stream.values?.plannerSession?.threadId;
|
||||
const plannerRunId = stream.values?.plannerSession?.runId;
|
||||
const [programmerSession, setProgrammerSession] =
|
||||
|
|
@ -43,7 +48,13 @@ export function ThreadView({
|
|||
|
||||
const handleSendMessage = () => {
|
||||
if (chatInput.trim()) {
|
||||
alert("SENDING MANAGER FOLLOWUPS NOT HOOKED UP YET");
|
||||
const newHumanMessage = new HumanMessage({
|
||||
id: uuidv4(),
|
||||
content: chatInput,
|
||||
});
|
||||
stream.submit({
|
||||
messages: [newHumanMessage],
|
||||
});
|
||||
setChatInput("");
|
||||
}
|
||||
};
|
||||
|
|
@ -160,6 +171,10 @@ export function ThreadView({
|
|||
<Tabs
|
||||
defaultValue="planner"
|
||||
className="w-full"
|
||||
value={selectedTab}
|
||||
onValueChange={(value) =>
|
||||
setSelectedTab(value as "planner" | "programmer")
|
||||
}
|
||||
>
|
||||
<TabsList className="bg-muted/70 dark:bg-gray-800">
|
||||
<TabsTrigger value="planner">Planner</TabsTrigger>
|
||||
|
|
@ -177,6 +192,7 @@ export function ThreadView({
|
|||
runId={plannerRunId}
|
||||
setProgrammerSession={setProgrammerSession}
|
||||
programmerSession={programmerSession}
|
||||
setSelectedTab={setSelectedTab}
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue