Merge pull request #4273 from pipecat-ai/ac/test-fixes

Fix LLM test constructors and wake phrase test race
This commit is contained in:
Aleix Conchillo Flaqué
2026-04-10 21:27:00 -07:00
committed by GitHub
5 changed files with 70 additions and 29 deletions

View File

@@ -22,7 +22,9 @@ async def test_novita_llm_stream_closed_on_cancellation():
This prevents socket leaks when the pipeline is interrupted (e.g., user interruption). This prevents socket leaks when the pipeline is interrupted (e.g., user interruption).
""" """
with patch.object(NovitaLLMService, "create_client"): with patch.object(NovitaLLMService, "create_client"):
service = NovitaLLMService(api_key="test-key", model="test-model") service = NovitaLLMService(
api_key="test-key", settings=NovitaLLMService.Settings(model="test-model")
)
service._client = AsyncMock() service._client = AsyncMock()
stream_closed = False stream_closed = False

View File

@@ -29,7 +29,7 @@ async def test_openai_llm_emits_error_frame_on_timeout():
primary LLM times out. primary LLM times out.
""" """
with patch.object(OpenAILLMService, "create_client"): with patch.object(OpenAILLMService, "create_client"):
service = OpenAILLMService(model="gpt-4") service = OpenAILLMService(settings=OpenAILLMService.Settings(model="gpt-4"))
service._client = AsyncMock() service._client = AsyncMock()
# Track pushed frames and errors # Track pushed frames and errors
@@ -96,7 +96,7 @@ async def test_openai_llm_timeout_still_pushes_end_frame():
The finally block should ensure proper cleanup regardless of timeout. The finally block should ensure proper cleanup regardless of timeout.
""" """
with patch.object(OpenAILLMService, "create_client"): with patch.object(OpenAILLMService, "create_client"):
service = OpenAILLMService(model="gpt-4") service = OpenAILLMService(settings=OpenAILLMService.Settings(model="gpt-4"))
service._client = AsyncMock() service._client = AsyncMock()
pushed_frames = [] pushed_frames = []
@@ -137,7 +137,7 @@ async def test_openai_llm_stream_closed_on_cancellation():
import asyncio import asyncio
with patch.object(OpenAILLMService, "create_client"): with patch.object(OpenAILLMService, "create_client"):
service = OpenAILLMService(model="gpt-4") service = OpenAILLMService(settings=OpenAILLMService.Settings(model="gpt-4"))
service._client = AsyncMock() service._client = AsyncMock()
# Track if close was called # Track if close was called
@@ -195,7 +195,7 @@ async def test_openai_llm_emits_error_frame_on_exception():
This enables proper error handling for API errors, rate limits, and other failures. This enables proper error handling for API errors, rate limits, and other failures.
""" """
with patch.object(OpenAILLMService, "create_client"): with patch.object(OpenAILLMService, "create_client"):
service = OpenAILLMService(model="gpt-4") service = OpenAILLMService(settings=OpenAILLMService.Settings(model="gpt-4"))
service._client = AsyncMock() service._client = AsyncMock()
pushed_errors = [] pushed_errors = []
@@ -233,7 +233,7 @@ async def test_openai_llm_async_iterator_closed_on_stream_end():
See MagicStack/uvloop#699. See MagicStack/uvloop#699.
""" """
with patch.object(OpenAILLMService, "create_client"): with patch.object(OpenAILLMService, "create_client"):
service = OpenAILLMService(model="gpt-4") service = OpenAILLMService(settings=OpenAILLMService.Settings(model="gpt-4"))
service._client = AsyncMock() service._client = AsyncMock()
# Track if the iterator's aclose was called # Track if the iterator's aclose was called

View File

@@ -30,12 +30,15 @@ async def test_openai_run_inference_with_llm_context():
"""Test run_inference with LLMContext returns expected response.""" """Test run_inference with LLMContext returns expected response."""
# Create service with mocked client and specific parameters # Create service with mocked client and specific parameters
with patch.object(OpenAILLMService, "create_client"): with patch.object(OpenAILLMService, "create_client"):
from pipecat.services.openai.base_llm import BaseOpenAILLMService service = OpenAILLMService(
settings=OpenAILLMService.Settings(
params = BaseOpenAILLMService.InputParams( model="gpt-4",
temperature=0.7, max_tokens=100, frequency_penalty=0.5, seed=42 temperature=0.7,
max_tokens=100,
frequency_penalty=0.5,
seed=42,
)
) )
service = OpenAILLMService(model="gpt-4", params=params)
service._client = AsyncMock() service._client = AsyncMock()
# Setup mocks # Setup mocks
@@ -87,7 +90,7 @@ async def test_openai_run_inference_with_llm_context():
async def test_openai_run_inference_client_exception(): async def test_openai_run_inference_client_exception():
"""Test that exceptions from the client are propagated.""" """Test that exceptions from the client are propagated."""
with patch.object(OpenAILLMService, "create_client"): with patch.object(OpenAILLMService, "create_client"):
service = OpenAILLMService(model="gpt-4") service = OpenAILLMService(settings=OpenAILLMService.Settings(model="gpt-4"))
service._client = AsyncMock() service._client = AsyncMock()
mock_context = MagicMock(spec=LLMContext) mock_context = MagicMock(spec=LLMContext)
@@ -108,9 +111,15 @@ async def test_anthropic_run_inference_with_llm_context():
# Create service with mocked client and specific parameters # Create service with mocked client and specific parameters
from pipecat.services.anthropic.llm import AnthropicLLMService from pipecat.services.anthropic.llm import AnthropicLLMService
params = AnthropicLLMService.InputParams(max_tokens=2048, temperature=0.6, top_k=50, top_p=0.95)
service = AnthropicLLMService( service = AnthropicLLMService(
api_key="test-key", model="claude-3-sonnet-20240229", params=params api_key="test-key",
settings=AnthropicLLMService.Settings(
model="claude-3-sonnet-20240229",
max_tokens=2048,
temperature=0.6,
top_k=50,
top_p=0.95,
),
) )
service._client = AsyncMock() service._client = AsyncMock()
@@ -156,7 +165,9 @@ async def test_anthropic_run_inference_with_llm_context():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_anthropic_run_inference_client_exception(): async def test_anthropic_run_inference_client_exception():
"""Test that exceptions from the Anthropic client are propagated.""" """Test that exceptions from the Anthropic client are propagated."""
service = AnthropicLLMService(api_key="test-key", model="claude-3-sonnet-20240229") service = AnthropicLLMService(
api_key="test-key", settings=AnthropicLLMService.Settings(model="claude-3-sonnet-20240229")
)
service._client = AsyncMock() service._client = AsyncMock()
mock_context = MagicMock(spec=LLMContext) mock_context = MagicMock(spec=LLMContext)
@@ -175,7 +186,9 @@ async def test_anthropic_run_inference_client_exception():
async def test_google_run_inference_with_llm_context(): async def test_google_run_inference_with_llm_context():
"""Test run_inference with LLMContext returns expected response for Google.""" """Test run_inference with LLMContext returns expected response for Google."""
# Create service with mocked client # Create service with mocked client
service = GoogleLLMService(api_key="test-key", model="gemini-2.0-flash") service = GoogleLLMService(
api_key="test-key", settings=GoogleLLMService.Settings(model="gemini-2.0-flash")
)
service._client = AsyncMock() service._client = AsyncMock()
# Setup mocks # Setup mocks
@@ -213,7 +226,9 @@ async def test_google_run_inference_with_llm_context():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_google_run_inference_client_exception(): async def test_google_run_inference_client_exception():
"""Test that exceptions from the Google client are propagated.""" """Test that exceptions from the Google client are propagated."""
service = GoogleLLMService(api_key="test-key", model="gemini-2.0-flash") service = GoogleLLMService(
api_key="test-key", settings=GoogleLLMService.Settings(model="gemini-2.0-flash")
)
service._client = AsyncMock() service._client = AsyncMock()
mock_context = MagicMock(spec=LLMContext) mock_context = MagicMock(spec=LLMContext)
@@ -238,8 +253,14 @@ async def test_aws_bedrock_run_inference_with_llm_context():
# Create service with specific parameters # Create service with specific parameters
from pipecat.services.aws.llm import AWSBedrockLLMService from pipecat.services.aws.llm import AWSBedrockLLMService
params = AWSBedrockLLMService.InputParams(max_tokens=1024, temperature=0.5, top_p=0.85) service = AWSBedrockLLMService(
service = AWSBedrockLLMService(model="anthropic.claude-3-sonnet-20240229-v1:0", params=params) settings=AWSBedrockLLMService.Settings(
model="anthropic.claude-3-sonnet-20240229-v1:0",
max_tokens=1024,
temperature=0.5,
top_p=0.85,
)
)
# Setup mocks # Setup mocks
mock_context = MagicMock(spec=LLMContext) mock_context = MagicMock(spec=LLMContext)
@@ -289,7 +310,9 @@ async def test_aws_bedrock_run_inference_with_llm_context():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_aws_bedrock_run_inference_client_exception(): async def test_aws_bedrock_run_inference_client_exception():
"""Test that exceptions from the AWS Bedrock client are propagated.""" """Test that exceptions from the AWS Bedrock client are propagated."""
service = AWSBedrockLLMService(model="anthropic.claude-3-sonnet-20240229-v1:0") service = AWSBedrockLLMService(
settings=AWSBedrockLLMService.Settings(model="anthropic.claude-3-sonnet-20240229-v1:0")
)
mock_context = MagicMock(spec=LLMContext) mock_context = MagicMock(spec=LLMContext)
mock_adapter = MagicMock() mock_adapter = MagicMock()
@@ -319,7 +342,7 @@ async def test_aws_bedrock_run_inference_client_exception():
async def test_openai_run_inference_system_instruction_overrides_context(): async def test_openai_run_inference_system_instruction_overrides_context():
"""Test that system_instruction overrides the system message from context.""" """Test that system_instruction overrides the system message from context."""
with patch.object(OpenAILLMService, "create_client"): with patch.object(OpenAILLMService, "create_client"):
service = OpenAILLMService(model="gpt-4") service = OpenAILLMService(settings=OpenAILLMService.Settings(model="gpt-4"))
service._client = AsyncMock() service._client = AsyncMock()
mock_context = MagicMock(spec=LLMContext) mock_context = MagicMock(spec=LLMContext)
@@ -356,7 +379,7 @@ async def test_openai_run_inference_system_instruction_overrides_context():
async def test_openai_run_inference_system_instruction_none_unchanged(): async def test_openai_run_inference_system_instruction_none_unchanged():
"""Test that when system_instruction is None, behavior is unchanged.""" """Test that when system_instruction is None, behavior is unchanged."""
with patch.object(OpenAILLMService, "create_client"): with patch.object(OpenAILLMService, "create_client"):
service = OpenAILLMService(model="gpt-4") service = OpenAILLMService(settings=OpenAILLMService.Settings(model="gpt-4"))
service._client = AsyncMock() service._client = AsyncMock()
mock_context = MagicMock(spec=LLMContext) mock_context = MagicMock(spec=LLMContext)
@@ -387,7 +410,9 @@ async def test_openai_run_inference_system_instruction_none_unchanged():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_anthropic_run_inference_system_instruction_overrides_context(): async def test_anthropic_run_inference_system_instruction_overrides_context():
"""Test that system_instruction overrides the system message for Anthropic.""" """Test that system_instruction overrides the system message for Anthropic."""
service = AnthropicLLMService(api_key="test-key", model="claude-3-sonnet-20240229") service = AnthropicLLMService(
api_key="test-key", settings=AnthropicLLMService.Settings(model="claude-3-sonnet-20240229")
)
service._client = AsyncMock() service._client = AsyncMock()
mock_context = MagicMock(spec=LLMContext) mock_context = MagicMock(spec=LLMContext)
@@ -417,7 +442,9 @@ async def test_anthropic_run_inference_system_instruction_overrides_context():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_anthropic_run_inference_system_instruction_none_unchanged(): async def test_anthropic_run_inference_system_instruction_none_unchanged():
"""Test that when system_instruction is None, Anthropic behavior is unchanged.""" """Test that when system_instruction is None, Anthropic behavior is unchanged."""
service = AnthropicLLMService(api_key="test-key", model="claude-3-sonnet-20240229") service = AnthropicLLMService(
api_key="test-key", settings=AnthropicLLMService.Settings(model="claude-3-sonnet-20240229")
)
service._client = AsyncMock() service._client = AsyncMock()
mock_context = MagicMock(spec=LLMContext) mock_context = MagicMock(spec=LLMContext)
@@ -443,7 +470,9 @@ async def test_anthropic_run_inference_system_instruction_none_unchanged():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_google_run_inference_system_instruction_overrides_context(): async def test_google_run_inference_system_instruction_overrides_context():
"""Test that system_instruction overrides the system message for Google.""" """Test that system_instruction overrides the system message for Google."""
service = GoogleLLMService(api_key="test-key", model="gemini-2.0-flash") service = GoogleLLMService(
api_key="test-key", settings=GoogleLLMService.Settings(model="gemini-2.0-flash")
)
service._client = AsyncMock() service._client = AsyncMock()
mock_context = MagicMock(spec=LLMContext) mock_context = MagicMock(spec=LLMContext)
@@ -475,7 +504,9 @@ async def test_google_run_inference_system_instruction_overrides_context():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_google_run_inference_system_instruction_none_unchanged(): async def test_google_run_inference_system_instruction_none_unchanged():
"""Test that when system_instruction is None, Google behavior is unchanged.""" """Test that when system_instruction is None, Google behavior is unchanged."""
service = GoogleLLMService(api_key="test-key", model="gemini-2.0-flash") service = GoogleLLMService(
api_key="test-key", settings=GoogleLLMService.Settings(model="gemini-2.0-flash")
)
service._client = AsyncMock() service._client = AsyncMock()
mock_context = MagicMock(spec=LLMContext) mock_context = MagicMock(spec=LLMContext)
@@ -506,7 +537,9 @@ async def test_google_run_inference_system_instruction_none_unchanged():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_aws_bedrock_run_inference_system_instruction_overrides_context(): async def test_aws_bedrock_run_inference_system_instruction_overrides_context():
"""Test that system_instruction overrides the system message for AWS Bedrock.""" """Test that system_instruction overrides the system message for AWS Bedrock."""
service = AWSBedrockLLMService(model="anthropic.claude-3-sonnet-20240229-v1:0") service = AWSBedrockLLMService(
settings=AWSBedrockLLMService.Settings(model="anthropic.claude-3-sonnet-20240229-v1:0")
)
mock_context = MagicMock(spec=LLMContext) mock_context = MagicMock(spec=LLMContext)
mock_adapter = MagicMock() mock_adapter = MagicMock()
@@ -542,7 +575,9 @@ async def test_aws_bedrock_run_inference_system_instruction_overrides_context():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_aws_bedrock_run_inference_system_instruction_none_unchanged(): async def test_aws_bedrock_run_inference_system_instruction_none_unchanged():
"""Test that when system_instruction is None, AWS Bedrock behavior is unchanged.""" """Test that when system_instruction is None, AWS Bedrock behavior is unchanged."""
service = AWSBedrockLLMService(model="anthropic.claude-3-sonnet-20240229-v1:0") service = AWSBedrockLLMService(
settings=AWSBedrockLLMService.Settings(model="anthropic.claude-3-sonnet-20240229-v1:0")
)
mock_context = MagicMock(spec=LLMContext) mock_context = MagicMock(spec=LLMContext)
mock_adapter = MagicMock() mock_adapter = MagicMock()

View File

@@ -23,7 +23,9 @@ async def test_sambanova_llm_stream_closed_on_cancellation():
See issue #3639. See issue #3639.
""" """
with patch.object(SambaNovaLLMService, "create_client"): with patch.object(SambaNovaLLMService, "create_client"):
service = SambaNovaLLMService(api_key="test-key", model="test-model") service = SambaNovaLLMService(
api_key="test-key", settings=SambaNovaLLMService.Settings(model="test-model")
)
service._client = AsyncMock() service._client = AsyncMock()
stream_closed = False stream_closed = False

View File

@@ -33,6 +33,8 @@ class TestWakePhraseUserTurnStartStrategy(unittest.IsolatedAsyncioTestCase):
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
task_manager.setup(TaskManagerParams(loop=loop)) task_manager.setup(TaskManagerParams(loop=loop))
await strategy.setup(task_manager) await strategy.setup(task_manager)
# The tests are quick, so make sure the schedule starts all tasks.
await asyncio.sleep(0)
return task_manager return task_manager
async def test_wake_phrase_in_final_transcription(self): async def test_wake_phrase_in_final_transcription(self):