Feat: Principio Memoria de Hilo Conversacional
This commit is contained in:
@@ -51,7 +51,7 @@ namespace ChatbotApi.Controllers
|
||||
private static readonly string _siteUrl = "https://www.eldia.com/";
|
||||
private static readonly string[] PrefijosAQuitar = { "VIDEO.- ", "VIDEO. ", "FOTOS.- ", "FOTOS. " };
|
||||
const int OutTokens = 8192;
|
||||
|
||||
|
||||
public ChatController(IConfiguration configuration, IMemoryCache memoryCache, IServiceProvider serviceProvider, ILogger<ChatController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
@@ -64,7 +64,45 @@ namespace ChatbotApi.Controllers
|
||||
_apiUrl = $"{baseUrl}{apiKey}";
|
||||
}
|
||||
|
||||
private async Task<IntentType> GetIntentAsync(string userMessage, string? activeArticleContent)
|
||||
private async Task<string> UpdateConversationSummaryAsync(string? oldSummary, string userMessage, string botResponse)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(oldSummary))
|
||||
{
|
||||
oldSummary = "Esta es una nueva conversación.";
|
||||
}
|
||||
|
||||
var promptBuilder = new StringBuilder();
|
||||
promptBuilder.AppendLine("Tu tarea es actualizar un resumen de conversación. Basado en el RESUMEN ANTERIOR y el ÚLTIMO INTERCAMBIO, crea un nuevo resumen conciso. Mantén solo los puntos clave y el tema principal de la conversación.");
|
||||
promptBuilder.AppendLine("\n--- RESUMEN ANTERIOR ---");
|
||||
promptBuilder.AppendLine(oldSummary);
|
||||
promptBuilder.AppendLine("\n--- ÚLTIMO INTERCAMBIO ---");
|
||||
promptBuilder.AppendLine($"Usuario: \"{userMessage}\"");
|
||||
promptBuilder.AppendLine($"Bot: \"{new string(botResponse.Take(300).ToArray())}...\"");
|
||||
promptBuilder.AppendLine("\n--- NUEVO RESUMEN CONCISO ---");
|
||||
|
||||
var finalPrompt = promptBuilder.ToString();
|
||||
var requestData = new GeminiRequest { Contents = new[] { new Content { Parts = new[] { new Part { Text = finalPrompt } } } } };
|
||||
var nonStreamingApiUrl = _apiUrl.Replace(":streamGenerateContent?alt=sse&", ":generateContent?");
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PostAsJsonAsync(nonStreamingApiUrl, requestData);
|
||||
if (!response.IsSuccessStatusCode) return oldSummary ?? "";
|
||||
|
||||
var geminiResponse = await response.Content.ReadFromJsonAsync<GeminiResponse>();
|
||||
var newSummary = geminiResponse?.Candidates?.FirstOrDefault()?.Content?.Parts?.FirstOrDefault()?.Text?.Trim();
|
||||
|
||||
_logger.LogInformation("Resumen de conversación actualizado: '{NewSummary}'", newSummary);
|
||||
return newSummary ?? oldSummary ?? "";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Excepción en UpdateConversationSummaryAsync. Se mantendrá el resumen anterior.");
|
||||
return oldSummary ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IntentType> GetIntentAsync(string userMessage, string? activeArticleContent, string? conversationSummary)
|
||||
{
|
||||
var promptBuilder = new StringBuilder();
|
||||
promptBuilder.AppendLine("Tu tarea es actuar como un router de intenciones. Basado en la PREGUNTA DEL USUARIO, decide qué herramienta es la más apropiada para encontrar la respuesta. Responde única y exclusivamente con una de las siguientes tres opciones: [ARTICULO_ACTUAL], [BASE_DE_DATOS], [NOTICIAS_PORTADA].");
|
||||
@@ -73,6 +111,12 @@ namespace ChatbotApi.Controllers
|
||||
promptBuilder.AppendLine("[BASE_DE_DATOS]: Úsala si la pregunta es sobre información específica y general del diario, como datos de contacto (teléfono, dirección), publicidad o suscripciones.");
|
||||
promptBuilder.AppendLine("[NOTICIAS_PORTADA]: Úsala para preguntas generales sobre noticias actuales, eventos, o si ninguna de las otras herramientas parece adecuada.");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(conversationSummary))
|
||||
{
|
||||
promptBuilder.AppendLine("\n--- RESUMEN DE LA CONVERSACIÓN ACTUAL ---");
|
||||
promptBuilder.AppendLine(conversationSummary);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(activeArticleContent))
|
||||
{
|
||||
promptBuilder.AppendLine("\n--- CONVERSACIÓN ACTUAL (Contexto del artículo) ---");
|
||||
@@ -133,8 +177,9 @@ namespace ChatbotApi.Controllers
|
||||
{
|
||||
articleContext = await GetArticleContentAsync(request.ContextUrl);
|
||||
}
|
||||
|
||||
intent = await GetIntentAsync(userMessage, articleContext);
|
||||
|
||||
// Le pasamos el resumen al router de intenciones
|
||||
intent = await GetIntentAsync(userMessage, articleContext, request.ConversationSummary);
|
||||
|
||||
switch (intent)
|
||||
{
|
||||
@@ -147,7 +192,7 @@ namespace ChatbotApi.Controllers
|
||||
case IntentType.Database:
|
||||
_logger.LogInformation("Ejecutando intención: Base de Datos.");
|
||||
var knowledgeBase = await GetKnowledgeAsync();
|
||||
context = await FindBestDbItemAsync(userMessage, knowledgeBase) ?? "No se encontró información relevante en la base de datos.";
|
||||
context = await FindBestDbItemAsync(userMessage, request.ConversationSummary, knowledgeBase) ?? "No se encontró información relevante en la base de datos.";
|
||||
promptInstructions = "Tu tarea es responder la 'PREGUNTA DEL USUARIO' basándote ESTRICTA Y ÚNICAMENTE en el 'CONTEXTO' que contiene información específica (contacto, suscripciones, etc.).";
|
||||
break;
|
||||
|
||||
@@ -174,6 +219,8 @@ namespace ChatbotApi.Controllers
|
||||
}
|
||||
|
||||
Stream? responseStream = null;
|
||||
var fullBotReply = new StringBuilder();
|
||||
|
||||
try
|
||||
{
|
||||
var promptBuilder = new StringBuilder();
|
||||
@@ -222,7 +269,6 @@ namespace ChatbotApi.Controllers
|
||||
yield break;
|
||||
}
|
||||
|
||||
var fullBotReply = new StringBuilder();
|
||||
if (responseStream != null)
|
||||
{
|
||||
await using (responseStream)
|
||||
@@ -258,7 +304,14 @@ namespace ChatbotApi.Controllers
|
||||
|
||||
if (fullBotReply.Length > 0)
|
||||
{
|
||||
// Guardamos el log de la conversación como antes
|
||||
await SaveConversationLogAsync(userMessage, fullBotReply.ToString());
|
||||
|
||||
// Creamos el nuevo resumen
|
||||
var newSummary = await UpdateConversationSummaryAsync(request.ConversationSummary, userMessage, fullBotReply.ToString());
|
||||
|
||||
// Enviamos el nuevo resumen al frontend como el último mensaje del stream
|
||||
yield return $"SUMMARY::{newSummary}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,14 +338,22 @@ namespace ChatbotApi.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string?> FindBestDbItemAsync(string userMessage, Dictionary<string, string> knowledgeBase)
|
||||
private async Task<string?> FindBestDbItemAsync(string userMessage, string? conversationSummary, Dictionary<string, string> knowledgeBase)
|
||||
{
|
||||
if (knowledgeBase == null || !knowledgeBase.Any()) return null;
|
||||
|
||||
var availableKeys = string.Join(", ", knowledgeBase.Keys);
|
||||
|
||||
var promptBuilder = new StringBuilder();
|
||||
promptBuilder.AppendLine("Tu tarea es actuar como un buscador semántico. Basado en la PREGUNTA DEL USUARIO, elige la CLAVE más relevante de la LISTA DE CLAVES DISPONIBLES. Responde única y exclusivamente con la clave que elijas.");
|
||||
promptBuilder.AppendLine("Tu tarea es actuar como un buscador semántico. Usa el RESUMEN para entender el contexto de la conversación. Basado en la PREGUNTA DEL USUARIO, elige la CLAVE más relevante de la lista. Responde única y exclusivamente con la clave que elijas.");
|
||||
|
||||
// Añadimos el resumen al prompt del buscador
|
||||
if (!string.IsNullOrWhiteSpace(conversationSummary))
|
||||
{
|
||||
promptBuilder.AppendLine("\n--- RESUMEN DE LA CONVERSACIÓN ---");
|
||||
promptBuilder.AppendLine(conversationSummary);
|
||||
}
|
||||
|
||||
promptBuilder.AppendLine("\n--- LISTA DE CLAVES DISPONIBLES ---");
|
||||
promptBuilder.AppendLine(availableKeys);
|
||||
promptBuilder.AppendLine("\n--- PREGUNTA DEL USUARIO ---");
|
||||
|
||||
@@ -6,4 +6,5 @@ public class ChatRequest
|
||||
[MaxLength(200)]
|
||||
public required string Message { get; set; }
|
||||
public string? ContextUrl { get; set; }
|
||||
public string? ConversationSummary { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user