feat: add ListAllMessageBatchesAsync method and corresponding tests
This commit is contained in:
@@ -48,6 +48,13 @@ public interface IAnthropicApiClient
|
|||||||
/// <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="MessageBatchResponse"/>.</returns>
|
/// <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="MessageBatchResponse"/>.</returns>
|
||||||
Task<AnthropicResult<Page<MessageBatchResponse>>> ListMessageBatchesAsync(PagingRequest? request = null);
|
Task<AnthropicResult<Page<MessageBatchResponse>>> ListMessageBatchesAsync(PagingRequest? request = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lists all message batches asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="limit">The maximum number of message batches 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="MessageBatchResponse"/>.</returns>
|
||||||
|
IAsyncEnumerable<AnthropicResult<Page<MessageBatchResponse>>> ListAllMessageBatchesAsync(int limit = 20);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the results of a message batch asynchronously.
|
/// Gets the results of a message batch asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -368,6 +375,15 @@ public class AnthropicApiClient : IAnthropicApiClient
|
|||||||
return AnthropicResult<Page<MessageBatchResponse>>.Success(page, anthropicHeaders);
|
return AnthropicResult<Page<MessageBatchResponse>>.Success(page, anthropicHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async IAsyncEnumerable<AnthropicResult<Page<MessageBatchResponse>>> ListAllMessageBatchesAsync(int limit = 20)
|
||||||
|
{
|
||||||
|
await foreach (var result in GetAllPagesAsync<MessageBatchResponse>(MessageBatchesEndpoint, limit))
|
||||||
|
{
|
||||||
|
yield return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<AnthropicResult<IAsyncEnumerable<MessageBatchResultItem>>> GetMessageBatchResultsAsync(string batchId)
|
public async Task<AnthropicResult<IAsyncEnumerable<MessageBatchResultItem>>> GetMessageBatchResultsAsync(string batchId)
|
||||||
{
|
{
|
||||||
@@ -439,37 +455,10 @@ public class AnthropicApiClient : IAnthropicApiClient
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async IAsyncEnumerable<AnthropicResult<Page<AnthropicModel>>> ListAllModelsAsync(int limit = 20)
|
public async IAsyncEnumerable<AnthropicResult<Page<AnthropicModel>>> ListAllModelsAsync(int limit = 20)
|
||||||
{
|
{
|
||||||
var pagingRequest = new PagingRequest(limit: limit);
|
await foreach (var result in GetAllPagesAsync<AnthropicModel>(ModelsEndpoint, limit))
|
||||||
string Endpoint() => $"{ModelsEndpoint}?{pagingRequest.ToQueryParameters()}";
|
|
||||||
bool hasMore;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
var response = await SendRequestAsync(Endpoint());
|
yield return result;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -490,6 +479,41 @@ public class AnthropicApiClient : IAnthropicApiClient
|
|||||||
return AnthropicResult<AnthropicModel>.Success(model, anthropicHeaders);
|
return AnthropicResult<AnthropicModel>.Success(model, anthropicHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async IAsyncEnumerable<AnthropicResult<Page<T>>> GetAllPagesAsync<T>(string endpoint, int limit = 20)
|
||||||
|
{
|
||||||
|
var pagingRequest = new PagingRequest(limit: limit);
|
||||||
|
string Endpoint() => $"{endpoint}?{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<T>>.Failure(error, anthropicHeaders);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var page = Deserialize<Page<T>>(responseContent) ?? new Page<T>();
|
||||||
|
|
||||||
|
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<T>>.Success(page, anthropicHeaders);
|
||||||
|
} while (hasMore);
|
||||||
|
}
|
||||||
|
|
||||||
private ToolCall? GetToolCall(MessageResponse response, List<Tool> tools)
|
private ToolCall? GetToolCall(MessageResponse response, List<Tool> tools)
|
||||||
{
|
{
|
||||||
var toolUse = response.Content.OfType<ToolUseContent>().FirstOrDefault();
|
var toolUse = response.Content.OfType<ToolUseContent>().FirstOrDefault();
|
||||||
|
|||||||
@@ -403,4 +403,36 @@ public class AnthropicApiClientTests(ConfigurationFixture configFixture) : EndTo
|
|||||||
result.Value.Data.Should().HaveCountGreaterThan(0);
|
result.Value.Data.Should().HaveCountGreaterThan(0);
|
||||||
result.Value.Data.Should().ContainSingle(b => b.Id == createResult.Value.Id);
|
result.Value.Data.Should().ContainSingle(b => b.Id == createResult.Value.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ListAllMessageBatchesAsync_WhenCalled_ItShouldReturnResponse()
|
||||||
|
{
|
||||||
|
var createRequest = (string id) => new MessageBatchRequest([
|
||||||
|
new(
|
||||||
|
id,
|
||||||
|
new(
|
||||||
|
model: AnthropicModels.Claude3Haiku,
|
||||||
|
messages: [new(MessageRole.User, [new TextContent("Hello!")])]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
var requestNumberOne = createRequest(Guid.NewGuid().ToString());
|
||||||
|
var requestNumberTwo = createRequest(Guid.NewGuid().ToString());
|
||||||
|
|
||||||
|
var createResultOne = await _client.CreateMessageBatchAsync(requestNumberOne);
|
||||||
|
var createResultTwo = await _client.CreateMessageBatchAsync(requestNumberTwo);
|
||||||
|
|
||||||
|
var responses = await _client.ListAllMessageBatchesAsync(limit: 1).ToListAsync();
|
||||||
|
|
||||||
|
responses.Should().HaveCountGreaterThan(2);
|
||||||
|
|
||||||
|
var batches = responses
|
||||||
|
.Where(r => r.IsSuccess)
|
||||||
|
.Select(r => r.Value)
|
||||||
|
.SelectMany(r => r.Data);
|
||||||
|
|
||||||
|
batches.Should().ContainSingle(b => b.Id == createResultOne.Value.Id);
|
||||||
|
batches.Should().ContainSingle(b => b.Id == createResultTwo.Value.Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1696,4 +1696,131 @@ public class AnthropicApiClientTests : IntegrationTest
|
|||||||
result.Value.LastId.Should().BeEmpty();
|
result.Value.LastId.Should().BeEmpty();
|
||||||
result.Value.Data.Should().BeEmpty();
|
result.Value.Data.Should().BeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ListAllMessageBatchesAsync_WhenCalled_ItShouldReturnAllBatches()
|
||||||
|
{
|
||||||
|
_mockHttpMessageHandler
|
||||||
|
.WhenListMessageBatchesRequest()
|
||||||
|
.WithExactQueryString(new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "limit", "20" },
|
||||||
|
})
|
||||||
|
.Respond(
|
||||||
|
HttpStatusCode.OK,
|
||||||
|
"application/json",
|
||||||
|
@"{
|
||||||
|
""data"": [
|
||||||
|
{
|
||||||
|
""id"": ""msgbatch_013Zva2CMHLNnXjNJJKqJ2EF"",
|
||||||
|
""type"": ""message_batch"",
|
||||||
|
""processing_status"": ""in_progress"",
|
||||||
|
""request_counts"": {
|
||||||
|
""processing"": 100,
|
||||||
|
""succeeded"": 50,
|
||||||
|
""errored"": 30,
|
||||||
|
""canceled"": 10,
|
||||||
|
""expired"": 10
|
||||||
|
},
|
||||||
|
""ended_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||||
|
""created_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||||
|
""expires_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||||
|
""archived_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||||
|
""cancel_initiated_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||||
|
""results_url"": ""https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""has_more"": true,
|
||||||
|
""first_id"": ""1"",
|
||||||
|
""last_id"": ""1""
|
||||||
|
}"
|
||||||
|
);
|
||||||
|
|
||||||
|
_mockHttpMessageHandler
|
||||||
|
.WhenListMessageBatchesRequest()
|
||||||
|
.WithExactQueryString(new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "after_id", "1" },
|
||||||
|
{ "limit", "20" },
|
||||||
|
})
|
||||||
|
.Respond(
|
||||||
|
HttpStatusCode.OK,
|
||||||
|
"application/json",
|
||||||
|
@"{
|
||||||
|
""data"": [
|
||||||
|
{
|
||||||
|
""id"": ""msgbatch_013Zva2CMHLNnXjNJJKqJ2EF"",
|
||||||
|
""type"": ""message_batch"",
|
||||||
|
""processing_status"": ""in_progress"",
|
||||||
|
""request_counts"": {
|
||||||
|
""processing"": 100,
|
||||||
|
""succeeded"": 50,
|
||||||
|
""errored"": 30,
|
||||||
|
""canceled"": 10,
|
||||||
|
""expired"": 10
|
||||||
|
},
|
||||||
|
""ended_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||||
|
""created_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||||
|
""expires_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||||
|
""archived_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||||
|
""cancel_initiated_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||||
|
""results_url"": ""https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""has_more"": false,
|
||||||
|
""first_id"": ""2"",
|
||||||
|
""last_id"": ""2""
|
||||||
|
}"
|
||||||
|
);
|
||||||
|
|
||||||
|
var pageResponses = Client.ListAllMessageBatchesAsync();
|
||||||
|
var collectedPages = new List<Page<MessageBatchResponse>>();
|
||||||
|
|
||||||
|
await foreach (var response in pageResponses)
|
||||||
|
{
|
||||||
|
response.IsSuccess.Should().BeTrue();
|
||||||
|
response.Value.Should().BeOfType<Page<MessageBatchResponse>>();
|
||||||
|
collectedPages.Add(response.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedMessageBatchResponse = new MessageBatchResponse()
|
||||||
|
{
|
||||||
|
Id = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF",
|
||||||
|
Type = "message_batch",
|
||||||
|
ProcessingStatus = "in_progress",
|
||||||
|
RequestCounts = new MessageBatchRequestCounts
|
||||||
|
{
|
||||||
|
Processing = 100,
|
||||||
|
Succeeded = 50,
|
||||||
|
Errored = 30,
|
||||||
|
Canceled = 10,
|
||||||
|
Expired = 10
|
||||||
|
},
|
||||||
|
EndedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||||
|
CreatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||||
|
ExpiresAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||||
|
ArchivedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||||
|
CancelInitiatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||||
|
ResultsUrl = "https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results"
|
||||||
|
};
|
||||||
|
|
||||||
|
collectedPages.Should().HaveCount(2);
|
||||||
|
collectedPages.Should().BeEquivalentTo(new List<Page<MessageBatchResponse>>()
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Data = [expectedMessageBatchResponse],
|
||||||
|
FirstId = "1",
|
||||||
|
LastId = "1",
|
||||||
|
HasMore = true
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Data = [expectedMessageBatchResponse],
|
||||||
|
FirstId = "2",
|
||||||
|
LastId = "2",
|
||||||
|
HasMore = false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user