wulf-pulse/lib/services/pipeline-steps/ai-analyze.ts

116 lines
3.8 KiB
TypeScript

/**
* AI Analyze Step — send data to AI for analysis/summary.
* Config: { purpose: "summarize_alert", prompt: "...", system_prompt: "...", prompt_template_id?: 1 }
*/
import { registerStepExecutor } from '../pipeline-engine';
import { postgresClient } from '../postgres-client';
import { PipelineStep, PipelineContext, StepExecutorResult } from '../../types/pipeline';
async function executeAiAnalyze(
step: PipelineStep,
_context: PipelineContext,
_executionId: number
): Promise<StepExecutorResult> {
const userPrompt = step.config.prompt || '';
let systemPrompt = step.config.system_prompt || 'You are a helpful IT operations assistant.';
// If a prompt_template_id is provided, load from DB
if (step.config.prompt_template_id) {
const tplResult = await postgresClient.query(
`SELECT system_prompt, user_prompt_template, provider, model, temperature, max_tokens
FROM ai_prompt_templates WHERE id = $1 AND is_active = true`,
[step.config.prompt_template_id]
);
if (tplResult.rows.length > 0) {
systemPrompt = tplResult.rows[0].system_prompt || systemPrompt;
}
}
if (!userPrompt) {
return { success: false, error: 'Missing prompt for AI analysis' };
}
// Load AI settings
const settingsResult = await postgresClient.query(
`SELECT key, value FROM workflow_settings WHERE key IN ('default_ai_provider', 'openai_api_key', 'openai_model', 'anthropic_api_key', 'anthropic_model')`
);
const settings: Record<string, any> = {};
for (const row of settingsResult.rows) {
try { settings[row.key] = JSON.parse(row.value); } catch { settings[row.key] = row.value; }
}
const provider = step.config.provider || settings.default_ai_provider || 'openai';
const model = step.config.model || (provider === 'anthropic' ? settings.anthropic_model : settings.openai_model) || 'gpt-4o';
const apiKey = provider === 'anthropic' ? settings.anthropic_api_key : settings.openai_api_key;
if (!apiKey) {
return { success: false, error: `No API key configured for ${provider}` };
}
console.log(`[PIPELINE:ai_analyze] Calling ${provider}/${model}`);
let aiResponse: string;
if (provider === 'anthropic') {
const resp = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
},
body: JSON.stringify({
model,
max_tokens: Number(step.config.max_tokens) || 2000,
system: systemPrompt,
messages: [{ role: 'user', content: userPrompt }],
}),
});
if (!resp.ok) {
const errText = await resp.text();
return { success: false, error: `Anthropic API error (${resp.status}): ${errText.substring(0, 200)}` };
}
const data = await resp.json();
aiResponse = data.content?.[0]?.text || '';
} else {
const resp = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify({
model,
temperature: Number(step.config.temperature) || 0.3,
max_tokens: Number(step.config.max_tokens) || 2000,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt },
],
}),
});
if (!resp.ok) {
const errText = await resp.text();
return { success: false, error: `OpenAI API error (${resp.status}): ${errText.substring(0, 200)}` };
}
const data = await resp.json();
aiResponse = data.choices?.[0]?.message?.content || '';
}
return {
success: true,
output: {
ai_response: aiResponse,
ai_provider: provider,
ai_model: model,
},
};
}
registerStepExecutor('ai_analyze', executeAiAnalyze);