Files
2026-07-13 12:37:47 +08:00

112 lines
3.6 KiB
TypeScript

import 'server-only';
import { WorkflowNode, WorkflowState } from '../types';
import { substituteVariables } from '../variable-substitution';
/**
* Execute Extract Node - Uses LLM with JSON schema to extract structured data
* Server-side only - called from API routes
*/
export async function executeExtractNode(
node: WorkflowNode,
state: WorkflowState,
apiKeys?: { anthropic?: string; groq?: string; openai?: string; firecrawl?: string }
): Promise<any> {
const { data } = node;
try {
// Substitute variables in instructions
const instructions = substituteVariables(data.instructions || 'Extract information from the input', state);
// Build context from previous node output
const lastOutput = state.variables?.lastOutput;
// Validate API keys are provided
if (!apiKeys) {
throw new Error('API keys are required for server-side execution');
}
// Server-side execution only
const OpenAI = (await import('openai')).default;
const client = new OpenAI({ apiKey: apiKeys?.openai });
// Build full prompt with context
let fullPrompt = instructions;
if (lastOutput) {
const contextData = typeof lastOutput === 'string'
? lastOutput
: JSON.stringify(lastOutput, null, 2);
fullPrompt = `${fullPrompt}\n\nData to extract from:\n${contextData.substring(0, 10000)}`;
}
// Parse JSON schema
const schema = typeof data.jsonSchema === 'string'
? JSON.parse(data.jsonSchema)
: data.jsonSchema;
// If MCP tools are configured, use Responses API
if (data.mcpTools && data.mcpTools.length > 0) {
const tools = data.mcpTools.map((mcp: any) => ({
type: 'mcp' as const,
server_label: mcp.name,
server_url: mcp.url.includes('{FIRECRAWL_API_KEY}')
? mcp.url.replace('{FIRECRAWL_API_KEY}', apiKeys?.firecrawl || '')
: mcp.url,
authorization: mcp.accessToken ? `Bearer ${mcp.accessToken}` : undefined,
require_approval: 'never' as const,
}));
const response = await client.responses.create({
model: 'gpt-4.1',
tools,
input: fullPrompt,
text: {
format: {
type: 'json_schema',
name: 'extraction',
schema,
strict: true,
},
},
});
const extractedData = JSON.parse(response.output_text || '{}');
return {
extractedData,
model: 'gpt-4.1',
tokensUsed: response.usage?.total_tokens || 0,
mcpToolsUsed: response.output.filter((item: any) => item.type === 'mcp_call').length,
__variableUpdates: { lastOutput: extractedData }, // Return as separate field for reducer
};
}
// No MCP - use regular Chat Completions with JSON mode
const completion = await client.chat.completions.create({
model: data.model || 'gpt-5-mini',
messages: [
{ role: 'user', content: fullPrompt },
],
response_format: {
type: 'json_schema',
json_schema: {
name: 'extraction',
schema,
strict: true,
},
},
});
const extractedData = JSON.parse(completion.choices[0].message.content || '{}');
return {
extractedData,
model: data.model,
tokensUsed: completion.usage?.total_tokens || 0,
__variableUpdates: { lastOutput: extractedData }, // Return as separate field for reducer
};
} catch (error) {
console.error('Extract execution error:', error);
throw new Error(`Failed to execute extract: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}