mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-08 05:18:49 +03:00
feat: preserve Responses reasoning state and compact context (#5172)
This commit is contained in:
@@ -11,7 +11,7 @@ from nanobot.providers.azure_openai_provider import (
|
||||
AzureOpenAIProvider,
|
||||
_AzureTokenProvider,
|
||||
)
|
||||
from nanobot.providers.base import LLMResponse
|
||||
from nanobot.providers.base import LLMResponse, ProviderCallContext
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Init & validation
|
||||
@@ -234,6 +234,7 @@ def test_build_body_basic():
|
||||
assert body["max_output_tokens"] == 4096
|
||||
assert body["store"] is False
|
||||
assert "reasoning" not in body
|
||||
assert "include" not in body
|
||||
# input should contain the converted user message only (system extracted)
|
||||
assert any(
|
||||
item.get("role") == "user"
|
||||
@@ -241,6 +242,30 @@ def test_build_body_basic():
|
||||
)
|
||||
|
||||
|
||||
def test_build_body_enables_server_compaction():
|
||||
provider = AzureOpenAIProvider(
|
||||
api_key="k",
|
||||
api_base="https://res.openai.azure.com",
|
||||
default_model="gpt-5.6",
|
||||
)
|
||||
|
||||
body = provider._build_body(
|
||||
[{"role": "user", "content": "hello"}],
|
||||
None,
|
||||
None,
|
||||
10_000,
|
||||
0.1,
|
||||
"high",
|
||||
None,
|
||||
provider_context=ProviderCallContext(context_window_tokens=200_000),
|
||||
)
|
||||
|
||||
assert body["context_management"] == [{
|
||||
"type": "compaction",
|
||||
"compact_threshold": 180_000,
|
||||
}]
|
||||
|
||||
|
||||
def test_build_body_max_tokens_minimum():
|
||||
"""max_output_tokens should never be less than 1."""
|
||||
provider = AzureOpenAIProvider(api_key="k", api_base="https://r.com", default_model="gpt-4o")
|
||||
@@ -358,6 +383,38 @@ async def test_chat_success():
|
||||
assert result.usage["prompt_tokens"] == 10
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_retries_without_unsupported_server_compaction():
|
||||
provider = AzureOpenAIProvider(
|
||||
api_key="test-key",
|
||||
api_base="https://test.openai.azure.com",
|
||||
default_model="gpt-5.6",
|
||||
)
|
||||
|
||||
class UnsupportedCompactionError(Exception):
|
||||
status_code = 400
|
||||
body = {"error": {"message": "Unknown parameter: context_management"}}
|
||||
|
||||
provider._client.responses = MagicMock()
|
||||
provider._client.responses.create = AsyncMock(side_effect=[
|
||||
UnsupportedCompactionError(),
|
||||
_make_sdk_response(content="compaction fallback"),
|
||||
])
|
||||
|
||||
result = await provider.chat(
|
||||
[{"role": "user", "content": "Hi"}],
|
||||
provider_context=ProviderCallContext(context_window_tokens=200_000),
|
||||
)
|
||||
|
||||
create = provider._client.responses.create
|
||||
assert result.content == "compaction fallback"
|
||||
assert result.provider_state is not None
|
||||
assert create.await_count == 2
|
||||
assert "context_management" in create.call_args_list[0].kwargs
|
||||
assert "context_management" not in create.call_args_list[1].kwargs
|
||||
assert provider.supports_native_compaction() is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_uses_default_model():
|
||||
provider = AzureOpenAIProvider(
|
||||
@@ -411,6 +468,7 @@ async def test_chat_with_tool_calls():
|
||||
assert len(result.tool_calls) == 1
|
||||
assert result.tool_calls[0].name == "get_weather"
|
||||
assert result.tool_calls[0].arguments == {"location": "SF"}
|
||||
assert result.provider_state is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -510,6 +568,7 @@ async def test_chat_stream_with_tool_calls():
|
||||
item_done.name = "get_weather"
|
||||
ev_item_done = MagicMock(type="response.output_item.done", item=item_done)
|
||||
resp_obj = MagicMock(status="completed")
|
||||
resp_obj.model_dump.return_value = {"status": "completed", "output": []}
|
||||
ev_completed = MagicMock(type="response.completed", response=resp_obj)
|
||||
|
||||
async def mock_stream():
|
||||
@@ -527,6 +586,7 @@ async def test_chat_stream_with_tool_calls():
|
||||
assert len(result.tool_calls) == 1
|
||||
assert result.tool_calls[0].name == "get_weather"
|
||||
assert result.tool_calls[0].arguments == {"location": "SF"}
|
||||
assert result.provider_state is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user