feat: version 4.1.0 -- Consumo de IA por usuario en Configuracion
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+22
-1
@@ -1,12 +1,31 @@
|
||||
import { getAiConfigInternal } from '../modules/settings/service.js';
|
||||
import { HttpError } from './http.js';
|
||||
import { pool } from '../config/db.js';
|
||||
|
||||
// USD por millon de tokens (entrada, salida), para estimar el costo de cada llamada.
|
||||
const PRICES = { 'claude-haiku-5-5': [0.10, 0.50], 'claude-sonnet-5-5': [2.00, 10.00] };
|
||||
|
||||
// Registra el consumo; nunca debe romper la respuesta al usuario.
|
||||
async function recordUsage(usage, model, tokens) {
|
||||
if (!usage) return;
|
||||
const [inPrice, outPrice] = PRICES[model] || [0, 0];
|
||||
const input = tokens?.input_tokens || 0;
|
||||
const output = tokens?.output_tokens || 0;
|
||||
const cost = (input * inPrice + output * outPrice) / 1e6;
|
||||
try {
|
||||
await pool.query('INSERT INTO ai_usage (user_id, action, model, input_tokens, output_tokens, cost_usd) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[usage.userId || null, usage.action, model, input, output, cost]);
|
||||
} catch (err) {
|
||||
console.error('[ai] no se pudo registrar el consumo:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
const API_URL = 'https://api.anthropic.com/v1/messages';
|
||||
const TIMEOUT_MS = 60_000;
|
||||
|
||||
// Llama a la API de Mensajes forzando una herramienta, para recibir JSON con el esquema pedido
|
||||
// (la IA solo sugiere: quien llama valida y el usuario revisa antes de guardar).
|
||||
export async function askStructured({ system, prompt, toolName, schema, maxTokens = 2000, configOverride }) {
|
||||
export async function askStructured({ system, prompt, toolName, schema, maxTokens = 2000, configOverride, usage }) {
|
||||
const config = configOverride || await getAiConfigInternal();
|
||||
if (!config) throw new HttpError(400, 'La integracion con IA no esta configurada');
|
||||
const controller = new AbortController();
|
||||
@@ -40,6 +59,8 @@ export async function askStructured({ system, prompt, toolName, schema, maxToken
|
||||
throw new HttpError(502, `Servicio de IA: ${detail}`);
|
||||
}
|
||||
// Consumo real de cada llamada (para estimar costos: ver precios del modelo en la consola).
|
||||
// Se cobra aunque la respuesta venga cortada: se registra antes de validar.
|
||||
await recordUsage(usage, config.model, body?.usage);
|
||||
console.log(`[ai] ${config.model} tokens entrada=${body?.usage?.input_tokens} salida=${body?.usage?.output_tokens} fin=${body?.stop_reason}`);
|
||||
// Respuesta cortada por el tope de tokens: el JSON de la herramienta llega incompleto.
|
||||
if (body?.stop_reason === 'max_tokens') throw new HttpError(502, 'La respuesta de la IA fue demasiado larga y quedo incompleta. Intenta con un objetivo mas acotado.');
|
||||
|
||||
Reference in New Issue
Block a user