Compare commits

...

4 Commits

Author SHA1 Message Date
Mark Backman
f179364fde Add changelog for OpenRouter updates 2026-05-18 10:43:18 -04:00
Mark Backman
d3c978e8ca Align function calling examples 2026-05-18 10:34:24 -04:00
Mark Backman
6b42aaead8 Update OpenRouter default model 2026-05-18 10:26:25 -04:00
Mark Backman
4b98c2b7f1 Handle developer messages conservatively for OpenRouter 2026-05-18 10:11:46 -04:00
18 changed files with 77 additions and 3 deletions

View File

@@ -0,0 +1 @@
- OpenRouter LLM requests now convert `developer` messages to `user` messages by default for broader model compatibility. Override this by subclassing `OpenRouterLLMService` or setting `llm.supports_developer_role = True` for models that support the `developer` role.

View File

@@ -0,0 +1 @@
- OpenRouter LLM service now defaults to `openai/gpt-4.1`.

View File

@@ -133,6 +133,9 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -142,6 +142,9 @@ Start by asking me for my location. Then, use 'get_weather_current' to give me a
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -143,6 +143,9 @@ Start by asking me for my location. Then, use 'get_weather_current' to give me a
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -134,6 +134,9 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -131,6 +131,9 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -144,6 +144,9 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -149,6 +149,9 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -135,6 +135,9 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -149,6 +149,9 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -187,6 +187,9 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -148,6 +148,9 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -76,7 +76,6 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
llm = OpenRouterLLMService(
api_key=os.environ["OPENROUTER_API_KEY"],
settings=OpenRouterLLMService.Settings(
model="openai/gpt-4o-2024-11-20",
system_instruction="You are a helpful assistant in a voice conversation. Your responses will be spoken aloud, so avoid emojis, bullet points, or other formatting that can't be spoken. Respond to what the user said in a creative, helpful, and brief way.",
),
)
@@ -136,6 +135,9 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -134,6 +134,9 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -133,6 +133,9 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation.
context.add_message(
{"role": "developer", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")

View File

@@ -37,6 +37,7 @@ class OpenRouterLLMService(OpenAILLMService):
Settings = OpenRouterLLMSettings
_settings: Settings
supports_developer_role = False
def __init__(
self,
@@ -52,7 +53,7 @@ class OpenRouterLLMService(OpenAILLMService):
Args:
api_key: The API key for accessing OpenRouter's API. If None, will attempt
to read from environment variables.
model: The model identifier to use. Defaults to "openai/gpt-4o-2024-11-20".
model: The model identifier to use. Defaults to "openai/gpt-4.1".
.. deprecated:: 0.0.105
Use ``settings=OpenRouterLLMService.Settings(model=...)`` instead.
@@ -63,7 +64,7 @@ class OpenRouterLLMService(OpenAILLMService):
**kwargs: Additional keyword arguments passed to OpenAILLMService.
"""
# 1. Initialize default_settings with hardcoded defaults
default_settings = self.Settings(model="openai/gpt-4o-2024-11-20")
default_settings = self.Settings(model="openai/gpt-4.1")
# 2. Apply direct init arg overrides (deprecated)
if model is not None:

View File

@@ -23,6 +23,7 @@ from pipecat.services.openai.responses.llm import (
OpenAIResponsesHttpLLMService,
OpenAIResponsesLLMService,
)
from pipecat.services.openrouter.llm import OpenRouterLLMService
@pytest.mark.asyncio
@@ -105,6 +106,35 @@ async def test_openai_run_inference_client_exception():
await service.run_inference(mock_context)
@pytest.mark.asyncio
async def test_openrouter_run_inference_converts_developer_messages_to_user():
"""Test OpenRouter requests convert developer messages for broad model compatibility."""
with patch.object(OpenRouterLLMService, "create_client"):
service = OpenRouterLLMService(settings=OpenRouterLLMService.Settings(model="gpt-4"))
service._client = AsyncMock()
mock_context = MagicMock(spec=LLMContext)
mock_adapter = MagicMock()
mock_adapter.get_llm_invocation_params.return_value = OpenAILLMInvocationParams(
messages=[{"role": "user", "content": "Tool result"}],
tools=OPENAI_NOT_GIVEN,
tool_choice=OPENAI_NOT_GIVEN,
)
service.get_llm_adapter = MagicMock(return_value=mock_adapter)
mock_response = MagicMock()
mock_response.choices = [MagicMock()]
mock_response.choices[0].message.content = "Done"
service._client.chat.completions.create.return_value = mock_response
result = await service.run_inference(mock_context)
assert result == "Done"
mock_adapter.get_llm_invocation_params.assert_called_once_with(
mock_context, system_instruction=None, convert_developer_to_user=True
)
@pytest.mark.asyncio
async def test_anthropic_run_inference_with_llm_context():
"""Test run_inference with LLMContext returns expected response for Anthropic."""