RealtimeSession: execute server-side tools at response.done
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
using System.Text.Json.Nodes;
|
||||
using backend.Conversations;
|
||||
using backend.Data;
|
||||
using backend.Realtime;
|
||||
using backend.Tools;
|
||||
using FluentAssertions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace backend.tests;
|
||||
|
||||
public class RealtimeSessionToolsTests(ApiFactory factory) : IClassFixture<ApiFactory>
|
||||
{
|
||||
private readonly ApiFactory _factory = factory;
|
||||
|
||||
[Fact]
|
||||
public async Task Get_current_time_call_returns_function_call_output_and_persists_tool_turn()
|
||||
{
|
||||
using var scope = _factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
var log = scope.ServiceProvider.GetRequiredService<ConversationLog>();
|
||||
var reg = new ToolRegistry(new ITool[] { new GetCurrentTimeTool() });
|
||||
var upstream = new ScriptedRealtimeUpstream();
|
||||
var sink = new RecordingSink();
|
||||
var cfg = new RealtimeSettings("m", "v", "p", 30, new HashSet<string> { "get_current_time" });
|
||||
var session = new RealtimeSession(Guid.NewGuid(), upstream, sink, log, reg, cfg,
|
||||
new FakeDeviceChannel(), NullLogger.Instance);
|
||||
|
||||
upstream.Push(new JsonObject { ["type"] = "session.updated" });
|
||||
var cts = new CancellationTokenSource();
|
||||
var run = session.RunAsync(cts.Token);
|
||||
await sink.WaitForAsync("session_started", TimeSpan.FromSeconds(2));
|
||||
|
||||
upstream.Push(new JsonObject
|
||||
{
|
||||
["type"] = "response.function_call_arguments.done",
|
||||
["call_id"] = "call_abc",
|
||||
["name"] = "get_current_time",
|
||||
["arguments"] = "{}",
|
||||
});
|
||||
upstream.Push(new JsonObject { ["type"] = "response.done" });
|
||||
|
||||
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(2);
|
||||
while (DateTime.UtcNow < deadline && !upstream.Sent.Any(e =>
|
||||
(string?)e["type"] == "conversation.item.create"))
|
||||
{
|
||||
await Task.Delay(20);
|
||||
}
|
||||
|
||||
var item = upstream.Sent.First(e => (string?)e["type"] == "conversation.item.create");
|
||||
((string?)item["item"]!["type"]).Should().Be("function_call_output");
|
||||
((string?)item["item"]!["call_id"]).Should().Be("call_abc");
|
||||
((string?)item["item"]!["output"]).Should().Contain("\"now\"");
|
||||
|
||||
upstream.Sent.Should().Contain(e => (string?)e["type"] == "response.create");
|
||||
|
||||
var convoId = session.ConversationId!.Value;
|
||||
var toolTurn = await db.Turns.FirstAsync(t =>
|
||||
t.ConversationId == convoId && t.Role == TurnRole.Tool);
|
||||
toolTurn.ToolName.Should().Be("get_current_time");
|
||||
toolTurn.ToolResultJson.Should().Contain("\"now\"");
|
||||
|
||||
cts.Cancel();
|
||||
await run;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user