Files
mistral-vibe/tests/backend/test_reasoning_adapter.py
Mathias Gesbert e9a9217cc8 v2.7.4 (#579)
Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai>
Co-authored-by: Kim-Adeline Miguel <kimadeline.miguel@mistral.ai>
Co-authored-by: Lucas Marandat <31749711+lucasmrdt@users.noreply.github.com>
Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com>
Co-authored-by: Paul Cacheux <paul.cacheux@mistral.ai>
Co-authored-by: Peter Evers <pevers90@gmail.com>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Pierre Rossinès <pierre.rossines@protonmail.com>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Simon Van de Kerckhove <simon.vandekerckhove@mistral.ai>
Co-authored-by: Val <102326092+vdeva@users.noreply.github.com>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-04-09 18:40:46 +02:00

255 lines
8.6 KiB
Python

from __future__ import annotations
import json
import pytest
from vibe.core.config import ProviderConfig
from vibe.core.llm.backend.reasoning_adapter import ReasoningAdapter
from vibe.core.types import (
AvailableFunction,
AvailableTool,
FunctionCall,
LLMMessage,
Role,
ToolCall,
)
@pytest.fixture
def adapter():
return ReasoningAdapter()
@pytest.fixture
def provider():
return ProviderConfig(
name="test-reasoning",
api_base="https://api.example.com/v1",
api_key_env_var="TEST_API_KEY",
api_style="reasoning",
)
def _prepare(adapter, provider, messages, **kwargs):
defaults = dict(
model_name="m",
messages=messages,
temperature=0,
tools=None,
max_tokens=None,
tool_choice=None,
enable_streaming=False,
provider=provider,
)
defaults.update(kwargs)
return json.loads(adapter.prepare_request(**defaults).body)
class TestReasoningEffort:
@pytest.mark.parametrize("level", ["low", "medium", "high"])
def test_sets_reasoning_effort(self, adapter, provider, level):
payload = _prepare(
adapter,
provider,
[LLMMessage(role=Role.user, content="Hi")],
thinking=level,
)
assert payload["reasoning_effort"] == level
def test_omitted_when_off(self, adapter, provider):
payload = _prepare(
adapter,
provider,
[LLMMessage(role=Role.user, content="Hi")],
thinking="off",
)
assert "reasoning_effort" not in payload
class TestThinkingBlocksConversion:
def test_assistant_with_reasoning_to_content_blocks(self, adapter, provider):
messages = [
LLMMessage(role=Role.user, content="Hi"),
LLMMessage(
role=Role.assistant,
content="Answer",
reasoning_content="Let me think...",
),
]
payload = _prepare(adapter, provider, messages, thinking="medium")
msg = payload["messages"][1]
assert msg["content"] == [
{
"type": "thinking",
"thinking": [{"type": "text", "text": "Let me think..."}],
},
{"type": "text", "text": "Answer"},
]
def test_assistant_without_reasoning_is_plain_string(self, adapter, provider):
messages = [
LLMMessage(role=Role.user, content="Hi"),
LLMMessage(role=Role.assistant, content="Hello"),
]
payload = _prepare(adapter, provider, messages)
assert payload["messages"][1]["content"] == "Hello"
def test_assistant_with_reasoning_and_tool_calls(self, adapter, provider):
messages = [
LLMMessage(role=Role.user, content="Hi"),
LLMMessage(
role=Role.assistant,
content="Let me search.",
reasoning_content="I should look this up.",
tool_calls=[
ToolCall(
id="tc_1",
index=0,
function=FunctionCall(name="search", arguments='{"q": "test"}'),
)
],
),
]
payload = _prepare(adapter, provider, messages, thinking="medium")
msg = payload["messages"][1]
assert msg["content"][0]["type"] == "thinking"
assert msg["content"][1] == {"type": "text", "text": "Let me search."}
assert msg["tool_calls"][0]["id"] == "tc_1"
assert msg["tool_calls"][0]["function"]["name"] == "search"
def test_tools_in_payload(self, adapter, provider):
tools = [
AvailableTool(
function=AvailableFunction(
name="search",
description="Search things",
parameters={"type": "object", "properties": {}},
)
)
]
payload = _prepare(
adapter, provider, [LLMMessage(role=Role.user, content="Hi")], tools=tools
)
assert len(payload["tools"]) == 1
assert payload["tools"][0]["function"]["name"] == "search"
class TestParseThinkingBlocks:
def test_string_content(self, adapter, provider):
data = {
"choices": [{"message": {"role": "assistant", "content": "Hello!"}}],
"usage": {"prompt_tokens": 10, "completion_tokens": 5},
}
chunk = adapter.parse_response(data, provider)
assert chunk.message.content == "Hello!"
assert chunk.message.reasoning_content is None
def test_thinking_and_text_blocks(self, adapter, provider):
data = {
"choices": [
{
"message": {
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": [
{"type": "text", "text": "Let me reason..."}
],
},
{"type": "text", "text": "Final answer"},
],
}
}
],
"usage": {"prompt_tokens": 1, "completion_tokens": 1},
}
chunk = adapter.parse_response(data, provider)
assert chunk.message.content == "Final answer"
assert chunk.message.reasoning_content == "Let me reason..."
def test_multiple_thinking_inner_blocks(self, adapter, provider):
data = {
"choices": [
{
"message": {
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": [
{"type": "text", "text": "Step 1. "},
{"type": "text", "text": "Step 2."},
],
},
{"type": "text", "text": "Done"},
],
}
}
],
"usage": {"prompt_tokens": 1, "completion_tokens": 1},
}
chunk = adapter.parse_response(data, provider)
assert chunk.message.reasoning_content == "Step 1. Step 2."
def test_tool_calls_in_response(self, adapter, provider):
data = {
"choices": [
{
"message": {
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": [
{"type": "text", "text": "need to search"}
],
},
{"type": "text", "text": "Searching..."},
],
"tool_calls": [
{
"id": "tc_1",
"index": 0,
"function": {
"name": "search",
"arguments": '{"q": "test"}',
},
}
],
}
}
],
"usage": {"prompt_tokens": 1, "completion_tokens": 1},
}
chunk = adapter.parse_response(data, provider)
assert chunk.message.reasoning_content == "need to search"
assert chunk.message.content == "Searching..."
assert chunk.message.tool_calls[0].function.name == "search"
def test_streaming_text_delta_is_plain_string(self, adapter, provider):
data = {"choices": [{"delta": {"role": "assistant", "content": "Hi"}}]}
chunk = adapter.parse_response(data, provider)
assert chunk.message.content == "Hi"
assert chunk.message.reasoning_content is None
def test_thinking_delta_streaming(self, adapter, provider):
data = {
"choices": [
{
"delta": {
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": [{"type": "text", "text": "hmm"}],
}
],
}
}
]
}
chunk = adapter.parse_response(data, provider)
assert chunk.message.reasoning_content == "hmm"