mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +08:00
core/agent: send the resolved reasoning effort with each request
This commit is contained in:
@@ -211,6 +211,20 @@ async def test_run_chat_loop_text_only() -> None:
|
||||
assert len(client.calls) == 1
|
||||
|
||||
|
||||
async def test_run_chat_loop_carries_reasoning_effort() -> None:
|
||||
client = ScriptedClient([_response(AssistantChatMessage(content="hello"))])
|
||||
messages: list[AnyChatMessage] = [UserChatMessage(content="hi")]
|
||||
_ = [e async for e in run_chat_loop(client, messages, model="m", stream=False, reasoning_effort="high")]
|
||||
assert client.calls[0].reasoning_effort == "high"
|
||||
|
||||
|
||||
async def test_run_chat_loop_omits_unset_reasoning_effort() -> None:
|
||||
client = ScriptedClient([_response(AssistantChatMessage(content="hello"))])
|
||||
messages: list[AnyChatMessage] = [UserChatMessage(content="hi")]
|
||||
_ = [e async for e in run_chat_loop(client, messages, model="m", stream=False)]
|
||||
assert client.calls[0].reasoning_effort is UNSET
|
||||
|
||||
|
||||
async def test_non_stream_emits_usage() -> None:
|
||||
client = ScriptedClient(
|
||||
[
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from msgspec import UNSET
|
||||
|
||||
from plyngent.agent.messages_bridge import (
|
||||
@@ -29,6 +31,7 @@ from plyngent.lmproto.openai_compatible.model import (
|
||||
AssistantFunctionToolCall,
|
||||
ChatCompletionsParam,
|
||||
DeveloperChatMessage,
|
||||
ReasoningEffort,
|
||||
SystemChatMessage,
|
||||
ToolChatMessage,
|
||||
ToolFunction,
|
||||
@@ -36,6 +39,9 @@ from plyngent.lmproto.openai_compatible.model import (
|
||||
UserChatMessage,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from plyngent.typedef import Unset
|
||||
|
||||
|
||||
def test_tool_items_to_anthropic_tools() -> None:
|
||||
items = [
|
||||
@@ -198,3 +204,53 @@ def test_chat_param_to_anthropic_param() -> None:
|
||||
assert body.tools is not UNSET
|
||||
assert len(body.tools) == 1
|
||||
assert body.temperature == 0.2
|
||||
# No effort configured → no thinking block, provider default applies.
|
||||
assert body.thinking is UNSET
|
||||
|
||||
|
||||
def _effort_param(effort: ReasoningEffort | Unset = UNSET) -> ChatCompletionsParam:
|
||||
return ChatCompletionsParam(
|
||||
model="claude-test",
|
||||
messages=[UserChatMessage(content="hi")],
|
||||
reasoning_effort=effort,
|
||||
)
|
||||
|
||||
|
||||
def test_chat_param_to_anthropic_param_thinking_from_effort() -> None:
|
||||
body = chat_param_to_anthropic_param(_effort_param("medium"))
|
||||
assert body.thinking is not UNSET
|
||||
assert body.thinking.type == "enabled"
|
||||
assert body.thinking.budget_tokens == 8192
|
||||
# A budget equal to max_tokens is invalid for Anthropic, so the default
|
||||
# output limit grows to leave room after it.
|
||||
assert body.max_tokens == 8192 + 1024
|
||||
|
||||
|
||||
def test_chat_param_to_anthropic_param_thinking_raises_max_tokens() -> None:
|
||||
# Anthropic requires budget_tokens < max_tokens; the answer keeps room after
|
||||
# the reasoning budget instead of the budget being shrunk to fit.
|
||||
body = chat_param_to_anthropic_param(_effort_param("high"))
|
||||
assert body.thinking is not UNSET
|
||||
assert body.thinking.budget_tokens == 16384
|
||||
assert body.max_tokens == 16384 + 1024
|
||||
|
||||
|
||||
def test_chat_param_to_anthropic_param_explicit_thinking_budget() -> None:
|
||||
body = chat_param_to_anthropic_param(_effort_param("minimal"), thinking_budget_tokens=20000)
|
||||
assert body.thinking is not UNSET
|
||||
assert body.thinking.budget_tokens == 20000
|
||||
assert body.max_tokens == 20000 + 1024
|
||||
|
||||
|
||||
def test_chat_param_to_anthropic_param_thinking_budget_floor() -> None:
|
||||
body = chat_param_to_anthropic_param(_effort_param("minimal"), thinking_budget_tokens=100)
|
||||
assert body.thinking is not UNSET
|
||||
assert body.thinking.budget_tokens == 1024
|
||||
assert body.max_tokens == 8192
|
||||
|
||||
|
||||
def test_chat_param_to_anthropic_param_thinking_disabled() -> None:
|
||||
body = chat_param_to_anthropic_param(_effort_param("none"))
|
||||
assert body.thinking is not UNSET
|
||||
assert body.thinking.type == "disabled"
|
||||
assert body.thinking.budget_tokens is UNSET
|
||||
|
||||
@@ -71,6 +71,8 @@ class ScriptedMessagesClient:
|
||||
|
||||
kind: str = "messages"
|
||||
calls: list[AnthropicMessagesParam]
|
||||
# Mirrors AnthropicClient: explicit thinking budget (0 = derive from effort).
|
||||
thinking_budget_tokens: int
|
||||
_non_stream: list[AnthropicMessageResponse]
|
||||
_stream_events: list[list[AnthropicStreamEvent]]
|
||||
|
||||
@@ -81,6 +83,7 @@ class ScriptedMessagesClient:
|
||||
stream_events: Sequence[Sequence[AnthropicStreamEvent]] | None = None,
|
||||
) -> None:
|
||||
self.calls = []
|
||||
self.thinking_budget_tokens = 0
|
||||
self._non_stream = list(non_stream or ())
|
||||
self._stream_events = [list(events) for events in (stream_events or ())]
|
||||
|
||||
@@ -186,6 +189,46 @@ async def test_run_chat_loop_messages_kind_text() -> None:
|
||||
assert any(isinstance(e, UsageEvent) and e.usage.prompt_tokens == 10 for e in events)
|
||||
assert isinstance(messages[-1], AssistantChatMessage)
|
||||
assert messages[-1].content == "done"
|
||||
# No thinking configured → the request carries no thinking block.
|
||||
assert client.calls[0].thinking is UNSET
|
||||
|
||||
|
||||
async def test_run_chat_loop_passes_reasoning_effort_and_client_thinking_budget() -> None:
|
||||
client = ScriptedMessagesClient(non_stream=[_message_response(text="done")])
|
||||
client.thinking_budget_tokens = 12000
|
||||
events = [
|
||||
event
|
||||
async for event in run_chat_loop(
|
||||
cast("Any", client),
|
||||
[UserChatMessage(content="go")],
|
||||
model="claude-test",
|
||||
stream=False,
|
||||
reasoning_effort="high",
|
||||
)
|
||||
]
|
||||
assert any(isinstance(e, TextDeltaEvent) for e in events)
|
||||
thinking = client.calls[0].thinking
|
||||
assert thinking is not UNSET
|
||||
assert thinking.type == "enabled"
|
||||
# The client's explicit budget wins over the level's derived one.
|
||||
assert thinking.budget_tokens == 12000
|
||||
|
||||
|
||||
async def test_run_chat_loop_reasoning_effort_none_disables_thinking() -> None:
|
||||
client = ScriptedMessagesClient(non_stream=[_message_response(text="done")])
|
||||
_ = [
|
||||
event
|
||||
async for event in run_chat_loop(
|
||||
cast("Any", client),
|
||||
[UserChatMessage(content="go")],
|
||||
model="claude-test",
|
||||
stream=False,
|
||||
reasoning_effort="none",
|
||||
)
|
||||
]
|
||||
thinking = client.calls[0].thinking
|
||||
assert thinking is not UNSET
|
||||
assert thinking.type == "disabled"
|
||||
|
||||
|
||||
async def test_run_chat_loop_messages_kind_tool_round() -> None:
|
||||
|
||||
@@ -235,6 +235,18 @@ def test_chat_param_to_responses_kwargs() -> None:
|
||||
assert kwargs["store"] is False
|
||||
assert kwargs["temperature"] == 0.2
|
||||
assert len(kwargs["tools"]) == 1
|
||||
# No effort configured → no reasoning block.
|
||||
assert "reasoning" not in kwargs
|
||||
|
||||
|
||||
def test_chat_param_to_responses_kwargs_reasoning_effort() -> None:
|
||||
param = ChatCompletionsParam(
|
||||
model="gpt-test",
|
||||
messages=[UserChatMessage(content="hi")],
|
||||
reasoning_effort="xhigh",
|
||||
)
|
||||
kwargs = chat_param_to_responses_kwargs(param)
|
||||
assert kwargs["reasoning"].effort == "xhigh"
|
||||
|
||||
|
||||
def test_provider_tools_merged_after_local_functions() -> None:
|
||||
|
||||
Reference in New Issue
Block a user