235 lines
8.9 KiB
TypeScript
235 lines
8.9 KiB
TypeScript
import { createClientFromRequest } from 'npm:@base44/sdk@0.8.31';
|
|
|
|
Deno.serve(async (req) => {
|
|
try {
|
|
const base44 = createClientFromRequest(req);
|
|
const user = await base44.auth.me();
|
|
if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
|
|
const body = await req.json();
|
|
const {
|
|
event_id,
|
|
event_type,
|
|
subject_entity_type,
|
|
subject_entity_id,
|
|
context_summary,
|
|
metadata = {},
|
|
related_entities = []
|
|
} = body;
|
|
|
|
const entities = base44.asServiceRole.entities;
|
|
|
|
// 1. Fetch context from the Workforce Graph
|
|
let graphContext = {};
|
|
try {
|
|
const graphRes = await base44.asServiceRole.functions.invoke('queryWorkforceGraph', {
|
|
query_type: 'subgraph',
|
|
entity_type: subject_entity_type,
|
|
entity_id: subject_entity_id
|
|
});
|
|
graphContext = graphRes.data?.graph || {};
|
|
} catch (e) {
|
|
console.log('Graph query failed (non-blocking):', e.message);
|
|
}
|
|
|
|
// 2. Fetch similar past memories (Operational Memory)
|
|
const similarMemories = await entities.OperationalMemory.filter(
|
|
{ trigger_event_type: event_type },
|
|
'-created_date',
|
|
5
|
|
);
|
|
|
|
const memoryContext = similarMemories.map(m => ({
|
|
decision: m.decision?.recommendation || 'N/A',
|
|
outcome: m.outcome || 'pending',
|
|
outcome_score: m.outcome_score,
|
|
reasoning: m.reasoning?.substring(0, 200) || ''
|
|
}));
|
|
|
|
// 3. Determine department based on event type
|
|
const departmentMap = {
|
|
'shift.completed': 'operations',
|
|
'shift.cancelled': 'operations',
|
|
'shift.no_show': 'operations',
|
|
'order.created': 'operations',
|
|
'order.cancelled': 'operations',
|
|
'order.filled': 'operations',
|
|
'assignment.cancelled': 'operations',
|
|
'invoice.overdue': 'finance',
|
|
'vendor.rate_changed': 'procurement',
|
|
'worker.availability_changed': 'operations',
|
|
'worker.certification_expired': 'compliance',
|
|
'budget.threshold_exceeded': 'finance',
|
|
'compliance.flagged': 'compliance',
|
|
'system.anomaly_detected': 'executive'
|
|
};
|
|
const department = departmentMap[event_type] || 'operations';
|
|
|
|
// 4. Build the reasoning prompt with full context — CAUSAL CHAIN reasoning
|
|
const prompt = `You are the KROW Intelligence Engine — an AI workforce reasoning system for contingent labor management.
|
|
|
|
You do not answer. You REASON.
|
|
|
|
When an event occurs, you trace the CAUSAL CHAIN backwards — from the observed event through each contributing factor (connected by ↓) to the root cause(s). You do not give generic analysis. You follow the chain.
|
|
|
|
AN EVENT HAS OCCURRED:
|
|
Event Type: ${event_type}
|
|
Summary: ${context_summary || 'No summary provided'}
|
|
Subject: ${subject_entity_type} (${subject_entity_id})
|
|
|
|
CURRENT GRAPH STATE (connected entities, relationships, metrics):
|
|
${JSON.stringify(graphContext, null, 2).substring(0, 3000)}
|
|
|
|
SIMILAR PAST DECISIONS (Operational Memory — what happened last time):
|
|
${memoryContext.length > 0 ? JSON.stringify(memoryContext, null, 2) : 'No similar past decisions found.'}
|
|
|
|
EVENT METADATA:
|
|
${JSON.stringify(metadata, null, 2).substring(0, 1000)}
|
|
|
|
REASONING INSTRUCTIONS:
|
|
|
|
1. OBSERVATION: State what happened as a fact.
|
|
|
|
2. CAUSAL CHAIN: Trace WHY this event occurred. Walk backwards from the event through contributing factors. Each link in the chain is a factor that CAUSED the next. The chain flows:
|
|
|
|
Factor A → caused → Factor B → caused → Factor C → caused → [Root Cause]
|
|
|
|
Use the graph state, metadata, and past memories as evidence. Reference specific entities, workers, vendors, shifts, or metrics. Do not be generic.
|
|
|
|
Categories: vendor, operations, workforce, training, demand, external, compliance, finance, equipment, transportation, system.
|
|
|
|
3. ROOT CAUSE(S): The deepest factor(s) — the thing that, if fixed, prevents recurrence.
|
|
|
|
4. PREDICTION: What will happen next if no action is taken.
|
|
|
|
5. RECOMMENDATION: Target the root cause, not just the symptom.
|
|
|
|
Respond as JSON.`;
|
|
|
|
// 5. Call the LLM for reasoning
|
|
let aiDecision = {};
|
|
try {
|
|
const llmResponse = await base44.asServiceRole.integrations.Core.InvokeLLM({
|
|
prompt,
|
|
response_json_schema: {
|
|
type: 'object',
|
|
properties: {
|
|
observation: { type: 'string', description: 'What happened, stated as a fact' },
|
|
causal_chain: {
|
|
type: 'array',
|
|
description: 'Causal chain traced backwards from the event to root cause(s)',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
step: { type: 'number' },
|
|
factor: { type: 'string' },
|
|
evidence: { type: 'string' },
|
|
category: { type: 'string', enum: ['vendor', 'operations', 'workforce', 'training', 'demand', 'external', 'compliance', 'finance', 'equipment', 'transportation', 'system'] },
|
|
leads_to: { type: 'string' }
|
|
}
|
|
}
|
|
},
|
|
root_causes: {
|
|
type: 'array',
|
|
items: { type: 'string' }
|
|
},
|
|
prediction: { type: 'string' },
|
|
recommendation: { type: 'string' },
|
|
confidence_score: { type: 'number' },
|
|
risk_level: { type: 'string' },
|
|
proposed_actions: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
action_type: { type: 'string' },
|
|
description: { type: 'string' },
|
|
auto_execute: { type: 'boolean' }
|
|
}
|
|
}
|
|
},
|
|
reasoning: { type: 'string', description: 'Full reasoning chain in natural language' }
|
|
}
|
|
}
|
|
});
|
|
|
|
aiDecision = typeof llmResponse === 'string' ? JSON.parse(llmResponse) : llmResponse;
|
|
} catch (llmErr) {
|
|
console.log('LLM reasoning failed:', llmErr.message);
|
|
aiDecision = {
|
|
prediction: 'Unable to generate prediction',
|
|
recommendation: 'Manual review recommended',
|
|
confidence_score: 0,
|
|
risk_level: 'medium',
|
|
proposed_actions: [],
|
|
reasoning: 'LLM reasoning failed: ' + llmErr.message
|
|
};
|
|
}
|
|
|
|
// 6. Create Operational Memory — store the causal chain
|
|
const memory_id = `MEM-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
const memory = await entities.OperationalMemory.create({
|
|
memory_id,
|
|
trigger_event_id: event_id,
|
|
trigger_event_type: event_type,
|
|
context_snapshot: { graph: graphContext, metadata },
|
|
decision: aiDecision,
|
|
reasoning: aiDecision.reasoning || '',
|
|
causal_chain: aiDecision.causal_chain || [],
|
|
root_causes: aiDecision.root_causes || [],
|
|
similar_memories_consulted: similarMemories.map(m => m.memory_id),
|
|
status: 'pending',
|
|
agent_name: `${department}_agent`,
|
|
department,
|
|
tags: [event_type, department, aiDecision.risk_level || 'medium']
|
|
});
|
|
|
|
// 7. Mark event as reasoning-triggered
|
|
await entities.WorkforceEvent.update(event_id, {
|
|
reasoning_triggered: true,
|
|
memory_id: memory.id
|
|
});
|
|
|
|
// 8. Log agent activity
|
|
await entities.AgentActivity.create({
|
|
agent_name: `${department}_agent`,
|
|
department,
|
|
activity_type: 'event_processed',
|
|
trigger_event_id: event_id,
|
|
memory_id: memory.id,
|
|
summary: `Processed ${event_type}: ${aiDecision.recommendation || 'No recommendation'}`,
|
|
details: { confidence: aiDecision.confidence_score, risk: aiDecision.risk_level },
|
|
impact_level: aiDecision.risk_level === 'high' ? 'high' : (aiDecision.risk_level === 'medium' ? 'medium' : 'low'),
|
|
entities_affected: [{ entity_type: subject_entity_type, entity_id: subject_entity_id }]
|
|
});
|
|
|
|
// 9. If high risk, queue for human approval; if low risk + auto_execute, execute
|
|
const hasAutoActions = (aiDecision.proposed_actions || []).some(a => a.auto_execute);
|
|
if (aiDecision.risk_level === 'high' || (aiDecision.confidence_score || 0) < 60) {
|
|
await entities.DecisionQueue.create({
|
|
memory_id: memory.id,
|
|
trigger_event_id: event_id,
|
|
title: `${department} decision: ${event_type}`,
|
|
summary: aiDecision.recommendation || 'AI recommends manual review',
|
|
department,
|
|
agent_name: `${department}_agent`,
|
|
proposed_actions: aiDecision.proposed_actions || [],
|
|
confidence_score: aiDecision.confidence_score || 0,
|
|
risk_level: aiDecision.risk_level || 'medium',
|
|
status: 'pending_approval',
|
|
expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
context_snapshot: { graph: graphContext, event_summary: context_summary }
|
|
});
|
|
}
|
|
|
|
return Response.json({
|
|
success: true,
|
|
memory_id: memory.id,
|
|
decision: aiDecision,
|
|
graph_nodes: graphContext.nodes?.length || 0,
|
|
similar_memories: similarMemories.length
|
|
});
|
|
} catch (error) {
|
|
return Response.json({ error: error.message }, { status: 500 });
|
|
}
|
|
}); |