feat: implement dynamic knowledge graph updates and state management services
This commit is contained in:
@@ -24,9 +24,11 @@ public static class DependencyInjection
|
||||
services.Configure<AiSettings>(configuration.GetSection(AiSettings.SectionName));
|
||||
var aiSettings = configuration.GetSection(AiSettings.SectionName).Get<AiSettings>() ?? new AiSettings();
|
||||
|
||||
Console.WriteLine($"[Infrastructure] AI Configured: Model={aiSettings.Model}, KeyPresent={!string.IsNullOrWhiteSpace(aiSettings.ApiKey) && aiSettings.ApiKey != "PLACEHOLDER"}");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(aiSettings.ApiKey) || aiSettings.ApiKey == "PLACEHOLDER")
|
||||
{
|
||||
// We don't throw here to allow the app to start, but services using AI will fail gracefully
|
||||
Console.WriteLine("[Infrastructure] WARNING: AI API Key is missing or placeholder!");
|
||||
}
|
||||
|
||||
services.AddResiliencePipeline("ai-retry", builder =>
|
||||
|
||||
@@ -41,12 +41,15 @@ public class KnowledgeService : IKnowledgeService
|
||||
return Result.Fail("Input text is empty.");
|
||||
}
|
||||
|
||||
Console.WriteLine($"[KnowledgeService] Starting extraction for text: {text.Substring(0, Math.Min(text.Length, 50))}...");
|
||||
|
||||
// Normalize text to ensure consistent hashing and reduce token noise
|
||||
var normalizedText = ContentHasher.Normalize(text);
|
||||
|
||||
// Phase 4: Request Pre-processing (Token Saving)
|
||||
if (normalizedText.Length > _settings.MaxInputLength)
|
||||
{
|
||||
Console.WriteLine($"[KnowledgeService] Error: Input too long ({normalizedText.Length} > {_settings.MaxInputLength})");
|
||||
return Result.Fail($"Input text is too long ({normalizedText.Length} characters after normalization). Max allowed is {_settings.MaxInputLength}.");
|
||||
}
|
||||
|
||||
@@ -62,6 +65,7 @@ public class KnowledgeService : IKnowledgeService
|
||||
|
||||
if (cached != null)
|
||||
{
|
||||
Console.WriteLine($"[KnowledgeService] Cache hit for hash: {hash}");
|
||||
try
|
||||
{
|
||||
var packet = JsonSerializer.Deserialize<KnowledgePacket>(cached.JsonData);
|
||||
@@ -70,18 +74,19 @@ public class KnowledgeService : IKnowledgeService
|
||||
return Result.Ok(packet);
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
catch (JsonException ex)
|
||||
{
|
||||
// If deserialization fails, we proceed to call the AI
|
||||
Console.WriteLine($"[KnowledgeService] Cache deserialization error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Call AI Client
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"[KnowledgeService] Calling Gemini AI with Model: {_settings.Model}...");
|
||||
var options = new ChatOptions
|
||||
{
|
||||
ResponseFormat = ChatResponseFormat.Json,
|
||||
// ResponseFormat = ChatResponseFormat.Json, // Disabled due to GeminiMappingException in current library version
|
||||
Temperature = (float)_settings.Temperature,
|
||||
MaxOutputTokens = _settings.MaxOutputTokens
|
||||
};
|
||||
@@ -96,19 +101,24 @@ public class KnowledgeService : IKnowledgeService
|
||||
var jsonResponse = response.Text;
|
||||
if (string.IsNullOrWhiteSpace(jsonResponse))
|
||||
{
|
||||
Console.WriteLine("[KnowledgeService] AI returned empty response.");
|
||||
return Result.Fail("AI returned an empty response.");
|
||||
}
|
||||
|
||||
Console.WriteLine($"[KnowledgeService] AI Response received ({jsonResponse.Length} chars).");
|
||||
|
||||
// Cleanup potential markdown if Gemini still adds it despite options
|
||||
jsonResponse = jsonResponse.Replace("```json", "").Replace("```", "").Trim();
|
||||
|
||||
var knowledgePacket = JsonSerializer.Deserialize<KnowledgePacket>(jsonResponse);
|
||||
if (knowledgePacket == null)
|
||||
{
|
||||
Console.WriteLine("[KnowledgeService] Failed to deserialize JSON response.");
|
||||
return Result.Fail("Failed to deserialize AI response.");
|
||||
}
|
||||
|
||||
// 3. Save to Cache
|
||||
Console.WriteLine("[KnowledgeService] Saving result to cache...");
|
||||
var cacheEntry = new SemanticKnowledgeCache
|
||||
{
|
||||
ContentHash = hash,
|
||||
@@ -118,7 +128,6 @@ public class KnowledgeService : IKnowledgeService
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
// Handle potential race condition if multiple requests for same text arrive
|
||||
if (cached == null)
|
||||
{
|
||||
_dbContext.SemanticKnowledgeCache.Add(cacheEntry);
|
||||
@@ -130,12 +139,34 @@ public class KnowledgeService : IKnowledgeService
|
||||
}
|
||||
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
Console.WriteLine("[KnowledgeService] Extraction successful.");
|
||||
|
||||
return Result.Ok(knowledgePacket);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[KnowledgeService] CRITICAL ERROR: {ex.GetType().Name}: {ex.Message}");
|
||||
if (ex.InnerException != null)
|
||||
Console.WriteLine($"[KnowledgeService] Inner Error: {ex.InnerException.Message}");
|
||||
|
||||
return Result.Fail(new Error("Failed to extract knowledge from AI").CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result> ClearCacheAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine("[KnowledgeService] Clearing SemanticKnowledgeCache...");
|
||||
_dbContext.SemanticKnowledgeCache.RemoveRange(_dbContext.SemanticKnowledgeCache);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
Console.WriteLine("[KnowledgeService] Cache cleared successfully.");
|
||||
return Result.Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[KnowledgeService] Error clearing cache: {ex.Message}");
|
||||
return Result.Fail($"Failed to clear cache: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@ namespace NexusReader.Infrastructure.Services;
|
||||
public static class PromptRegistry
|
||||
{
|
||||
public const string KnowledgeExtractionSystemPrompt =
|
||||
"You are an expert educator. Analyze the provided text to extract key concepts and generate relevant quizzes. " +
|
||||
"You are an expert educator. Analyze the provided text to extract key concepts, generate relevant quizzes, and construct a knowledge graph. " +
|
||||
"CRITICAL: Return ONLY a minified JSON object. Do NOT include markdown formatting like ```json or ```. Do NOT include explanations. " +
|
||||
"Schema: { \"concepts\": [ { \"title\": \"string\", \"description\": \"string\" } ], \"quizzes\": [ { \"question\": \"string\", \"options\": [ \"string\" ], \"correct_index\": 0 } ] }.";
|
||||
"Schema: { " +
|
||||
"\"concepts\": [ { \"title\": \"string\", \"description\": \"string\" } ], " +
|
||||
"\"quizzes\": [ { \"question\": \"string\", \"options\": [ \"string\" ], \"correct_index\": 0 } ], " +
|
||||
"\"graph\": { \"nodes\": [ { \"id\": \"string\", \"label\": \"string\", \"group\": \"concept\" } ], \"links\": [ { \"source\": \"string\", \"target\": \"string\", \"value\": 1 } ] } " +
|
||||
"}.";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user