Meeting Summary from Audio
The crm-report-demo reaktor (in operaide-apps/apps/op-demo-crm-report) converts a recorded meeting into a structured CRM report, stores it via a mock ERP REST API, and returns the created report URL.
Reaktor registration
import { aktorSetting, registerReaktorDefinition } from '@operaide/aktor';
import { z } from 'zod';
import { aktorCreateCRMReport, aktorGenerateAnswer, aktorSaveReport } from './aktors';
registerReaktorDefinition({
reaktorDefinitionId: 'crm-report-demo',
label: 'CRM Report',
description: 'A CRM report for a given company',
createReaktor({ audio, customerName }) {
const apiKey = aktorSetting(z.string().describe('API Key for the ERP'), '5440aa7de35c3be:681d56e565bd709');
const baseUrl = aktorSetting(z.string().describe('Base URL for the ERP'), 'https://erp.case.operaide.ai');
const report = aktorCreateCRMReport({ audio, customerName });
const savedReportUrl = aktorSaveReport({ reportJson: report, baseUrl, apiKey });
return aktorGenerateAnswer({ reportUrl: savedReportUrl, report });
},
inputSchema: z.object({
audio: z
.string()
.describe('[file-upload] Audio file in base64 format')
.refine((val) => val.startsWith('data:') && val.includes(';base64,'), {
message: 'Must be a data URL with base64 encoding',
})
.openapi({
example: 'data:audio/mp3;name=recording.mp3;base64,//uQxAAA...',
description:
'Base64 encoded audio file. Use data URL format (data:type;name=filename;base64,data) for auto-detection of MIME type and filename',
}),
customerName: z.string().openapi({ example: 'John Doe' }),
}),
outputSchema: z.string().describe('[textarea] JSON output'),
});
Aktor pipeline
aktorCreateCRMReport orchestrates the heavy lifting. It:
- Transcribes the uploaded audio with
aktorSpeechToText. - Generates a summary, follow-up tasks, and an email subject via dedicated LLM prompts.
- Builds the final CRM payload and returns it as JSON.
export function aktorCreateCRMReport(input: { audio: Aktor<string>; customerName: Aktor<string> }) {
return defineAktor(
'aktorCreateCRMReport',
{
createAktor() {
const transcript = aktorSpeechToText({ audio: input.audio });
const summary = aktorCreateSummary({ transcript });
const todos = aktorCreateTodos({ transcript });
const subject = aktorCreateSubject({ transcript });
const startDate = aktorGetDate({});
const endDate = aktorGenerateEndDate({ transcript, startDate });
return aktorCreateReport({
summary,
todos,
customerName: input.customerName,
subject,
startDate,
endDate,
});
},
},
input
);
}
aktorSaveReport posts the generated JSON to the mock ERP endpoint, while aktorGenerateAnswer formats the final response (including the saved report URL).
Sample request
Send a request similar to the following payload (trimmed for brevity):
POST http://localhost:8811/api/v1/aktor/crm-report-demo/run
Content-Type: application/json
{
"audio": "data:audio/mp3;name=call.mp3;base64,//uQxAAA...trimmed...",
"customerName": "Contoso GmbH"
}
The response body contains the generated CRM report JSON together with the URL returned by the mock ERP API.