vibe/tests/core/experiments/test_resume.py
Mathias Gesbert 228f3c65a9
v2.10.0 (#697)
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Corentin André <corentin.andre@mistral.ai>
Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com>
Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com>
Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai>
Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@users.noreply.github.com>
Co-authored-by: Peter Evers <pevers90@gmail.com>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: MichisGitIsKing <MichisGitIsKing@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-05-19 11:56:25 +02:00

168 lines
5.3 KiB
Python

from __future__ import annotations
import json
from pathlib import Path
import pytest
from tests.conftest import build_test_vibe_config
from vibe.core.agents import AgentManager
from vibe.core.experiments.active import ExperimentName
from vibe.core.experiments.client import RemoteEvalClient
from vibe.core.experiments.manager import ExperimentManager
from vibe.core.experiments.models import EvalResponse, ExperimentAttributes
from vibe.core.session.session_loader import SessionLoader
from vibe.core.skills.manager import SkillManager
from vibe.core.system_prompt import get_universal_system_prompt
from vibe.core.tools.manager import ToolManager
from vibe.core.types import SessionMetadata
class _StubClient(RemoteEvalClient):
def __init__(self, response: EvalResponse | None) -> None:
self._response = response
self.calls: list[ExperimentAttributes] = []
async def evaluate(self, attributes: ExperimentAttributes) -> EvalResponse | None:
self.calls.append(attributes)
return self._response
async def aclose(self) -> None:
pass
def _attrs() -> ExperimentAttributes:
return ExperimentAttributes(
userId="x", entrypoint="cli", agent_version="0", os="darwin"
)
def _response_forcing(variant: str) -> EvalResponse:
return EvalResponse.model_validate({
"features": {
ExperimentName.SYSTEM_PROMPT.value: {
"defaultValue": "cli",
"rules": [
{
"force": variant,
"tracks": [
{
"experiment": {
"key": ExperimentName.SYSTEM_PROMPT.value
},
"result": {
"key": "1",
"variationId": 1,
"inExperiment": True,
},
}
],
}
],
}
}
})
@pytest.mark.asyncio
async def test_resume_round_trip_preserves_variant() -> None:
response = _response_forcing("explore")
fresh = ExperimentManager(client=_StubClient(response))
await fresh.initialize(_attrs())
persisted_state = fresh.export_state()
assert persisted_state is not None
serialized = persisted_state.model_dump_json()
rehydrated_response = EvalResponse.model_validate_json(serialized)
stub = _StubClient(None)
resumed = ExperimentManager(client=stub)
resumed.hydrate(rehydrated_response)
assert resumed.get_variant(ExperimentName.SYSTEM_PROMPT) == "explore"
assert resumed.assignments() == fresh.assignments()
assert stub.calls == []
def test_resume_old_session_without_experiments_field_falls_back_to_defaults(
tmp_path: Path,
) -> None:
legacy_metadata = {
"session_id": "legacy",
"start_time": "2025-01-01T00:00:00+00:00",
"end_time": None,
"git_commit": None,
"git_branch": None,
"environment": {"working_directory": str(tmp_path)},
"username": "tester",
"loops": [],
}
meta_path = tmp_path / "meta.json"
meta_path.write_text(json.dumps(legacy_metadata))
metadata = SessionLoader.load_metadata(tmp_path)
assert metadata.experiments is None
def test_resume_overrides_still_win_over_hydrated_state() -> None:
response = _response_forcing("explore")
manager = ExperimentManager(
client=_StubClient(None), overrides={ExperimentName.SYSTEM_PROMPT.value: "lean"}
)
manager.hydrate(response)
assert manager.get_variant(ExperimentName.SYSTEM_PROMPT) == "lean"
def test_session_metadata_round_trips_experiments_field() -> None:
response = _response_forcing("explore")
metadata = SessionMetadata(
session_id="s",
start_time="2025-01-01T00:00:00+00:00",
end_time=None,
git_commit=None,
git_branch=None,
environment={"working_directory": "/tmp"},
username="tester",
experiments=response,
)
serialized = metadata.model_dump_json()
restored = SessionMetadata.model_validate_json(serialized)
assert restored.experiments is not None
assert restored.experiments == response
def _build_managers(config):
return (
ToolManager(lambda: config),
SkillManager(lambda: config),
AgentManager(lambda: config),
)
@pytest.mark.asyncio
async def test_graduated_experiment_with_deleted_variant_file_falls_back() -> None:
config = build_test_vibe_config(
system_prompt_id="cli",
include_project_context=False,
include_prompt_detail=False,
include_model_info=False,
include_commit_signature=False,
)
response = _response_forcing("removed_after_graduation_2025_07")
manager = ExperimentManager(client=_StubClient(None))
manager.hydrate(response)
tool_manager, skill_manager, agent_manager = _build_managers(config)
prompt = get_universal_system_prompt(
tool_manager, config, skill_manager, agent_manager, experiment_manager=manager
)
default_prompt = get_universal_system_prompt(
tool_manager, config, skill_manager, agent_manager, experiment_manager=None
)
assert prompt == default_prompt