Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Jean-Malo Delignon <56539593+jean-malo@users.noreply.github.com> Co-authored-by: Paul Cacheux <paul.cacheux@mistral.ai> Co-authored-by: Quentin <torroba.q@gmail.com> Co-authored-by: angelapopopo <angele.lenglemetz@mistral.ai> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
161 lines
5.6 KiB
Python
161 lines
5.6 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
from acp.schema import AgentMessageChunk, AgentThoughtChunk, TextContentBlock
|
|
import pytest
|
|
|
|
from tests.acp.conftest import _create_acp_agent
|
|
from tests.conftest import build_test_vibe_config
|
|
from tests.stubs.fake_backend import FakeBackend
|
|
from tests.stubs.fake_client import FakeClient
|
|
from vibe.acp.acp_agent_loop import VibeAcpAgentLoop
|
|
from vibe.core.agent_loop import AgentLoop
|
|
from vibe.core.types import LLMChunk, LLMMessage, LLMUsage, Role
|
|
|
|
|
|
def _create_backend_with_reasoning(
|
|
reasoning_content: str, content: str = "Hi"
|
|
) -> FakeBackend:
|
|
return FakeBackend(
|
|
LLMChunk(
|
|
message=LLMMessage(
|
|
role=Role.assistant,
|
|
content=content,
|
|
reasoning_content=reasoning_content,
|
|
),
|
|
usage=LLMUsage(prompt_tokens=1, completion_tokens=1),
|
|
)
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def backend_with_reasoning() -> FakeBackend:
|
|
return _create_backend_with_reasoning("Let me think about this...")
|
|
|
|
|
|
@pytest.fixture
|
|
def acp_agent_loop_with_reasoning(
|
|
backend_with_reasoning: FakeBackend,
|
|
) -> VibeAcpAgentLoop:
|
|
config = build_test_vibe_config(active_model="devstral-latest")
|
|
|
|
class PatchedAgentLoop(AgentLoop):
|
|
def __init__(self, *args, **kwargs) -> None:
|
|
super().__init__(*args, **{**kwargs, "backend": backend_with_reasoning})
|
|
self._base_config = config
|
|
self.agent_manager.invalidate_config()
|
|
|
|
patch("vibe.acp.acp_agent_loop.AgentLoop", side_effect=PatchedAgentLoop).start()
|
|
return _create_acp_agent()
|
|
|
|
|
|
class TestACPAgentThought:
|
|
@pytest.mark.asyncio
|
|
async def test_prompt_with_reasoning_emits_agent_thought_chunk(
|
|
self, acp_agent_loop_with_reasoning: VibeAcpAgentLoop
|
|
) -> None:
|
|
session_response = await acp_agent_loop_with_reasoning.new_session(
|
|
cwd=str(Path.cwd()), mcp_servers=[]
|
|
)
|
|
session_id = session_response.session_id
|
|
|
|
await acp_agent_loop_with_reasoning.prompt(
|
|
session_id=session_id,
|
|
prompt=[TextContentBlock(type="text", text="Just say hi")],
|
|
)
|
|
|
|
fake_client: FakeClient = acp_agent_loop_with_reasoning.client # type: ignore
|
|
thought_updates = [
|
|
update
|
|
for update in fake_client._session_updates
|
|
if isinstance(update.update, AgentThoughtChunk)
|
|
]
|
|
|
|
assert len(thought_updates) == 1
|
|
thought_chunk = thought_updates[0].update
|
|
assert thought_chunk.session_update == "agent_thought_chunk"
|
|
assert thought_chunk.content is not None
|
|
assert isinstance(thought_chunk.content, TextContentBlock)
|
|
assert thought_chunk.content.text == "Let me think about this..."
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prompt_without_reasoning_does_not_emit_agent_thought_chunk(
|
|
self, acp_agent_loop: VibeAcpAgentLoop
|
|
) -> None:
|
|
session_response = await acp_agent_loop.new_session(
|
|
cwd=str(Path.cwd()), mcp_servers=[]
|
|
)
|
|
session_id = session_response.session_id
|
|
|
|
await acp_agent_loop.prompt(
|
|
session_id=session_id,
|
|
prompt=[TextContentBlock(type="text", text="Just say hi")],
|
|
)
|
|
|
|
fake_client: FakeClient = acp_agent_loop.client # type: ignore
|
|
thought_updates = [
|
|
update
|
|
for update in fake_client._session_updates
|
|
if isinstance(update.update, AgentThoughtChunk)
|
|
]
|
|
|
|
assert len(thought_updates) == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_agent_thought_chunk_contains_text_content_block(
|
|
self, acp_agent_loop_with_reasoning: VibeAcpAgentLoop
|
|
) -> None:
|
|
session_response = await acp_agent_loop_with_reasoning.new_session(
|
|
cwd=str(Path.cwd()), mcp_servers=[]
|
|
)
|
|
session_id = session_response.session_id
|
|
|
|
await acp_agent_loop_with_reasoning.prompt(
|
|
session_id=session_id, prompt=[TextContentBlock(type="text", text="Hello")]
|
|
)
|
|
|
|
fake_client: FakeClient = acp_agent_loop_with_reasoning.client # type: ignore
|
|
thought_updates = [
|
|
update
|
|
for update in fake_client._session_updates
|
|
if isinstance(update.update, AgentThoughtChunk)
|
|
]
|
|
|
|
assert len(thought_updates) == 1
|
|
thought_chunk = thought_updates[0].update
|
|
assert thought_chunk.content.type == "text"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_agent_thought_chunk_contains_message_id(
|
|
self, acp_agent_loop_with_reasoning: VibeAcpAgentLoop
|
|
) -> None:
|
|
session_response = await acp_agent_loop_with_reasoning.new_session(
|
|
cwd=str(Path.cwd()), mcp_servers=[]
|
|
)
|
|
session_id = session_response.session_id
|
|
|
|
await acp_agent_loop_with_reasoning.prompt(
|
|
session_id=session_id, prompt=[TextContentBlock(type="text", text="Hello")]
|
|
)
|
|
|
|
fake_client: FakeClient = acp_agent_loop_with_reasoning.client # type: ignore
|
|
thought_updates = [
|
|
update
|
|
for update in fake_client._session_updates
|
|
if isinstance(update.update, AgentThoughtChunk)
|
|
]
|
|
agent_updates = [
|
|
update
|
|
for update in fake_client._session_updates
|
|
if isinstance(update.update, AgentMessageChunk)
|
|
]
|
|
|
|
assert len(thought_updates) == 1
|
|
thought_chunk = thought_updates[0].update
|
|
assert thought_chunk.message_id is not None
|
|
|
|
assert len(agent_updates) == 1
|
|
agent_chunk = agent_updates[0].update
|
|
assert thought_chunk.message_id != agent_chunk.message_id
|