@@ -27,7 +27,6 @@ public sealed class AnthropicService(
2727{
2828 private readonly MaINSettings _settings = settings ?? throw new ArgumentNullException ( nameof ( settings ) ) ;
2929
30- private static readonly HashSet < string > AnthropicImageExtensions = [ ".jpg" , ".jpeg" , ".png" , ".gif" , ".webp" ] ;
3130 private static readonly ConcurrentDictionary < string , List < ChatMessage > > SessionCache = new ( ) ;
3231
3332 private const string CompletionsUrl = ServiceConstants . ApiUrls . AnthropicChatMessages ;
@@ -70,16 +69,14 @@ private void ValidateApiKey()
7069 if ( ! chat . Messages . Any ( ) )
7170 return null ;
7271
73- var apiKey = GetApiKey ( ) ;
74-
7572 var lastMessage = chat . Messages . Last ( ) ;
76- await ExtractImageFromFiles ( lastMessage ) ;
73+ await ChatHelper . ExtractImageFromFiles ( lastMessage ) ;
7774
7875 var conversation = GetOrCreateConversation ( chat , options . CreateSession ) ;
7976 var resultBuilder = new StringBuilder ( ) ;
8077 var tokens = new List < LLMTokenValue > ( ) ;
8178
82- if ( HasFiles ( lastMessage ) )
79+ if ( ChatHelper . HasFiles ( lastMessage ) )
8380 {
8481 var result = ChatHelper . ExtractMemoryOptions ( lastMessage ) ;
8582 var memoryResult = await AskMemory ( chat , result , options , cancellationToken ) ;
@@ -103,7 +100,6 @@ await options.TokenCallback(new LLMTokenValue()
103100 return await ProcessWithToolsAsync (
104101 chat ,
105102 conversation ,
106- apiKey ,
107103 tokens ,
108104 options ,
109105 cancellationToken ) ;
@@ -114,7 +110,6 @@ await options.TokenCallback(new LLMTokenValue()
114110 await ProcessStreamingChatAsync (
115111 chat ,
116112 conversation ,
117- apiKey ,
118113 tokens ,
119114 resultBuilder ,
120115 options . TokenCallback ,
@@ -126,7 +121,6 @@ await ProcessStreamingChatAsync(
126121 await ProcessNonStreamingChatAsync (
127122 chat ,
128123 conversation ,
129- apiKey ,
130124 resultBuilder ,
131125 cancellationToken ) ;
132126 }
@@ -150,7 +144,6 @@ await notificationService.DispatchNotification(
150144 private async Task < ChatResult > ProcessWithToolsAsync (
151145 Chat chat ,
152146 List < ChatMessage > conversation ,
153- string apiKey ,
154147 List < LLMTokenValue > tokens ,
155148 ChatRequestOptions options ,
156149 CancellationToken cancellationToken )
@@ -183,7 +176,6 @@ await notificationService.DispatchNotification(
183176 currentToolUses = await ProcessStreamingChatWithToolsAsync (
184177 chat ,
185178 conversation ,
186- apiKey ,
187179 tokens ,
188180 resultBuilder ,
189181 options ,
@@ -194,7 +186,6 @@ await notificationService.DispatchNotification(
194186 currentToolUses = await ProcessNonStreamingChatWithToolsAsync (
195187 chat ,
196188 conversation ,
197- apiKey ,
198189 resultBuilder ,
199190 cancellationToken ) ;
200191 }
@@ -319,15 +310,14 @@ await notificationService.DispatchNotification(
319310 private async Task < List < AnthropicToolUse > ? > ProcessStreamingChatWithToolsAsync (
320311 Chat chat ,
321312 List < ChatMessage > conversation ,
322- string apiKey ,
323313 List < LLMTokenValue > tokens ,
324314 StringBuilder resultBuilder ,
325315 ChatRequestOptions options ,
326316 CancellationToken cancellationToken )
327317 {
328318 var httpClient = CreateAnthropicHttpClient ( ) ;
329319
330- var requestBody = BuildAnthropicRequestBody ( chat , conversation , true ) ;
320+ var requestBody = await BuildAnthropicRequestBody ( chat , conversation , true ) ;
331321 var requestJson = JsonSerializer . Serialize ( requestBody ) ;
332322 var content = new StringContent ( requestJson , Encoding . UTF8 , "application/json" ) ;
333323
@@ -464,18 +454,17 @@ private async Task HandleApiError(HttpResponseMessage response, CancellationToke
464454 private async Task < List < AnthropicToolUse > ? > ProcessNonStreamingChatWithToolsAsync (
465455 Chat chat ,
466456 List < ChatMessage > conversation ,
467- string apiKey ,
468457 StringBuilder resultBuilder ,
469458 CancellationToken cancellationToken )
470459 {
471460 var httpClient = CreateAnthropicHttpClient ( ) ;
472461
473- var requestBody = BuildAnthropicRequestBody ( chat , conversation , false ) ;
462+ var requestBody = await BuildAnthropicRequestBody ( chat , conversation , false ) ;
474463 var requestJson = JsonSerializer . Serialize ( requestBody ) ;
475464 var content = new StringContent ( requestJson , Encoding . UTF8 , "application/json" ) ;
476465
477466 using var response = await httpClient . PostAsync ( CompletionsUrl , content , cancellationToken ) ;
478-
467+
479468 if ( ! response . IsSuccessStatusCode )
480469 {
481470 await HandleApiError ( response , cancellationToken ) ;
@@ -510,7 +499,7 @@ private async Task HandleApiError(HttpResponseMessage response, CancellationToke
510499 return toolUses . Any ( ) ? toolUses : null ;
511500 }
512501
513- private object BuildAnthropicRequestBody ( Chat chat , List < ChatMessage > conversation , bool stream )
502+ private async Task < Dictionary < string , object > > BuildAnthropicRequestBody ( Chat chat , List < ChatMessage > conversation , bool stream )
514503 {
515504 var anthParams = chat . BackendParams as AnthropicInferenceParams ;
516505
@@ -519,7 +508,7 @@ private object BuildAnthropicRequestBody(Chat chat, List<ChatMessage> conversati
519508 [ "model" ] = chat . ModelId ,
520509 [ "max_tokens" ] = anthParams ? . MaxTokens ?? 4096 ,
521510 [ "stream" ] = stream ,
522- [ "messages" ] = BuildAnthropicMessages ( conversation )
511+ [ "messages" ] = await ChatHelper . BuildMessagesArray ( conversation , chat , ImageType . AsBase64 )
523512 } ;
524513
525514 if ( anthParams != null )
@@ -562,40 +551,6 @@ private object BuildAnthropicRequestBody(Chat chat, List<ChatMessage> conversati
562551 return requestBody ;
563552 }
564553
565- private List < object > BuildAnthropicMessages ( List < ChatMessage > conversation )
566- {
567- var messages = new List < object > ( ) ;
568-
569- foreach ( var msg in conversation )
570- {
571- if ( msg . Role . Equals ( "system" , StringComparison . OrdinalIgnoreCase ) )
572- continue ;
573-
574- object content ;
575-
576- if ( msg . Content is string textContent )
577- {
578- content = textContent ;
579- }
580- else if ( msg . Content is List < object > contentBlocks )
581- {
582- content = contentBlocks ;
583- }
584- else
585- {
586- content = msg . Content ;
587- }
588-
589- messages . Add ( new
590- {
591- role = msg . Role ,
592- content = content
593- } ) ;
594- }
595-
596- return messages ;
597- }
598-
599554 public async Task < ChatResult ? > AskMemory ( Chat chat , ChatMemoryOptions memoryOptions , ChatRequestOptions requestOptions ,
600555 CancellationToken cancellationToken = default )
601556 {
@@ -639,7 +594,7 @@ private List<ChatMessage> GetOrCreateConversation(Chat chat, bool createSession)
639594 conversation = new List < ChatMessage > ( ) ;
640595 }
641596
642- OpenAiCompatibleService . MergeMessages ( conversation , chat . Messages ) ;
597+ ChatHelper . MergeMessages ( conversation , chat . Messages ) ;
643598 return conversation ;
644599 }
645600
@@ -651,51 +606,9 @@ private void UpdateSessionCache(string chatId, string assistantResponse, bool cr
651606 }
652607 }
653608
654- private static bool HasFiles ( Message message )
655- {
656- return message . Files != null && message . Files . Count > 0 ;
657- }
658-
659- private static async Task ExtractImageFromFiles ( Message message )
660- {
661- if ( message . Files == null || message . Files . Count == 0 )
662- return ;
663-
664- var imageFiles = message . Files
665- . Where ( f => AnthropicImageExtensions . Contains ( f . Extension . ToLowerInvariant ( ) ) )
666- . ToList ( ) ;
667-
668- if ( imageFiles . Count == 0 )
669- return ;
670-
671- var imageBytesList = new List < byte [ ] > ( ) ;
672- foreach ( var imageFile in imageFiles )
673- {
674- if ( imageFile . StreamContent != null )
675- {
676- using var ms = new MemoryStream ( ) ;
677- imageFile . StreamContent . Position = 0 ;
678- await imageFile . StreamContent . CopyToAsync ( ms ) ;
679- imageBytesList . Add ( ms . ToArray ( ) ) ;
680- }
681- else if ( imageFile . Path != null )
682- {
683- imageBytesList . Add ( await File . ReadAllBytesAsync ( imageFile . Path ) ) ;
684- }
685-
686- message . Files . Remove ( imageFile ) ;
687- }
688-
689- message . Images = imageBytesList ;
690-
691- if ( message . Files . Count == 0 )
692- message . Files = null ;
693- }
694-
695609 private async Task ProcessStreamingChatAsync (
696610 Chat chat ,
697611 List < ChatMessage > conversation ,
698- string apiKey ,
699612 List < LLMTokenValue > tokens ,
700613 StringBuilder resultBuilder ,
701614 Func < LLMTokenValue , Task > ? tokenCallback ,
@@ -704,29 +617,7 @@ private async Task ProcessStreamingChatAsync(
704617 {
705618 var httpClient = CreateAnthropicHttpClient ( ) ;
706619
707- var anthParams2 = chat . BackendParams as AnthropicInferenceParams ;
708- var requestBody = new Dictionary < string , object >
709- {
710- [ "model" ] = chat . ModelId ,
711- [ "max_tokens" ] = anthParams2 ? . MaxTokens ?? 4096 ,
712- [ "stream" ] = true ,
713- [ "system" ] = chat . InferenceGrammar is not null
714- ? $ "Respond only using the following grammar format: \n { chat . InferenceGrammar . Value } \n . Do not add explanations, code tags, or any extra content."
715- : "" ,
716- [ "messages" ] = await OpenAiCompatibleService . BuildMessagesArray ( conversation , chat , ImageType . AsBase64 )
717- } ;
718- if ( anthParams2 != null )
719- {
720- if ( anthParams2 . Temperature . HasValue ) requestBody [ "temperature" ] = anthParams2 . Temperature . Value ;
721- if ( anthParams2 . TopP . HasValue ) requestBody [ "top_p" ] = anthParams2 . TopP . Value ;
722- if ( anthParams2 . TopK . HasValue ) requestBody [ "top_k" ] = anthParams2 . TopK . Value ;
723- }
724- if ( chat . BackendParams ? . AdditionalParams != null )
725- {
726- foreach ( var ( key , value ) in chat . BackendParams . AdditionalParams )
727- requestBody [ key ] = value ;
728- }
729-
620+ var requestBody = await BuildAnthropicRequestBody ( chat , conversation , true ) ;
730621 var requestJson = JsonSerializer . Serialize ( requestBody ) ;
731622 var content = new StringContent ( requestJson , Encoding . UTF8 , "application/json" ) ;
732623
@@ -798,35 +689,12 @@ await notificationService.DispatchNotification(
798689 private async Task ProcessNonStreamingChatAsync (
799690 Chat chat ,
800691 List < ChatMessage > conversation ,
801- string apiKey ,
802692 StringBuilder resultBuilder ,
803693 CancellationToken cancellationToken )
804694 {
805695 var httpClient = CreateAnthropicHttpClient ( ) ;
806696
807- var anthParams3 = chat . BackendParams as AnthropicInferenceParams ;
808- var requestBody = new Dictionary < string , object >
809- {
810- [ "model" ] = chat . ModelId ,
811- [ "max_tokens" ] = anthParams3 ? . MaxTokens ?? 4096 ,
812- [ "stream" ] = false ,
813- [ "system" ] = chat . InferenceGrammar is not null
814- ? $ "Respond only using the following grammar format: \n { chat . InferenceGrammar . Value } \n . Do not add explanations, code tags, or any extra content."
815- : "" ,
816- [ "messages" ] = await OpenAiCompatibleService . BuildMessagesArray ( conversation , chat , ImageType . AsBase64 )
817- } ;
818- if ( anthParams3 != null )
819- {
820- if ( anthParams3 . Temperature . HasValue ) requestBody [ "temperature" ] = anthParams3 . Temperature . Value ;
821- if ( anthParams3 . TopP . HasValue ) requestBody [ "top_p" ] = anthParams3 . TopP . Value ;
822- if ( anthParams3 . TopK . HasValue ) requestBody [ "top_k" ] = anthParams3 . TopK . Value ;
823- }
824- if ( chat . BackendParams ? . AdditionalParams != null )
825- {
826- foreach ( var ( key , value ) in chat . BackendParams . AdditionalParams )
827- requestBody [ key ] = value ;
828- }
829-
697+ var requestBody = await BuildAnthropicRequestBody ( chat , conversation , false ) ;
830698 var requestJson = JsonSerializer . Serialize ( requestBody ) ;
831699 var content = new StringContent ( requestJson , Encoding . UTF8 , "application/json" ) ;
832700
0 commit comments