|
6 | 6 | using System.Collections.Generic;
|
7 | 7 | using System.Diagnostics;
|
8 | 8 | using System.Linq;
|
| 9 | +using System.Text.Json; |
9 | 10 | using System.Threading;
|
10 | 11 | using System.Threading.Tasks;
|
11 | 12 | using Microsoft.Extensions.DependencyInjection;
|
@@ -37,6 +38,35 @@ public void Ctor_HasExpectedDefaults()
|
37 | 38 | Assert.False(client.IncludeDetailedErrors);
|
38 | 39 | Assert.Equal(10, client.MaximumIterationsPerRequest);
|
39 | 40 | Assert.Equal(3, client.MaximumConsecutiveErrorsPerRequest);
|
| 41 | + Assert.Null(client.FunctionInvoker); |
| 42 | + } |
| 43 | + |
| 44 | + [Fact] |
| 45 | + public void Properties_Roundtrip() |
| 46 | + { |
| 47 | + using TestChatClient innerClient = new(); |
| 48 | + using FunctionInvokingChatClient client = new(innerClient); |
| 49 | + |
| 50 | + Assert.False(client.AllowConcurrentInvocation); |
| 51 | + client.AllowConcurrentInvocation = true; |
| 52 | + Assert.True(client.AllowConcurrentInvocation); |
| 53 | + |
| 54 | + Assert.False(client.IncludeDetailedErrors); |
| 55 | + client.IncludeDetailedErrors = true; |
| 56 | + Assert.True(client.IncludeDetailedErrors); |
| 57 | + |
| 58 | + Assert.Equal(10, client.MaximumIterationsPerRequest); |
| 59 | + client.MaximumIterationsPerRequest = 5; |
| 60 | + Assert.Equal(5, client.MaximumIterationsPerRequest); |
| 61 | + |
| 62 | + Assert.Equal(3, client.MaximumConsecutiveErrorsPerRequest); |
| 63 | + client.MaximumConsecutiveErrorsPerRequest = 1; |
| 64 | + Assert.Equal(1, client.MaximumConsecutiveErrorsPerRequest); |
| 65 | + |
| 66 | + Assert.Null(client.FunctionInvoker); |
| 67 | + Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>> invoker = (ctx, ct) => new ValueTask<object?>("test"); |
| 68 | + client.FunctionInvoker = invoker; |
| 69 | + Assert.Same(invoker, client.FunctionInvoker); |
40 | 70 | }
|
41 | 71 |
|
42 | 72 | [Fact]
|
@@ -208,6 +238,49 @@ public async Task ConcurrentInvocationOfParallelCallsDisabledByDefaultAsync()
|
208 | 238 | await InvokeAndAssertStreamingAsync(options, plan);
|
209 | 239 | }
|
210 | 240 |
|
| 241 | + [Fact] |
| 242 | + public async Task FunctionInvokerDelegateOverridesHandlingAsync() |
| 243 | + { |
| 244 | + var options = new ChatOptions |
| 245 | + { |
| 246 | + Tools = |
| 247 | + [ |
| 248 | + AIFunctionFactory.Create(() => "Result 1", "Func1"), |
| 249 | + AIFunctionFactory.Create((int i) => $"Result 2: {i}", "Func2"), |
| 250 | + AIFunctionFactory.Create((int i) => { }, "VoidReturn"), |
| 251 | + ] |
| 252 | + }; |
| 253 | + |
| 254 | + List<ChatMessage> plan = |
| 255 | + [ |
| 256 | + new ChatMessage(ChatRole.User, "hello"), |
| 257 | + new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1")]), |
| 258 | + new ChatMessage(ChatRole.Tool, [new FunctionResultContent("callId1", result: "Result 1 from delegate")]), |
| 259 | + new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId2", "Func2", arguments: new Dictionary<string, object?> { { "i", 42 } })]), |
| 260 | + new ChatMessage(ChatRole.Tool, [new FunctionResultContent("callId2", result: "Result 2: 42 from delegate")]), |
| 261 | + new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId3", "VoidReturn", arguments: new Dictionary<string, object?> { { "i", 43 } })]), |
| 262 | + new ChatMessage(ChatRole.Tool, [new FunctionResultContent("callId3", result: "Success: Function completed.")]), |
| 263 | + new ChatMessage(ChatRole.Assistant, "world"), |
| 264 | + ]; |
| 265 | + |
| 266 | + Func<ChatClientBuilder, ChatClientBuilder> configure = b => b.Use( |
| 267 | + s => new FunctionInvokingChatClient(s) |
| 268 | + { |
| 269 | + FunctionInvoker = async (ctx, cancellationToken) => |
| 270 | + { |
| 271 | + Assert.NotNull(ctx); |
| 272 | + var result = await ctx.Function.InvokeAsync(ctx.Arguments, cancellationToken); |
| 273 | + return result is JsonElement e ? |
| 274 | + JsonSerializer.SerializeToElement($"{e.GetString()} from delegate", AIJsonUtilities.DefaultOptions) : |
| 275 | + result; |
| 276 | + } |
| 277 | + }); |
| 278 | + |
| 279 | + await InvokeAndAssertAsync(options, plan, configurePipeline: configure); |
| 280 | + |
| 281 | + await InvokeAndAssertStreamingAsync(options, plan, configurePipeline: configure); |
| 282 | + } |
| 283 | + |
211 | 284 | [Fact]
|
212 | 285 | public async Task ContinuesWithSuccessfulCallsUntilMaximumIterations()
|
213 | 286 | {
|
|
0 commit comments