mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-06 22:52:13 +00:00
feat: Add a planning summary step in planner subgraph (#13)
This commit is contained in:
parent
66f32b4afc
commit
513cf6556d
4 changed files with 98 additions and 2 deletions
|
|
@ -8,7 +8,12 @@
|
||||||
import { END, Send, START, StateGraph } from "@langchain/langgraph";
|
import { END, Send, START, StateGraph } from "@langchain/langgraph";
|
||||||
import { PlannerGraphState, PlannerGraphStateObj } from "./types.js";
|
import { PlannerGraphState, PlannerGraphStateObj } from "./types.js";
|
||||||
import { GraphConfiguration } from "../../types.js";
|
import { GraphConfiguration } from "../../types.js";
|
||||||
import { generateAction, generatePlan, takeAction } from "./nodes/index.js";
|
import {
|
||||||
|
generateAction,
|
||||||
|
generatePlan,
|
||||||
|
summarizer,
|
||||||
|
takeAction,
|
||||||
|
} from "./nodes/index.js";
|
||||||
import { isAIMessage, ToolMessage } from "@langchain/core/messages";
|
import { isAIMessage, ToolMessage } from "@langchain/core/messages";
|
||||||
|
|
||||||
function takeActionOrGeneratePlan(
|
function takeActionOrGeneratePlan(
|
||||||
|
|
@ -52,6 +57,7 @@ const workflow = new StateGraph(PlannerGraphStateObj, GraphConfiguration)
|
||||||
.addNode("generate-plan-context-action", generateAction)
|
.addNode("generate-plan-context-action", generateAction)
|
||||||
.addNode("take-plan-action", takeAction)
|
.addNode("take-plan-action", takeAction)
|
||||||
.addNode("generate-plan", generatePlan)
|
.addNode("generate-plan", generatePlan)
|
||||||
|
.addNode("summarizer", summarizer)
|
||||||
.addEdge(START, "generate-plan-context-action")
|
.addEdge(START, "generate-plan-context-action")
|
||||||
.addConditionalEdges(
|
.addConditionalEdges(
|
||||||
"generate-plan-context-action",
|
"generate-plan-context-action",
|
||||||
|
|
@ -59,7 +65,8 @@ const workflow = new StateGraph(PlannerGraphStateObj, GraphConfiguration)
|
||||||
["take-plan-action", "generate-plan"],
|
["take-plan-action", "generate-plan"],
|
||||||
)
|
)
|
||||||
.addEdge("take-plan-action", "generate-plan-context-action")
|
.addEdge("take-plan-action", "generate-plan-context-action")
|
||||||
.addEdge("generate-plan", END);
|
.addEdge("generate-plan", "summarizer")
|
||||||
|
.addEdge("summarizer", END);
|
||||||
|
|
||||||
// TODO: Fix zod types
|
// TODO: Fix zod types
|
||||||
export const plannerGraph = workflow.compile() as any;
|
export const plannerGraph = workflow.compile() as any;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
export * from "./generate-message.js";
|
export * from "./generate-message.js";
|
||||||
export * from "./take-action.js";
|
export * from "./take-action.js";
|
||||||
export * from "./generate-plan.js";
|
export * from "./generate-plan.js";
|
||||||
|
export * from "./summarizer.js";
|
||||||
|
|
|
||||||
78
src/subgraphs/planner/nodes/summarizer.ts
Normal file
78
src/subgraphs/planner/nodes/summarizer.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
import { GraphConfig } from "../../../types.js";
|
||||||
|
import { PlannerGraphState, PlannerGraphUpdate } from "../types.js";
|
||||||
|
import { loadModel, Task } from "../../../utils/load-model.js";
|
||||||
|
import { isHumanMessage, ToolMessage } from "@langchain/core/messages";
|
||||||
|
import { getMessageContentString } from "../../../utils/message-content.js";
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
You've been given a task to summarize the messages in your conversation history. You just finished gathering context to be used when generating an development plan to address the user's request.
|
||||||
|
You do not want to keep the entire conversation history, but instead you want to keep the most relevant and important snippets for future context
|
||||||
|
|
||||||
|
You MUST adhere to the following criteria when summarizing the conversation history:
|
||||||
|
- Retain context such as file paths, versions, and installed software.
|
||||||
|
- Do not retain any full code snippets.
|
||||||
|
- Do not retain any full file contents.
|
||||||
|
- Ensure your summary is concise, but useful for future context.
|
||||||
|
|
||||||
|
Here is the user's initial request
|
||||||
|
## User request:
|
||||||
|
{USER_REQUEST}
|
||||||
|
|
||||||
|
With all of this in mind, please carefully summarize and condense the following conversation history. Ensure you pass this condensed context to the \`condense_planning_context\` tool.
|
||||||
|
`;
|
||||||
|
|
||||||
|
const formatPrompt = (userRequest: string): string =>
|
||||||
|
systemPrompt.replace("{USER_REQUEST}", userRequest);
|
||||||
|
|
||||||
|
const condenseContextToolSchema = z.object({
|
||||||
|
context: z
|
||||||
|
.string()
|
||||||
|
.describe("The condensed context to be used when generating a plan."),
|
||||||
|
});
|
||||||
|
const condenseContextTool = {
|
||||||
|
name: "condense_planning_context",
|
||||||
|
description:
|
||||||
|
"Condense the conversation history into a concise summary, while still retaining the most relevant and important snippets.",
|
||||||
|
schema: condenseContextToolSchema,
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function summarizer(
|
||||||
|
state: PlannerGraphState,
|
||||||
|
config: GraphConfig,
|
||||||
|
): Promise<PlannerGraphUpdate> {
|
||||||
|
const model = await loadModel(config, Task.PLANNER);
|
||||||
|
const modelWithTools = model.bindTools([condenseContextTool], {
|
||||||
|
tool_choice: condenseContextTool.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const firstUserMessage = state.messages.find(isHumanMessage);
|
||||||
|
|
||||||
|
const response = await modelWithTools.invoke([
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content: formatPrompt(
|
||||||
|
getMessageContentString(
|
||||||
|
firstUserMessage?.content ?? "No user request provided.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
...state.plannerMessages,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const toolCall = response.tool_calls?.[0];
|
||||||
|
if (!toolCall) {
|
||||||
|
throw new Error("Failed to generate plan");
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolMessage = new ToolMessage({
|
||||||
|
tool_call_id: toolCall.id ?? "",
|
||||||
|
name: toolCall.name,
|
||||||
|
content: `Successfully summarized planning context.`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: [response, toolMessage],
|
||||||
|
};
|
||||||
|
}
|
||||||
10
src/utils/message-content.ts
Normal file
10
src/utils/message-content.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { MessageContent } from "@langchain/core/messages";
|
||||||
|
|
||||||
|
export function getMessageContentString(content: MessageContent): string {
|
||||||
|
if (typeof content === "string") return content;
|
||||||
|
|
||||||
|
return content
|
||||||
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
||||||
|
.map((c) => c.text)
|
||||||
|
.join(" ");
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue