feat: implement ListAllModelsAsync and ListModelsAsync
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AnthropicClient.Json;
|
||||
using AnthropicClient.Models;
|
||||
@@ -39,15 +40,15 @@ public interface IAnthropicApiClient
|
||||
/// </summary>
|
||||
/// <param name="request">The paging request to use for listing the models.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="Page{T}"/> where T is <see cref="AnthropicModel"/>.</returns>
|
||||
Task<AnthropicResult<Page<AnthropicModel>>> ListModelsAsync(PagingRequest request);
|
||||
Task<AnthropicResult<Page<AnthropicModel>>> ListModelsAsync(PagingRequest? request = null);
|
||||
|
||||
/// <summary>
|
||||
/// Lists the models asynchronously.
|
||||
/// Lists the models asynchronously
|
||||
/// </summary>
|
||||
/// <param name="limit">The maximum number of models to return in each page.</param>
|
||||
/// <returns>An asynchronous enumerable that yields the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="Page{T}"/> where T is <see cref="AnthropicModel"/>.</returns>
|
||||
///
|
||||
IAsyncEnumerable<AnthropicResult<Page<AnthropicModel>>> ListModelsAsync(int limit = 20);
|
||||
IAsyncEnumerable<AnthropicResult<Page<AnthropicModel>>> ListAllModelsAsync(int limit = 20);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IAnthropicApiClient"/>
|
||||
@@ -57,6 +58,7 @@ public class AnthropicApiClient : IAnthropicApiClient
|
||||
private const string ApiKeyHeader = "x-api-key";
|
||||
private const string MessagesEndpoint = "messages";
|
||||
private const string CountTokensEndpoint = "messages/count_tokens";
|
||||
private const string ModelsEndpoint = "models";
|
||||
private const string JsonContentType = "application/json";
|
||||
private const string EventPrefix = "event:";
|
||||
private const string DataPrefix = "data:";
|
||||
@@ -292,10 +294,64 @@ public class AnthropicApiClient : IAnthropicApiClient
|
||||
}
|
||||
|
||||
var msgResponse = Deserialize<TokenCountResponse>(responseContent) ?? new TokenCountResponse();
|
||||
|
||||
return AnthropicResult<TokenCountResponse>.Success(msgResponse, anthropicHeaders);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<AnthropicResult<Page<AnthropicModel>>> ListModelsAsync(PagingRequest? request = null)
|
||||
{
|
||||
var pagingRequest = request ?? new PagingRequest();
|
||||
var endpoint = $"{ModelsEndpoint}?{pagingRequest.ToQueryParameters()}";
|
||||
var response = await SendRequestAsync(endpoint);
|
||||
var anthropicHeaders = new AnthropicHeaders(response.Headers);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode is false)
|
||||
{
|
||||
var error = Deserialize<AnthropicError>(responseContent) ?? new AnthropicError();
|
||||
return AnthropicResult<Page<AnthropicModel>>.Failure(error, anthropicHeaders);
|
||||
}
|
||||
|
||||
var page = Deserialize<Page<AnthropicModel>>(responseContent) ?? new Page<AnthropicModel>();
|
||||
return AnthropicResult<Page<AnthropicModel>>.Success(page, anthropicHeaders);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<AnthropicResult<Page<AnthropicModel>>> ListAllModelsAsync(int limit = 20)
|
||||
{
|
||||
var pagingRequest = new PagingRequest(limit: limit);
|
||||
string Endpoint() => $"{ModelsEndpoint}?{pagingRequest.ToQueryParameters()}";
|
||||
bool hasMore;
|
||||
|
||||
do
|
||||
{
|
||||
var response = await SendRequestAsync(Endpoint());
|
||||
var anthropicHeaders = new AnthropicHeaders(response.Headers);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode is false)
|
||||
{
|
||||
var error = Deserialize<AnthropicError>(responseContent) ?? new AnthropicError();
|
||||
yield return AnthropicResult<Page<AnthropicModel>>.Failure(error, anthropicHeaders);
|
||||
yield break;
|
||||
}
|
||||
|
||||
var page = Deserialize<Page<AnthropicModel>>(responseContent) ?? new Page<AnthropicModel>();
|
||||
|
||||
if (page.HasMore && page.LastId is not null)
|
||||
{
|
||||
hasMore = true;
|
||||
pagingRequest = new PagingRequest(limit: limit, afterId: page.LastId);
|
||||
}
|
||||
else
|
||||
{
|
||||
hasMore = false;
|
||||
}
|
||||
|
||||
yield return AnthropicResult<Page<AnthropicModel>>.Success(page, anthropicHeaders);
|
||||
} while (hasMore);
|
||||
}
|
||||
|
||||
private ToolCall? GetToolCall(MessageResponse response, List<Tool> tools)
|
||||
{
|
||||
var toolUse = response.Content.OfType<ToolUseContent>().FirstOrDefault();
|
||||
@@ -315,6 +371,11 @@ public class AnthropicApiClient : IAnthropicApiClient
|
||||
return new ToolCall(tool, toolUse);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendRequestAsync(string endpoint)
|
||||
{
|
||||
return await _httpClient.GetAsync(endpoint);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendRequestAsync<T>(string endpoint, T request)
|
||||
{
|
||||
var requestJson = Serialize(request);
|
||||
@@ -324,16 +385,4 @@ public class AnthropicApiClient : IAnthropicApiClient
|
||||
|
||||
private string Serialize<T>(T obj) => JsonSerializer.Serialize(obj, JsonSerializationOptions.DefaultOptions);
|
||||
private T? Deserialize<T>(string json) => JsonSerializer.Deserialize<T>(json, JsonSerializationOptions.DefaultOptions);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<AnthropicResult<Page<AnthropicModel>>> ListModelsAsync(PagingRequest request)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IAsyncEnumerable<AnthropicResult<Page<AnthropicModel>>> ListModelsAsync(int limit = 20)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,13 @@ public class Page
|
||||
/// The id of the first item in the page.
|
||||
/// </summary>
|
||||
[JsonPropertyName("first_id")]
|
||||
public string FirstId { get; init; } = string.Empty;
|
||||
public string? FirstId { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The id of the last item in the page.
|
||||
/// </summary>
|
||||
[JsonPropertyName("last_id")]
|
||||
public string LastId { get; init; } = string.Empty;
|
||||
public string? LastId { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether there is more data to be retrieved.
|
||||
|
||||
@@ -70,10 +70,7 @@ public class PagingRequest
|
||||
parameters.Add($"after_id={AfterId}");
|
||||
}
|
||||
|
||||
if (Limit is not DefaultLimit)
|
||||
{
|
||||
parameters.Add($"limit={Limit}");
|
||||
}
|
||||
parameters.Add($"limit={Limit}");
|
||||
|
||||
return string.Join("&", parameters);
|
||||
}
|
||||
|
||||
@@ -303,4 +303,32 @@ public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(conf
|
||||
result.Value.Should().BeOfType<TokenCountResponse>();
|
||||
result.Value.InputTokens.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListModelsAsync_WhenCalled_ItShouldReturnResponse()
|
||||
{
|
||||
var result = await _client.ListModelsAsync();
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().BeOfType<Page<AnthropicModel>>();
|
||||
result.Value.Data.Should().HaveCountGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListModelsAsync_WhenCalledWithPagination_ItShouldReturnResponse()
|
||||
{
|
||||
var result = await _client.ListModelsAsync(new PagingRequest(limit: 1));
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().BeOfType<Page<AnthropicModel>>();
|
||||
result.Value.Data.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListAllModelsAsync_WhenCalled_ItShouldReturnResponse()
|
||||
{
|
||||
var responses = await _client.ListAllModelsAsync(limit: 1).ToListAsync();
|
||||
|
||||
responses.Should().HaveCountGreaterThan(0);
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public class AnthropicApiClientTests : IntegrationTest
|
||||
""input_tokens"": 10,
|
||||
""output_tokens"": 25
|
||||
}
|
||||
}"
|
||||
}"
|
||||
);
|
||||
|
||||
var request = new MessageRequest(
|
||||
@@ -115,7 +115,7 @@ public class AnthropicApiClientTests : IntegrationTest
|
||||
""input_tokens"": 10,
|
||||
""output_tokens"": 25
|
||||
}
|
||||
}"
|
||||
}"
|
||||
);
|
||||
|
||||
var func = (string ticker) => ticker;
|
||||
@@ -191,7 +191,7 @@ public class AnthropicApiClientTests : IntegrationTest
|
||||
""input_tokens"": 10,
|
||||
""output_tokens"": 25
|
||||
}
|
||||
}"
|
||||
}"
|
||||
);
|
||||
|
||||
var request = new MessageRequest(
|
||||
@@ -331,12 +331,12 @@ public class AnthropicApiClientTests : IntegrationTest
|
||||
HttpStatusCode.BadRequest,
|
||||
"application/json",
|
||||
@"{
|
||||
""type"": ""error"",
|
||||
""error"": {
|
||||
""type"": ""invalid_request_error"",
|
||||
""message"": ""messages: roles must alternate between user and assistant, but found multiple user roles in a row""
|
||||
}
|
||||
}"
|
||||
""type"": ""error"",
|
||||
""error"": {
|
||||
""type"": ""invalid_request_error"",
|
||||
""message"": ""messages: roles must alternate between user and assistant, but found multiple user roles in a row""
|
||||
}
|
||||
}"
|
||||
);
|
||||
|
||||
var request = new StreamMessageRequest(
|
||||
@@ -385,7 +385,7 @@ public class AnthropicApiClientTests : IntegrationTest
|
||||
""input_tokens"": 10,
|
||||
""output_tokens"": 25
|
||||
}
|
||||
}"
|
||||
}"
|
||||
);
|
||||
|
||||
var request = new MessageRequest(
|
||||
@@ -428,8 +428,8 @@ public class AnthropicApiClientTests : IntegrationTest
|
||||
HttpStatusCode.OK,
|
||||
"application/json",
|
||||
@"{
|
||||
""input_tokens"": 10
|
||||
}"
|
||||
""input_tokens"": 10
|
||||
}"
|
||||
);
|
||||
|
||||
var request = new CountMessageTokensRequest(
|
||||
@@ -455,12 +455,12 @@ public class AnthropicApiClientTests : IntegrationTest
|
||||
HttpStatusCode.BadRequest,
|
||||
"application/json",
|
||||
@"{
|
||||
""type"": ""error"",
|
||||
""error"": {
|
||||
""type"": ""invalid_request_error"",
|
||||
""message"": ""messages: roles must alternate between user and assistant, but found multiple user roles in a row""
|
||||
}
|
||||
}"
|
||||
""type"": ""error"",
|
||||
""error"": {
|
||||
""type"": ""invalid_request_error"",
|
||||
""message"": ""messages: roles must alternate between user and assistant, but found multiple user roles in a row""
|
||||
}
|
||||
}"
|
||||
);
|
||||
|
||||
var request = new CountMessageTokensRequest(
|
||||
@@ -503,4 +503,387 @@ public class AnthropicApiClientTests : IntegrationTest
|
||||
result.Error.Should().BeOfType<AnthropicError>();
|
||||
result.Error.Error.Should().BeOfType<ApiError>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListModelsAsync_WhenCalledWithPagingRequestUsingDefaultValues_ItShouldReturnListOfModels()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.WhenListModelsRequest()
|
||||
.Respond(
|
||||
HttpStatusCode.OK,
|
||||
"application/json",
|
||||
@"{
|
||||
""data"": [
|
||||
{
|
||||
""type"": ""model"",
|
||||
""id"": ""claude-3-5-sonnet-20241022"",
|
||||
""display_name"": ""Claude 3.5 Sonnet (New)"",
|
||||
""created_at"": ""2024-10-22T00:00:00Z""
|
||||
}
|
||||
],
|
||||
""has_more"": true,
|
||||
""first_id"": ""first_id"",
|
||||
""last_id"": ""last_id""
|
||||
}"
|
||||
);
|
||||
|
||||
var result = await Client.ListModelsAsync();
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().BeOfType<Page<AnthropicModel>>();
|
||||
result.Value.HasMore.Should().BeTrue();
|
||||
result.Value.FirstId.Should().Be("first_id");
|
||||
result.Value.LastId.Should().Be("last_id");
|
||||
result.Value.Data.Should().BeEquivalentTo(new AnthropicModel[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
Type = "model",
|
||||
Id = "claude-3-5-sonnet-20241022",
|
||||
DisplayName = "Claude 3.5 Sonnet (New)",
|
||||
CreatedAt = DateTimeOffset.Parse("2024-10-22T00:00:00Z")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListModelsAsync_WhenCalledWithPagingRequestUsingCustomValues_ItShouldReturnListOfModels()
|
||||
{
|
||||
var pagingRequest = new PagingRequest("prev_id", "next_id", 10);
|
||||
|
||||
_mockHttpMessageHandler
|
||||
.WhenListModelsRequest()
|
||||
.WithQueryString(new Dictionary<string, string>
|
||||
{
|
||||
{ "before_id", pagingRequest.BeforeId },
|
||||
{ "after_id", pagingRequest.AfterId },
|
||||
{ "limit", pagingRequest.Limit.ToString() },
|
||||
})
|
||||
.Respond(
|
||||
HttpStatusCode.OK,
|
||||
"application/json",
|
||||
@"{
|
||||
""data"": [
|
||||
{
|
||||
""type"": ""model"",
|
||||
""id"": ""claude-3-5-sonnet-20241022"",
|
||||
""display_name"": ""Claude 3.5 Sonnet (New)"",
|
||||
""created_at"": ""2024-10-22T00:00:00Z""
|
||||
}
|
||||
],
|
||||
""has_more"": true,
|
||||
""first_id"": ""first_id"",
|
||||
""last_id"": ""last_id""
|
||||
}"
|
||||
);
|
||||
|
||||
var result = await Client.ListModelsAsync(pagingRequest);
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().BeOfType<Page<AnthropicModel>>();
|
||||
result.Value.HasMore.Should().BeTrue();
|
||||
result.Value.FirstId.Should().Be("first_id");
|
||||
result.Value.LastId.Should().Be("last_id");
|
||||
result.Value.Data.Should().BeEquivalentTo(new AnthropicModel[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
Type = "model",
|
||||
Id = "claude-3-5-sonnet-20241022",
|
||||
DisplayName = "Claude 3.5 Sonnet (New)",
|
||||
CreatedAt = DateTimeOffset.Parse("2024-10-22T00:00:00Z")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListModelsAsync_WhenCalledAndNoModelsReturned_ItShouldReturnEmptyList()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.WhenListModelsRequest()
|
||||
.Respond(
|
||||
HttpStatusCode.OK,
|
||||
"application/json",
|
||||
@"{
|
||||
""data"": [],
|
||||
""has_more"": false,
|
||||
""first_id"": null,
|
||||
""last_id"": null
|
||||
}"
|
||||
);
|
||||
|
||||
var result = await Client.ListModelsAsync();
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().BeOfType<Page<AnthropicModel>>();
|
||||
result.Value.HasMore.Should().BeFalse();
|
||||
result.Value.FirstId.Should().BeNull();
|
||||
result.Value.LastId.Should().BeNull();
|
||||
result.Value.Data.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListModelsAsync_WhenCalledAndErrorReturned_ItShouldHandleError()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.WhenListModelsRequest()
|
||||
.Respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
"application/json",
|
||||
@"{
|
||||
""type"": ""error"",
|
||||
""error"": {
|
||||
""type"": ""invalid_request_error"",
|
||||
""message"": ""messages: roles must alternate between user and assistant, but found multiple user roles in a row""
|
||||
}
|
||||
}"
|
||||
);
|
||||
|
||||
var result = await Client.ListModelsAsync();
|
||||
|
||||
result.IsSuccess.Should().BeFalse();
|
||||
result.Error.Should().BeOfType<AnthropicError>();
|
||||
result.Error.Error.Should().BeOfType<InvalidRequestError>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListModelsAsync_WhenCalledRequestFailsAndCanNotSerializeError_ItShouldReturnUnknownError()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.WhenListModelsRequest()
|
||||
.Respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
"application/json",
|
||||
@"{}"
|
||||
);
|
||||
|
||||
var result = await Client.ListModelsAsync();
|
||||
|
||||
result.IsSuccess.Should().BeFalse();
|
||||
result.Error.Should().BeOfType<AnthropicError>();
|
||||
result.Error.Error.Should().BeOfType<ApiError>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListAllModelsAsync_WhenCalled_ItShouldReturnAllModels()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.WhenListModelsRequest()
|
||||
.WithExactQueryString(new Dictionary<string, string>()
|
||||
{
|
||||
{ "limit", "20" },
|
||||
})
|
||||
.Respond(
|
||||
HttpStatusCode.OK,
|
||||
"application/json",
|
||||
@"{
|
||||
""data"": [
|
||||
{
|
||||
""type"": ""model"",
|
||||
""id"": ""claude-3-5-sonnet-20241022"",
|
||||
""display_name"": ""Claude 3.5 Sonnet (New)"",
|
||||
""created_at"": ""2024-10-22T00:00:00Z""
|
||||
}
|
||||
],
|
||||
""has_more"": true,
|
||||
""first_id"": ""1"",
|
||||
""last_id"": ""1""
|
||||
}"
|
||||
);
|
||||
|
||||
_mockHttpMessageHandler
|
||||
.WhenListModelsRequest()
|
||||
.WithExactQueryString(new Dictionary<string, string>()
|
||||
{
|
||||
{ "after_id", "1" },
|
||||
{ "limit", "20" },
|
||||
})
|
||||
.Respond(
|
||||
HttpStatusCode.OK,
|
||||
"application/json",
|
||||
@"{
|
||||
""data"": [
|
||||
{
|
||||
""type"": ""model"",
|
||||
""id"": ""claude-3-5-sonnet-20241023"",
|
||||
""display_name"": ""Claude 3.5 Sonnet (New)"",
|
||||
""created_at"": ""2024-10-23T00:00:00Z""
|
||||
}
|
||||
],
|
||||
""has_more"": false,
|
||||
""first_id"": ""2"",
|
||||
""last_id"": ""2""
|
||||
}"
|
||||
);
|
||||
|
||||
var pageResponses = Client.ListAllModelsAsync();
|
||||
var collectedPages = new List<Page<AnthropicModel>>();
|
||||
|
||||
await foreach (var response in pageResponses)
|
||||
{
|
||||
response.IsSuccess.Should().BeTrue();
|
||||
response.Value.Should().BeOfType<Page<AnthropicModel>>();
|
||||
collectedPages.Add(response.Value);
|
||||
}
|
||||
|
||||
collectedPages.Should().HaveCount(2);
|
||||
collectedPages.Should().BeEquivalentTo(new Page<AnthropicModel>[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
HasMore = true,
|
||||
FirstId = "1",
|
||||
LastId = "1",
|
||||
Data = [
|
||||
new()
|
||||
{
|
||||
Type = "model",
|
||||
Id = "claude-3-5-sonnet-20241022",
|
||||
DisplayName = "Claude 3.5 Sonnet (New)",
|
||||
CreatedAt = DateTimeOffset.Parse("2024-10-22T00:00:00Z")
|
||||
}
|
||||
]
|
||||
},
|
||||
new()
|
||||
{
|
||||
HasMore = false,
|
||||
FirstId = "2",
|
||||
LastId = "2",
|
||||
Data = [
|
||||
new()
|
||||
{
|
||||
Type = "model",
|
||||
Id = "claude-3-5-sonnet-20241023",
|
||||
DisplayName = "Claude 3.5 Sonnet (New)",
|
||||
CreatedAt = DateTimeOffset.Parse("2024-10-23T00:00:00Z")
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListAllModelsAsync_WhenCalledAndErrorReturned_ItShouldHandleError()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.WhenListModelsRequest()
|
||||
.Respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
"application/json",
|
||||
@"{
|
||||
""type"": ""error"",
|
||||
""error"": {
|
||||
""type"": ""invalid_request_error"",
|
||||
""message"": ""messages: roles must alternate between user and assistant, but found multiple user roles in a row""
|
||||
}
|
||||
}"
|
||||
);
|
||||
|
||||
var responses = Client.ListAllModelsAsync();
|
||||
var count = 0;
|
||||
|
||||
await foreach (var page in responses)
|
||||
{
|
||||
count++;
|
||||
page.IsSuccess.Should().BeFalse();
|
||||
page.Error.Should().BeOfType<AnthropicError>();
|
||||
page.Error.Error.Should().BeOfType<InvalidRequestError>();
|
||||
}
|
||||
|
||||
count.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListAllModelsAsync_WhenCalledRequestFailsAndCanNotSerializeError_ItShouldReturnUnknownError()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.WhenListModelsRequest()
|
||||
.Respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
"application/json",
|
||||
@"{}"
|
||||
);
|
||||
|
||||
var responses = Client.ListAllModelsAsync();
|
||||
var count = 0;
|
||||
|
||||
await foreach (var page in responses)
|
||||
{
|
||||
count++;
|
||||
page.IsSuccess.Should().BeFalse();
|
||||
page.Error.Should().BeOfType<AnthropicError>();
|
||||
page.Error.Error.Should().BeOfType<ApiError>();
|
||||
}
|
||||
|
||||
count.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListAllModelsAsync_WhenFirstPageSucceedsAndSecondPageFails_ItShouldReturnFirstPageAndError()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.WhenListModelsRequest()
|
||||
.WithExactQueryString(new Dictionary<string, string>
|
||||
{
|
||||
{ "limit", "20" },
|
||||
})
|
||||
.Respond(
|
||||
HttpStatusCode.OK,
|
||||
"application/json",
|
||||
@"{
|
||||
""data"": [
|
||||
{
|
||||
""type"": ""model"",
|
||||
""id"": ""claude-3-5-sonnet-20241022"",
|
||||
""display_name"": ""Claude 3.5 Sonnet (New)"",
|
||||
""created_at"": ""2024-10-22T00:00:00Z""
|
||||
}
|
||||
],
|
||||
""has_more"": true,
|
||||
""first_id"": ""1"",
|
||||
""last_id"": ""1""
|
||||
}"
|
||||
);
|
||||
|
||||
_mockHttpMessageHandler
|
||||
.WhenListModelsRequest()
|
||||
.WithExactQueryString(new Dictionary<string, string>
|
||||
{
|
||||
{ "after_id", "1" },
|
||||
{ "limit", "20" },
|
||||
})
|
||||
.Respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
"application/json",
|
||||
@"{
|
||||
""type"": ""error"",
|
||||
""error"": {
|
||||
""type"": ""invalid_request_error"",
|
||||
""message"": ""messages: roles must alternate between user and assistant, but found multiple user roles in a row""
|
||||
}
|
||||
}"
|
||||
);
|
||||
|
||||
var responses = Client.ListAllModelsAsync();
|
||||
var count = 0;
|
||||
|
||||
await foreach (var page in responses)
|
||||
{
|
||||
count++;
|
||||
|
||||
if (count == 1)
|
||||
{
|
||||
page.IsSuccess.Should().BeTrue();
|
||||
page.Value.Should().BeOfType<Page<AnthropicModel>>();
|
||||
}
|
||||
else
|
||||
{
|
||||
page.IsSuccess.Should().BeFalse();
|
||||
page.Error.Should().BeOfType<AnthropicError>();
|
||||
page.Error.Error.Should().BeOfType<InvalidRequestError>();
|
||||
}
|
||||
}
|
||||
|
||||
count.Should().Be(2);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ public static class MockHttpMessageHandlerExtensions
|
||||
private const string BaseUrl = "https://api.anthropic.com/v1";
|
||||
private static readonly string MessagesEndpoint = $"{BaseUrl}/messages";
|
||||
private static readonly string CountTokensEndpoint = $"{BaseUrl}/messages/count_tokens";
|
||||
private static readonly string ModelsEndpoint = $"{BaseUrl}/models";
|
||||
|
||||
private static MockedRequest SetupBaseRequest(
|
||||
this MockHttpMessageHandler mockHttpMessageHandler,
|
||||
@@ -51,4 +52,10 @@ public static class MockHttpMessageHandlerExtensions
|
||||
return mockHttpMessageHandler
|
||||
.SetupBaseRequest(HttpMethod.Post, CountTokensEndpoint);
|
||||
}
|
||||
|
||||
public static MockedRequest WhenListModelsRequest(this MockHttpMessageHandler mockHttpMessageHandler)
|
||||
{
|
||||
return mockHttpMessageHandler
|
||||
.SetupBaseRequest(HttpMethod.Get, ModelsEndpoint);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public class PagingRequestTests : SerializationTest
|
||||
|
||||
var result = pagingRequest.ToQueryParameters();
|
||||
|
||||
result.Should().BeEmpty();
|
||||
result.Should().Be("limit=20");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -35,7 +35,7 @@ public class PagingRequestTests : SerializationTest
|
||||
|
||||
var result = pagingRequest.ToQueryParameters();
|
||||
|
||||
result.Should().Be("before_id=before-id");
|
||||
result.Should().Be("before_id=before-id&limit=20");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -45,7 +45,7 @@ public class PagingRequestTests : SerializationTest
|
||||
|
||||
var result = pagingRequest.ToQueryParameters();
|
||||
|
||||
result.Should().Be("after_id=after-id");
|
||||
result.Should().Be("after_id=after-id&limit=20");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user