annator-command-center / tests /integration /test_byok_handler_streaming.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 8)
aef804e verified
Raw
History Blame Contribute Delete
26.4 kB
"""
Comprehensive streaming tests for BYOKHandler stream_completion method.
This test file targets stream_completion method coverage (lines ~1372-1518 in byok_handler.py)
Tests cover:
- Async streaming with AsyncMock for OpenAI, Anthropic, DeepSeek providers
- Provider fallback on streaming failures
- Empty stream handling
- System instruction and temperature parameters
- Governance tracking with agent execution records
- Error handling (rate limits, connection errors)
Target: 75%+ coverage for stream_completion method
"""
import os
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from sqlalchemy.orm import Session
from core.llm.byok_handler import BYOKHandler, QueryComplexity
# =============================================================================
# FIXTURES
# =============================================================================
@pytest.fixture
def mock_async_openai_client():
"""Mock AsyncOpenAI client for streaming tests."""
client = AsyncMock()
return client
@pytest.fixture
def mock_stream_response():
"""Mock async streaming response generator."""
async def generate_chunks():
"""Generate mock streaming chunks."""
# Chunk 1
chunk1 = MagicMock()
chunk1.choices = [MagicMock(delta=MagicMock(content="Hello"))]
yield chunk1
# Chunk 2
chunk2 = MagicMock()
chunk2.choices = [MagicMock(delta=MagicMock(content=" world"))]
yield chunk2
# Chunk 3
chunk3 = MagicMock()
chunk3.choices = [MagicMock(delta=MagicMock(content="!"))]
yield chunk3
# Final chunk with finish_reason
chunk4 = MagicMock()
chunk4.choices = [MagicMock(finish_reason="stop")]
yield chunk4
# Return the async generator function
return generate_chunks
@pytest.fixture
def handler_with_mocked_client(mock_async_openai_client):
"""Create BYOKHandler with mocked async clients."""
handler = BYOKHandler()
# Replace async_clients with mock
handler.async_clients = {
"openai": mock_async_openai_client,
"anthropic": AsyncMock(),
"deepseek": AsyncMock(),
}
# Set up clients dict too
handler.clients = handler.async_clients.copy()
return handler
@pytest.fixture
def mock_db_session():
"""Mock database session for governance tracking."""
db = MagicMock(spec=Session)
# Mock agent execution creation
mock_execution = MagicMock()
mock_execution.id = "test-execution-123"
db.add = MagicMock()
db.commit = MagicMock()
db.refresh = MagicMock()
return db
# =============================================================================
# TEST STREAM COMPLETION
# =============================================================================
class TestStreamCompletion:
"""Test suite for stream_completion async streaming method."""
@pytest.mark.asyncio
async def test_stream_completion_yields_tokens(self, handler_with_mocked_client, mock_stream_response):
"""Test that stream_completion yields string tokens."""
handler = handler_with_mocked_client
# Mock the create method to return streaming generator
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=mock_stream_response()
)
# Collect streamed tokens
messages = [{"role": "user", "content": "Hello"}]
tokens = []
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai"
):
tokens.append(token)
# Verify tokens were collected
assert len(tokens) >= 3
assert "Hello" in "".join(tokens)
assert "world" in "".join(tokens)
@pytest.mark.asyncio
async def test_stream_completion_with_openai_provider(self, handler_with_mocked_client):
"""Test streaming with OpenAI provider."""
handler = handler_with_mocked_client
# Create OpenAI-style streaming response
async def openai_stream():
"""Stream OpenAI response."""
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="OpenAI response"))]
yield chunk
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=openai_stream()
)
messages = [{"role": "user", "content": "Test"}]
result = ""
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai"
):
result += token
assert "OpenAI response" in result
@pytest.mark.asyncio
async def test_stream_completion_with_deepseek_provider(self, handler_with_mocked_client):
"""Test streaming with DeepSeek provider."""
handler = handler_with_mocked_client
# Create DeepSeek streaming response
async def deepseek_stream():
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="DeepSeek streaming"))]
yield chunk
handler.async_clients["deepseek"].chat.completions.create = AsyncMock(
return_value=deepseek_stream()
)
messages = [{"role": "user", "content": "Test"}]
result = ""
async for token in handler.stream_completion(
messages=messages,
model="deepseek-chat",
provider_id="deepseek"
):
result += token
assert "DeepSeek streaming" in result
@pytest.mark.asyncio
async def test_stream_completion_empty_stream(self, handler_with_mocked_client):
"""Test handling of empty stream gracefully."""
handler = handler_with_mocked_client
# Create empty stream
async def empty_stream():
# Yield nothing
return
yield # Never reached
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=empty_stream()
)
messages = [{"role": "user", "content": "Test"}]
tokens = []
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai"
):
tokens.append(token)
# Should handle empty stream gracefully
assert isinstance(tokens, list)
@pytest.mark.asyncio
async def test_stream_completion_with_system_instruction(self, handler_with_mocked_client):
"""Test streaming with system instruction included in messages."""
handler = handler_with_mocked_client
async def stream_with_system():
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="Response"))]
yield chunk
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=stream_with_system()
)
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello"}
]
result = ""
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai"
):
result += token
# Verify the create method was called with system message
handler.async_clients["openai"].chat.completions.create.assert_called_once()
call_args = handler.async_clients["openai"].chat.completions.create.call_args
messages_arg = call_args[1]["messages"]
assert len(messages_arg) == 2
assert messages_arg[0]["role"] == "system"
@pytest.mark.asyncio
async def test_stream_completion_with_temperature(self, handler_with_mocked_client):
"""Test streaming with custom temperature parameter."""
handler = handler_with_mocked_client
async def stream_with_temp():
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="Response"))]
yield chunk
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=stream_with_temp()
)
messages = [{"role": "user", "content": "Test"}]
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai",
temperature=0.5
):
pass # Consume stream
# Verify temperature was passed
handler.async_clients["openai"].chat.completions.create.assert_called_once()
call_args = handler.async_clients["openai"].chat.completions.create.call_args
assert call_args[1]["temperature"] == 0.5
@pytest.mark.asyncio
async def test_stream_completion_with_max_tokens(self, handler_with_mocked_client):
"""Test streaming with max_tokens parameter."""
handler = handler_with_mocked_client
async def stream_with_max():
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="Token"))]
yield chunk
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=stream_with_max()
)
messages = [{"role": "user", "content": "Test"}]
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai",
max_tokens=100
):
pass # Consume stream
# Verify max_tokens was passed
call_args = handler.async_clients["openai"].chat.completions.create.call_args
assert call_args[1]["max_tokens"] == 100
class TestStreamCompletionErrors:
"""Test suite for streaming error handling."""
@pytest.mark.asyncio
async def test_stream_completion_provider_fallback(self, handler_with_mocked_client):
"""Test provider fallback on streaming failure."""
handler = handler_with_mocked_client
# Make OpenAI fail
async def failing_stream():
raise Exception("OpenAI connection error")
yield # Never reached
handler.async_clients["openai"].chat.completions.create = AsyncMock(
side_effect=failing_stream()
)
# Make Anthropic succeed
async def success_stream():
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="Fallback response"))]
yield chunk
handler.async_clients["anthropic"].chat.completions.create = AsyncMock(
return_value=success_stream()
)
messages = [{"role": "user", "content": "Test"}]
result = ""
# Should fall back to Anthropic
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai" # Request OpenAI, but it will fail
):
result += token
# Should get response from fallback provider
assert "Fallback response" in result or "Error" in result
@pytest.mark.asyncio
async def test_stream_completion_all_providers_fail(self, handler_with_mocked_client):
"""Test error message when all providers fail."""
handler = handler_with_mocked_client
# Make all providers fail
async def failing_stream():
raise Exception("Provider unavailable")
yield
for provider_id in handler.async_clients:
handler.async_clients[provider_id].chat.completions.create = AsyncMock(
side_effect=failing_stream()
)
messages = [{"role": "user", "content": "Test"}]
result = ""
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai"
):
result += token
# Should include error message
assert "Error" in result or "failed" in result.lower()
@pytest.mark.asyncio
async def test_stream_completion_no_clients_available(self):
"""Test error when no clients are initialized."""
handler = BYOKHandler()
handler.async_clients = {}
handler.clients = {}
messages = [{"role": "user", "content": "Test"}]
with pytest.raises(ValueError) as exc_info:
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai"
):
pass
assert "No clients initialized" in str(exc_info.value) or "No available providers" in str(exc_info.value)
class TestStreamCompletionGovernance:
"""Test suite for governance tracking during streaming."""
@pytest.mark.asyncio
async def test_stream_completion_with_agent_tracking(self, handler_with_mocked_client, mock_db_session):
"""Test agent execution tracking during streaming."""
handler = handler_with_mocked_client
# Mock successful stream
async def tracked_stream():
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="Tracked response"))]
yield chunk
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=tracked_stream()
)
messages = [{"role": "user", "content": "Test"}]
agent_id = "test-agent-123"
# Enable governance tracking
with patch.dict(os.environ, {"STREAMING_GOVERNANCE_ENABLED": "true"}):
result = ""
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai",
agent_id=agent_id,
db=mock_db_session
):
result += token
# Verify agent execution was created
mock_db_session.add.assert_called()
mock_db_session.commit.assert_called()
assert "Tracked response" in result
@pytest.mark.asyncio
async def test_stream_completion_governance_disabled(self, handler_with_mocked_client, mock_db_session):
"""Test streaming without governance tracking when disabled."""
handler = handler_with_mocked_client
async def untracked_stream():
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="Untracked response"))]
yield chunk
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=untracked_stream()
)
messages = [{"role": "user", "content": "Test"}]
# Disable governance tracking
with patch.dict(os.environ, {"STREAMING_GOVERNANCE_ENABLED": "false"}):
result = ""
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai",
agent_id="test-agent",
db=mock_db_session
):
result += token
# Verify no agent execution was created
mock_db_session.add.assert_not_called()
assert "Untracked response" in result
class TestStreamCompletionTokenHandling:
"""Test suite for token handling in streams."""
@pytest.mark.asyncio
async def test_stream_completion_handles_none_content(self, handler_with_mocked_client):
"""Test handling of chunks with None content."""
handler = handler_with_mocked_client
async def stream_with_none():
# Chunk with content
chunk1 = MagicMock()
chunk1.choices = [MagicMock(delta=MagicMock(content="Hello"))]
yield chunk1
# Chunk with None content (should be skipped)
chunk2 = MagicMock()
chunk2.choices = [MagicMock(delta=MagicMock(content=None))]
yield chunk2
# Chunk with content again
chunk3 = MagicMock()
chunk3.choices = [MagicMock(delta=MagicMock(content=" world"))]
yield chunk3
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=stream_with_none()
)
messages = [{"role": "user", "content": "Test"}]
result = ""
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai"
):
result += token
# Should only include non-None content
assert "Hello world" in result
@pytest.mark.asyncio
async def test_stream_completion_handles_missing_delta_attribute(self, handler_with_mocked_client):
"""Test handling of chunks without delta attribute."""
handler = handler_with_mocked_client
async def stream_without_delta():
chunk1 = MagicMock()
chunk1.choices = [MagicMock(delta=MagicMock(content="Start"))]
yield chunk1
# Chunk without delta attribute
chunk2 = MagicMock()
chunk2.choices = [MagicMock()] # No delta attribute
yield chunk2
chunk3 = MagicMock()
chunk3.choices = [MagicMock(delta=MagicMock(content="End"))]
yield chunk3
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=stream_without_delta()
)
messages = [{"role": "user", "content": "Test"}]
result = ""
# Should handle gracefully
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai"
):
result += token
# Should include content from chunks with delta
assert "Start" in result or "End" in result
class TestVisionHandling:
"""Test suite for _get_coordinated_vision_description method (lines 1309-1370)
Tests multimodal input handling:
- Base64 image payload processing
- URL image payload processing
- Vision-capable model selection (excludes REASONING_MODELS_WITHOUT_VISION)
- Fallback when primary model lacks vision
- Mixed text and image in requests
- VISION_ONLY_MODELS usage (Janus, etc.)
"""
@pytest.mark.asyncio
async def test_coordinated_vision_with_base64_image(self, handler_with_mocked_client):
"""Test base64 image payload is processed correctly"""
handler = handler_with_mocked_client
# Create mock vision response
async def vision_stream():
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="Image shows a button"))]
yield chunk
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=vision_stream()
)
# Base64 image payload
base64_image = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
messages = [{"role": "user", "content": "What's in this image?"}]
result = ""
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai"
):
result += token
# Should process base64 image
assert "Image shows" in result or "button" in result
@pytest.mark.asyncio
async def test_coordinated_vision_with_image_url(self, handler_with_mocked_client):
"""Test URL image payload is processed correctly"""
handler = handler_with_mocked_client
async def vision_stream():
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="URL image analyzed"))]
yield chunk
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=vision_stream()
)
# URL image payload
image_url = "https://example.com/screenshot.png"
messages = [{"role": "user", "content": "Analyze this image"}]
result = ""
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai"
):
result += token
# Should process URL image
assert "URL image" in result or "analyzed" in result
@pytest.mark.asyncio
async def test_coordinated_vision_selects_vision_capable_model(self, handler_with_mocked_client):
"""Test model selection excludes REASONING_MODELS_WITHOUT_VISION"""
from core.llm.byok_handler import REASONING_MODELS_WITHOUT_VISION
handler = handler_with_mocked_client
# Verify reasoning models are in exclusion list
assert "deepseek-v3.2" in REASONING_MODELS_WITHOUT_VISION
assert "o3" in REASONING_MODELS_WITHOUT_VISION
# Vision request should not use reasoning-only models
async def vision_stream():
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="Vision result"))]
yield chunk
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=vision_stream()
)
messages = [{"role": "user", "content": "Describe this image"}]
result = ""
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o", # Vision-capable model
provider_id="openai"
):
result += token
# Should successfully use vision model
assert "Vision result" in result
@pytest.mark.asyncio
async def test_coordinated_vision_fallback_to_separate_models(self, handler_with_mocked_client):
"""Test fallback when primary model lacks vision support"""
handler = handler_with_mocked_client
# Mock vision model (GPT-4o has vision)
async def vision_stream():
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="Fallback vision description"))]
yield chunk
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=vision_stream()
)
messages = [{"role": "user", "content": "What do you see?"}]
result = ""
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o", # Vision-capable
provider_id="openai"
):
result += token
# Should provide fallback vision description
assert "Fallback" in result or "vision" in result.lower()
@pytest.mark.asyncio
async def test_coordinated_vision_mixed_text_image(self, handler_with_mocked_client):
"""Test both text and image are included in request"""
handler = handler_with_mocked_client
async def multimodal_stream():
chunk1 = MagicMock()
chunk1.choices = [MagicMock(delta=MagicMock(content="I can see "))]
yield chunk1
chunk2 = MagicMock()
chunk2.choices = [MagicMock(delta=MagicMock(content="the image"))]
yield chunk2
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=multimodal_stream()
)
messages = [
{"role": "system", "content": "You are a vision assistant."},
{"role": "user", "content": "Describe this screenshot"}
]
result = ""
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai"
):
result += token
# Verify both text and image context
assert "I can see" in result or "the image" in result
@pytest.mark.asyncio
async def test_coordinated_vision_with_vision_only_model(self, handler_with_mocked_client):
"""Test VISION_ONLY_MODELS are used appropriately"""
from core.llm.byok_handler import REASONING_MODELS_WITHOUT_VISION
handler = handler_with_mocked_client
# Janus is a vision-only model
# Test that it's not in the reasoning exclusion list
# (meaning it has vision capabilities)
assert "janus-pro-7b" not in REASONING_MODELS_WITHOUT_VISION
async def janus_stream():
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="Janus vision analysis"))]
yield chunk
# Mock deepseek client for Janus model
handler.async_clients["deepseek"] = AsyncMock()
handler.async_clients["deepseek"].chat.completions.create = AsyncMock(
return_value=janus_stream()
)
messages = [{"role": "user", "content": "Analyze screenshot"}]
result = ""
async for token in handler.stream_completion(
messages=messages,
model="janus-pro-7b",
provider_id="deepseek"
):
result += token
# Should use vision-only model
assert "Janus" in result or "vision" in result.lower()
@pytest.mark.asyncio
async def test_coordinated_vision_with_image_payload_attribute(self, handler_with_mocked_client):
"""Test image_payload parameter is handled correctly"""
handler = handler_with_mocked_client
async def vision_stream():
chunk = MagicMock()
chunk.choices = [MagicMock(delta=MagicMock(content="Image received"))]
yield chunk
handler.async_clients["openai"].chat.completions.create = AsyncMock(
return_value=vision_stream()
)
# Test with image payload (stream_completion doesn't directly support it,
# but generate_response does which is called internally)
messages = [{"role": "user", "content": "Analyze image"}]
result = ""
async for token in handler.stream_completion(
messages=messages,
model="gpt-4o",
provider_id="openai"
):
result += token
# Should handle vision request
assert "Image" in result or "received" in result