v2.13.0 (#733)
Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai> Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com> Co-authored-by: Peter Evers <peter.evers@mistral.ai> Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai> Co-authored-by: Quentin <quentin.torroba@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>
This commit is contained in:
parent
198277af3f
commit
ad0d5c9520
61 changed files with 969 additions and 461 deletions
|
|
@ -315,7 +315,7 @@ class TestAdapterPrepareRequest:
|
|||
payload = json.loads(req.body)
|
||||
assert payload["model"] == "claude-sonnet-4-20250514"
|
||||
assert payload["max_tokens"] == 1024
|
||||
assert payload["temperature"] == 0.5
|
||||
assert "temperature" not in payload
|
||||
assert req.endpoint == "/v1/messages"
|
||||
assert req.headers["anthropic-version"] == "2023-06-01"
|
||||
|
||||
|
|
@ -392,9 +392,10 @@ class TestAdapterPrepareRequest:
|
|||
thinking="medium",
|
||||
)
|
||||
payload = json.loads(req.body)
|
||||
assert payload["thinking"] == {"type": "enabled", "budget_tokens": 10000}
|
||||
assert payload["thinking"] == {"type": "adaptive", "display": "summarized"}
|
||||
assert payload["output_config"] == {"effort": "medium"}
|
||||
assert payload["max_tokens"] == 1024
|
||||
assert payload["temperature"] == 1
|
||||
assert "temperature" not in payload
|
||||
|
||||
def test_system_cached(self, adapter, provider):
|
||||
messages = [
|
||||
|
|
@ -439,12 +440,8 @@ class TestAdapterPrepareRequest:
|
|||
assert len(payload["tools"]) == 1
|
||||
assert payload["tools"][0]["name"] == "test_tool"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"level,expected_budget", [("low", 1024), ("medium", 10_000), ("high", 32_000)]
|
||||
)
|
||||
def test_thinking_levels_budget_model(
|
||||
self, adapter, provider, level, expected_budget
|
||||
):
|
||||
@pytest.mark.parametrize("level", ["low", "medium", "high", "max"])
|
||||
def test_thinking_levels(self, adapter, provider, level):
|
||||
messages = [LLMMessage(role=Role.user, content="Hello")]
|
||||
req = adapter.prepare_request(
|
||||
model_name="claude-sonnet-4-20250514",
|
||||
|
|
@ -458,59 +455,12 @@ class TestAdapterPrepareRequest:
|
|||
thinking=level,
|
||||
)
|
||||
payload = json.loads(req.body)
|
||||
assert payload["thinking"] == {
|
||||
"type": "enabled",
|
||||
"budget_tokens": expected_budget,
|
||||
}
|
||||
assert payload["temperature"] == 1
|
||||
assert payload["max_tokens"] == expected_budget + 8192
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model_name", ["claude-opus-4-6-20260101", "claude-opus-4-7-20260418"]
|
||||
)
|
||||
@pytest.mark.parametrize("level", ["low", "medium", "high"])
|
||||
def test_thinking_levels_adaptive_model(self, adapter, provider, model_name, level):
|
||||
messages = [LLMMessage(role=Role.user, content="Hello")]
|
||||
req = adapter.prepare_request(
|
||||
model_name=model_name,
|
||||
messages=messages,
|
||||
temperature=0.5,
|
||||
tools=None,
|
||||
max_tokens=None,
|
||||
tool_choice=None,
|
||||
enable_streaming=False,
|
||||
provider=provider,
|
||||
thinking=level,
|
||||
)
|
||||
payload = json.loads(req.body)
|
||||
assert payload["thinking"] == {"type": "adaptive", "display": "summarized"}
|
||||
assert payload["output_config"] == {"effort": level}
|
||||
if "opus-4-7" in model_name:
|
||||
assert "temperature" not in payload
|
||||
else:
|
||||
assert payload["temperature"] == 1
|
||||
assert "temperature" not in payload
|
||||
assert payload["max_tokens"] == 32_768
|
||||
|
||||
@pytest.mark.parametrize("thinking_level", ["off", "low", "medium", "high"])
|
||||
def test_temperature_omitted_for_deprecated_model(
|
||||
self, adapter, provider, thinking_level
|
||||
):
|
||||
messages = [LLMMessage(role=Role.user, content="Hello")]
|
||||
req = adapter.prepare_request(
|
||||
model_name="claude-opus-4-7-20260418",
|
||||
messages=messages,
|
||||
temperature=0.5,
|
||||
tools=None,
|
||||
max_tokens=None,
|
||||
tool_choice=None,
|
||||
enable_streaming=False,
|
||||
provider=provider,
|
||||
thinking=thinking_level,
|
||||
)
|
||||
payload = json.loads(req.body)
|
||||
assert "temperature" not in payload
|
||||
|
||||
def test_history_forced_thinking_budget_model(self, adapter, provider):
|
||||
def test_history_forced_thinking(self, adapter, provider):
|
||||
messages = [
|
||||
LLMMessage(role=Role.user, content="Hello"),
|
||||
LLMMessage(
|
||||
|
|
@ -532,34 +482,9 @@ class TestAdapterPrepareRequest:
|
|||
provider=provider,
|
||||
)
|
||||
payload = json.loads(req.body)
|
||||
assert payload["thinking"] == {"type": "enabled", "budget_tokens": 10_000}
|
||||
assert payload["temperature"] == 1
|
||||
assert payload["max_tokens"] == 18_192
|
||||
|
||||
def test_history_forced_thinking_adaptive_model(self, adapter, provider):
|
||||
messages = [
|
||||
LLMMessage(role=Role.user, content="Hello"),
|
||||
LLMMessage(
|
||||
role=Role.assistant,
|
||||
content="Answer",
|
||||
reasoning_content="thinking...",
|
||||
reasoning_signature="sig",
|
||||
),
|
||||
LLMMessage(role=Role.user, content="Follow up"),
|
||||
]
|
||||
req = adapter.prepare_request(
|
||||
model_name="claude-opus-4-6-20260101",
|
||||
messages=messages,
|
||||
temperature=0.5,
|
||||
tools=None,
|
||||
max_tokens=None,
|
||||
tool_choice=None,
|
||||
enable_streaming=False,
|
||||
provider=provider,
|
||||
)
|
||||
payload = json.loads(req.body)
|
||||
assert payload["thinking"] == {"type": "adaptive", "display": "summarized"}
|
||||
assert payload["output_config"] == {"effort": "medium"}
|
||||
assert "temperature" not in payload
|
||||
assert payload["max_tokens"] == 32_768
|
||||
|
||||
|
||||
|
|
|
|||
140
tests/backend/test_ssl_integration.py
Normal file
140
tests/backend/test_ssl_integration.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from ipaddress import IPv4Address
|
||||
from pathlib import Path
|
||||
import ssl
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.x509.oid import ExtendedKeyUsageOID, NameOID
|
||||
import pytest
|
||||
|
||||
from tests.e2e.mock_server import StreamingMockServer
|
||||
from vibe.core.config import ModelConfig, ProviderConfig
|
||||
from vibe.core.llm.backend.generic import GenericBackend
|
||||
from vibe.core.types import Backend, LLMMessage, Role
|
||||
from vibe.core.utils import build_ssl_context, configure_ssl_context
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _TLSMaterial:
|
||||
ca_file: str
|
||||
cert_file: str
|
||||
key_file: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _HttpsStreamingMockServer:
|
||||
server: StreamingMockServer
|
||||
ca_file: str
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def https_streaming_mock_server(tmp_path: Path) -> Iterator[_HttpsStreamingMockServer]:
|
||||
tls = _write_tls_material(tmp_path)
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ssl_context.load_cert_chain(certfile=tls.cert_file, keyfile=tls.key_file)
|
||||
server = StreamingMockServer(ssl_context=ssl_context)
|
||||
server.start()
|
||||
try:
|
||||
yield _HttpsStreamingMockServer(server=server, ca_file=tls.ca_file)
|
||||
finally:
|
||||
server.stop()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generic_backend_streaming_uses_ssl_cert_file(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
https_streaming_mock_server: _HttpsStreamingMockServer,
|
||||
) -> None:
|
||||
monkeypatch.setenv("SSL_CERT_FILE", https_streaming_mock_server.ca_file)
|
||||
monkeypatch.delenv("SSL_CERT_DIR", raising=False)
|
||||
configure_ssl_context(enable_system_trust_store=False)
|
||||
build_ssl_context.cache_clear()
|
||||
|
||||
chunks = []
|
||||
try:
|
||||
provider = ProviderConfig(
|
||||
name="mock-provider",
|
||||
api_base=https_streaming_mock_server.server.api_base,
|
||||
api_key_env_var="MISTRAL_API_KEY",
|
||||
backend=Backend.GENERIC,
|
||||
)
|
||||
model = ModelConfig(
|
||||
name="mock-model", provider="mock-provider", alias="mock-model"
|
||||
)
|
||||
|
||||
async with GenericBackend(provider=provider, timeout=5.0) as backend:
|
||||
chunks = [
|
||||
chunk
|
||||
async for chunk in backend.complete_streaming(
|
||||
model=model, messages=[LLMMessage(role=Role.user, content="Greet")]
|
||||
)
|
||||
]
|
||||
finally:
|
||||
configure_ssl_context(enable_system_trust_store=False)
|
||||
build_ssl_context.cache_clear()
|
||||
|
||||
content = "".join(chunk.message.content or "" for chunk in chunks)
|
||||
request_payload = https_streaming_mock_server.server.requests[-1]
|
||||
assert content == "Hello from mock server"
|
||||
assert request_payload.get("stream") is True
|
||||
assert request_payload.get("model") == "mock-model"
|
||||
|
||||
|
||||
def _write_tls_material(tmp_path: Path) -> _TLSMaterial:
|
||||
now = datetime.now(UTC)
|
||||
ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
ca_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Vibe test CA")])
|
||||
ca_cert = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(ca_name)
|
||||
.issuer_name(ca_name)
|
||||
.public_key(ca_key.public_key())
|
||||
.serial_number(x509.random_serial_number())
|
||||
.not_valid_before(now - timedelta(minutes=1))
|
||||
.not_valid_after(now + timedelta(days=1))
|
||||
.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
|
||||
.sign(ca_key, hashes.SHA256())
|
||||
)
|
||||
|
||||
server_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
server_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "127.0.0.1")])
|
||||
server_cert = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(server_name)
|
||||
.issuer_name(ca_cert.subject)
|
||||
.public_key(server_key.public_key())
|
||||
.serial_number(x509.random_serial_number())
|
||||
.not_valid_before(now - timedelta(minutes=1))
|
||||
.not_valid_after(now + timedelta(days=1))
|
||||
.add_extension(
|
||||
x509.SubjectAlternativeName([x509.IPAddress(IPv4Address("127.0.0.1"))]),
|
||||
critical=False,
|
||||
)
|
||||
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
|
||||
.add_extension(
|
||||
x509.ExtendedKeyUsage([ExtendedKeyUsageOID.SERVER_AUTH]), critical=False
|
||||
)
|
||||
.sign(ca_key, hashes.SHA256())
|
||||
)
|
||||
|
||||
ca_file = tmp_path / "ca.pem"
|
||||
cert_file = tmp_path / "server.pem"
|
||||
key_file = tmp_path / "server.key"
|
||||
ca_file.write_bytes(ca_cert.public_bytes(serialization.Encoding.PEM))
|
||||
cert_file.write_bytes(server_cert.public_bytes(serialization.Encoding.PEM))
|
||||
key_file.write_bytes(
|
||||
server_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
)
|
||||
)
|
||||
return _TLSMaterial(
|
||||
ca_file=str(ca_file), cert_file=str(cert_file), key_file=str(key_file)
|
||||
)
|
||||
|
|
@ -91,7 +91,7 @@ class TestPrepareRequest:
|
|||
assert payload["anthropic_version"] == "vertex-2023-10-16"
|
||||
assert "model" not in payload
|
||||
assert payload["max_tokens"] == 1024
|
||||
assert payload["temperature"] == 0.5
|
||||
assert "temperature" not in payload
|
||||
assert req.headers["Authorization"] == "Bearer fake-token"
|
||||
assert req.headers["anthropic-beta"] == adapter.BETA_FEATURES
|
||||
assert "rawPredict" in req.endpoint
|
||||
|
|
@ -147,9 +147,10 @@ class TestPrepareRequest:
|
|||
)
|
||||
|
||||
payload = json.loads(req.body)
|
||||
assert payload["thinking"] == {"type": "enabled", "budget_tokens": 10000}
|
||||
assert payload["thinking"] == {"type": "adaptive", "display": "summarized"}
|
||||
assert payload["output_config"] == {"effort": "medium"}
|
||||
assert payload["max_tokens"] == 1024
|
||||
assert payload["temperature"] == 1
|
||||
assert "temperature" not in payload
|
||||
|
||||
def test_with_tools(self, adapter, provider):
|
||||
messages = [LLMMessage(role=Role.user, content="Hello")]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue