Files
wehub-resource-sync 25576b0be6
Checks (magui2.0) / python-lint (push) Failing after 1s
Checks (magui2.0) / python-test (push) Failing after 0s
Checks (magui2.0) / frontend-lint (push) Failing after 0s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
Checks (magui2.0) / python-format (push) Failing after 1s
CodeQL Advanced / Analyze (python) (push) Failing after 0s
Checks (magui2.0) / python-pyright (push) Failing after 1s
Checks (magui2.0) / frontend-format (push) Failing after 1s
Checks (magui2.0) / frontend-typecheck (push) Failing after 0s
Checks (magui2.0) / frontend-test (push) Failing after 2s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:24:32 +08:00

134 lines
4.5 KiB
Python

"""Integration tests for OmniAgent thinking-state stream output.
Asserts that ``OmniAgent.run_stream`` surfaces the transient
``agent_state`` signals (``calling_model`` / ``generating``) around each
LLM call and stamps ``thinking_seconds`` onto the reasoning message
when the model streamed at least one token.
"""
from __future__ import annotations
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from magentic_ui.magentic_ui_config import ApprovalPolicy
from magentic_ui.sandbox._null import NullSandbox
from magentic_ui.teams.omniagent._omni_agent import OmniAgent
from ._stream_mock import StreamScript, content_delta, install_stream_mock
def _make_completion(text: str, total_tokens: int = 50) -> MagicMock:
response = MagicMock()
response.choices = [MagicMock()]
response.choices[0].message.content = text
response.usage = MagicMock(
prompt_tokens=total_tokens - 5,
completion_tokens=5,
total_tokens=total_tokens,
)
return response
async def _build_agent(
tmp_path: Path, client: MagicMock
) -> tuple[OmniAgent, NullSandbox]:
sandbox = NullSandbox(workspace=tmp_path)
await sandbox.__aenter__()
agent = OmniAgent(
client=client,
model="test-model",
host_workspace=tmp_path,
sandbox=sandbox,
approval_policy=ApprovalPolicy.AUTO_APPROVE,
)
return agent, sandbox
def _props(event: object) -> dict:
return getattr(event, "additional_properties", {}) or {}
def _of_type(events: list[object], type_name: str) -> list[object]:
return [e for e in events if _props(e).get("type") == type_name]
class TestThinkingStateStream:
@pytest.mark.asyncio
async def test_emits_calling_model_then_generating_around_llm_call(
self, tmp_path: Path
) -> None:
"""Each LLM call yields a ``calling_model`` signal, then a
``generating`` signal once the first token streams back."""
client = MagicMock()
install_stream_mock(
client,
[
StreamScript(
events=[content_delta()],
completion=_make_completion("Let me think.\n<answer>done</answer>"),
)
],
)
agent, sandbox = await _build_agent(tmp_path, client)
try:
events = [evt async for evt in agent.run_stream("go")]
finally:
await sandbox.__aexit__(None, None, None)
states = [_props(e).get("state") for e in _of_type(events, "agent_state")]
# calling_model is emitted before the request; generating after the
# first streamed token. Order matters.
assert states == ["calling_model", "generating"]
@pytest.mark.asyncio
async def test_reasoning_carries_thinking_seconds_when_streamed(
self, tmp_path: Path
) -> None:
"""A streamed token starts the clock, so the reasoning message is
stamped with a non-negative integer ``thinking_seconds``."""
client = MagicMock()
install_stream_mock(
client,
[
StreamScript(
events=[content_delta()],
completion=_make_completion("Let me think.\n<answer>done</answer>"),
)
],
)
agent, sandbox = await _build_agent(tmp_path, client)
try:
events = [evt async for evt in agent.run_stream("go")]
finally:
await sandbox.__aexit__(None, None, None)
reasoning = _of_type(events, "reasoning")
assert len(reasoning) == 1
secs = _props(reasoning[0]).get("thinking_seconds")
assert isinstance(secs, int)
assert secs >= 0
@pytest.mark.asyncio
async def test_reasoning_omits_thinking_seconds_without_tokens(
self, tmp_path: Path
) -> None:
"""With no token stream there is no ``thinking`` signal, so the
reasoning message omits ``thinking_seconds`` and the frontend
falls back to its timestamp-diff heuristic."""
client = MagicMock()
install_stream_mock(
client, [_make_completion("Let me think.\n<answer>done</answer>")]
)
agent, sandbox = await _build_agent(tmp_path, client)
try:
events = [evt async for evt in agent.run_stream("go")]
finally:
await sandbox.__aexit__(None, None, None)
reasoning = _of_type(events, "reasoning")
assert len(reasoning) == 1
assert "thinking_seconds" not in _props(reasoning[0])