WIP - using deep agent to create dog using workflow
This commit is contained in:
332
backend/scripts/seed-demo-process.ts
Normal file
332
backend/scripts/seed-demo-process.ts
Normal file
@@ -0,0 +1,332 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import { AiProcess, AiProcessVersion, AiToolConfig } from '../src/models/ai-process.model';
|
||||
|
||||
// Bootstrap NestJS to get proper services
|
||||
async function getTenantContext(tenantSlugOrId: string) {
|
||||
const { NestFactory } = await import('@nestjs/core');
|
||||
const { AppModule } = await import('../src/app.module');
|
||||
const { TenantDatabaseService } = await import('../src/tenant/tenant-database.service');
|
||||
|
||||
// Create app context (without listening)
|
||||
const app = await NestFactory.createApplicationContext(AppModule, {
|
||||
logger: false,
|
||||
});
|
||||
|
||||
const tenantDbService = app.get(TenantDatabaseService);
|
||||
|
||||
// Resolve tenant ID
|
||||
const tenantId = await tenantDbService.resolveTenantId(tenantSlugOrId);
|
||||
|
||||
// Get proper Knex connection
|
||||
const knex = await tenantDbService.getTenantKnexById(tenantId);
|
||||
|
||||
return { tenantId, knex, app };
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed script for demo AI Process: Register New Pet
|
||||
*
|
||||
* This process demonstrates:
|
||||
* - Conditional logic (find or create account/contact)
|
||||
* - Tool usage (findAccount, createAccount, findContact, createContact, createPet)
|
||||
* - Sequential execution
|
||||
* - LLM decision nodes with structured JSON output
|
||||
*
|
||||
* Usage:
|
||||
* npm run seed:demo-process -- <tenant-slug-or-id>
|
||||
*/
|
||||
|
||||
const demoProcessGraph = {
|
||||
id: 'register_new_pet',
|
||||
name: 'Register New Pet',
|
||||
description: 'Complete pet registration workflow with account and contact resolution',
|
||||
allowCycles: false,
|
||||
nodes: [
|
||||
{
|
||||
id: 'start',
|
||||
type: 'Start',
|
||||
position: { x: 250, y: 50 },
|
||||
data: { label: 'Start' },
|
||||
},
|
||||
{
|
||||
id: 'extract_info',
|
||||
type: 'LLMDecisionNode',
|
||||
position: { x: 250, y: 150 },
|
||||
data: {
|
||||
label: 'Extract Pet Info',
|
||||
promptTemplate: `Extract pet registration information from the user message.
|
||||
|
||||
User message: {{state.message}}
|
||||
|
||||
Extract:
|
||||
- Pet name (required)
|
||||
- Pet species (required, e.g., "dog", "cat", "bird")
|
||||
- Pet breed (optional)
|
||||
- Pet age (optional, as number)
|
||||
- Owner first name (required)
|
||||
- Owner last name (required)
|
||||
- Owner email (optional)
|
||||
- Owner phone (optional)
|
||||
- Account/Company name (optional, defaults to owner's full name)
|
||||
|
||||
Return JSON with these exact fields.`,
|
||||
inputKeys: ['message'],
|
||||
outputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
petName: { type: 'string' },
|
||||
species: { type: 'string' },
|
||||
breed: { type: 'string' },
|
||||
age: { type: 'number' },
|
||||
ownerFirstName: { type: 'string' },
|
||||
ownerLastName: { type: 'string' },
|
||||
ownerEmail: { type: 'string' },
|
||||
ownerPhone: { type: 'string' },
|
||||
accountName: { type: 'string' },
|
||||
},
|
||||
required: ['petName', 'species', 'ownerFirstName', 'ownerLastName'],
|
||||
},
|
||||
model: { name: 'gpt-4o', temperature: 0 },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'find_account',
|
||||
type: 'ToolNode',
|
||||
position: { x: 250, y: 280 },
|
||||
data: {
|
||||
label: 'Find Account',
|
||||
toolName: 'findAccount',
|
||||
argsTemplate: {
|
||||
name: '{{state.accountName}}',
|
||||
email: '{{state.ownerEmail}}',
|
||||
},
|
||||
outputMapping: {
|
||||
found: 'accountFound',
|
||||
accountId: 'accountId',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'create_account',
|
||||
type: 'ToolNode',
|
||||
position: { x: 450, y: 380 },
|
||||
data: {
|
||||
label: 'Create Account',
|
||||
toolName: 'createAccount',
|
||||
argsTemplate: {
|
||||
name: '{{state.accountName}}',
|
||||
email: '{{state.ownerEmail}}',
|
||||
phone: '{{state.ownerPhone}}',
|
||||
},
|
||||
outputMapping: {
|
||||
accountId: 'accountId',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'find_contact',
|
||||
type: 'ToolNode',
|
||||
position: { x: 250, y: 480 },
|
||||
data: {
|
||||
label: 'Find Contact',
|
||||
toolName: 'findContact',
|
||||
argsTemplate: {
|
||||
firstName: '{{state.ownerFirstName}}',
|
||||
lastName: '{{state.ownerLastName}}',
|
||||
email: '{{state.ownerEmail}}',
|
||||
accountId: '{{state.accountId}}',
|
||||
},
|
||||
outputMapping: {
|
||||
found: 'contactFound',
|
||||
contactId: 'contactId',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'create_contact',
|
||||
type: 'ToolNode',
|
||||
position: { x: 450, y: 580 },
|
||||
data: {
|
||||
label: 'Create Contact',
|
||||
toolName: 'createContact',
|
||||
argsTemplate: {
|
||||
firstName: '{{state.ownerFirstName}}',
|
||||
lastName: '{{state.ownerLastName}}',
|
||||
email: '{{state.ownerEmail}}',
|
||||
phone: '{{state.ownerPhone}}',
|
||||
accountId: '{{state.accountId}}',
|
||||
},
|
||||
outputMapping: {
|
||||
contactId: 'contactId',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'create_pet',
|
||||
type: 'ToolNode',
|
||||
position: { x: 250, y: 680 },
|
||||
data: {
|
||||
label: 'Create Pet Record',
|
||||
toolName: 'createPet',
|
||||
argsTemplate: {
|
||||
name: '{{state.petName}}',
|
||||
species: '{{state.species}}',
|
||||
breed: '{{state.breed}}',
|
||||
age: '{{state.age}}',
|
||||
ownerId: '{{state.contactId}}',
|
||||
},
|
||||
outputMapping: {
|
||||
petId: 'petId',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'end',
|
||||
type: 'End',
|
||||
position: { x: 250, y: 780 },
|
||||
data: { label: 'End' },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{ id: 'e1', source: 'start', target: 'extract_info' },
|
||||
{ id: 'e2', source: 'extract_info', target: 'find_account' },
|
||||
{
|
||||
id: 'e3',
|
||||
source: 'find_account',
|
||||
target: 'find_contact',
|
||||
condition: { '==': [{ var: 'accountFound' }, true] },
|
||||
},
|
||||
{
|
||||
id: 'e4',
|
||||
source: 'find_account',
|
||||
target: 'create_account',
|
||||
condition: { '==': [{ var: 'accountFound' }, false] },
|
||||
},
|
||||
{ id: 'e5', source: 'create_account', target: 'find_contact' },
|
||||
{
|
||||
id: 'e6',
|
||||
source: 'find_contact',
|
||||
target: 'create_pet',
|
||||
condition: { '==': [{ var: 'contactFound' }, true] },
|
||||
},
|
||||
{
|
||||
id: 'e7',
|
||||
source: 'find_contact',
|
||||
target: 'create_contact',
|
||||
condition: { '==': [{ var: 'contactFound' }, false] },
|
||||
},
|
||||
{ id: 'e8', source: 'create_contact', target: 'create_pet' },
|
||||
{ id: 'e9', source: 'create_pet', target: 'end' },
|
||||
],
|
||||
};
|
||||
|
||||
const demoTools = [
|
||||
'findAccount',
|
||||
'createAccount',
|
||||
'findContact',
|
||||
'createContact',
|
||||
'createPet',
|
||||
];
|
||||
|
||||
async function seedDemoProcess(tenantSlugOrId: string) {
|
||||
let app;
|
||||
try {
|
||||
console.log(`\n🌱 Seeding demo AI process for tenant: ${tenantSlugOrId}\n`);
|
||||
|
||||
const context = await getTenantContext(tenantSlugOrId);
|
||||
const { tenantId, knex, app: nestApp } = context;
|
||||
app = nestApp;
|
||||
|
||||
console.log(`✓ Resolved tenant ID: ${tenantId}`);
|
||||
console.log(`✓ Connected to tenant database`);
|
||||
|
||||
// Check if process already exists
|
||||
const existing = await AiProcess.query(knex)
|
||||
.where('name', demoProcessGraph.name)
|
||||
.first();
|
||||
|
||||
if (existing) {
|
||||
console.log(`⚠ Process "${demoProcessGraph.name}" already exists (ID: ${existing.id})`);
|
||||
console.log(` To create a new version, update via the UI.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create process in transaction
|
||||
await knex.transaction(async (trx) => {
|
||||
const processId = randomUUID();
|
||||
const userId = 'system'; // System user for seed data
|
||||
|
||||
// Create process
|
||||
await AiProcess.query(trx).insert({
|
||||
id: processId,
|
||||
name: demoProcessGraph.name,
|
||||
description: demoProcessGraph.description,
|
||||
latestVersion: 1,
|
||||
createdBy: userId,
|
||||
});
|
||||
console.log(`✓ Created process: ${demoProcessGraph.name} (${processId})`);
|
||||
|
||||
// Create initial version
|
||||
// Note: In production, this would call the compiler service
|
||||
// For seed, we're storing a simplified version
|
||||
await AiProcessVersion.query(trx).insert({
|
||||
id: randomUUID(),
|
||||
processId,
|
||||
version: 1,
|
||||
graphJson: demoProcessGraph,
|
||||
compiledJson: {
|
||||
graphId: demoProcessGraph.id,
|
||||
version: 1,
|
||||
nodes: demoProcessGraph.nodes,
|
||||
edges: demoProcessGraph.edges,
|
||||
startNodeId: 'start',
|
||||
endNodeIds: ['end'],
|
||||
adjacency: {},
|
||||
},
|
||||
createdBy: userId,
|
||||
});
|
||||
console.log(`✓ Created process version 1`);
|
||||
|
||||
// Enable demo tools for tenant
|
||||
for (const toolName of demoTools) {
|
||||
const existingTool = await AiToolConfig.query(trx)
|
||||
.where('tool_name', toolName)
|
||||
.first();
|
||||
|
||||
if (!existingTool) {
|
||||
await AiToolConfig.query(trx).insert({
|
||||
id: randomUUID(),
|
||||
toolName,
|
||||
enabled: true,
|
||||
});
|
||||
console.log(`✓ Enabled tool: ${toolName}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`\n✅ Demo process seeded successfully!\n`);
|
||||
console.log(`Next steps:`);
|
||||
console.log(` 1. Navigate to /ai-processes in your frontend`);
|
||||
console.log(` 2. Open the "${demoProcessGraph.name}" process`);
|
||||
console.log(` 3. Test it by sending a message like:`);
|
||||
console.log(` "Register a dog named Max, owned by John Smith (john@email.com)"`);
|
||||
console.log();
|
||||
|
||||
if (app) await app.close();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ Seed failed:', error);
|
||||
if (app) await app.close();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Get tenant from command line args
|
||||
const tenantSlugOrId = process.argv[2];
|
||||
|
||||
if (!tenantSlugOrId) {
|
||||
console.error('Usage: npm run seed:demo-process -- <tenant-slug-or-id>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
seedDemoProcess(tenantSlugOrId);
|
||||
Reference in New Issue
Block a user