Browse Source

feat(35-03): add subgraph extraction and scoped context to chat API

- Import extractSubgraph from subgraphExtractor
- Parse selectedNodeIds from request body
- Call extractSubgraph before building workflow context
- Pass subgraph.selectedNodes and selectedEdges to buildWorkflowContext
- Update buildEditSystemPrompt to accept optional restSummary parameter
- Add WORKFLOW CONTEXT (SELECTED SUBSET) section when scoped
- Include rest-of-workflow summary with type breakdown and boundary connections
- Add metadata placeholder note to system prompt
- Enhance error handling to return 413 for oversized payloads

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 5 months ago
parent
commit
89fca5f417
  1. 29
      src/app/api/chat/route.ts
  2. 36
      src/lib/chat/tools.ts

29
src/app/api/chat/route.ts

@ -2,6 +2,7 @@ import { streamText, convertToModelMessages, UIMessage, stepCountIs } from 'ai';
import { createGoogleGenerativeAI } from '@ai-sdk/google';
import { createChatTools, buildEditSystemPrompt } from '@/lib/chat/tools';
import { buildWorkflowContext } from '@/lib/chat/contextBuilder';
import { extractSubgraph } from '@/lib/chat/subgraphExtractor';
import { WorkflowNode } from '@/types';
import { WorkflowEdge } from '@/types/workflow';
@ -9,9 +10,10 @@ export const maxDuration = 60; // 1 minute timeout
export async function POST(request: Request) {
try {
const { messages, workflowState } = await request.json() as {
const { messages, workflowState, selectedNodeIds } = await request.json() as {
messages: UIMessage[];
workflowState?: { nodes: WorkflowNode[]; edges: WorkflowEdge[] };
selectedNodeIds?: string[];
};
// Get API key from environment
@ -20,14 +22,21 @@ export async function POST(request: Request) {
return new Response('GEMINI_API_KEY not configured', { status: 500 });
}
// Build workflow context from client-provided state
const context = buildWorkflowContext(
// Extract subgraph if nodes are selected, otherwise use full workflow
const subgraph = extractSubgraph(
workflowState?.nodes || [],
workflowState?.edges || []
workflowState?.edges || [],
selectedNodeIds || []
);
// Build workflow context from selected subgraph
const context = buildWorkflowContext(
subgraph.selectedNodes,
subgraph.selectedEdges
);
// Build context-aware system prompt
const systemPrompt = buildEditSystemPrompt(context);
// Build context-aware system prompt with optional rest summary
const systemPrompt = buildEditSystemPrompt(context, subgraph.restSummary);
// Extract node IDs for tool validation
const nodeIds = (workflowState?.nodes || []).map(n => n.id);
@ -60,6 +69,14 @@ export async function POST(request: Request) {
return new Response('Rate limit reached. Please wait and try again.', { status: 429 });
}
// Check for token/size errors and return 413
if (error instanceof Error) {
const errorMsg = error.message.toLowerCase();
if (errorMsg.includes('too large') || errorMsg.includes('token limit') || errorMsg.includes('payload') || errorMsg.includes('request entity too large')) {
return new Response('This workflow is too large for the AI to process. Try selecting fewer nodes.', { status: 413 });
}
}
return new Response(
error instanceof Error ? error.message : 'Chat request failed',
{ status: 500 }

36
src/lib/chat/tools.ts

@ -2,6 +2,7 @@ import { tool } from "ai";
import { z } from "zod";
import { EditOperation } from "./editOperations";
import { WorkflowContext, formatContextForPrompt } from "./contextBuilder";
import { SubgraphResult } from "./subgraphExtractor";
import { NodeType } from "@/types";
/**
@ -23,9 +24,13 @@ const VALID_NODE_TYPES: NodeType[] = [
* Builds the enhanced system prompt with current workflow context and tool usage rules.
*
* @param workflowContext - Current workflow state summary
* @param restSummary - Optional summary of unselected nodes (when selection scoped)
* @returns Complete system prompt with context and rules
*/
export function buildEditSystemPrompt(workflowContext: WorkflowContext): string {
export function buildEditSystemPrompt(
workflowContext: WorkflowContext,
restSummary?: SubgraphResult['restSummary']
): string {
// Base domain expertise from existing SYSTEM_PROMPT
const baseDomainExpertise = `You are a workflow expert for Node Banana, a visual node-based AI image generation tool. Be concise and direct — short bullet points, no fluff. Use the same language the user sees in the UI. Never expose internal property names, JSON structure, or code.
@ -92,12 +97,39 @@ Displays the final generated image or video. Connect any image or video output h
- Ask one clarifying question at a time if goal is unclear`;
// Current workflow context
const contextSection = `
let contextSection = `
## CURRENT WORKFLOW
${formatContextForPrompt(workflowContext)}`;
// Add subgraph summary if scoped to selected nodes
if (restSummary && restSummary.nodeCount > 0) {
const typeBreakdown = Object.entries(restSummary.typeBreakdown)
.map(([type, count]) => `${count} ${type}`)
.join(', ');
const boundaryInfo = restSummary.boundaryConnections.length > 0
? `\nConnections to selected nodes: ${restSummary.boundaryConnections.map(bc =>
`${bc.direction === 'incoming' ? 'Input from' : 'Output to'} ${bc.otherNodeId} (${bc.handleType})`
).join(', ')}`
: '';
contextSection += `
## WORKFLOW CONTEXT (SELECTED SUBSET)
You are focused on the selected nodes. The rest of the workflow:
- ${restSummary.nodeCount} other node(s): ${typeBreakdown}${boundaryInfo}
Note: Binary data (images, videos) has been replaced with metadata descriptions like [image: 1024x768, 245KB]. These are not editable - they represent existing content that will be preserved.`;
} else if (!restSummary) {
// Full workflow - add metadata note
contextSection += `
Note: Binary data (images, videos) has been replaced with metadata descriptions like [image: 1024x768, 245KB]. These are not editable - they represent existing content that will be preserved.`;
}
// Tool usage rules
const toolUsageRules = `

Loading…
Cancel
Save