1+ using BotSharp . Abstraction . Agents . Enums ;
2+ using BotSharp . Abstraction . Agents . Models ;
3+ using BotSharp . Abstraction . Agents ;
4+ using BotSharp . Abstraction . Conversations . Models ;
5+ using BotSharp . Abstraction . Conversations ;
6+ using BotSharp . Abstraction . Files ;
7+ using BotSharp . Abstraction . Files . Utilities ;
8+ using BotSharp . Abstraction . Loggers ;
9+ using BotSharp . Abstraction . MLTasks ;
10+ using Microsoft . Extensions . AI ;
11+ using Microsoft . Extensions . DependencyInjection ;
12+ using Microsoft . Extensions . Logging ;
13+ using System . Collections . Generic ;
14+ using System . Threading . Tasks ;
15+ using System ;
16+ using System . Linq ;
17+ using System . Text . Json ;
18+ using System . Threading ;
19+
20+ namespace BotSharp . Plugin . MicrosoftExtensionsAI ;
21+
22+ /// <summary>
23+ /// Provides an implementation of <see cref="IChatCompletion"/> for Microsoft.Extensions.AI.
24+ /// </summary>
25+ public sealed class MicrosoftExtensionsAIChatCompletionProvider : IChatCompletion
26+ {
27+ private readonly IChatClient _client ;
28+ private readonly ILogger < MicrosoftExtensionsAIChatCompletionProvider > _logger ;
29+ private readonly IServiceProvider _services ;
30+ private string ? _model ;
31+
32+ /// <summary>
33+ /// Creates an instance of the <see cref="MicrosoftExtensionsAIChatCompletionProvider"/> class.
34+ /// </summary>
35+ public MicrosoftExtensionsAIChatCompletionProvider (
36+ IChatClient client ,
37+ ILogger < MicrosoftExtensionsAIChatCompletionProvider > logger ,
38+ IServiceProvider services )
39+ {
40+ _client = client ;
41+ _model = _client . Metadata . ModelId ;
42+ _logger = logger ;
43+ _services = services ;
44+ }
45+
46+ /// <inheritdoc/>
47+ public string Provider => "microsoft.extensions.ai" ;
48+
49+ /// <inheritdoc/>
50+ public void SetModelName ( string model ) => _model = model ;
51+
52+ /// <inheritdoc/>
53+ public async Task < RoleDialogModel > GetChatCompletions ( Agent agent , List < RoleDialogModel > conversations )
54+ {
55+ // Before chat completion hook
56+ var hooks = _services . GetServices < IContentGeneratingHook > ( ) . ToArray ( ) ;
57+ await Task . WhenAll ( hooks . Select ( hook => hook . BeforeGenerating ( agent , conversations ) ) ) ;
58+
59+ // Configure options
60+ var state = _services . GetRequiredService < IConversationStateService > ( ) ;
61+ var options = new ChatOptions ( )
62+ {
63+ Temperature = float . Parse ( state . GetState ( "temperature" , "0.0" ) ) ,
64+ MaxOutputTokens = int . Parse ( state . GetState ( "max_tokens" , "1024" ) )
65+ } ;
66+
67+ if ( _services . GetService < IAgentService > ( ) is { } agentService )
68+ {
69+ foreach ( var function in agent . Functions )
70+ {
71+ if ( agentService . RenderFunction ( agent , function ) )
72+ {
73+ var property = agentService . RenderFunctionProperty ( agent , function ) ;
74+ ( options . Tools ??= [ ] ) . Add ( new NopAIFunction ( new ( function . Name )
75+ {
76+ Description = function . Description ,
77+ Parameters = property ? . Properties . RootElement . Deserialize < Dictionary < string , object ? > > ( ) ? . Select ( p => new AIFunctionParameterMetadata ( p . Key )
78+ {
79+ Schema = p . Value ,
80+ } ) . ToList ( ) ?? [ ] ,
81+ } ) ) ;
82+ }
83+ }
84+ }
85+
86+ // Configure messages
87+ List < ChatMessage > messages = [ ] ;
88+
89+ if ( _services . GetRequiredService < IAgentService > ( ) . RenderedInstruction ( agent ) is string instruction &&
90+ instruction . Length > 0 )
91+ {
92+ messages . Add ( new ( ChatRole . System , instruction ) ) ;
93+ }
94+
95+ if ( ! string . IsNullOrEmpty ( agent . Knowledges ) )
96+ {
97+ messages . Add ( new ( ChatRole . System , agent . Knowledges ) ) ;
98+ }
99+
100+ foreach ( var sample in ProviderHelper . GetChatSamples ( agent . Samples ) )
101+ {
102+ messages . Add ( new ( sample . Role == AgentRole . Assistant ? ChatRole . Assistant : ChatRole . User , sample . Content ) ) ;
103+ }
104+
105+ var fileStorage = _services . GetService < IFileStorageService > ( ) ;
106+ bool allowMultiModal = fileStorage is not null && _services . GetService < ILlmProviderService > ( ) ? . GetSetting ( Provider , _model ?? "default" ) ? . MultiModal is true ;
107+ foreach ( var x in conversations )
108+ {
109+ if ( x . Role == AgentRole . Function && x . FunctionName is not null )
110+ {
111+ messages . Add ( new ( ChatRole . Assistant ,
112+ [
113+ new FunctionCallContent ( x . FunctionName , x . FunctionName , JsonSerializer . Deserialize < Dictionary < string , object ? > > ( x . FunctionArgs ?? "{}" ) ) ,
114+ new FunctionResultContent ( x . FunctionName , x . FunctionName , x . Content )
115+ ] ) ) ;
116+ }
117+ else if ( x . Role == AgentRole . System || x . Role == AgentRole . Assistant )
118+ {
119+ messages . Add ( new ( x . Role == AgentRole . System ? ChatRole . System : ChatRole . Assistant , x . Content ) ) ;
120+ }
121+ else if ( x . Role == AgentRole . User )
122+ {
123+ List < AIContent > contents = [ new TextContent ( ! string . IsNullOrWhiteSpace ( x . Payload ) ? x . Payload : x . Content ) ] ;
124+ if ( allowMultiModal )
125+ {
126+ foreach ( var file in x . Files )
127+ {
128+ if ( ! string . IsNullOrEmpty ( file . FileData ) )
129+ {
130+ contents . Add ( new ImageContent ( file . FileData ) ) ;
131+ }
132+ else if ( ! string . IsNullOrEmpty ( file . FileStorageUrl ) )
133+ {
134+ var contentType = FileUtility . GetFileContentType ( file . FileStorageUrl ) ;
135+ var bytes = fileStorage ! . GetFileBytes ( file . FileStorageUrl ) ;
136+ contents . Add ( new ImageContent ( bytes , contentType ) ) ;
137+ }
138+ else if ( ! string . IsNullOrEmpty ( file . FileUrl ) )
139+ {
140+ contents . Add ( new ImageContent ( file . FileUrl ) ) ;
141+ }
142+ }
143+ }
144+
145+ messages . Add ( new ( ChatRole . User , contents ) { AuthorName = x . FunctionName } ) ;
146+ }
147+ }
148+
149+ var completion = await _client . CompleteAsync ( messages ) ;
150+
151+ RoleDialogModel result = new ( AgentRole . Assistant , string . Concat ( completion . Message . Contents . OfType < TextContent > ( ) ) )
152+ {
153+ CurrentAgentId = agent . Id
154+ } ;
155+
156+ if ( completion . Message . Contents . OfType < FunctionCallContent > ( ) . FirstOrDefault ( ) is { } fcc )
157+ {
158+ result . Role = AgentRole . Function ;
159+ result . MessageId = conversations . LastOrDefault ( ) ? . MessageId ?? string . Empty ;
160+ result . FunctionName = fcc . Name ;
161+ result . FunctionArgs = fcc . Arguments is not null ? JsonSerializer . Serialize ( fcc . Arguments ) : null ;
162+ }
163+
164+ // After chat completion hook
165+ await Task . WhenAll ( hooks . Select ( hook => hook . AfterGenerated ( result , new ( ) { Model = _model ?? "default" } ) ) ) ;
166+
167+ return result ;
168+ }
169+
170+ /// <inheritdoc/>
171+ public Task < bool > GetChatCompletionsAsync ( Agent agent , List < RoleDialogModel > conversations , Func < RoleDialogModel , Task > onMessageReceived , Func < RoleDialogModel , Task > onFunctionExecuting ) =>
172+ throw new NotImplementedException ( ) ;
173+
174+ /// <inheritdoc/>
175+ public Task < bool > GetChatCompletionsStreamingAsync ( Agent agent , List < RoleDialogModel > conversations , Func < RoleDialogModel , Task > onMessageReceived ) =>
176+ throw new NotImplementedException ( ) ;
177+
178+ private sealed class NopAIFunction ( AIFunctionMetadata metadata ) : AIFunction
179+ {
180+ public override AIFunctionMetadata Metadata { get ; } = metadata ;
181+
182+ protected override Task < object ? > InvokeCoreAsync ( IEnumerable < KeyValuePair < string , object ? > > arguments , CancellationToken cancellationToken ) =>
183+ throw new NotSupportedException ( ) ;
184+ }
185+ }
0 commit comments