tests: add additional streaming tests

This commit is contained in:
Stevan Freeborn
2024-07-03 13:58:58 -05:00
parent fec7de2f66
commit bd8e4c70ab
2 changed files with 350 additions and 1 deletions
@@ -236,7 +236,7 @@ public class AnthropicApiClientTests : IntegrationTest
[Theory]
[ClassData(typeof(EventTestData))]
public async Task CreateChatMessageAsync_WhenCalledAndMessageIsStreamed_ItShouldReturnAllEvents(string eventString, AnthropicEvent anthropicEvent)
public async Task CreateChatMessageAsync_WhenCalledAndMessageIsStreamed_ItShouldHandleAllEventTypes(string eventString, AnthropicEvent anthropicEvent)
{
_mockHttpMessageHandler
.WhenCreateStreamMessageRequest()
@@ -258,4 +258,72 @@ public class AnthropicApiClientTests : IntegrationTest
e.Should().BeEquivalentTo(anthropicEvent);
}
[Fact]
public async Task CreateChatMessageAsync_WhenCalledAndMessageIsStreamed_ItShouldReturnAllExpectedEvents()
{
var eventStream = EventTestData.GetEventStream();
var events = EventTestData.GetAllEvents();
_mockHttpMessageHandler
.WhenCreateStreamMessageRequest()
.Respond(
HttpStatusCode.OK,
"text/event-stream",
eventStream
);
var request = new StreamChatMessageRequest(
model: AnthropicModels.Claude35Sonnet,
messages: [
new(MessageRole.User, [new TextContent("Hello!")]),
]
);
var result = Client.CreateChatMessageAsync(request);
var actualEvents = await result.ToListAsync();
actualEvents.Should().BeEquivalentTo(events);
}
[Fact]
public async Task CreateChatMessageAsync_WhenCalledMessageIsStreamedAndToolProvide_ItShouldReturnToolCall()
{
var eventStream = EventTestData.GetEventStream();
_mockHttpMessageHandler
.WhenCreateStreamMessageRequest()
.Respond(
HttpStatusCode.OK,
"text/event-stream",
eventStream
);
var getWeather = (string location, string unit) => $"The weather in {location} is 72°{unit}";
var request = new StreamChatMessageRequest(
model: AnthropicModels.Claude35Sonnet,
messages: [
new(MessageRole.User, [new TextContent("Hello!")]),
],
tools: [
Tool.CreateFromFunction("get_weather", "Gets the weather for a location", getWeather)
]
);
var result = Client.CreateChatMessageAsync(request);
var msgCompleteEvent = await result
.Where(e => e.Type is EventType.MessageComplete)
.FirstAsync();
msgCompleteEvent.Data.Should().BeOfType<MessageCompleteEventData>();
var toolCall = msgCompleteEvent.Data.As<MessageCompleteEventData>().Message.ToolCall;
var toolCallResult = await toolCall!.InvokeAsync<string>();
toolCallResult.IsSuccess.Should().BeTrue();
toolCallResult.Error.Should().BeNull();
toolCallResult.Value.Should().Be(getWeather("San Francisco, CA","fahrenheit"));
}
}
@@ -2,6 +2,45 @@ namespace AnthropicClient.Tests.Integration;
public class EventTestData : IEnumerable<object[]>
{
public static MemoryStream GetEventStream()
{
var events = new EventTestData().Select(x => x[0].ToString())!;
var content = string.Join("\n\n", events);
return new MemoryStream(Encoding.UTF8.GetBytes(content));
}
public static List<AnthropicEvent> GetAllEvents() => new EventTestData().Select(x => x[1] as AnthropicEvent).Append(MessageCompleteEvent).ToList()!;
public static readonly AnthropicEvent MessageCompleteEvent = new()
{
Type = EventType.MessageComplete,
Data = new MessageCompleteEventData(
new()
{
Id = "msg_014p7gG3wDgGV9EUtLvnow3U",
Type = "message",
Role = MessageRole.Assistant,
Model = AnthropicModels.Claude3Haiku,
StopSequence = null,
Usage = new ChatUsage { InputTokens = 472, OutputTokens = 91 },
StopReason = "tool_use",
Content = [
new TextContent("Okay, let's check the weather for San Francisco, CA:"),
new ToolUseContent()
{
Id = "toolu_01T1x1fJ34qAmk2tNTrN7Up6",
Name = "get_weather",
Input = new Dictionary<string, object?>
{
{ "location", "San Francisco, CA" },
{ "unit", "fahrenheit" }
},
},
]
},
new()
)
};
public IEnumerator<object[]> GetEnumerator()
{
yield return new object[]
@@ -379,6 +418,248 @@ public class EventTestData : IEnumerable<object[]>
},
},
};
yield return new object[]
{
"""
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":""}}
""",
new AnthropicEvent()
{
Type = EventType.ContentBlockDelta,
Data = new ContentDeltaEventData()
{
Index = 1,
Delta = new JsonDelta()
{
Type = "input_json_delta",
PartialJson = "",
},
},
},
};
yield return new object[]
{
"""
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"location\":"}}
""",
new AnthropicEvent()
{
Type = EventType.ContentBlockDelta,
Data = new ContentDeltaEventData()
{
Index = 1,
Delta = new JsonDelta()
{
Type = "input_json_delta",
PartialJson = "{\"location\":",
},
},
},
};
yield return new object[]
{
"""
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" \"San"}}
""",
new AnthropicEvent()
{
Type = EventType.ContentBlockDelta,
Data = new ContentDeltaEventData()
{
Index = 1,
Delta = new JsonDelta()
{
Type = "input_json_delta",
PartialJson = " \"San",
},
},
},
};
yield return new object[]
{
"""
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" Francisc"}}
""",
new AnthropicEvent()
{
Type = EventType.ContentBlockDelta,
Data = new ContentDeltaEventData()
{
Index = 1,
Delta = new JsonDelta()
{
Type = "input_json_delta",
PartialJson = " Francisc",
},
},
},
};
yield return new object[]
{
"""
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"o,"}}
""",
new AnthropicEvent()
{
Type = EventType.ContentBlockDelta,
Data = new ContentDeltaEventData()
{
Index = 1,
Delta = new JsonDelta()
{
Type = "input_json_delta",
PartialJson = "o,",
},
},
},
};
yield return new object[]
{
"""
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" CA\""}}
""",
new AnthropicEvent()
{
Type = EventType.ContentBlockDelta,
Data = new ContentDeltaEventData()
{
Index = 1,
Delta = new JsonDelta()
{
Type = "input_json_delta",
PartialJson = " CA\"",
},
},
},
};
yield return new object[]
{
"""
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":", "}}
""",
new AnthropicEvent()
{
Type = EventType.ContentBlockDelta,
Data = new ContentDeltaEventData()
{
Index = 1,
Delta = new JsonDelta()
{
Type = "input_json_delta",
PartialJson = ", ",
},
},
},
};
yield return new object[]
{
"""
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"\"unit\": \"fah"}}
""",
new AnthropicEvent()
{
Type = EventType.ContentBlockDelta,
Data = new ContentDeltaEventData()
{
Index = 1,
Delta = new JsonDelta()
{
Type = "input_json_delta",
PartialJson = "\"unit\": \"fah",
},
},
},
};
yield return new object[]
{
"""
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"renheit\"}"}}
""",
new AnthropicEvent()
{
Type = EventType.ContentBlockDelta,
Data = new ContentDeltaEventData()
{
Index = 1,
Delta = new JsonDelta()
{
Type = "input_json_delta",
PartialJson = "renheit\"}",
},
},
},
};
yield return new object[]
{
"""
event: content_block_stop
data: {"type":"content_block_stop","index":1}
""",
new AnthropicEvent()
{
Type = EventType.ContentBlockStop,
Data = new ContentStopEventData()
{
Index = 1,
},
},
};
yield return new object[]
{
"""
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"output_tokens":89}}
""",
new AnthropicEvent()
{
Type = EventType.MessageDelta,
Data = new MessageDeltaEventData()
{
Delta = new MessageDelta()
{
StopReason = "tool_use",
StopSequence = null,
},
Usage = new ChatUsage()
{
OutputTokens = 89,
},
},
}
};
yield return new object[]
{
"""
event: message_stop
data: {"type":"message_stop"}
""",
new AnthropicEvent()
{
Type = EventType.MessageStop,
Data = new MessageStopEventData(),
},
};
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();