11using Anthropic ;
22using Anthropic . Core ;
3+ using Anthropic . Models . Messages ;
34using Microsoft . Extensions . AI ;
45using Microsoft . SemanticKernel . ChatCompletion ;
56
@@ -19,13 +20,15 @@ public sealed class AnthropicKernelMixin : KernelMixinBase
1920 /// </summary>
2021 public AnthropicKernelMixin ( CustomAssistant customAssistant , HttpClient httpClient ) : base ( customAssistant )
2122 {
22- var anthropicClient = new AnthropicClient ( new ClientOptions
23- {
24- ApiKey = ApiKey ,
25- HttpClient = httpClient ,
26- BaseUrl = Endpoint
27- } ) . AsIChatClient ( defaultModelId : ModelId ) ;
28- _client = new OptimizedChatClient ( customAssistant , anthropicClient ) ;
23+ var anthropicClient = new AnthropicClient (
24+ new ClientOptions
25+ {
26+ ApiKey = ApiKey ,
27+ HttpClient = httpClient ,
28+ BaseUrl = Endpoint ,
29+ Timeout = TimeSpan . FromSeconds ( customAssistant . RequestTimeoutSeconds )
30+ } ) . AsIChatClient ( ) ;
31+ _client = new OptimizedChatClient ( customAssistant , ModelId , anthropicClient ) ;
2932 ChatCompletionService = _client . AsChatCompletionService ( ) ;
3033 }
3134
@@ -34,17 +37,80 @@ public override void Dispose()
3437 _client . Dispose ( ) ;
3538 }
3639
37- private sealed class OptimizedChatClient ( CustomAssistant customAssistant , IChatClient anthropicClient ) : DelegatingChatClient ( anthropicClient )
40+ private sealed class OptimizedChatClient ( CustomAssistant customAssistant , string modelId , IChatClient anthropicClient )
41+ : DelegatingChatClient ( anthropicClient )
3842 {
3943 private void BuildOptions ( ref ChatOptions ? options )
4044 {
41- options ??= new ChatOptions ( ) ;
45+ var chatOptions = options ??= new ChatOptions ( ) ;
4246
4347 double ? temperature = customAssistant . Temperature . IsCustomValueSet ? customAssistant . Temperature . ActualValue : null ;
4448 double ? topP = customAssistant . TopP . IsCustomValueSet ? customAssistant . TopP . ActualValue : null ;
4549
4650 if ( temperature is not null ) options . Temperature = ( float ) temperature . Value ;
4751 if ( topP is not null ) options . TopP = ( float ) topP . Value ;
52+
53+ options . RawRepresentationFactory = OptionsRawRepresentationFactory ;
54+
55+ object ? OptionsRawRepresentationFactory ( IChatClient _ )
56+ {
57+ // TODO: fuck these shits
58+ var maxTokens = modelId switch
59+ {
60+ _ when modelId . StartsWith ( "claude-3-haiku" ) => 4096 ,
61+ _ when modelId . StartsWith ( "claude-3-5-haiku" ) => 8192 ,
62+ _ when modelId . StartsWith ( "claude-opus-4" ) => 32000 ,
63+ _ when modelId . StartsWith ( "claude-opus-4-1" ) => 32000 ,
64+ _ when modelId . StartsWith ( "claude-opus-4-6" ) => 128000 ,
65+ _ => 64000 ,
66+ } ;
67+
68+ ThinkingConfigParam thinking ;
69+ if ( customAssistant . IsDeepThinkingSupported )
70+ {
71+ int budgetTokens ;
72+ if ( chatOptions . AdditionalProperties ? . TryGetValue ( "reasoning_effort_level" , out var reasoningEffortLevelObj ) is not true ||
73+ reasoningEffortLevelObj is not ReasoningEffortLevel reasoningEffortLevel )
74+ {
75+ budgetTokens = - 1 ;
76+ }
77+ else
78+ {
79+ budgetTokens = reasoningEffortLevel switch
80+ {
81+ ReasoningEffortLevel . Detailed => Math . Min ( maxTokens / 2 , 4096 ) ,
82+ ReasoningEffortLevel . Minimal => 1024 ,
83+ _ => - 1
84+ } ;
85+ }
86+
87+ if ( budgetTokens == - 1 && modelId . StartsWith ( "claude-opus-4-6" ) )
88+ {
89+ thinking = new ThinkingConfigParam ( new ThinkingConfigAdaptive ( ) ) ;
90+ }
91+ else
92+ {
93+ thinking = new ThinkingConfigParam (
94+ new ThinkingConfigEnabled
95+ {
96+ BudgetTokens = Math . Max ( budgetTokens , 2048 )
97+ } ) ;
98+ }
99+ }
100+ else
101+ {
102+ thinking = new ThinkingConfigParam ( new ThinkingConfigDisabled ( ) ) ;
103+ }
104+
105+ return new MessageCreateParams
106+ {
107+ MaxTokens = maxTokens ,
108+ Messages = [ ] , // Leave empty and underlying implementation will handle it
109+ Model = modelId ,
110+ Thinking = thinking ,
111+ CacheControl = new CacheControlEphemeral ( )
112+ } ;
113+ }
48114 }
49115
50116 public override Task < ChatResponse > GetResponseAsync (
@@ -56,13 +122,33 @@ public override Task<ChatResponse> GetResponseAsync(
56122 return base . GetResponseAsync ( messages , options , cancellationToken ) ;
57123 }
58124
59- public override IAsyncEnumerable < ChatResponseUpdate > GetStreamingResponseAsync (
125+ public override async IAsyncEnumerable < ChatResponseUpdate > GetStreamingResponseAsync (
60126 IEnumerable < ChatMessage > messages ,
61127 ChatOptions ? options = null ,
62- CancellationToken cancellationToken = default )
128+ [ EnumeratorCancellation ] CancellationToken cancellationToken = default )
63129 {
64130 BuildOptions ( ref options ) ;
65- return base . GetStreamingResponseAsync ( messages , options , cancellationToken ) ;
131+
132+ // Extract reasoning contents since SK didn't convert them from MEAI
133+ await foreach ( var update in base . GetStreamingResponseAsync ( messages , options , cancellationToken ) )
134+ {
135+ for ( var i = 0 ; i < update . Contents . Count ; i ++ )
136+ {
137+ if ( update . Contents [ i ] is TextReasoningContent textReasoningContent )
138+ {
139+ update . Contents [ i ] = new TextContent ( textReasoningContent . Text )
140+ {
141+ // This line actually takes no effect because
142+ // Microsoft.Extensions.AI.ChatResponseUpdateExtensions.ToStreamingChatMessageContent
143+ // forget to include item's AdditionalProperties in Metadata
144+ AdditionalProperties = ReasoningProperties
145+ } ;
146+ update . AdditionalProperties = ApplyReasoningProperties ( update . AdditionalProperties ) ;
147+ }
148+ }
149+
150+ yield return update ;
151+ }
66152 }
67153 }
68154}
0 commit comments