feat(chat-frasa): acceso total a datos (13 herramientas con escopo por rol), streaming SSE, caché de embeddings, markdown y mantenimiento en Configuraciones
This commit is contained in:
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { HerramientaDef, MensajeApi, NanClient, RespuestaCruda } from './ai.service.js'
|
||||
import {
|
||||
HERRAMIENTAS,
|
||||
MAX_RONDAS_TOOLS,
|
||||
chat,
|
||||
construirSystemPrompt,
|
||||
crearEjecutoresHerramientas,
|
||||
@@ -169,8 +170,10 @@ describe('construirSystemPrompt', () => {
|
||||
{ titulo: 'Catálogo DIAGONAL', texto: 'Modelo 1R desde $2,850,000', score: 0.9 },
|
||||
{ titulo: 'Proceso de venta', texto: 'Apartado de $20,000', score: 0.8 },
|
||||
])
|
||||
expect(prompt).toContain('asistente interno de Frasa Desarrollos')
|
||||
expect(prompt).toContain('Responde SOLO con base en el CONTEXTO')
|
||||
expect(prompt).toContain('Chat Frasa')
|
||||
expect(prompt).toContain('Nunca inventes cifras')
|
||||
expect(prompt).toContain('buscar_contactos')
|
||||
expect(prompt).toContain('estadisticas')
|
||||
expect(prompt).toContain('[1] Fuente: Catálogo DIAGONAL')
|
||||
expect(prompt).toContain('[2] Fuente: Proceso de venta')
|
||||
expect(prompt).toContain('Modelo 1R desde $2,850,000')
|
||||
@@ -631,13 +634,15 @@ describe('chat con herramientas (tool calling)', () => {
|
||||
expect(roles).toEqual(['user', 'assistant'])
|
||||
})
|
||||
|
||||
it('máx 2 rondas de tools: la llamada de cierre va sin herramientas', async () => {
|
||||
it('agota las rondas de tools: la última llamada va sin herramientas', async () => {
|
||||
mocksPrismaChat()
|
||||
const toolCall = (id: string): RespuestaCruda => ({
|
||||
contenido: null,
|
||||
toolCalls: [{ id, nombre: 'buscar_unidad', argumentosJson: '{}' }],
|
||||
})
|
||||
const client = clienteConTools([toolCall('c1'), toolCall('c2'), { contenido: 'cierre forzado', toolCalls: [] }])
|
||||
// El cliente pide herramientas SIEMPRE; el loop debe cortar en MAX_RONDAS_TOOLS
|
||||
const respuestas = Array.from({ length: MAX_RONDAS_TOOLS + 1 }, (_, i) => toolCall(`c${i + 1}`))
|
||||
const client = clienteConTools(respuestas)
|
||||
const ejecutores = {
|
||||
async cotizar_unidad() {
|
||||
return {}
|
||||
@@ -659,11 +664,36 @@ describe('chat con herramientas (tool calling)', () => {
|
||||
},
|
||||
}
|
||||
|
||||
const r = await chat(null, 'búscame algo y cotízalo todo', client, { ejecutores })
|
||||
// La ronda de cierre (MAX_RONDAS_TOOLS) va sin herramientas y el cliente
|
||||
// devuelve texto plano ahí (la última respuesta simulada es un tool call,
|
||||
// así que el loop lanza al no poder cerrar con texto)
|
||||
await expect(chat(null, 'búscame algo y cotízalo todo', client, { ejecutores })).rejects.toThrow(
|
||||
/no cerró la respuesta/,
|
||||
)
|
||||
expect(client.llamadas).toHaveLength(MAX_RONDAS_TOOLS + 1)
|
||||
expect(client.llamadas[MAX_RONDAS_TOOLS]!.herramientas).toEqual([])
|
||||
})
|
||||
|
||||
expect(client.llamadas).toHaveLength(3) // 2 rondas de tools + cierre
|
||||
expect(client.llamadas[2]!.herramientas).toEqual([])
|
||||
expect(r.respuesta).toBe('cierre forzado')
|
||||
it('cierra con texto cuando el modelo deja de pedir herramientas', async () => {
|
||||
mocksPrismaChat()
|
||||
const toolCall = (id: string): RespuestaCruda => ({
|
||||
contenido: null,
|
||||
toolCalls: [{ id, nombre: 'buscar_unidad', argumentosJson: '{}' }],
|
||||
})
|
||||
const client = clienteConTools([toolCall('c1'), toolCall('c2'), { contenido: 'cierre', toolCalls: [] }])
|
||||
const r = await chat(null, 'búscame algo', client, {
|
||||
ejecutores: {
|
||||
cotizar_unidad: async () => ({}),
|
||||
buscar_unidad: async () => [{ codigo: 'SOLANA-6' }],
|
||||
buscar_leads: async () => [],
|
||||
ver_cliente: async () => ({}),
|
||||
ver_oportunidades: async () => [],
|
||||
ver_cobranza: async () => ({}),
|
||||
},
|
||||
})
|
||||
expect(client.llamadas).toHaveLength(3)
|
||||
expect(r.respuesta).toBe('cierre')
|
||||
expect(r.herramientasUsadas).toEqual(['buscar_unidad', 'buscar_unidad'])
|
||||
})
|
||||
|
||||
it('respuesta directa sin tools: una sola llamada', async () => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user