-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/ai agents #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -13,6 +13,7 @@ | |||
| "dependencies": { | ||||
| "@getbrevo/brevo": "^2.2.0", | ||||
| "@hono/zod-openapi": "^0.18.3", | ||||
| "@langchain/core": "^0.3.77", | ||||
| "@scalar/hono-api-reference": "^0.5.162", | ||||
| "@types/json2csv": "^5.0.7", | ||||
| "@types/uuid": "^10.0.0", | ||||
|
|
@@ -28,6 +29,7 @@ | |||
| "i": "^0.3.7", | ||||
| "jose": "^5.9.6", | ||||
| "json2csv": "^6.0.0-alpha.2", | ||||
| "langchain": "^0.3.34", | ||||
|
||||
| "langchain": "^0.3.34", |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| # Sistema de Agentes de IA | ||
|
|
||
| Este módulo implementa un sistema de agentes de IA para procesar comandos de voz y crear automáticamente transacciones, metas de ahorro y presupuestos. | ||
|
|
||
| ## Arquitectura | ||
|
|
||
| ### Componentes Principales | ||
|
|
||
| 1. **VoiceOrchestratorService**: Orquestador principal que coordina todos los agentes | ||
| 2. **Agentes Especializados**: | ||
| - `TransactionAgentService`: Maneja creación de gastos e ingresos | ||
| - `GoalAgentService`: Maneja creación de metas de ahorro | ||
| - `BudgetAgentService`: Maneja creación de presupuestos | ||
| - `ValidationAgentService`: Valida y corrige datos extraídos | ||
|
|
||
| ### Flujo de Procesamiento | ||
|
|
||
| 1. **Audio Input** → El usuario graba un comando de voz | ||
| 2. **Transcripción** → OpenAI Whisper convierte audio a texto | ||
| 3. **Clasificación de Intención** → LLM determina el tipo de comando | ||
| 4. **Extracción de Datos** → LLM extrae entidades relevantes | ||
| 5. **Procesamiento por Agente** → El agente especializado procesa los datos | ||
| 6. **Validación** → Se validan y corrigen los datos extraídos | ||
| 7. **Respuesta** → Se devuelve la respuesta estructurada al frontend | ||
|
|
||
| ## Configuración | ||
|
|
||
| ### Variables de Entorno | ||
|
|
||
| ```bash | ||
| OPENAI_API_KEY=your_openai_api_key_here | ||
| ``` | ||
|
|
||
| ### Endpoint | ||
|
|
||
| ``` | ||
| POST /voice-command | ||
| Authorization: Bearer <token> | ||
| Content-Type: multipart/form-data | ||
|
|
||
| Body: | ||
| - audio: File (audio/wav) | ||
| ``` | ||
|
|
||
| ### Respuesta | ||
|
|
||
| ```json | ||
| { | ||
| "success": true, | ||
| "intent": "CREATE_TRANSACTION", | ||
| "extractedData": { | ||
| "user_id": 1, | ||
| "amount": 25.50, | ||
| "type": "EXPENSE", | ||
| "description": "Almuerzo en restaurante", | ||
| "category_id": null, | ||
| "payment_method_id": null, | ||
| "date": "2024-01-15T10:30:00.000Z" | ||
| }, | ||
| "confidence": 0.95, | ||
| "message": "He identificado una transacción: Gasto de $25.5 en Almuerzo en restaurante", | ||
| "validationErrors": [], | ||
| "suggestedCorrections": {} | ||
| } | ||
| ``` | ||
|
|
||
| ## Ejemplos de Comandos de Voz | ||
|
|
||
| ### Transacciones | ||
| - "Gasté 25 dólares en comida" | ||
| - "Recibí 500 dólares de mi trabajo" | ||
| - "Pagué 80 dólares en gasolina con mi tarjeta de crédito" | ||
|
|
||
| ### Metas de Ahorro | ||
| - "Quiero ahorrar 1000 dólares para vacaciones hasta diciembre" | ||
| - "Crear una meta de 500 dólares para emergencias" | ||
| - "Ahorrar para un auto, necesito 15000 dólares" | ||
|
|
||
| ### Presupuestos | ||
| - "Crear un presupuesto de 300 dólares para comida este mes" | ||
| - "Quiero limitar mis gastos en entretenimiento a 100 dólares" | ||
| - "Presupuesto de 200 dólares para transporte" | ||
|
|
||
| ## Extensibilidad | ||
|
|
||
| Para agregar nuevos tipos de comandos: | ||
|
|
||
| 1. Agregar nueva intención en `CommandIntent` | ||
| 2. Crear nuevo agente especializado | ||
| 3. Agregar lógica en `VoiceOrchestratorService` | ||
| 4. Actualizar prompts en `OpenAILLMAdapter` | ||
| 5. Agregar validaciones en `ValidationAgentService` |
65 changes: 65 additions & 0 deletions
65
src/features/ai-agents/application/services/budget-agent.service.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import { AgentResponse } from "../../domain/entities/voice-command.entity"; | ||
|
|
||
| export class BudgetAgentService { | ||
| async processBudget(extractedData: Record<string, any>, userId: number): Promise<AgentResponse> { | ||
| try { | ||
| const processedData = this.normalizeBudgetData(extractedData, userId); | ||
|
|
||
| return { | ||
| success: true, | ||
| intent: 'CREATE_BUDGET', | ||
| extractedData: processedData, | ||
| confidence: 0.9, | ||
| message: `He identificado un presupuesto de $${processedData.limit_amount} para ${processedData.month}` | ||
| }; | ||
| } catch (error) { | ||
| return { | ||
| success: false, | ||
| intent: 'CREATE_BUDGET', | ||
| extractedData: {}, | ||
| confidence: 0, | ||
| message: "No pude procesar los datos del presupuesto" | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| private normalizeBudgetData(data: Record<string, any>, userId: number) { | ||
| return { | ||
| user_id: userId, | ||
| category_id: data.category_id || null, | ||
| limit_amount: this.parseAmount(data.limit_amount || data.amount), | ||
| current_amount: 0, | ||
| month: this.parseMonth(data.month || data.period) | ||
| }; | ||
| } | ||
|
|
||
| private parseAmount(amount: any): number { | ||
| if (typeof amount === 'number') return amount; | ||
| if (typeof amount === 'string') { | ||
| const cleanAmount = amount.replace(/[^\d.,]/g, ''); | ||
| return parseFloat(cleanAmount.replace(',', '.')); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| private parseMonth(monthInput: any): string { | ||
| if (!monthInput) { | ||
| const currentDate = new Date(); | ||
| return new Date(currentDate.getFullYear(), currentDate.getMonth(), 1).toISOString(); | ||
| } | ||
|
|
||
| if (monthInput instanceof Date) { | ||
| return new Date(monthInput.getFullYear(), monthInput.getMonth(), 1).toISOString(); | ||
| } | ||
|
|
||
| if (typeof monthInput === 'string') { | ||
| const date = new Date(monthInput); | ||
| if (!isNaN(date.getTime())) { | ||
| return new Date(date.getFullYear(), date.getMonth(), 1).toISOString(); | ||
| } | ||
| } | ||
|
|
||
| const currentDate = new Date(); | ||
| return new Date(currentDate.getFullYear(), currentDate.getMonth(), 1).toISOString(); | ||
| } | ||
| } |
70 changes: 70 additions & 0 deletions
70
src/features/ai-agents/application/services/goal-agent.service.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import { AgentResponse } from "../../domain/entities/voice-command.entity"; | ||
|
|
||
| export class GoalAgentService { | ||
| async processGoal(extractedData: Record<string, any>, userId: number): Promise<AgentResponse> { | ||
| try { | ||
| const processedData = this.normalizeGoalData(extractedData, userId); | ||
|
|
||
| return { | ||
| success: true, | ||
| intent: 'CREATE_GOAL', | ||
| extractedData: processedData, | ||
| confidence: 0.9, | ||
| message: `He identificado una meta de ahorro: "${processedData.name}" con objetivo de $${processedData.target_amount}` | ||
| }; | ||
| } catch (error) { | ||
| return { | ||
| success: false, | ||
| intent: 'CREATE_GOAL', | ||
| extractedData: {}, | ||
| confidence: 0, | ||
| message: "No pude procesar los datos de la meta de ahorro" | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| private normalizeGoalData(data: Record<string, any>, userId: number) { | ||
| return { | ||
| user_id: userId, | ||
| name: data.name || data.goal_name || 'Meta sin nombre', | ||
| target_amount: this.parseAmount(data.target_amount || data.amount), | ||
| current_amount: 0, | ||
| end_date: this.parseDate(data.end_date || data.deadline), | ||
| category_id: data.category_id || null, | ||
| contribution_frequency: data.contribution_frequency || null, | ||
| contribution_amount: data.contribution_amount ? this.parseAmount(data.contribution_amount) : null | ||
| }; | ||
| } | ||
|
|
||
| private parseAmount(amount: any): number { | ||
| if (typeof amount === 'number') return amount; | ||
| if (typeof amount === 'string') { | ||
| const cleanAmount = amount.replace(/[^\d.,]/g, ''); | ||
| return parseFloat(cleanAmount.replace(',', '.')); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| private parseDate(dateInput: any): string { | ||
| if (!dateInput) { | ||
| const futureDate = new Date(); | ||
| futureDate.setFullYear(futureDate.getFullYear() + 1); | ||
| return futureDate.toISOString(); | ||
| } | ||
|
|
||
| if (dateInput instanceof Date) { | ||
| return dateInput.toISOString(); | ||
| } | ||
|
|
||
| if (typeof dateInput === 'string') { | ||
| const date = new Date(dateInput); | ||
| if (!isNaN(date.getTime())) { | ||
| return date.toISOString(); | ||
| } | ||
| } | ||
|
|
||
| const futureDate = new Date(); | ||
| futureDate.setFullYear(futureDate.getFullYear() + 1); | ||
| return futureDate.toISOString(); | ||
| } | ||
| } |
59 changes: 59 additions & 0 deletions
59
src/features/ai-agents/application/services/transaction-agent.service.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import { AgentResponse } from "../../domain/entities/voice-command.entity"; | ||
|
|
||
| export class TransactionAgentService { | ||
| async processTransaction(extractedData: Record<string, any>, userId: number): Promise<AgentResponse> { | ||
| try { | ||
| const processedData = this.normalizeTransactionData(extractedData, userId); | ||
|
|
||
| return { | ||
| success: true, | ||
| intent: 'CREATE_TRANSACTION', | ||
| extractedData: processedData, | ||
| confidence: 0.9, | ||
| message: `He identificado una transacción: ${processedData.type === 'EXPENSE' ? 'Gasto' : 'Ingreso'} de $${processedData.amount} en ${processedData.description || 'Sin descripción'}` | ||
| }; | ||
| } catch (error) { | ||
| return { | ||
| success: false, | ||
| intent: 'CREATE_TRANSACTION', | ||
| extractedData: {}, | ||
| confidence: 0, | ||
| message: "No pude procesar los datos de la transacción" | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| private normalizeTransactionData(data: Record<string, any>, userId: number) { | ||
| return { | ||
| user_id: userId, | ||
| amount: this.parseAmount(data.amount), | ||
| type: this.determineTransactionType(data), | ||
| description: data.description || data.concept || '', | ||
| category_id: data.category_id || null, | ||
| payment_method_id: data.payment_method_id || null, | ||
| date: data.date || new Date().toISOString() | ||
| }; | ||
| } | ||
|
|
||
| private parseAmount(amount: any): number { | ||
| if (typeof amount === 'number') return amount; | ||
| if (typeof amount === 'string') { | ||
| const cleanAmount = amount.replace(/[^\d.,]/g, ''); | ||
| return parseFloat(cleanAmount.replace(',', '.')); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| private determineTransactionType(data: Record<string, any>): 'INCOME' | 'EXPENSE' { | ||
| const type = data.type?.toUpperCase(); | ||
| if (type === 'INCOME' || type === 'INGRESO') return 'INCOME'; | ||
| if (type === 'EXPENSE' || type === 'GASTO') return 'EXPENSE'; | ||
|
|
||
| const keywords = data.description?.toLowerCase() || ''; | ||
| if (keywords.includes('gané') || keywords.includes('recibí') || keywords.includes('ingreso')) { | ||
| return 'INCOME'; | ||
| } | ||
|
|
||
| return 'EXPENSE'; | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LangChain dependencies are added but not used in the codebase. Consider removing these unused dependencies or implement LangChain integration if intended.