chore: import upstream snapshot with attribution
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,758 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.OpenAI.UnitTests;
|
||||
|
||||
internal static class TestHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple mock implementation of IChatClient for basic testing purposes.
|
||||
/// </summary>
|
||||
internal sealed class SimpleMockChatClient : IChatClient
|
||||
{
|
||||
private readonly string _responseText;
|
||||
|
||||
public ChatOptions? LastChatOptions { get; private set; }
|
||||
|
||||
public SimpleMockChatClient(string responseText = "Test response")
|
||||
{
|
||||
this._responseText = responseText;
|
||||
}
|
||||
|
||||
public ChatClientMetadata Metadata { get; } = new("Test", new Uri("https://test.example.com"), "test-model");
|
||||
|
||||
public Task<ChatResponse> GetResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options is not null)
|
||||
{
|
||||
this.LastChatOptions = options;
|
||||
}
|
||||
|
||||
// Count input messages to simulate context size
|
||||
int messageCount = messages.Count();
|
||||
ChatMessage message = new(ChatRole.Assistant, this._responseText);
|
||||
ChatResponse response = new([message])
|
||||
{
|
||||
ModelId = "test-model",
|
||||
FinishReason = ChatFinishReason.Stop,
|
||||
Usage = new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10 + (messageCount * 5), // More messages = more tokens
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15 + (messageCount * 5)
|
||||
}
|
||||
};
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options is not null)
|
||||
{
|
||||
this.LastChatOptions = options;
|
||||
}
|
||||
|
||||
await Task.Delay(1, cancellationToken);
|
||||
|
||||
// Count input messages to simulate context size
|
||||
int messageCount = messages.Count();
|
||||
|
||||
// Split response into words to simulate streaming
|
||||
string[] words = this._responseText.Split(' ');
|
||||
for (int i = 0; i < words.Length; i++)
|
||||
{
|
||||
string content = i < words.Length - 1 ? words[i] + " " : words[i];
|
||||
ChatResponseUpdate update = new()
|
||||
{
|
||||
Contents = [new TextContent(content)],
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
|
||||
// Add usage to the last update
|
||||
if (i == words.Length - 1)
|
||||
{
|
||||
update.Contents.Add(new UsageContent(new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10 + (messageCount * 5),
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15 + (messageCount * 5)
|
||||
}));
|
||||
}
|
||||
|
||||
yield return update;
|
||||
}
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType, object? serviceKey = null) =>
|
||||
serviceType.IsInstanceOfType(this) ? this : null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stateful mock implementation of IChatClient that returns different responses for each call.
|
||||
/// </summary>
|
||||
internal sealed class StatefulMockChatClient : IChatClient
|
||||
{
|
||||
private readonly string[] _responseTexts;
|
||||
private int _callIndex;
|
||||
|
||||
public StatefulMockChatClient(string[] responseTexts)
|
||||
{
|
||||
this._responseTexts = responseTexts;
|
||||
this._callIndex = 0;
|
||||
}
|
||||
|
||||
public ChatClientMetadata Metadata { get; } = new("Test", new Uri("https://test.example.com"), "test-model");
|
||||
|
||||
public Task<ChatResponse> GetResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Get the response text for this call
|
||||
string responseText = this._callIndex < this._responseTexts.Length
|
||||
? this._responseTexts[this._callIndex]
|
||||
: this._responseTexts[this._responseTexts.Length - 1];
|
||||
|
||||
this._callIndex++;
|
||||
|
||||
// Count input messages to simulate context size
|
||||
int messageCount = messages.Count();
|
||||
ChatMessage message = new(ChatRole.Assistant, responseText);
|
||||
ChatResponse response = new([message])
|
||||
{
|
||||
ModelId = "test-model",
|
||||
FinishReason = ChatFinishReason.Stop,
|
||||
Usage = new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10 + (messageCount * 5), // More messages = more tokens
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15 + (messageCount * 5)
|
||||
}
|
||||
};
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.Delay(1, cancellationToken);
|
||||
|
||||
// Get the response text for this call
|
||||
string responseText = this._callIndex < this._responseTexts.Length
|
||||
? this._responseTexts[this._callIndex]
|
||||
: this._responseTexts[this._responseTexts.Length - 1];
|
||||
|
||||
this._callIndex++;
|
||||
|
||||
// Count input messages to simulate context size
|
||||
int messageCount = messages.Count();
|
||||
|
||||
// Split response into words to simulate streaming
|
||||
string[] words = responseText.Split(' ');
|
||||
for (int i = 0; i < words.Length; i++)
|
||||
{
|
||||
string content = i < words.Length - 1 ? words[i] + " " : words[i];
|
||||
ChatResponseUpdate update = new()
|
||||
{
|
||||
Contents = [new TextContent(content)],
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
|
||||
// Add usage to the last update
|
||||
if (i == words.Length - 1)
|
||||
{
|
||||
update.Contents.Add(new UsageContent(new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10 + (messageCount * 5),
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15 + (messageCount * 5)
|
||||
}));
|
||||
}
|
||||
|
||||
yield return update;
|
||||
}
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType, object? serviceKey = null) =>
|
||||
serviceType.IsInstanceOfType(this) ? this : null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock implementation of IChatClient that returns responses with image content.
|
||||
/// </summary>
|
||||
internal sealed class ImageContentMockChatClient : IChatClient
|
||||
{
|
||||
private readonly string _imageUrl;
|
||||
|
||||
public ImageContentMockChatClient(string imageUrl = "https://example.com/image.png")
|
||||
{
|
||||
this._imageUrl = imageUrl;
|
||||
}
|
||||
|
||||
public ChatClientMetadata Metadata { get; } = new("Test", new Uri("https://test.example.com"), "test-model");
|
||||
|
||||
public Task<ChatResponse> GetResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ChatMessage message = new(ChatRole.Assistant, [
|
||||
new TextContent("Here is an image:"),
|
||||
new UriContent(this._imageUrl, "image/png")
|
||||
]);
|
||||
ChatResponse response = new([message])
|
||||
{
|
||||
ModelId = "test-model",
|
||||
FinishReason = ChatFinishReason.Stop,
|
||||
Usage = new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10,
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15
|
||||
}
|
||||
};
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.Delay(1, cancellationToken);
|
||||
|
||||
yield return new ChatResponseUpdate
|
||||
{
|
||||
Contents = [new TextContent("Here is an image:")],
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
|
||||
yield return new ChatResponseUpdate
|
||||
{
|
||||
Contents = [
|
||||
new UriContent(this._imageUrl, "image/png"),
|
||||
new UsageContent(new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10,
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15
|
||||
})
|
||||
],
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType, object? serviceKey = null) =>
|
||||
serviceType.IsInstanceOfType(this) ? this : null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock implementation of IChatClient that returns responses with audio content.
|
||||
/// </summary>
|
||||
internal sealed class AudioContentMockChatClient : IChatClient
|
||||
{
|
||||
private readonly byte[] _audioData;
|
||||
private readonly string _transcript;
|
||||
|
||||
public AudioContentMockChatClient(string audioData = "base64audiodata", string transcript = "This is a transcript")
|
||||
{
|
||||
this._audioData = System.Text.Encoding.UTF8.GetBytes(audioData);
|
||||
this._transcript = transcript;
|
||||
}
|
||||
|
||||
public ChatClientMetadata Metadata { get; } = new("Test", new Uri("https://test.example.com"), "test-model");
|
||||
|
||||
public Task<ChatResponse> GetResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ChatMessage message = new(ChatRole.Assistant, [
|
||||
new DataContent(this._audioData, "audio/wav")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
["transcript"] = this._transcript
|
||||
}
|
||||
}
|
||||
]);
|
||||
ChatResponse response = new([message])
|
||||
{
|
||||
ModelId = "test-model",
|
||||
FinishReason = ChatFinishReason.Stop,
|
||||
Usage = new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10,
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15
|
||||
}
|
||||
};
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.Delay(1, cancellationToken);
|
||||
|
||||
yield return new ChatResponseUpdate
|
||||
{
|
||||
Contents = [
|
||||
new DataContent(this._audioData, "audio/wav")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
["transcript"] = this._transcript
|
||||
}
|
||||
},
|
||||
new UsageContent(new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10,
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15
|
||||
})
|
||||
],
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType, object? serviceKey = null) =>
|
||||
serviceType.IsInstanceOfType(this) ? this : null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock implementation of IChatClient that returns responses with function calls.
|
||||
/// </summary>
|
||||
internal sealed class FunctionCallMockChatClient : IChatClient
|
||||
{
|
||||
private readonly string _functionName;
|
||||
private readonly Dictionary<string, object?> _arguments;
|
||||
|
||||
public FunctionCallMockChatClient(string functionName = "test_function", string arguments = "{\"param\":\"value\"}")
|
||||
{
|
||||
this._functionName = functionName;
|
||||
this._arguments = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object?>>(arguments) ?? [];
|
||||
}
|
||||
|
||||
public ChatClientMetadata Metadata { get; } = new("Test", new Uri("https://test.example.com"), "test-model");
|
||||
|
||||
public Task<ChatResponse> GetResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ChatMessage message = new(ChatRole.Assistant, [
|
||||
new FunctionCallContent("call_123", this._functionName)
|
||||
{
|
||||
Arguments = this._arguments
|
||||
}
|
||||
]);
|
||||
ChatResponse response = new([message])
|
||||
{
|
||||
ModelId = "test-model",
|
||||
FinishReason = ChatFinishReason.ToolCalls,
|
||||
Usage = new UsageDetails
|
||||
{
|
||||
InputTokenCount = 80,
|
||||
OutputTokenCount = 25,
|
||||
TotalTokenCount = 105
|
||||
}
|
||||
};
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.Delay(1, cancellationToken);
|
||||
|
||||
yield return new ChatResponseUpdate
|
||||
{
|
||||
Contents = [
|
||||
new FunctionCallContent("call_123", this._functionName)
|
||||
{
|
||||
Arguments = this._arguments
|
||||
},
|
||||
new UsageContent(new UsageDetails
|
||||
{
|
||||
InputTokenCount = 80,
|
||||
OutputTokenCount = 25,
|
||||
TotalTokenCount = 105
|
||||
})
|
||||
],
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType, object? serviceKey = null) =>
|
||||
serviceType.IsInstanceOfType(this) ? this : null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock implementation of IChatClient that returns mixed content types.
|
||||
/// </summary>
|
||||
internal sealed class MixedContentMockChatClient : IChatClient
|
||||
{
|
||||
public ChatClientMetadata Metadata { get; } = new("Test", new Uri("https://test.example.com"), "test-model");
|
||||
|
||||
public Task<ChatResponse> GetResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ChatMessage message = new(ChatRole.Assistant, [
|
||||
new TextContent("Here are multiple content types:"),
|
||||
new UriContent("https://example.com/image.png", "image/png"),
|
||||
new TextContent("And some more text after the image.")
|
||||
]);
|
||||
ChatResponse response = new([message])
|
||||
{
|
||||
ModelId = "test-model",
|
||||
FinishReason = ChatFinishReason.Stop,
|
||||
Usage = new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10,
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15
|
||||
}
|
||||
};
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.Delay(1, cancellationToken);
|
||||
|
||||
yield return new ChatResponseUpdate
|
||||
{
|
||||
Contents = [new TextContent("Here"), new TextContent(" are"), new TextContent(" multiple")],
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
|
||||
yield return new ChatResponseUpdate
|
||||
{
|
||||
Contents = [new TextContent(" content"), new TextContent(" types:")],
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
|
||||
yield return new ChatResponseUpdate
|
||||
{
|
||||
Contents = [new UriContent("https://example.com/image.png", "image/png")],
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
|
||||
yield return new ChatResponseUpdate
|
||||
{
|
||||
Contents = [new TextContent("And"), new TextContent(" some"), new TextContent(" more")],
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
|
||||
yield return new ChatResponseUpdate
|
||||
{
|
||||
Contents = [
|
||||
new TextContent(" text"),
|
||||
new TextContent(" after"),
|
||||
new TextContent(" the"),
|
||||
new TextContent(" image."),
|
||||
new UsageContent(new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10,
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15
|
||||
})
|
||||
],
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType, object? serviceKey = null) =>
|
||||
serviceType.IsInstanceOfType(this) ? this : null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock implementation of IChatClient that returns function call content for tool testing.
|
||||
/// </summary>
|
||||
internal sealed class ToolCallMockChatClient : IChatClient
|
||||
{
|
||||
private readonly string _functionName;
|
||||
private readonly Dictionary<string, object?> _arguments;
|
||||
|
||||
public ToolCallMockChatClient(string functionName, string argumentsJson)
|
||||
{
|
||||
this._functionName = functionName;
|
||||
// Parse JSON arguments into dictionary
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(argumentsJson);
|
||||
this._arguments = [];
|
||||
foreach (var prop in doc.RootElement.EnumerateObject())
|
||||
{
|
||||
this._arguments[prop.Name] = prop.Value.ValueKind switch
|
||||
{
|
||||
System.Text.Json.JsonValueKind.String => prop.Value.GetString(),
|
||||
System.Text.Json.JsonValueKind.Number => prop.Value.GetDouble(),
|
||||
System.Text.Json.JsonValueKind.True => true,
|
||||
System.Text.Json.JsonValueKind.False => false,
|
||||
System.Text.Json.JsonValueKind.Null => null,
|
||||
_ => prop.Value.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public ChatClientMetadata Metadata { get; } = new("Test", new Uri("https://test.example.com"), "test-model");
|
||||
|
||||
public Task<ChatResponse> GetResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
int messageCount = messages.Count();
|
||||
FunctionCallContent functionCall = new("call_test123", this._functionName, this._arguments);
|
||||
ChatMessage message = new(ChatRole.Assistant, [functionCall]);
|
||||
ChatResponse response = new([message])
|
||||
{
|
||||
ModelId = "test-model",
|
||||
FinishReason = ChatFinishReason.ToolCalls,
|
||||
Usage = new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10 + (messageCount * 5),
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15 + (messageCount * 5)
|
||||
}
|
||||
};
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.Delay(1, cancellationToken);
|
||||
|
||||
int messageCount = messages.Count();
|
||||
FunctionCallContent functionCall = new("call_test123", this._functionName, this._arguments);
|
||||
|
||||
yield return new ChatResponseUpdate
|
||||
{
|
||||
Contents = [functionCall, new UsageContent(new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10 + (messageCount * 5),
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15 + (messageCount * 5)
|
||||
})],
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType, object? serviceKey = null) =>
|
||||
serviceType.IsInstanceOfType(this) ? this : null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock IChatClient that captures the full message list on each call.
|
||||
/// Used to verify conversation history is passed correctly.
|
||||
/// </summary>
|
||||
internal sealed class ConversationMemoryMockChatClient : IChatClient
|
||||
{
|
||||
private readonly string _responseText;
|
||||
|
||||
/// <summary>Each entry is the messages list received for that call.</summary>
|
||||
public List<List<ChatMessage>> CallHistory { get; } = [];
|
||||
|
||||
public ConversationMemoryMockChatClient(string responseText = "Test response")
|
||||
{
|
||||
this._responseText = responseText;
|
||||
}
|
||||
|
||||
public ChatClientMetadata Metadata { get; } = new("Test", new Uri("https://test.example.com"), "test-model");
|
||||
|
||||
public Task<ChatResponse> GetResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.CallHistory.Add(messages.ToList());
|
||||
|
||||
ChatMessage message = new(ChatRole.Assistant, this._responseText);
|
||||
ChatResponse response = new([message])
|
||||
{
|
||||
ModelId = "test-model",
|
||||
FinishReason = ChatFinishReason.Stop,
|
||||
Usage = new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10,
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15
|
||||
}
|
||||
};
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.CallHistory.Add(messages.ToList());
|
||||
await Task.Delay(1, cancellationToken);
|
||||
|
||||
string[] words = this._responseText.Split(' ');
|
||||
for (int i = 0; i < words.Length; i++)
|
||||
{
|
||||
string content = i < words.Length - 1 ? words[i] + " " : words[i];
|
||||
ChatResponseUpdate update = new()
|
||||
{
|
||||
Contents = [new TextContent(content)],
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
|
||||
if (i == words.Length - 1)
|
||||
{
|
||||
update.Contents.Add(new UsageContent(new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10,
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15
|
||||
}));
|
||||
}
|
||||
|
||||
yield return update;
|
||||
}
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType, object? serviceKey = null) =>
|
||||
serviceType.IsInstanceOfType(this) ? this : null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom content mock implementation of IChatClient that returns custom content based on a provider function.
|
||||
/// </summary>
|
||||
internal sealed class CustomContentMockChatClient : IChatClient
|
||||
{
|
||||
private readonly Func<ChatMessage, IEnumerable<AIContent>> _contentProvider;
|
||||
|
||||
public CustomContentMockChatClient(Func<ChatMessage, IEnumerable<AIContent>> contentProvider)
|
||||
{
|
||||
this._contentProvider = contentProvider;
|
||||
}
|
||||
|
||||
public ChatClientMetadata Metadata { get; } = new("Test", new Uri("https://test.example.com"), "test-model");
|
||||
|
||||
public Task<ChatResponse> GetResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ChatMessage lastMessage = messages.Last();
|
||||
IEnumerable<AIContent> contents = this._contentProvider(lastMessage);
|
||||
ChatMessage message = new(ChatRole.Assistant, contents.ToList());
|
||||
ChatResponse response = new([message])
|
||||
{
|
||||
ModelId = "test-model",
|
||||
FinishReason = ChatFinishReason.Stop,
|
||||
Usage = new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10,
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15
|
||||
}
|
||||
};
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.Delay(1, cancellationToken);
|
||||
|
||||
ChatMessage lastMessage = messages.Last();
|
||||
IEnumerable<AIContent> contents = this._contentProvider(lastMessage);
|
||||
List<AIContent> contentList = contents.ToList();
|
||||
|
||||
// Stream each content item separately
|
||||
for (int i = 0; i < contentList.Count; i++)
|
||||
{
|
||||
List<AIContent> updateContents = [contentList[i]];
|
||||
|
||||
// Add usage to the last update
|
||||
if (i == contentList.Count - 1)
|
||||
{
|
||||
updateContents.Add(new UsageContent(new UsageDetails
|
||||
{
|
||||
InputTokenCount = 10,
|
||||
OutputTokenCount = 5,
|
||||
TotalTokenCount = 15
|
||||
}));
|
||||
}
|
||||
|
||||
yield return new ChatResponseUpdate
|
||||
{
|
||||
Contents = updateContents,
|
||||
Role = ChatRole.Assistant
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType, object? serviceKey = null) =>
|
||||
serviceType.IsInstanceOfType(this) ? this : null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user