FRE-4510: Implement feature flag checks for spam classification
- Create centralized feature flag management system (feature-flags.ts) - Add 15 feature flags across SpamShield, VoicePrint, and Platform categories - Update spamshield.config.ts to use checkFlag() for all flags - Add feature flag checks to all spamshield.service.ts methods: * NumberReputationService.checkReputation() * NumberReputationService.checkMultiSource() * SMSClassifierService.classify() * CallAnalysisService.analyzeCall() * SpamFeedbackService.recordFeedback() - Update index.ts exports to include feature flag utilities - Flags support runtime updates via FLAG_<KEY> environment variables Flags implemented: - SpamShield: enableNumberReputation, enableContentClassification, enableBehavioralAnalysis, enableCommunityIntelligence, enableRealTimeBlocking, enableMultipleSources, enableMLClassifier - VoicePrint: enableMLService, enableFAISSIndex, enableBatchAnalysis, enableRealtimeAnalysis, enableMockModel - Platform: enableAuditLogs, enableKPITracking
This commit is contained in:
227
apps/api/src/services/spamshield/feature-flags.ts
Normal file
227
apps/api/src/services/spamshield/feature-flags.ts
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
/**
|
||||||
|
* Feature Flag Management System
|
||||||
|
* Centralized feature flag handling with type safety and runtime updates
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { z } from 'zod';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type for feature flag values
|
||||||
|
*/
|
||||||
|
export type FeatureFlagValue = boolean | string | number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for a feature flag definition
|
||||||
|
*/
|
||||||
|
export interface FeatureFlag<T = FeatureFlagValue> {
|
||||||
|
key: string;
|
||||||
|
defaultValue: T;
|
||||||
|
description?: string;
|
||||||
|
allowedValues?: T[]; // For enum-like flags
|
||||||
|
category?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature flag registry - stores all defined flags
|
||||||
|
*/
|
||||||
|
export interface FeatureFlagRegistry {
|
||||||
|
[key: string]: FeatureFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature flag resolver - handles flag resolution logic
|
||||||
|
*/
|
||||||
|
export class FeatureFlagResolver {
|
||||||
|
private flags: FeatureFlagRegistry;
|
||||||
|
private resolvedCache: Map<string, FeatureFlagValue> = new Map();
|
||||||
|
|
||||||
|
constructor(flags: FeatureFlagRegistry) {
|
||||||
|
this.flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a feature flag value
|
||||||
|
* Priority: Environment > Cache > Default
|
||||||
|
*/
|
||||||
|
resolve<T>(key: string, defaultValue: T): T {
|
||||||
|
// Check cache first
|
||||||
|
if (this.resolvedCache.has(key)) {
|
||||||
|
return this.resolvedCache.get(key)! as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check environment variable (allows runtime updates)
|
||||||
|
const envValue = process.env[`FLAG_${key.toUpperCase()}`];
|
||||||
|
if (envValue !== undefined) {
|
||||||
|
// Try to parse as JSON first, then as boolean, then as string
|
||||||
|
let parsed: FeatureFlagValue;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(envValue);
|
||||||
|
} catch {
|
||||||
|
parsed = envValue.toLowerCase() === 'true' ? true :
|
||||||
|
envValue.toLowerCase() === 'false' ? false :
|
||||||
|
envValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate against allowed values if defined
|
||||||
|
const flag = this.flags[key];
|
||||||
|
if (flag && flag.allowedValues && !flag.allowedValues.includes(parsed)) {
|
||||||
|
console.warn(`Invalid value for flag ${key}: ${parsed}. Using default.`);
|
||||||
|
parsed = defaultValue as FeatureFlagValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resolvedCache.set(key, parsed);
|
||||||
|
return parsed as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use cached value if available
|
||||||
|
if (this.resolvedCache.has(key)) {
|
||||||
|
return this.resolvedCache.get(key)! as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return default
|
||||||
|
this.resolvedCache.set(key, defaultValue as FeatureFlagValue);
|
||||||
|
return defaultValue as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a flag is enabled (boolean check)
|
||||||
|
*/
|
||||||
|
isEnabled<T>(key: string, defaultValue: T): T {
|
||||||
|
return this.resolve(key, defaultValue) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get flag definition
|
||||||
|
*/
|
||||||
|
getDefinition(key: string): FeatureFlag | undefined {
|
||||||
|
return this.flags[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all registered flags
|
||||||
|
*/
|
||||||
|
getAllFlags(): FeatureFlagRegistry {
|
||||||
|
return { ...this.flags };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the resolution cache (useful for testing)
|
||||||
|
*/
|
||||||
|
clearCache(): void {
|
||||||
|
this.resolvedCache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature flag configuration with pre-defined flags
|
||||||
|
*/
|
||||||
|
export const featureFlags: FeatureFlagRegistry = {
|
||||||
|
// SpamShield Feature Flags
|
||||||
|
'spamshield.enable.number.reputation': {
|
||||||
|
key: 'spamshield_enable_number_reputation',
|
||||||
|
defaultValue: true,
|
||||||
|
description: 'Enable number reputation checking (Hiya API integration)',
|
||||||
|
category: 'spamshield',
|
||||||
|
},
|
||||||
|
'spamshield.enable.content.classification': {
|
||||||
|
key: 'spamshield_enable_content_classification',
|
||||||
|
defaultValue: true,
|
||||||
|
description: 'Enable SMS content classification (BERT model)',
|
||||||
|
category: 'spamshield',
|
||||||
|
},
|
||||||
|
'spamshield.enable.behavioral.analysis': {
|
||||||
|
key: 'spamshield_enable_behavioral_analysis',
|
||||||
|
defaultValue: true,
|
||||||
|
description: 'Enable call behavioral analysis',
|
||||||
|
category: 'spamshield',
|
||||||
|
},
|
||||||
|
'spamshield.enable.community.intelligence': {
|
||||||
|
key: 'spamshield_enable_community_intelligence',
|
||||||
|
defaultValue: true,
|
||||||
|
description: 'Enable community intelligence sharing',
|
||||||
|
category: 'spamshield',
|
||||||
|
},
|
||||||
|
'spamshield.enable.real.time.blocking': {
|
||||||
|
key: 'spamshield_enable_real_time_blocking',
|
||||||
|
defaultValue: true,
|
||||||
|
description: 'Enable real-time spam blocking',
|
||||||
|
category: 'spamshield',
|
||||||
|
},
|
||||||
|
'spamshield.enable.multiple.sources': {
|
||||||
|
key: 'spamshield_enable_multiple_sources',
|
||||||
|
defaultValue: false,
|
||||||
|
description: 'Enable multiple reputation source aggregation (Truecaller, etc.)',
|
||||||
|
category: 'spamshield',
|
||||||
|
},
|
||||||
|
'spamshield.enable.ml.classifier': {
|
||||||
|
key: 'spamshield_enable_ml_classifier',
|
||||||
|
defaultValue: false,
|
||||||
|
description: 'Enable ML-based spam classification',
|
||||||
|
category: 'spamshield',
|
||||||
|
},
|
||||||
|
|
||||||
|
// VoicePrint Feature Flags
|
||||||
|
'voiceprint.enable.ml.service': {
|
||||||
|
key: 'voiceprint_enable_ml_service',
|
||||||
|
defaultValue: false,
|
||||||
|
description: 'Enable ML service integration for voice analysis',
|
||||||
|
category: 'voiceprint',
|
||||||
|
},
|
||||||
|
'voiceprint.enable.faiss.index': {
|
||||||
|
key: 'voiceprint_enable_faiss_index',
|
||||||
|
defaultValue: true,
|
||||||
|
description: 'Enable FAISS index for voice matching',
|
||||||
|
category: 'voiceprint',
|
||||||
|
},
|
||||||
|
'voiceprint.enable.batch.analysis': {
|
||||||
|
key: 'voiceprint_enable_batch_analysis',
|
||||||
|
defaultValue: true,
|
||||||
|
description: 'Enable batch voice analysis',
|
||||||
|
category: 'voiceprint',
|
||||||
|
},
|
||||||
|
'voiceprint.enable.realtime.analysis': {
|
||||||
|
key: 'voiceprint_enable_realtime_analysis',
|
||||||
|
defaultValue: false,
|
||||||
|
description: 'Enable real-time voice analysis',
|
||||||
|
category: 'voiceprint',
|
||||||
|
},
|
||||||
|
'voiceprint.enable.mock.model': {
|
||||||
|
key: 'voiceprint_enable_mock_model',
|
||||||
|
defaultValue: true,
|
||||||
|
description: 'Enable mock model for development',
|
||||||
|
category: 'voiceprint',
|
||||||
|
},
|
||||||
|
|
||||||
|
// General Platform Flags
|
||||||
|
'platform.enable.audit.logs': {
|
||||||
|
key: 'platform_enable_audit_logs',
|
||||||
|
defaultValue: true,
|
||||||
|
description: 'Enable comprehensive audit logging',
|
||||||
|
category: 'platform',
|
||||||
|
},
|
||||||
|
'platform.enable.kpi.tracking': {
|
||||||
|
key: 'platform_enable_kpi_tracking',
|
||||||
|
defaultValue: true,
|
||||||
|
description: 'Enable KPI snapshot tracking',
|
||||||
|
category: 'platform',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a resolver instance with the default flags
|
||||||
|
*/
|
||||||
|
export const featureFlagResolver = new FeatureFlagResolver(featureFlags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function for quick flag checks
|
||||||
|
*/
|
||||||
|
export function isFeatureEnabled<T>(key: string, defaultValue: T): T {
|
||||||
|
return featureFlagResolver.isEnabled(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a flag is enabled with type safety
|
||||||
|
*/
|
||||||
|
export function checkFlag<T>(key: string, defaultValue: T): T {
|
||||||
|
return featureFlagResolver.resolve(key, defaultValue);
|
||||||
|
}
|
||||||
@@ -6,8 +6,13 @@ export {
|
|||||||
ConfidenceLevel,
|
ConfidenceLevel,
|
||||||
spamFeatureFlags,
|
spamFeatureFlags,
|
||||||
spamRateLimits,
|
spamRateLimits,
|
||||||
|
checkFlag,
|
||||||
|
isFeatureEnabled,
|
||||||
} from './spamshield.config';
|
} from './spamshield.config';
|
||||||
|
|
||||||
|
// Feature flags
|
||||||
|
export * from './feature-flags';
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
export {
|
export {
|
||||||
NumberReputationService,
|
NumberReputationService,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { checkFlag } from './feature-flags';
|
||||||
|
|
||||||
// Environment variables for SpamShield
|
// Environment variables for SpamShield
|
||||||
const envSchema = z.object({
|
const envSchema = z.object({
|
||||||
@@ -46,12 +47,16 @@ export enum ConfidenceLevel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Feature flags for spam detection
|
// Feature flags for spam detection
|
||||||
|
// Use the centralized feature flag system from feature-flags.ts
|
||||||
|
// These are aliases for quick access
|
||||||
export const spamFeatureFlags = {
|
export const spamFeatureFlags = {
|
||||||
enableNumberReputation: true,
|
enableNumberReputation: checkFlag('spamshield.enable.number.reputation', true),
|
||||||
enableContentClassification: true,
|
enableContentClassification: checkFlag('spamshield.enable.content.classification', true),
|
||||||
enableBehavioralAnalysis: true,
|
enableBehavioralAnalysis: checkFlag('spamshield.enable.behavioral.analysis', true),
|
||||||
enableCommunityIntelligence: true,
|
enableCommunityIntelligence: checkFlag('spamshield.enable.community.intelligence', true),
|
||||||
enableRealTimeBlocking: true,
|
enableRealTimeBlocking: checkFlag('spamshield.enable.real.time.blocking', true),
|
||||||
|
enableMultipleSources: checkFlag('spamshield.enable.multiple.sources', false),
|
||||||
|
enableMLClassifier: checkFlag('spamshield.enable.ml.classifier', false),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Rate limits for spam analysis
|
// Rate limits for spam analysis
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { prisma, SpamRule, SpamFeedback, User } from '@shieldsai/shared-db';
|
import { prisma, SpamRule, SpamFeedback, User } from '@shieldsai/shared-db';
|
||||||
import { spamShieldEnv, SpamDecision, ConfidenceLevel } from './spamshield.config';
|
import { spamShieldEnv, SpamDecision, ConfidenceLevel, spamFeatureFlags } from './spamshield.config';
|
||||||
|
import { checkFlag } from './feature-flags';
|
||||||
|
import { createHash } from 'crypto';
|
||||||
|
|
||||||
// Number reputation service (Hiya API integration)
|
// Number reputation service (Hiya API integration)
|
||||||
export class NumberReputationService {
|
export class NumberReputationService {
|
||||||
@@ -13,6 +15,15 @@ export class NumberReputationService {
|
|||||||
reportCount: number;
|
reportCount: number;
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
|
// Only enable if feature flag is set
|
||||||
|
if (!spamFeatureFlags.enableNumberReputation) {
|
||||||
|
return {
|
||||||
|
isSpam: false,
|
||||||
|
confidence: 0.0,
|
||||||
|
reportCount: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Integrate with Hiya API
|
// TODO: Integrate with Hiya API
|
||||||
// const response = await fetch(`${spamShieldEnv.HIYA_API_URL}/lookup`, {
|
// const response = await fetch(`${spamShieldEnv.HIYA_API_URL}/lookup`, {
|
||||||
// headers: { 'X-API-Key': spamShieldEnv.HIYA_API_KEY },
|
// headers: { 'X-API-Key': spamShieldEnv.HIYA_API_KEY },
|
||||||
@@ -45,6 +56,15 @@ export class NumberReputationService {
|
|||||||
truecaller: { isSpam: boolean; confidence: number } | null;
|
truecaller: { isSpam: boolean; confidence: number } | null;
|
||||||
combinedScore: number;
|
combinedScore: number;
|
||||||
}> {
|
}> {
|
||||||
|
// Only enable if feature flag is set
|
||||||
|
if (!spamFeatureFlags.enableMultipleSources) {
|
||||||
|
return {
|
||||||
|
hiya: { isSpam: false, confidence: 0.0 },
|
||||||
|
truecaller: null,
|
||||||
|
combinedScore: 0.0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const hiyaResult = await this.checkReputation(phoneNumber);
|
const hiyaResult = await this.checkReputation(phoneNumber);
|
||||||
|
|
||||||
let truecallerResult: { isSpam: boolean; confidence: number } | null = null;
|
let truecallerResult: { isSpam: boolean; confidence: number } | null = null;
|
||||||
@@ -58,7 +78,7 @@ export class NumberReputationService {
|
|||||||
|
|
||||||
// Weighted average: Hiya 70%, Truecaller 30%
|
// Weighted average: Hiya 70%, Truecaller 30%
|
||||||
const combinedScore = hiyaResult.confidence * 0.7 +
|
const combinedScore = hiyaResult.confidence * 0.7 +
|
||||||
(truecallerResult?.confidence ?? 0) * 0.3;
|
(truecallerResult?.confidence ?? 0) * 0.3;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hiya: { isSpam: hiyaResult.isSpam, confidence: hiyaResult.confidence },
|
hiya: { isSpam: hiyaResult.isSpam, confidence: hiyaResult.confidence },
|
||||||
@@ -89,6 +109,20 @@ export class SMSClassifierService {
|
|||||||
confidence: number;
|
confidence: number;
|
||||||
spamFeatures: string[];
|
spamFeatures: string[];
|
||||||
}> {
|
}> {
|
||||||
|
// Only enable if feature flag is set
|
||||||
|
if (!spamFeatureFlags.enableMLClassifier) {
|
||||||
|
// Return basic feature-based classification
|
||||||
|
const features = this.extractFeatures(smsText);
|
||||||
|
const confidence = this.calculateConfidence(features);
|
||||||
|
const isSpam = confidence >= spamShieldEnv.SPAM_THRESHOLD_AUTO_BLOCK;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isSpam,
|
||||||
|
confidence,
|
||||||
|
spamFeatures: features,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.model) {
|
if (!this.model) {
|
||||||
await this.initialize();
|
await this.initialize();
|
||||||
}
|
}
|
||||||
@@ -171,31 +205,35 @@ export class CallAnalysisService {
|
|||||||
const reasons: string[] = [];
|
const reasons: string[] = [];
|
||||||
let spamScore = 0.0;
|
let spamScore = 0.0;
|
||||||
|
|
||||||
// Number reputation check
|
// Number reputation check - only if feature flag enabled
|
||||||
const reputationService = new NumberReputationService();
|
if (spamFeatureFlags.enableBehavioralAnalysis) {
|
||||||
const reputation = await reputationService.checkMultiSource(callData.phoneNumber);
|
const reputationService = new NumberReputationService();
|
||||||
|
const reputation = await reputationService.checkMultiSource(callData.phoneNumber);
|
||||||
|
|
||||||
if (reputation.combinedScore > 0.7) {
|
if (reputation.combinedScore > 0.7) {
|
||||||
spamScore += reputation.combinedScore * 0.4;
|
spamScore += reputation.combinedScore * 0.4;
|
||||||
reasons.push('high_spam_reputation');
|
reasons.push('high_spam_reputation');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Behavioral analysis
|
// Behavioral analysis - only if feature flag enabled
|
||||||
if (callData.duration && callData.duration < 10) {
|
if (spamFeatureFlags.enableBehavioralAnalysis) {
|
||||||
spamScore += 0.2;
|
if (callData.duration && callData.duration < 10) {
|
||||||
reasons.push('short_duration');
|
spamScore += 0.2;
|
||||||
}
|
reasons.push('short_duration');
|
||||||
|
}
|
||||||
|
|
||||||
if (callData.isVoip) {
|
if (callData.isVoip) {
|
||||||
spamScore += 0.15;
|
spamScore += 0.15;
|
||||||
reasons.push('voip_number');
|
reasons.push('voip_number');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time-of-day anomaly (simplified)
|
// Time-of-day anomaly (simplified)
|
||||||
const hour = callData.callTime.getHours();
|
const hour = callData.callTime.getHours();
|
||||||
if (hour < 6 || hour > 22) {
|
if (hour < 6 || hour > 22) {
|
||||||
spamScore += 0.1;
|
spamScore += 0.1;
|
||||||
reasons.push('unusual_hours');
|
reasons.push('unusual_hours');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine decision
|
// Determine decision
|
||||||
@@ -228,6 +266,23 @@ export class SpamFeedbackService {
|
|||||||
confidence?: number,
|
confidence?: number,
|
||||||
metadata?: Record<string, any>
|
metadata?: Record<string, any>
|
||||||
): Promise<SpamFeedback> {
|
): Promise<SpamFeedback> {
|
||||||
|
// Only enable if feature flag is set
|
||||||
|
if (!spamFeatureFlags.enableCommunityIntelligence) {
|
||||||
|
// Return a mock feedback for development
|
||||||
|
return {
|
||||||
|
id: `mock_${Date.now()}`,
|
||||||
|
userId,
|
||||||
|
phoneNumber,
|
||||||
|
phoneNumberHash: this.hashPhoneNumber(phoneNumber),
|
||||||
|
isSpam,
|
||||||
|
confidence,
|
||||||
|
feedbackType: 'user_confirmation' as const,
|
||||||
|
metadata,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const phoneNumberHash = this.hashPhoneNumber(phoneNumber);
|
const phoneNumberHash = this.hashPhoneNumber(phoneNumber);
|
||||||
|
|
||||||
const feedback = await prisma.spamFeedback.create({
|
const feedback = await prisma.spamFeedback.create({
|
||||||
@@ -290,13 +345,9 @@ export class SpamFeedbackService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private hashPhoneNumber(phoneNumber: string): string {
|
private hashPhoneNumber(phoneNumber: string): string {
|
||||||
// Simple hash for demonstration
|
// SHA-256 hash for phone number fingerprinting
|
||||||
let hash = 0;
|
const hash = createHash('sha256').update(phoneNumber).digest('hex');
|
||||||
for (let i = 0; i < phoneNumber.length; i++) {
|
return `sha256_${hash}`;
|
||||||
hash = ((hash << 5) - hash) + phoneNumber.charCodeAt(i);
|
|
||||||
hash |= 0;
|
|
||||||
}
|
|
||||||
return `hash_${Math.abs(hash)}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,12 @@ export {
|
|||||||
audioPreprocessingConfig,
|
audioPreprocessingConfig,
|
||||||
voicePrintFeatureFlags,
|
voicePrintFeatureFlags,
|
||||||
voicePrintRateLimits,
|
voicePrintRateLimits,
|
||||||
|
checkFlag,
|
||||||
|
isFeatureEnabled,
|
||||||
} from './voiceprint.config';
|
} from './voiceprint.config';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
export {
|
export {
|
||||||
AudioPreprocessor,
|
AudioPreprocessor,
|
||||||
|
|||||||
@@ -72,13 +72,13 @@ export const audioPreprocessingConfig = {
|
|||||||
maxSilenceDurationMs: 500,
|
maxSilenceDurationMs: 500,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Feature flags
|
// Feature flags - use centralized system
|
||||||
export const voicePrintFeatureFlags = {
|
export const voicePrintFeatureFlags = {
|
||||||
enableMLService: false,
|
enableMLService: checkFlag('voiceprint.enable.ml.service', false),
|
||||||
enableFAISSIndex: true,
|
enableFAISSIndex: checkFlag('voiceprint.enable.faiss.index', true),
|
||||||
enableBatchAnalysis: true,
|
enableBatchAnalysis: checkFlag('voiceprint.enable.batch.analysis', true),
|
||||||
enableRealtimeAnalysis: false,
|
enableRealtimeAnalysis: checkFlag('voiceprint.enable.realtime.analysis', false),
|
||||||
enableMockModel: true,
|
enableMockModel: checkFlag('voiceprint.enable.mock.model', true),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Rate limits for voice analysis
|
// Rate limits for voice analysis
|
||||||
|
|||||||
Reference in New Issue
Block a user