fix: implemented TD-07, chat await path with deadline

This commit is contained in:
2026-06-12 12:08:27 +02:00
parent 2e633922f9
commit 66938c23f2
6 changed files with 124 additions and 6 deletions

View File

@@ -279,6 +279,24 @@ defmodule BDS.AITest do
end
end
defmodule ShutdownAwareBlockingRuntime do
def generate(endpoint, request, opts) do
Process.flag(:trap_exit, true)
test_pid = Keyword.fetch!(opts, :test_pid)
send(test_pid, {:blocking_runtime_started, endpoint, request, self()})
receive do
{:EXIT, _from, :shutdown} ->
send(test_pid, :blocking_runtime_shutdown)
exit(:shutdown)
after
5_000 ->
{:ok, %{content: "too late", usage: %{input_tokens: 1, output_tokens: 1}}}
end
end
end
# Always returns another tool call and never a final answer, so a chat would
# loop forever if the round count were not bounded.
defmodule LoopingToolRuntime do
@@ -1772,6 +1790,64 @@ defmodule BDS.AITest do
assert Enum.map(messages, & &1.role) == [:user]
end
@tag :chat_timeout
test "send_chat_message times out a stalled chat round and keeps persisted state consistent" do
original_chat_config = Application.get_env(:bds, :chat, [])
original_http_config = Application.get_env(:bds, BDS.AI.HttpClient, [])
Application.put_env(
:bds,
:chat,
original_chat_config
|> Keyword.put(:max_tool_rounds, 1)
|> Keyword.put(:await_timeout_margin_ms, 25)
)
Application.put_env(
:bds,
BDS.AI.HttpClient,
original_http_config
|> Keyword.put(:connect_timeout_ms, 50)
|> Keyword.put(:receive_timeout_ms, 50)
)
on_exit(fn ->
Application.put_env(:bds, :chat, original_chat_config)
Application.put_env(:bds, BDS.AI.HttpClient, original_http_config)
end)
assert {:ok, _endpoint} =
BDS.AI.put_endpoint(
:online,
%{
url: "https://api.example.test/v1",
api_key: "online-secret",
model: "gpt-4o-mini"
},
secret_backend: FakeSecretBackend
)
assert {:ok, conversation} = BDS.AI.start_chat(%{model: "gpt-4o-mini"})
started_at = System.monotonic_time(:millisecond)
assert {:error, :chat_timeout} =
BDS.AI.send_chat_message(conversation.id, "Please wait forever",
runtime: ShutdownAwareBlockingRuntime,
test_pid: self(),
secret_backend: FakeSecretBackend
)
elapsed_ms = System.monotonic_time(:millisecond) - started_at
assert elapsed_ms < 1_000
assert_receive {:blocking_runtime_started, _endpoint, %{operation: :chat}, _pid}, 500
assert_receive :blocking_runtime_shutdown, 500
messages = BDS.AI.list_chat_messages(conversation.id)
assert Enum.map(messages, & &1.role) == [:user]
end
test "get_surface_state and put_surface_state persist and restore surface UI state" do
assert {:ok, conversation} = BDS.AI.start_chat(%{title: "Surface State", model: "gpt-4.1"})

View File

@@ -1,5 +1,5 @@
defmodule BDS.WxrParserTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false
alias BDS.WxrParser
@@ -102,6 +102,8 @@ defmodule BDS.WxrParserTest do
</rss>
"""
_warmup = WxrParser.parse_xml(sample_wxr_xml())
atom_count_before = :erlang.system_info(:atom_count)
parsed = WxrParser.parse_xml(xml)
atom_count_after = :erlang.system_info(:atom_count)