Files
wehub-resource-sync e9a2f726c9
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / test (3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:29:51 +08:00

3092 lines
103 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""
End-to-end streaming tests for oMLX server.
Tests streaming response formats for OpenAI and Anthropic APIs
using mock AsyncIterator without loading actual models.
"""
import json
import pytest
from dataclasses import dataclass, field
from typing import Any, AsyncIterator, Dict, List, Optional
from unittest.mock import AsyncMock, MagicMock, patch
from omlx.engine.base import BaseEngine
@dataclass
class MockGenerationOutput:
"""Mock generation output for streaming tests."""
text: str = ""
tokens: List[int] = field(default_factory=list)
prompt_tokens: int = 10
completion_tokens: int = 0
finish_reason: Optional[str] = None
new_text: str = ""
finished: bool = False
tool_calls: Optional[List[Dict[str, Any]]] = None
cached_tokens: int = 0
class MockTokenizer:
"""Mock tokenizer for testing."""
def __init__(self):
self.eos_token_id = 2
def encode(self, text: str) -> List[int]:
return [100 + i for i, _ in enumerate(text.split())]
def decode(self, tokens: List[int], skip_special_tokens: bool = True) -> str:
return f"<decoded:{len(tokens)} tokens>"
def apply_chat_template(
self, messages: List[Dict], tokenize: bool = False, **kwargs
) -> str:
parts = []
for msg in messages:
role = msg.get("role", "user")
content = msg.get("content", "")
parts.append(f"{role}: {content}")
return "\n".join(parts)
class MockBaseEngine(BaseEngine):
"""Mock LLM engine with streaming support for testing."""
def __init__(self, model_name: str = "test-model"):
self._model_name = model_name
self._tokenizer = MockTokenizer()
self._model_type = "llama"
# Configurable streaming responses
self._stream_outputs: List[MockGenerationOutput] = []
@property
def model_name(self) -> str:
return self._model_name
@property
def tokenizer(self):
return self._tokenizer
@property
def model_type(self) -> Optional[str]:
return self._model_type
@property
def prefix_cache_enabled(self) -> bool:
return False
async def start(self) -> None:
pass
async def stop(self) -> None:
pass
def set_stream_outputs(self, outputs: List[MockGenerationOutput]):
"""Set custom streaming outputs for testing."""
self._stream_outputs = outputs
async def generate(self, prompt: str, **kwargs) -> MockGenerationOutput:
return MockGenerationOutput(
text="Generated response.",
completion_tokens=5,
finish_reason="stop",
finished=True,
)
async def stream_generate(self, prompt: str, **kwargs) -> AsyncIterator[MockGenerationOutput]:
if self._stream_outputs:
for output in self._stream_outputs:
yield output
else:
yield MockGenerationOutput(
text="Hello",
new_text="Hello",
completion_tokens=1,
finished=False,
)
yield MockGenerationOutput(
text="Hello world",
new_text=" world",
completion_tokens=2,
finished=True,
finish_reason="stop",
)
def count_chat_tokens(
self, messages: List[Dict], tools=None, chat_template_kwargs=None, **kwargs
) -> int:
prompt = self._tokenizer.apply_chat_template(messages, tokenize=False)
return len(self._tokenizer.encode(prompt))
async def chat(self, messages: List[Dict], **kwargs) -> MockGenerationOutput:
return MockGenerationOutput(
text="Chat response.",
completion_tokens=5,
finish_reason="stop",
finished=True,
)
async def stream_chat(self, messages: List[Dict], **kwargs) -> AsyncIterator[MockGenerationOutput]:
if self._stream_outputs:
for output in self._stream_outputs:
yield output
else:
yield MockGenerationOutput(
text="Hi",
new_text="Hi",
completion_tokens=1,
finished=False,
)
yield MockGenerationOutput(
text="Hi there",
new_text=" there",
completion_tokens=2,
finished=True,
finish_reason="stop",
)
def get_stats(self) -> Dict[str, Any]:
return {}
def get_cache_stats(self) -> Optional[Dict[str, Any]]:
return None
class MockEnginePool:
"""Mock engine pool for testing."""
def __init__(self, engine: Optional[MockBaseEngine] = None):
self._engine = engine or MockBaseEngine()
self._models = [
{"id": "test-model", "loaded": True, "pinned": False, "size": 1000000}
]
self._entries: Dict[str, Any] = {}
@property
def model_count(self) -> int:
return len(self._models)
@property
def loaded_model_count(self) -> int:
return 1
@property
def max_model_memory(self) -> int:
return 32 * 1024 * 1024 * 1024
@property
def current_model_memory(self) -> int:
return 1000000
def get_entry(self, model_id: str):
return self._entries.get(model_id)
def resolve_model_id(self, model_id_or_alias, settings_manager=None):
return model_id_or_alias
def get_model_ids(self) -> List[str]:
return [m["id"] for m in self._models]
def get_status(self) -> Dict[str, Any]:
return {"models": self._models}
async def get_engine(self, model_id: str, _lease: bool = False):
return self._engine
async def release_engine(self, model_id: str) -> None:
return None
def parse_sse_events(response_text: str) -> List[Dict]:
"""Parse SSE events from response text."""
events = []
for line in response_text.strip().split("\n"):
if line.startswith("data: "):
data = line[6:] # Remove "data: " prefix
if data == "[DONE]":
events.append({"done": True})
else:
try:
events.append(json.loads(data))
except json.JSONDecodeError:
pass
return events
class TestOpenAIStreamingFormat:
"""Tests for OpenAI streaming response format (SSE)."""
@pytest.fixture
def mock_engine(self):
"""Create mock engine."""
return MockBaseEngine()
@pytest.fixture
def mock_engine_pool(self, mock_engine):
"""Create mock engine pool."""
return MockEnginePool(mock_engine)
@pytest.fixture
def client(self, mock_engine_pool):
"""Create test client with mocked state."""
from fastapi.testclient import TestClient
from omlx.server import app, _server_state
original_pool = _server_state.engine_pool
original_default = _server_state.default_model
_server_state.engine_pool = mock_engine_pool
_server_state.default_model = "test-model"
yield TestClient(app)
_server_state.engine_pool = original_pool
_server_state.default_model = original_default
@pytest.mark.slow
@pytest.mark.integration
def test_chat_completion_streaming_format(self, client):
"""Test that streaming chat completion returns SSE format."""
response = client.post(
"/v1/chat/completions",
json={
"model": "test-model",
"messages": [{"role": "user", "content": "Hello"}],
"stream": True,
},
)
assert response.status_code == 200
assert "text/event-stream" in response.headers["content-type"]
@pytest.mark.slow
@pytest.mark.integration
def test_chat_completion_streaming_events(self, client):
"""Test streaming events structure."""
response = client.post(
"/v1/chat/completions",
json={
"model": "test-model",
"messages": [{"role": "user", "content": "Hello"}],
"stream": True,
},
)
assert response.status_code == 200
events = parse_sse_events(response.text)
# Should have at least one content event and a [DONE] event
assert len(events) >= 2
# Check that last event is DONE
assert events[-1].get("done") is True
# Check structure of first chunk
first_chunk = events[0]
assert "id" in first_chunk
assert first_chunk["object"] == "chat.completion.chunk"
assert "choices" in first_chunk
@pytest.mark.slow
@pytest.mark.integration
def test_chat_completion_streaming_role_in_first_chunk(self, client):
"""Test that first chunk contains role."""
response = client.post(
"/v1/chat/completions",
json={
"model": "test-model",
"messages": [{"role": "user", "content": "Test"}],
"stream": True,
},
)
events = parse_sse_events(response.text)
non_done_events = [e for e in events if not e.get("done")]
if non_done_events:
first_chunk = non_done_events[0]
# First chunk should have role in delta
assert "choices" in first_chunk
delta = first_chunk["choices"][0].get("delta", {})
assert delta.get("role") == "assistant"
@pytest.mark.slow
@pytest.mark.integration
def test_completion_streaming_format(self, client):
"""Test that streaming completion returns SSE format."""
response = client.post(
"/v1/completions",
json={
"model": "test-model",
"prompt": "Hello",
"stream": True,
},
)
assert response.status_code == 200
assert "text/event-stream" in response.headers["content-type"]
@pytest.mark.slow
@pytest.mark.integration
def test_completion_streaming_events(self, client):
"""Test completion streaming events structure."""
response = client.post(
"/v1/completions",
json={
"model": "test-model",
"prompt": "Once upon a time",
"stream": True,
},
)
events = parse_sse_events(response.text)
# Should have events and end with DONE
assert len(events) >= 2
assert events[-1].get("done") is True
# Check first content event structure
first_chunk = events[0]
assert first_chunk["object"] == "text_completion"
assert "choices" in first_chunk
assert "text" in first_chunk["choices"][0]
class TestAnthropicStreamingFormat:
"""Tests for Anthropic streaming response format (SSE events)."""
@pytest.fixture
def mock_engine(self):
"""Create mock engine."""
return MockBaseEngine()
@pytest.fixture
def mock_engine_pool(self, mock_engine):
"""Create mock engine pool."""
return MockEnginePool(mock_engine)
@pytest.fixture
def client(self, mock_engine_pool):
"""Create test client with mocked state."""
from fastapi.testclient import TestClient
from omlx.server import app, _server_state
original_pool = _server_state.engine_pool
original_default = _server_state.default_model
_server_state.engine_pool = mock_engine_pool
_server_state.default_model = "test-model"
yield TestClient(app)
_server_state.engine_pool = original_pool
_server_state.default_model = original_default
@pytest.mark.slow
@pytest.mark.integration
def test_anthropic_streaming_format(self, client):
"""Test that Anthropic streaming returns SSE format."""
response = client.post(
"/v1/messages",
json={
"model": "test-model",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello"}],
"stream": True,
},
)
assert response.status_code == 200
assert "text/event-stream" in response.headers["content-type"]
@pytest.mark.slow
@pytest.mark.integration
def test_anthropic_streaming_event_order(self, client):
"""Test Anthropic streaming event order."""
response = client.post(
"/v1/messages",
json={
"model": "test-model",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hi"}],
"stream": True,
},
)
events = parse_sse_events(response.text)
non_done_events = [e for e in events if not e.get("done")]
if len(non_done_events) >= 3:
# Check event type order
event_types = [e.get("type") for e in non_done_events]
# Should start with message_start
assert "message_start" in event_types
# Should have content_block_start
assert "content_block_start" in event_types
# Should end with message_stop
assert "message_stop" in event_types
@pytest.mark.slow
@pytest.mark.integration
def test_anthropic_message_start_event(self, client):
"""Test Anthropic message_start event structure."""
response = client.post(
"/v1/messages",
json={
"model": "test-model",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Test"}],
"stream": True,
},
)
events = parse_sse_events(response.text)
message_start = None
for event in events:
if event.get("type") == "message_start":
message_start = event
break
assert message_start is not None
assert "message" in message_start
@pytest.mark.slow
@pytest.mark.integration
def test_anthropic_message_delta_event(self, client):
"""Test Anthropic message_delta event has stop_reason."""
response = client.post(
"/v1/messages",
json={
"model": "test-model",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Quick test"}],
"stream": True,
},
)
events = parse_sse_events(response.text)
message_delta = None
for event in events:
if event.get("type") == "message_delta":
message_delta = event
break
if message_delta:
assert "delta" in message_delta
assert "usage" in message_delta
class TestStreamingHelperFunctions:
"""Tests for streaming helper functions in server module."""
@pytest.mark.asyncio
async def test_stream_completion_yields_sse(self):
"""Test stream_completion yields SSE formatted strings."""
from omlx.server import stream_completion
from omlx.api.openai_models import CompletionRequest
engine = MockBaseEngine()
request = CompletionRequest(model="test-model", prompt="Hello", stream=True)
events = []
async for event in stream_completion(engine, "Hello", request):
events.append(event)
# Should have content events and DONE
assert len(events) >= 2
assert events[-1] == "data: [DONE]\n\n"
# Check SSE format
for event in events[:-1]:
assert event.startswith("data: ")
assert event.endswith("\n\n")
@pytest.mark.asyncio
async def test_stream_completion_json_content(self):
"""Test stream_completion events contain valid JSON."""
from omlx.server import stream_completion
from omlx.api.openai_models import CompletionRequest
engine = MockBaseEngine()
request = CompletionRequest(model="test-model", prompt="Test", stream=True)
async for event in stream_completion(engine, "Test", request):
if event != "data: [DONE]\n\n":
json_str = event[6:-2] # Remove "data: " and "\n\n"
data = json.loads(json_str)
assert "id" in data
assert "model" in data
assert "choices" in data
@pytest.mark.asyncio
async def test_stream_chat_completion_yields_sse(self):
"""Test stream_chat_completion yields SSE formatted strings."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine, messages, request, max_tokens=256, temperature=0.7, top_p=0.9, top_k=40
):
events.append(event)
assert len(events) >= 2
assert events[-1] == "data: [DONE]\n\n"
@pytest.mark.asyncio
async def test_stream_chat_completion_first_chunk_has_role(self):
"""Test first streaming chunk has assistant role."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hello")],
stream=True,
)
first_event = None
messages = [{"role": "user", "content": "Hello"}]
async for event in stream_chat_completion(
engine, messages, request, max_tokens=256, temperature=0.7, top_p=0.9, top_k=40
):
if event != "data: [DONE]\n\n":
first_event = event
break
assert first_event is not None
json_str = first_event[6:-2]
data = json.loads(json_str)
assert data["choices"][0]["delta"].get("role") == "assistant"
@pytest.mark.asyncio
async def test_stream_chat_completion_prompt_opened_thinking_streams_as_reasoning(self):
"""If the rendered prompt opens <think>, initial deltas are reasoning."""
from omlx.api.openai_models import ChatCompletionRequest, Message
from omlx.server import stream_chat_completion
class PromptOpenedThinkingTokenizer(MockTokenizer):
think_start = "<think>"
think_end = "</think>"
think_start_id = 999
think_end_id = 998
unk_token_id = -1
def convert_tokens_to_ids(self, token: str):
if token == self.think_start:
return self.think_start_id
if token == self.think_end:
return self.think_end_id
return self.unk_token_id
def encode(self, text: str, add_special_tokens: bool = False) -> list[int]:
ids = [101]
if text.rstrip().endswith(self.think_start):
ids.append(self.think_start_id)
return ids
def apply_chat_template(
self, messages: list[dict], tokenize: bool = False, **kwargs
) -> str:
return "user: Hi\nassistant:<think>"
engine = MockBaseEngine()
engine._tokenizer = PromptOpenedThinkingTokenizer()
engine.set_stream_outputs([
MockGenerationOutput(
text="Need to inspect first.",
new_text="Need to inspect first.",
completion_tokens=1,
finished=False,
),
MockGenerationOutput(
text="Need to inspect first.</think>Done.",
new_text="</think>Done.",
completion_tokens=2,
finished=True,
finish_reason="stop",
),
])
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
)
payloads = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
):
if event.startswith("data: {"):
payloads.append(json.loads(event[6:-2]))
reasoning_deltas = []
content_deltas = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
delta = choices[0].get("delta", {})
if delta.get("reasoning_content"):
reasoning_deltas.append(delta["reasoning_content"])
if delta.get("content"):
content_deltas.append(delta["content"])
assert "".join(reasoning_deltas) == "Need to inspect first."
assert content_deltas == ["Done."]
@pytest.mark.asyncio
async def test_stream_chat_completion_partial_prompt_opened_thinking(self):
"""Partial-mode prompt detection must mirror continue_final_message."""
from omlx.api.openai_models import ChatCompletionRequest, Message
from omlx.server import stream_chat_completion
class PartialThinkingTokenizer(MockTokenizer):
think_start = "<think>"
think_end = "</think>"
think_start_id = 999
think_end_id = 998
unk_token_id = -1
def __init__(self):
super().__init__()
self.template_kwargs = None
def convert_tokens_to_ids(self, token: str):
if token == self.think_start:
return self.think_start_id
if token == self.think_end:
return self.think_end_id
return self.unk_token_id
def encode(self, text: str, add_special_tokens: bool = False) -> list[int]:
ids = [101]
if text.rstrip().endswith(self.think_start):
ids.append(self.think_start_id)
return ids
def apply_chat_template(
self, messages: list[dict], tokenize: bool = False, **kwargs
) -> str:
self.template_kwargs = dict(kwargs)
assert all("partial" not in message for message in messages)
if (
kwargs.get("add_generation_prompt") is False
and kwargs.get("continue_final_message") is True
):
return "user: Hi\nassistant:<think>"
return "user: Hi\nassistant:"
engine = MockBaseEngine()
engine._tokenizer = PartialThinkingTokenizer()
engine.set_stream_outputs([
MockGenerationOutput(
text="Hidden reasoning.",
new_text="Hidden reasoning.",
completion_tokens=1,
finished=False,
),
MockGenerationOutput(
text="Hidden reasoning.</think>Visible.",
new_text="</think>Visible.",
completion_tokens=2,
finished=True,
finish_reason="stop",
),
])
request = ChatCompletionRequest(
model="test-model",
messages=[
Message(role="user", content="Hi"),
Message(role="assistant", content="", partial=True),
],
stream=True,
)
payloads = []
messages = [
{"role": "user", "content": "Hi"},
{"role": "assistant", "content": "", "partial": True},
]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
is_partial=True,
):
if event.startswith("data: {"):
payloads.append(json.loads(event[6:-2]))
reasoning_deltas = []
content_deltas = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
delta = choices[0].get("delta", {})
if delta.get("reasoning_content"):
reasoning_deltas.append(delta["reasoning_content"])
if delta.get("content"):
content_deltas.append(delta["content"])
assert engine.tokenizer.template_kwargs["add_generation_prompt"] is False
assert engine.tokenizer.template_kwargs["continue_final_message"] is True
assert messages[-1]["partial"] is True
assert "".join(reasoning_deltas) == "Hidden reasoning."
assert content_deltas == ["Visible."]
@pytest.mark.asyncio
async def test_stream_chat_completion_with_tools_streams_content_incrementally(self):
"""Tool availability must not force full buffering of normal text deltas."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="Hello",
new_text="Hello",
completion_tokens=1,
finished=False,
),
MockGenerationOutput(
text="Hello world",
new_text=" world",
completion_tokens=2,
finished=True,
finish_reason="stop",
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = [
payload["choices"][0].get("delta", {}).get("content")
for payload in payloads
if payload.get("choices")
]
content_deltas = [delta for delta in content_deltas if delta]
# With two streamed model outputs, we expect two incremental content chunks,
# not one buffered chunk emitted only at completion.
assert content_deltas == ["Hello", " world"]
@pytest.mark.asyncio
async def test_stream_chat_completion_sanitizes_tool_call_markup_inside_reasoning(self):
"""Reasoning deltas should keep prose while suppressing tool-call markup."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="<think>Need to inspect first.",
new_text="<think>Need to inspect first.",
completion_tokens=1,
finished=False,
),
MockGenerationOutput(
text=(
"<think>Need to inspect first."
'<tool_call>{"name":"get_weather","arguments":{"city":"SF"}}</tool_call>'
"Then continue.</think>"
),
new_text=(
'<tool_call>{"name":"get_weather","arguments":{"city":"SF"}}</tool_call>'
"Then continue.</think>"
),
completion_tokens=2,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
reasoning_deltas = []
content_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
reasoning = delta.get("reasoning_content")
if reasoning:
reasoning_deltas.append(reasoning)
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_reasoning = "".join(reasoning_deltas)
assert "<tool_call>" not in streamed_reasoning
assert "</tool_call>" not in streamed_reasoning
assert "Need to inspect first." in streamed_reasoning
assert "Then continue." in streamed_reasoning
assert content_deltas == []
assert len(tool_call_deltas) == 1
assert tool_call_deltas[0]["function"]["name"] == "get_weather"
assert json.loads(tool_call_deltas[0]["function"]["arguments"]) == {"city": "SF"}
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_sanitizes_fragmented_reasoning_tool_call_markup(self):
"""Fragmented tool-call tags inside reasoning should never leak to the client."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="<think>Need to inspect ",
new_text="<think>Need to inspect ",
completion_tokens=1,
finished=False,
),
MockGenerationOutput(
text="<tool_",
new_text="<tool_",
completion_tokens=2,
finished=False,
),
MockGenerationOutput(
text='call>{"name":"get_weather","arguments":{"city":"SF"}}',
new_text='call>{"name":"get_weather","arguments":{"city":"SF"}}',
completion_tokens=3,
finished=False,
),
MockGenerationOutput(
text="</tool_call></think>",
new_text="</tool_call></think>",
completion_tokens=4,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
reasoning_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
reasoning = delta.get("reasoning_content")
if reasoning:
reasoning_deltas.append(reasoning)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_reasoning = "".join(reasoning_deltas)
assert "<tool_" not in streamed_reasoning
assert "<tool_call>" not in streamed_reasoning
assert "</tool_call>" not in streamed_reasoning
assert streamed_reasoning.strip() == "Need to inspect"
assert len(tool_call_deltas) == 1
assert tool_call_deltas[0]["function"]["name"] == "get_weather"
assert json.loads(tool_call_deltas[0]["function"]["arguments"]) == {"city": "SF"}
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_anthropic_messages_sanitizes_tool_call_markup_inside_thinking(self):
"""Anthropic thinking blocks should hide raw tool-call markup and emit tool_use."""
from omlx.server import stream_anthropic_messages
from omlx.api.anthropic_models import MessagesRequest
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="<think>Need to inspect first.",
new_text="<think>Need to inspect first.",
completion_tokens=1,
finished=False,
),
MockGenerationOutput(
text=(
"<think>Need to inspect first."
'<tool_call>{"name":"get_weather","arguments":{"city":"SF"}}</tool_call>'
"Then continue.</think>"
),
new_text=(
'<tool_call>{"name":"get_weather","arguments":{"city":"SF"}}</tool_call>'
"Then continue.</think>"
),
completion_tokens=2,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
anthropic_tools = [{
"name": "get_weather",
"description": "Get weather",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}]
internal_tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = MessagesRequest(
model="test-model",
max_tokens=256,
messages=[{"role": "user", "content": "Hi"}],
stream=True,
tools=anthropic_tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_anthropic_messages(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=internal_tools,
):
events.append(event)
parsed_events = []
for event in events:
for line in event.split("\n"):
if line.startswith("data: "):
try:
parsed_events.append(json.loads(line[6:]))
except json.JSONDecodeError:
pass
thinking_deltas = []
tool_use_blocks = []
block_start_indices = []
stop_reasons = []
for event in parsed_events:
if event.get("type") == "content_block_delta":
delta = event.get("delta", {})
if delta.get("type") == "thinking_delta":
thinking_deltas.append(delta["thinking"])
elif event.get("type") == "content_block_start":
block_start_indices.append(event["index"])
content_block = event.get("content_block", {})
if content_block.get("type") == "tool_use":
tool_use_blocks.append(content_block)
elif event.get("type") == "message_delta":
stop_reason = event.get("delta", {}).get("stop_reason")
if stop_reason:
stop_reasons.append(stop_reason)
streamed_thinking = "".join(thinking_deltas)
assert "<tool_call>" not in streamed_thinking
assert "</tool_call>" not in streamed_thinking
assert "Need to inspect first." in streamed_thinking
assert "Then continue." in streamed_thinking
assert block_start_indices == list(range(len(block_start_indices)))
assert len(tool_use_blocks) == 1
assert tool_use_blocks[0]["name"] == "get_weather"
assert "tool_use" in stop_reasons
@pytest.mark.asyncio
async def test_stream_anthropic_messages_starts_parser_when_prompt_opens_thinking(self):
"""Prompt-opened thinking must stream as thinking, not public text."""
from omlx.server import stream_anthropic_messages
from omlx.api.anthropic_models import MessagesRequest
class PromptOpenedThinkingTokenizer(MockTokenizer):
think_start = "<think>"
think_end = "</think>"
think_start_id = 9001
think_end_id = 9002
def encode(self, text: str, add_special_tokens: bool = False) -> List[int]:
if text.rstrip().endswith(self.think_start):
return [101, self.think_start_id]
return [101]
def apply_chat_template(
self, messages: List[Dict], tokenize: bool = False, **kwargs
) -> str:
return "system: hidden\nuser: hi\nassistant:<think>"
engine = MockBaseEngine()
engine._tokenizer = PromptOpenedThinkingTokenizer()
engine.set_stream_outputs([
MockGenerationOutput(
text="The user asked a simple informational question.</think>",
new_text="The user asked a simple informational question.</think>",
completion_tokens=1,
finished=False,
),
MockGenerationOutput(
text=(
"The user asked a simple informational question.</think>"
"Visible answer."
),
new_text="Visible answer.",
completion_tokens=2,
finished=True,
finish_reason="stop",
),
])
request = MessagesRequest(
model="test-model",
max_tokens=256,
messages=[{"role": "user", "content": "Hi"}],
stream=True,
)
events = []
async for event in stream_anthropic_messages(
engine,
[{"role": "user", "content": "Hi"}],
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
):
events.append(event)
parsed_events = []
for event in events:
for line in event.split("\n"):
if line.startswith("data: "):
parsed_events.append(json.loads(line[6:]))
thinking_deltas = []
text_deltas = []
for event in parsed_events:
if event.get("type") != "content_block_delta":
continue
delta = event.get("delta", {})
if delta.get("type") == "thinking_delta":
thinking_deltas.append(delta["thinking"])
elif delta.get("type") == "text_delta":
text_deltas.append(delta["text"])
streamed_thinking = "".join(thinking_deltas)
streamed_text = "".join(text_deltas)
assert "The user asked a simple informational question." in streamed_thinking
assert "The user asked a simple informational question." not in streamed_text
assert streamed_text == "Visible answer."
@pytest.mark.asyncio
async def test_anthropic_tool_only_stream_starts_with_tool_use_block(self):
"""A tool-only response should not emit an empty text block before tool_use."""
from omlx.server import stream_anthropic_messages
from omlx.api.anthropic_models import MessagesRequest
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="",
new_text="",
completion_tokens=1,
finished=True,
finish_reason="tool_calls",
tool_calls=[{
"name": "get_weather",
"arguments": "{\"city\":\"SF\"}",
}],
),
])
anthropic_tools = [{
"name": "get_weather",
"description": "Get weather",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}]
internal_tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": anthropic_tools[0]["input_schema"],
},
}]
request = MessagesRequest(
model="test-model",
max_tokens=256,
messages=[{"role": "user", "content": "Hi"}],
stream=True,
tools=anthropic_tools,
)
events = []
async for event in stream_anthropic_messages(
engine,
[{"role": "user", "content": "Hi"}],
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=internal_tools,
):
events.append(event)
parsed_events = []
for event in events:
for line in event.split("\n"):
if line.startswith("data: "):
parsed_events.append(json.loads(line[6:]))
block_starts = [
event
for event in parsed_events
if event.get("type") == "content_block_start"
]
assert block_starts[0]["index"] == 0
assert block_starts[0]["content_block"]["type"] == "tool_use"
assert all(
event["content_block"]["type"] != "text"
for event in block_starts
)
@pytest.mark.asyncio
async def test_stream_chat_completion_with_tools_and_tool_calls_keeps_prior_content(self):
"""A tool_call finish should end the turn, not suppress already-generated text."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="Let me check that for you.",
new_text="Let me check that for you.",
completion_tokens=1,
finished=False,
),
MockGenerationOutput(
text="Let me check that for you.",
new_text="",
completion_tokens=1,
finished=True,
finish_reason="tool_calls",
tool_calls=[{
"name": "get_weather",
"arguments": "{\"city\":\"SF\"}",
}],
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
saw_tool_call_delta = False
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
saw_tool_call_delta = True
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
assert content_deltas == ["Let me check that for you."]
assert saw_tool_call_delta is True
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_with_tools_parsed_from_text_does_not_leak_markup(self):
"""Parsed-from-text tool calls must not appear in streamed content deltas."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
# Non-Harmony path: no output.tool_calls; tool calls parsed from inline text.
engine.set_stream_outputs([
MockGenerationOutput(
text="Let me check",
new_text="Let me check",
completion_tokens=1,
finished=False,
finish_reason=None,
tool_calls=None,
),
MockGenerationOutput(
text=(
"Let me check that for you."
"<tool_call>{\"name\":\"get_weather\",\"arguments\":{\"city\":\"SF\"}}</tool_call>"
),
new_text=(
" that for you."
"<tool_call>{\"name\":\"get_weather\",\"arguments\":{\"city\":\"SF\"}}</tool_call>"
),
completion_tokens=2,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
content_event_indexes = []
tool_call_event_indexes = []
for idx, payload in enumerate(payloads):
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
content_event_indexes.append(idx)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
tool_call_event_indexes.append(idx)
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert "<tool_call>" not in streamed_content
assert "</tool_call>" not in streamed_content
assert content_deltas == ["Let me check", " that for you."]
assert streamed_content == "Let me check that for you."
assert streamed_content.count("Let me check that for you.") == 1
assert len(tool_call_deltas) == 1
assert tool_call_deltas[0]["function"]["name"] == "get_weather"
assert json.loads(tool_call_deltas[0]["function"]["arguments"]) == {"city": "SF"}
assert content_event_indexes
assert tool_call_event_indexes
assert max(content_event_indexes) < min(tool_call_event_indexes)
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_with_tools_parsed_from_split_text_does_not_leak_partial_markup(self):
"""Split <tool_call> markup across chunks must not leak raw fragments in content deltas."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
# Non-Harmony path with fragmented tool-call markup across chunks.
engine.set_stream_outputs([
MockGenerationOutput(
text="Let me check that for you.<tool_",
new_text="Let me check that for you.<tool_",
completion_tokens=1,
finished=False,
finish_reason=None,
tool_calls=None,
),
MockGenerationOutput(
text=(
"Let me check that for you."
"<tool_call>{\"name\":\"get_weather\",\"arguments\":{\"city\":\"SF\"}}</to"
),
new_text=(
"call>{\"name\":\"get_weather\",\"arguments\":{\"city\":\"SF\"}}</to"
),
completion_tokens=2,
finished=False,
finish_reason=None,
tool_calls=None,
),
MockGenerationOutput(
text=(
"Let me check that for you."
"<tool_call>{\"name\":\"get_weather\",\"arguments\":{\"city\":\"SF\"}}</tool_call>"
),
new_text="ol_call>",
completion_tokens=3,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
content_event_indexes = []
tool_call_event_indexes = []
for idx, payload in enumerate(payloads):
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
content_event_indexes.append(idx)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
tool_call_event_indexes.append(idx)
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert "<tool_" not in streamed_content
assert "</to" not in streamed_content
assert "<tool_call>" not in streamed_content
assert "</tool_call>" not in streamed_content
assert content_deltas == ["Let me check that for you."]
assert streamed_content == "Let me check that for you."
assert len(tool_call_deltas) == 1
assert tool_call_deltas[0]["function"]["name"] == "get_weather"
assert json.loads(tool_call_deltas[0]["function"]["arguments"]) == {"city": "SF"}
assert content_event_indexes
assert tool_call_event_indexes
assert max(content_event_indexes) < min(tool_call_event_indexes)
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_with_tools_parsed_from_namespaced_text_does_not_leak_markup(self):
"""Supported namespaced tags must not leak into streamed content deltas."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="Let me check ",
new_text="Let me check ",
completion_tokens=1,
finished=False,
finish_reason=None,
tool_calls=None,
),
MockGenerationOutput(
text=(
"Let me check "
"that for you."
"<minimax:tool_call>"
"<invoke name=\"get_weather\">"
"<parameter name=\"city\">\"SF\"</parameter>"
"</invoke>"
"</minimax:tool_call>"
),
new_text=(
"that for you."
"<minimax:tool_call>"
"<invoke name=\"get_weather\">"
"<parameter name=\"city\">\"SF\"</parameter>"
"</invoke>"
"</minimax:tool_call>"
),
completion_tokens=2,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
content_event_indexes = []
tool_call_event_indexes = []
for idx, payload in enumerate(payloads):
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
content_event_indexes.append(idx)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
tool_call_event_indexes.append(idx)
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert "<minimax:tool_call>" not in streamed_content
assert "</minimax:tool_call>" not in streamed_content
assert "<invoke" not in streamed_content
assert "<parameter" not in streamed_content
assert content_deltas == ["Let me check ", "that for you."]
assert streamed_content == "Let me check that for you."
assert len(tool_call_deltas) == 1
assert tool_call_deltas[0]["function"]["name"] == "get_weather"
assert json.loads(tool_call_deltas[0]["function"]["arguments"]) == {"city": "SF"}
assert content_event_indexes
assert tool_call_event_indexes
assert max(content_event_indexes) < min(tool_call_event_indexes)
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_with_tools_parsed_from_tokenizer_delimiters_does_not_leak_markup(self):
"""Split tokenizer delimiters must not leak into streamed content deltas."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.tokenizer.has_tool_calling = True
engine.tokenizer.tool_call_start = "<|tool|>"
engine.tokenizer.tool_call_end = "<|/tool|>"
def tool_parser(payload: str, _tools):
parsed = json.loads(payload)
return {"name": parsed["name"], "arguments": parsed["arguments"]}
engine.tokenizer.tool_parser = tool_parser
engine.set_stream_outputs([
MockGenerationOutput(
text="Let me check that for you.<|to",
new_text="Let me check that for you.<|to",
completion_tokens=1,
finished=False,
finish_reason=None,
tool_calls=None,
),
MockGenerationOutput(
text=(
"Let me check that for you."
"<|tool|>{\"name\":\"get_weather\",\"arguments\":{\"city\":\"SF\"}}<|/to"
),
new_text=(
"ol|>{\"name\":\"get_weather\",\"arguments\":{\"city\":\"SF\"}}<|/to"
),
completion_tokens=2,
finished=False,
finish_reason=None,
tool_calls=None,
),
MockGenerationOutput(
text=(
"Let me check that for you."
"<|tool|>{\"name\":\"get_weather\",\"arguments\":{\"city\":\"SF\"}}<|/tool|>"
),
new_text="ol|>",
completion_tokens=3,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
content_event_indexes = []
tool_call_event_indexes = []
for idx, payload in enumerate(payloads):
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
content_event_indexes.append(idx)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
tool_call_event_indexes.append(idx)
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert "<|to" not in streamed_content
assert "<|tool|>" not in streamed_content
assert "<|/tool|>" not in streamed_content
assert len(content_deltas) >= 1
assert streamed_content == "Let me check that for you."
assert len(tool_call_deltas) == 1
assert tool_call_deltas[0]["function"]["name"] == "get_weather"
assert json.loads(tool_call_deltas[0]["function"]["arguments"]) == {"city": "SF"}
assert content_event_indexes
assert tool_call_event_indexes
assert max(content_event_indexes) < min(tool_call_event_indexes)
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_suppresses_unmatched_tool_like_literal_suffix_under_clean_output_strict(self):
"""Clean-output strict contract suppresses unmatched tool-like suffixes."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="Use literal marker <tool_",
new_text="Use literal marker <tool_",
completion_tokens=1,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert "<tool_" not in streamed_content
assert streamed_content == "Use literal marker "
assert tool_call_deltas == []
assert "stop" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_literal_bracket_marker_without_parse_is_preserved(self):
"""Literal bracket marker text should not be truncated when no tool call parses."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="Heads up: [Calling tool:",
new_text="Heads up: [Calling tool:",
completion_tokens=1,
finished=False,
finish_reason=None,
tool_calls=None,
),
MockGenerationOutput(
text="Heads up: [Calling tool: maybe later]",
new_text=" maybe later]",
completion_tokens=2,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert streamed_content == "Heads up: [Calling tool: maybe later]"
assert tool_call_deltas == []
assert "stop" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_with_bracket_tool_call_parses_without_leak(self):
"""Valid bracket tool-call envelopes should not leak into streamed content."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="Let me check. [Calling tool:",
new_text="Let me check. [Calling tool:",
completion_tokens=1,
finished=False,
finish_reason=None,
tool_calls=None,
),
MockGenerationOutput(
text=(
"Let me check. "
"[Calling tool: get_weather({\"city\":\"SF\"})]"
),
new_text=' get_weather({"city":"SF"})]',
completion_tokens=2,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert "[Calling tool:" not in streamed_content
assert streamed_content == "Let me check. "
assert len(tool_call_deltas) == 1
assert tool_call_deltas[0]["function"]["name"] == "get_weather"
assert json.loads(tool_call_deltas[0]["function"]["arguments"]) == {"city": "SF"}
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_with_bracket_tool_call_then_visible_text_preserves_tail(self):
"""Tool envelope suppression must not truncate ordinary prose that follows it."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="Before [Calling tool:",
new_text="Before [Calling tool:",
completion_tokens=1,
finished=False,
finish_reason=None,
tool_calls=None,
),
MockGenerationOutput(
text=(
"Before "
"[Calling tool: get_weather({\"city\":\"SF\"})]"
" After text"
),
new_text=' get_weather({"city":"SF"})] After text',
completion_tokens=2,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert "[Calling tool:" not in streamed_content
assert streamed_content == "Before After text"
assert len(tool_call_deltas) == 1
assert tool_call_deltas[0]["function"]["name"] == "get_weather"
assert json.loads(tool_call_deltas[0]["function"]["arguments"]) == {"city": "SF"}
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_with_bracket_hyphen_tool_name_parses_without_leak(self):
"""Bracket parser/filter should accept common hyphenated tool names."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="Let me check. [Calling tool:",
new_text="Let me check. [Calling tool:",
completion_tokens=1,
finished=False,
finish_reason=None,
tool_calls=None,
),
MockGenerationOutput(
text=(
"Let me check. "
"[Calling tool: get-weather({\"city\":\"SF\"})]"
),
new_text=' get-weather({"city":"SF"})]',
completion_tokens=2,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get-weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert "[Calling tool:" not in streamed_content
assert streamed_content == "Let me check. "
assert len(tool_call_deltas) == 1
assert tool_call_deltas[0]["function"]["name"] == "get-weather"
assert json.loads(tool_call_deltas[0]["function"]["arguments"]) == {"city": "SF"}
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_with_long_bracket_tool_call_does_not_leak_markup(self):
"""Long bracket envelopes should remain suppressed until complete."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
long_note = "x" * 320
final_text = (
"Before "
f'[Calling tool: get_weather({{"note":"{long_note}"}})]'
" After"
)
split_at = final_text.index("] After")
engine.set_stream_outputs([
MockGenerationOutput(
text=final_text[:split_at],
new_text=final_text[:split_at],
completion_tokens=1,
finished=False,
finish_reason=None,
tool_calls=None,
),
MockGenerationOutput(
text=final_text,
new_text=final_text[split_at:],
completion_tokens=2,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"note": {"type": "string"}},
"required": ["note"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert "[Calling tool:" not in streamed_content
assert streamed_content == "Before After"
assert len(tool_call_deltas) == 1
assert tool_call_deltas[0]["function"]["name"] == "get_weather"
assert json.loads(tool_call_deltas[0]["function"]["arguments"]) == {"note": long_note}
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_drops_unresolved_bracket_control_fragment_at_finish(self):
"""Unclosed bracket control fragments should not leak at stream end."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text='Before [Calling tool: get_weather({"city":"SF"}',
new_text='Before [Calling tool: get_weather({"city":"SF"}',
completion_tokens=1,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert "[Calling tool:" not in streamed_content
assert streamed_content == "Before "
assert tool_call_deltas == []
assert "stop" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_preserves_literal_bracket_and_suppresses_later_parseable_envelope(self):
"""A literal early bracket marker must not block later valid bracket suppression."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
mixed = (
"literal [Calling tool: maybe later] and then "
'[Calling tool: get_weather({"city":"SF"})] done'
)
engine.set_stream_outputs([
MockGenerationOutput(
text=mixed,
new_text=mixed,
completion_tokens=1,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert "literal [Calling tool: maybe later] and then " in streamed_content
assert '[Calling tool: get_weather({"city":"SF"})]' not in streamed_content
assert streamed_content == "literal [Calling tool: maybe later] and then done"
assert len(tool_call_deltas) == 1
assert tool_call_deltas[0]["function"]["name"] == "get_weather"
assert json.loads(tool_call_deltas[0]["function"]["arguments"]) == {"city": "SF"}
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_sanitizes_unresolved_bracket_prefix_before_later_tool_call(self):
"""Unresolved early bracket prefixes should not leak even when a later bracket call parses."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
mixed = (
"Before [Calling tool: unfinished and then "
'[Calling tool: get_weather({"city":"NY"})] done'
)
engine.set_stream_outputs([
MockGenerationOutput(
text=mixed,
new_text=mixed,
completion_tokens=1,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert "[Calling tool:" not in streamed_content
assert streamed_content == "Before unfinished and then done"
assert len(tool_call_deltas) == 1
assert tool_call_deltas[0]["function"]["name"] == "get_weather"
assert json.loads(tool_call_deltas[0]["function"]["arguments"]) == {"city": "NY"}
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_with_hyphen_namespaced_tool_call_parses_without_leak(self):
"""Hyphenated namespaced tool_call tags should parse into structured tool_calls."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text=(
"Let me check."
"<foo-bar:tool_call>"
"<invoke name=\"get_weather\">"
"<parameter name=\"city\">\"SF\"</parameter>"
"</invoke>"
"</foo-bar:tool_call>"
),
new_text=(
"Let me check."
"<foo-bar:tool_call>"
"<invoke name=\"get_weather\">"
"<parameter name=\"city\">\"SF\"</parameter>"
"</invoke>"
"</foo-bar:tool_call>"
),
completion_tokens=1,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert "<foo-bar:tool_call>" not in streamed_content
assert "</foo-bar:tool_call>" not in streamed_content
assert streamed_content == "Let me check."
assert len(tool_call_deltas) == 1
assert tool_call_deltas[0]["function"]["name"] == "get_weather"
assert json.loads(tool_call_deltas[0]["function"]["arguments"]) == {"city": "SF"}
assert "tool_calls" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_preserves_non_tool_namespaced_like_suffix_literal(self):
"""Trailing namespaced-looking literals that are not tool_call tags should be preserved."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="Keep literal suffix <alpha:beta",
new_text="Keep literal suffix <alpha:beta",
completion_tokens=1,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert streamed_content == "Keep literal suffix <alpha:beta"
assert tool_call_deltas == []
assert "stop" in finish_reasons
@pytest.mark.asyncio
async def test_stream_chat_completion_preserves_non_tool_angle_identifier_suffix_literal(self):
"""Trailing '<identifier' literal should not be dropped as tool-control markup."""
from omlx.server import stream_chat_completion
from omlx.api.openai_models import ChatCompletionRequest, Message
engine = MockBaseEngine()
engine.set_stream_outputs([
MockGenerationOutput(
text="Use <alpha",
new_text="Use <alpha",
completion_tokens=1,
finished=True,
finish_reason="stop",
tool_calls=None,
),
])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
request = ChatCompletionRequest(
model="test-model",
messages=[Message(role="user", content="Hi")],
stream=True,
tools=tools,
)
events = []
messages = [{"role": "user", "content": "Hi"}]
async for event in stream_chat_completion(
engine,
messages,
request,
max_tokens=256,
temperature=0.7,
top_p=0.9,
top_k=40,
tools=tools,
):
events.append(event)
payloads = [
json.loads(event[6:-2])
for event in events
if event.startswith("data: {")
]
content_deltas = []
tool_call_deltas = []
finish_reasons = []
for payload in payloads:
choices = payload.get("choices", [])
if not choices:
continue
choice = choices[0]
delta = choice.get("delta", {})
content = delta.get("content")
if content:
content_deltas.append(content)
if delta.get("tool_calls"):
tool_call_deltas.extend(delta["tool_calls"])
finish_reason = choice.get("finish_reason")
if finish_reason:
finish_reasons.append(finish_reason)
streamed_content = "".join(content_deltas)
assert streamed_content == "Use <alpha"
assert tool_call_deltas == []
assert "stop" in finish_reasons
class TestStreamingEdgeCases:
"""Tests for edge cases in streaming responses."""
@pytest.fixture
def mock_engine(self):
"""Create mock engine."""
return MockBaseEngine()
@pytest.fixture
def mock_engine_pool(self, mock_engine):
"""Create mock engine pool."""
return MockEnginePool(mock_engine)
@pytest.fixture
def client(self, mock_engine_pool):
"""Create test client with mocked state."""
from fastapi.testclient import TestClient
from omlx.server import app, _server_state
original_pool = _server_state.engine_pool
original_default = _server_state.default_model
_server_state.engine_pool = mock_engine_pool
_server_state.default_model = "test-model"
yield TestClient(app)
_server_state.engine_pool = original_pool
_server_state.default_model = original_default
@pytest.mark.slow
@pytest.mark.integration
def test_streaming_with_empty_content(self, client, mock_engine):
"""Test streaming handles empty content chunks."""
mock_engine.set_stream_outputs([
MockGenerationOutput(text="", new_text="", finished=False),
MockGenerationOutput(text="Hello", new_text="Hello", finished=False),
MockGenerationOutput(
text="Hello there",
new_text=" there",
finished=True,
finish_reason="stop",
),
])
response = client.post(
"/v1/chat/completions",
json={
"model": "test-model",
"messages": [{"role": "user", "content": "Test"}],
"stream": True,
},
)
assert response.status_code == 200
events = parse_sse_events(response.text)
assert any(e.get("done") for e in events)
@pytest.mark.slow
@pytest.mark.integration
def test_streaming_finish_reason_propagation(self, client, mock_engine):
"""Test that finish_reason is propagated in streaming."""
mock_engine.set_stream_outputs([
MockGenerationOutput(text="Hi", new_text="Hi", finished=False),
MockGenerationOutput(
text="Hi!",
new_text="!",
finished=True,
finish_reason="stop",
),
])
response = client.post(
"/v1/chat/completions",
json={
"model": "test-model",
"messages": [{"role": "user", "content": "Hi"}],
"stream": True,
},
)
events = parse_sse_events(response.text)
non_done_events = [e for e in events if not e.get("done")]
# Find event with finish_reason
finish_reasons = []
for event in non_done_events:
if "choices" in event:
fr = event["choices"][0].get("finish_reason")
if fr:
finish_reasons.append(fr)
assert "stop" in finish_reasons
@pytest.mark.slow
@pytest.mark.integration
def test_streaming_max_tokens_finish(self, client, mock_engine):
"""Test streaming with max_tokens finish reason."""
mock_engine.set_stream_outputs([
MockGenerationOutput(text="Long", new_text="Long", finished=False),
MockGenerationOutput(
text="Long text",
new_text=" text",
finished=True,
finish_reason="length",
),
])
response = client.post(
"/v1/chat/completions",
json={
"model": "test-model",
"messages": [{"role": "user", "content": "Generate long text"}],
"stream": True,
"max_tokens": 5,
},
)
events = parse_sse_events(response.text)
non_done_events = [e for e in events if not e.get("done")]
finish_reasons = []
for event in non_done_events:
if "choices" in event:
fr = event["choices"][0].get("finish_reason")
if fr:
finish_reasons.append(fr)
assert "length" in finish_reasons