1- using System . Collections . Concurrent ;
21using System . Linq ;
32using System . Net . Http . Headers ;
43using System . Text ;
54using System . Text . Json ;
5+ using Microsoft . Extensions . Caching . Memory ;
66using Microsoft . Extensions . Logging ;
77using Microsoft . Extensions . Options ;
88using ModelContextProtocol . Client ;
99using OpenAI . Responses ;
1010
1111namespace EssentialCSharp . Chat . Common . Services ;
1212
13- public partial class LocalChatService : IChatCompletionService
13+ public partial class LocalChatService : IChatCompletionService , IDisposable
1414{
1515 private const int MaxConversationMessages = 20 ;
16+ private const int MaxConversationEntries = 500 ;
17+ private static readonly TimeSpan _HistoryTtl = TimeSpan . FromMinutes ( 30 ) ;
18+
1619 private readonly AIOptions _Options ;
1720 private readonly IHttpClientFactory _HttpClientFactory ;
1821 private readonly ILogger < LocalChatService > _Logger ;
19- private readonly ConcurrentDictionary < string , List < LocalChatMessage > > _ConversationHistory = new ( ) ;
22+ private readonly MemoryCache _ConversationHistory = new ( new MemoryCacheOptions { SizeLimit = MaxConversationEntries } ) ;
23+ private static readonly MemoryCacheEntryOptions _HistoryEntryOptions = new MemoryCacheEntryOptions ( )
24+ . SetSlidingExpiration ( _HistoryTtl )
25+ . SetSize ( 1 ) ;
2026
2127 public bool IsAvailable => true ;
2228
@@ -27,6 +33,12 @@ public LocalChatService(IOptions<AIOptions> options, IHttpClientFactory httpClie
2733 _Logger = logger ;
2834 }
2935
36+ public void Dispose ( )
37+ {
38+ _ConversationHistory . Dispose ( ) ;
39+ GC . SuppressFinalize ( this ) ;
40+ }
41+
3042 public async Task < ( string response , string responseId ) > GetChatCompletion (
3143 string prompt ,
3244 string ? systemPrompt = null ,
@@ -77,7 +89,7 @@ public LocalChatService(IOptions<AIOptions> options, IHttpClientFactory httpClie
7789 var ( text , responseId ) = ParseResponse ( body ) ;
7890 history . Add ( new LocalChatMessage ( "user" , prompt ) ) ;
7991 history . Add ( new LocalChatMessage ( "assistant" , text ) ) ;
80- _ConversationHistory [ responseId ] = history . TakeLast ( MaxConversationMessages ) . ToList ( ) ;
92+ _ConversationHistory . Set ( responseId , history . TakeLast ( MaxConversationMessages ) . ToList ( ) , _HistoryEntryOptions ) ;
8193 return ( text , responseId ) ;
8294 }
8395 catch ( Exception ex ) when ( ex is JsonException || ex is InvalidOperationException || ex is NotSupportedException )
@@ -147,7 +159,7 @@ private List<LocalChatMessage> ResolveHistory(string? previousResponseId)
147159 if ( string . IsNullOrWhiteSpace ( previousResponseId ) )
148160 return [ ] ;
149161
150- return _ConversationHistory . TryGetValue ( previousResponseId , out var history )
162+ return _ConversationHistory . TryGetValue ( previousResponseId , out List < LocalChatMessage > ? history ) && history is not null
151163 ? [ .. history ]
152164 : [ ] ;
153165 }
0 commit comments