Passa al contenuto principale

Chat Logger

Il sistema Chat Logger è responsabile del salvataggio delle conversazioni e dei messaggi delle chat su Laravel, garantendo la persistenza dei dati e la tracciabilità delle interazioni utente.

Architettura del Sistema

Chat Logger Worker

  • Scopo: Salvataggio asincrono dei messaggi chat
  • Capacità: 100 task concorrenti
  • Workflow: chat-logger-task per elaborazione messaggi
  • Integrazione: API REST Laravel per persistenza dati

Laravel Service Integration

/src/services/laravelService.ts
// Servizio di integrazione Laravel
class LaravelService {
private apiUrl: string
private apiKey: string

constructor(config: LaravelConfig) {
this.apiUrl = config.apiUrl
this.apiKey = config.apiKey
}
}

Struttura Dati Messaggi

Conversation Payload

/src/types/langgraphTypes.ts
interface ConversationPayload {
assistant_or_agent_uuid: string
conversation_uuid: string
date_time: string
sender: 'user' | 'assistant' | 'system' | 'tool'
message: string
message_uuid: string
location?: string
domain?: string
model: string
tool_calls?: any[]
tokens?: {
input_tokens: number
output_tokens: number
cache_read?: number
total_tokens: number
}
}

Validazione Dati

/src/services/laravelService.ts
// Schema di validazione Zod
const conversationPayloadSchema = z.object({
assistant_or_agent_uuid: z.string(),
conversation_uuid: z.string(),
date_time: z.string(),
sender: z.enum(['user', 'assistant', 'system', 'tool']),
message: z.string(),
message_uuid: z.uuid(),
location: z.string().optional(),
domain: z.string().optional(),
model: z.string(),
tool_calls: z.array(z.any()).optional(),
tokens: z
.object({
input_tokens: z.number(),
output_tokens: z.number(),
cache_read: z.number().optional().default(0),
total_tokens: z.number(),
})
.optional(),
})

Processo di Salvataggio

1. Ricezione Messaggio

/workflows/chatLogger.ts
// Ricezione messaggio dal Node Agent
const chatLoggerTask = hatchet.task({
name: 'chat-logger-task',
retries: 3,
executionTimeout: '1m',
backoff: {
maxSeconds: 30,
factor: 2,
},
fn: async (input: ConversationPayload, ctx): Promise<ChatLoggerOutput> => {
if (!input.message) {
throw new Error('Message is required')
}

await laravelService.saveMessageToLaravel(input, ctx)
return { success: true }
},
})

2. Validazione e Preparazione

/src/services/laravelService.ts
// Validazione payload messaggio
const validateMessage = (payload: ConversationPayload): ConversationPayload => {
try {
return conversationPayloadSchema.parse(payload)
} catch (error) {
throw new Error(`Invalid message payload: ${error.message}`)
}
}

3. Invio a Laravel

/src/services/laravelService.ts
// Salvataggio messaggio su Laravel
const saveMessageToLaravel = async (payload: ConversationPayload, ctx: Context): Promise<void> => {
const validatedPayload = validateMessage(payload)

try {
const response = await axios.post(`${this.apiUrl}/api/chat/messages`, validatedPayload, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
timeout: 30000,
})

if (response.status !== 201) {
throw new Error(`Laravel API returned status ${response.status}`)
}

logInfo(ctx, 'Message saved successfully', {
messageUuid: payload.message_uuid,
conversationUuid: payload.conversation_uuid,
sender: payload.sender,
})
} catch (error) {
logError(ctx, 'Failed to save message to Laravel', {
error: error.message,
messageUuid: payload.message_uuid,
conversationUuid: payload.conversation_uuid,
})
throw error
}
}

Gestione Errori e Retry

Retry Logic

/src/services/laravelService.ts
// Configurazione retry per salvataggio messaggi
const RETRY_CONFIG = {
maxRetries: 3,
backoff: {
initialDelay: 1000,
maxDelay: 30000,
factor: 2,
},
}

const saveWithRetry = async (payload: ConversationPayload, ctx: Context): Promise<void> => {
let lastError: Error

for (let attempt = 1; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
try {
await this.saveMessageToLaravel(payload, ctx)
return // Success
} catch (error) {
lastError = error

if (attempt < RETRY_CONFIG.maxRetries) {
const delay = Math.min(
RETRY_CONFIG.backoff.initialDelay * Math.pow(RETRY_CONFIG.backoff.factor, attempt - 1),
RETRY_CONFIG.backoff.maxDelay
)

logWarn(ctx, `Retry attempt ${attempt} after ${delay}ms`, {
error: error.message,
messageUuid: payload.message_uuid,
})

await new Promise(resolve => setTimeout(resolve, delay))
}
}
}

throw lastError
}

Error Handling

/src/services/laravelService.ts
// Gestione errori specifici
const handleSaveError = (error: any, payload: ConversationPayload, ctx: Context): void => {
if (error.response?.status === 401) {
logError(ctx, 'Laravel authentication failed', {
messageUuid: payload.message_uuid,
})
throw new Error('Laravel API authentication failed')
} else if (error.response?.status === 422) {
logError(ctx, 'Invalid message data', {
messageUuid: payload.message_uuid,
validationErrors: error.response.data.errors,
})
throw new Error('Invalid message data format')
} else if (error.response?.status >= 500) {
logError(ctx, 'Laravel server error', {
messageUuid: payload.message_uuid,
status: error.response.status,
})
throw new Error('Laravel server error')
} else if (error.code === 'ECONNREFUSED') {
logError(ctx, 'Laravel connection refused', {
messageUuid: payload.message_uuid,
})
throw new Error('Cannot connect to Laravel API')
} else if (error.code === 'ETIMEDOUT') {
logError(ctx, 'Laravel request timeout', {
messageUuid: payload.message_uuid,
})
throw new Error('Laravel API request timeout')
}

throw error
}

Configurazione

Laravel API Configuration

/src/types/langgraphTypes.ts
// Configurazione API Laravel
interface LaravelConfig {
apiUrl: string
apiKey: string
timeout: number
retry: {
maxRetries: number
backoff: {
initialDelay: number
maxDelay: number
factor: number
}
}
}

Environment Variables

# Laravel API Configuration
LARAVEL_API_URL=https://your-laravel-app.com
LARAVEL_API_KEY=your_api_key_here
LARAVEL_API_TIMEOUT=30000

Monitoraggio e Logging

Metriche Chat Logger

  • Messages Processed: Numero messaggi elaborati
  • Success Rate: Percentuale successo salvataggio
  • Average Processing Time: Tempo medio elaborazione
  • Error Rate: Percentuale errori per tipo
  • Retry Count: Numero retry per messaggio

Logging Strutturato

/src/utilities/loggerUtility.ts
// Log per salvataggio messaggi
logInfo(ctx, 'Chat logger task started', {
messageUuid: payload.message_uuid,
conversationUuid: payload.conversation_uuid,
sender: payload.sender,
messageLength: payload.message.length,
})

logInfo(ctx, 'Message saved to Laravel', {
messageUuid: payload.message_uuid,
conversationUuid: payload.conversation_uuid,
responseTime: Date.now() - startTime,
})

Performance e Ottimizzazione

Batch Processing

/src/services/laravelService.ts
// Elaborazione batch messaggi
const processBatchMessages = async (messages: ConversationPayload[]): Promise<void> => {
const batchSize = 10
const batches = []

for (let i = 0; i < messages.length; i += batchSize) {
batches.push(messages.slice(i, i + batchSize))
}

for (const batch of batches) {
const promises = batch.map(message => this.saveMessageToLaravel(message, ctx))
await Promise.all(promises)

// Pausa tra batch per evitare sovraccarico
await new Promise(resolve => setTimeout(resolve, 100))
}
}

Connection Pooling

/src/services/laravelService.ts
// Configurazione connection pooling
const axiosConfig = {
timeout: 30000,
maxRedirects: 5,
httpAgent: new http.Agent({
keepAlive: true,
maxSockets: 10,
maxFreeSockets: 5,
timeout: 30000,
}),
httpsAgent: new https.Agent({
keepAlive: true,
maxSockets: 10,
maxFreeSockets: 5,
timeout: 30000,
}),
}

Sicurezza

Autenticazione JWT

/src/utilities/validation.ts
// Verifica token JWT
const verifyJWT = (token: string): boolean => {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET)
return !!decoded
} catch (error) {
return false
}
}

Validazione Input

/src/utilities/validation.ts
// Sanitizzazione messaggi
const sanitizeMessage = (message: string): string => {
return message
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/<[^>]*>/g, '')
.trim()
}

Testing

Unit Tests

/tests/chat-logger.test.ts
// Test per chat logger
describe('Chat Logger', () => {
it('should save message successfully', async () => {
const payload: ConversationPayload = {
assistant_or_agent_uuid: 'test-uuid',
conversation_uuid: 'conv-uuid',
date_time: new Date().toISOString(),
sender: 'user',
message: 'Test message',
message_uuid: 'msg-uuid',
model: 'gpt-4',
}

const result = await chatLoggerTask(payload, mockContext)
expect(result.success).toBe(true)
})

it('should handle validation errors', async () => {
const invalidPayload = { message: 'test' }

await expect(chatLoggerTask(invalidPayload, mockContext)).rejects.toThrow(
'Invalid message payload'
)
})
})

Integration Tests

/tests/laravel-integration.test.ts
// Test integrazione con Laravel
describe('Laravel Integration', () => {
it('should send message to Laravel API', async () => {
const mockAxios = jest.mocked(axios)
mockAxios.post.mockResolvedValue({ status: 201 })

await laravelService.saveMessageToLaravel(validPayload, mockContext)

expect(mockAxios.post).toHaveBeenCalledWith(
expect.stringContaining('/api/chat/messages'),
validPayload,
expect.objectContaining({
headers: expect.objectContaining({
'Authorization': expect.stringContaining('Bearer'),
}),
})
)
})
})