Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/llm_endpoint_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ agent_model:
timeout: 60.0
```

Reasoning effort is a provider-defined string that is forwarded unchanged.
Supported values, such as `low`, `medium`, `high`, or `xhigh`, depend on the
selected model deployment.

**OpenAI-compatible Responses**

Provide the URL directory in `endpoint_url`, model name in `deployment` and do not set `account_name`.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description = "A self-contained agent that thinks inside the box"
requires-python = ">=3.10"
dependencies = [
"mcp[cli]",
"fastmcp",
"fastmcp>=3.4.7,<4",
"httpx",
"pydantic",
"pydantic[email]",
Expand Down
30 changes: 30 additions & 0 deletions tests/test_aoai_responses_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from thinkingbox.common.aoai_responses_session import AOAIResponsesSession
from thinkingbox.common.chat_types import Text
from thinkingbox.common.config_types import AOAIResponsesSessionConfig


def _mock_client_factory(response_payload):
Expand Down Expand Up @@ -48,6 +49,16 @@ async def post(self, url, *args, **kwargs):
}


@pytest.mark.parametrize("reasoning_effort", ["xhigh", "provider-defined"])
def test_config_accepts_provider_defined_reasoning_effort(reasoning_effort):
config = AOAIResponsesSessionConfig(
deployment="test",
reasoning_effort=reasoning_effort,
)

assert config.reasoning_effort == reasoning_effort


@pytest.mark.asyncio
async def test_response_schema_in_payload(monkeypatch):
"""response_schema should build the full text.format in the request payload."""
Expand Down Expand Up @@ -83,6 +94,25 @@ async def test_response_schema_in_payload(monkeypatch):
}


@pytest.mark.asyncio
async def test_xhigh_reasoning_effort_in_payload(monkeypatch):
session = AOAIResponsesSession(
deployment="test",
endpoint_url="https://test",
is_reasoning=True,
reasoning_source="none",
reasoning_effort="xhigh",
)
mock_client = _mock_client_factory(SIMPLE_RESPONSE)
monkeypatch.setattr(session, "get_client", partial(lambda c: c, mock_client))

await session.get_completion(
conversation=[Text(role="user", content="Hello")],
)

assert mock_client.last_post_kwargs["json"]["reasoning"] == {"effort": "xhigh"}


@pytest.mark.asyncio
async def test_response_schema_requires_explicit_conversation():
"""response_schema without explicit conversation should raise ValueError."""
Expand Down
27 changes: 27 additions & 0 deletions tests/test_aoai_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ def get_test_aoai_config():
)


@pytest.mark.parametrize("reasoning_effort", ["xhigh", "provider-defined"])
def test_config_accepts_provider_defined_reasoning_effort(reasoning_effort):
config = AOAISessionConfig(
deployment="test_deployment",
reasoning_effort=reasoning_effort,
)

assert config.reasoning_effort == reasoning_effort


SIMPLE_RESPONSE = {
"choices": [{"message": {"role": "assistant", "content": "Test response"}}]
}
Expand Down Expand Up @@ -148,6 +158,23 @@ async def test_response_schema_in_payload(mock_async_client):
}


@pytest.mark.asyncio
@patch("thinkingbox.common.llm_session_base.httpx.AsyncClient")
async def test_xhigh_reasoning_effort_in_payload(mock_async_client):
mock_async_client.return_value = get_mock_async_client(SIMPLE_RESPONSE)
config = get_test_aoai_config().model_copy(
update={"is_reasoning": True, "reasoning_effort": "xhigh"}
)
session = AOAISession.from_config(config)

await session._get_completion()

post_mock = mock_async_client.return_value.__aenter__.return_value.post
call_kwargs = post_mock.call_args
payload = call_kwargs.kwargs.get("json") or call_kwargs[1]["json"]
assert payload["reasoning_effort"] == "xhigh"


@pytest.mark.asyncio
async def test_response_schema_requires_explicit_conversation():
"""response_schema without explicit conversation should raise ValueError."""
Expand Down
6 changes: 3 additions & 3 deletions thinkingbox/common/aoai_responses_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import copy
import json
from enum import Enum
from typing import Any, Literal
from typing import Any

from thinkingbox.common.chat_types import (
Message,
Expand All @@ -14,7 +14,7 @@
ToolDef,
ToolResponse,
)
from thinkingbox.common.config_types import AOAIResponsesSessionConfig
from thinkingbox.common.config_types import AOAIResponsesSessionConfig, ReasoningEffort
from thinkingbox.common.credential_factory import create_credential
from thinkingbox.common.llm_session_base import HTTPLLMSessionBase
from thinkingbox.common.usage_types import Usage
Expand Down Expand Up @@ -251,7 +251,7 @@ def _get_conversation_messages(
async def _responses(
self,
messages: list[dict],
reasoning_effort_hint: Literal["low", "medium", "high", None],
reasoning_effort_hint: ReasoningEffort,
**kwargs,
):
payload = {
Expand Down
6 changes: 4 additions & 2 deletions thinkingbox/common/config_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

logger = logging.getLogger(__name__)

ReasoningEffort = str | None

# config.yaml


Expand Down Expand Up @@ -87,7 +89,7 @@ class AOAISessionConfig(HTTPLLMSessionConfig):
temperature: float = 1.0
max_completion_tokens: int = 4096
is_reasoning: bool = False
reasoning_effort: Literal["low", "medium", "high", None] = None
reasoning_effort: ReasoningEffort = None
api_version: str = "2024-10-21"
disabled_params: list[str] = Field(default_factory=list)
parallel_tool_calls: bool = False
Expand All @@ -103,7 +105,7 @@ class AOAIResponsesSessionConfig(HTTPLLMSessionConfig):
max_completion_tokens: int = 4096
is_reasoning: bool = False
reasoning_source: Literal["none", "summary", "content"] = "summary"
reasoning_effort: Literal["low", "medium", "high", None] = None
reasoning_effort: ReasoningEffort = None
use_stateful_protocol: bool = False
api_version: str = "2024-10-21"
parallel_tool_calls: bool = False
Expand Down