diff --git a/backend/services/pipecat/service_factory.py b/backend/services/pipecat/service_factory.py index 92389a5..13af449 100644 --- a/backend/services/pipecat/service_factory.py +++ b/backend/services/pipecat/service_factory.py @@ -117,7 +117,7 @@ def create_llm(cfg: AssistantConfig): raise ValueError(f"不支持的 LLM 接口类型: {cfg.llm_interface_type}") extra_body = cfg.llm_values.get("extraBody") extra = {"extra_body": extra_body} if isinstance(extra_body, dict) else {} - return OpenAILLMService( + service = OpenAILLMService( api_key=_require(cfg.llm_api_key, "LLM apiKey"), base_url=_require(cfg.llm_base_url, "LLM apiUrl"), settings=OpenAILLMService.Settings( @@ -125,6 +125,15 @@ def create_llm(cfg: AssistantConfig): extra=extra, ), ) + # Pipecat represents late async-tool results with the newer `developer` + # role. Most OpenAI-compatible providers (including DeepSeek) only accept + # system/assistant/user/tool. This flag uses Pipecat's built-in adapter to + # downgrade developer messages before sending the request. Official or + # otherwise compatible endpoints can opt in through the resource values. + service.supports_developer_role = ( + cfg.llm_values.get("supportsDeveloperRole") is True + ) + return service def create_tts(cfg: AssistantConfig): diff --git a/backend/tests/test_service_factory.py b/backend/tests/test_service_factory.py index b4862ae..8e7f099 100644 --- a/backend/tests/test_service_factory.py +++ b/backend/tests/test_service_factory.py @@ -4,13 +4,61 @@ import unittest from unittest.mock import patch from models import AssistantConfig +from pipecat.processors.aggregators.llm_context import LLMContext from services.pipecat.service_factory import ( HTTP_TTS_STOP_FRAME_TIMEOUT_S, WEBSOCKET_TTS_STOP_FRAME_TIMEOUT_S, + create_llm, create_tts, ) +class LLMServiceFactoryTest(unittest.TestCase): + def test_openai_compatible_llm_converts_async_tool_developer_result(self): + service = create_llm( + AssistantConfig( + llm_interface_type="openai-llm", + model="deepseek-chat", + llm_api_key="test-key", + llm_base_url="https://llm.example.test/v1", + ) + ) + context = LLMContext( + messages=[ + {"role": "system", "content": "你是助手"}, + { + "role": "developer", + "content": '{"type":"async_tool","status":"finished"}', + }, + {"role": "user", "content": "ok"}, + ] + ) + + params = service.get_llm_adapter().get_llm_invocation_params( + context, + convert_developer_to_user=not service.supports_developer_role, + ) + + self.assertEqual( + [message["role"] for message in params["messages"]], + ["system", "user", "user"], + ) + self.assertNotIn("developer", str(params["messages"])) + + def test_provider_can_explicitly_enable_developer_role(self): + service = create_llm( + AssistantConfig( + llm_interface_type="openai-llm", + model="gpt-compatible", + llm_api_key="test-key", + llm_base_url="https://llm.example.test/v1", + llm_values={"supportsDeveloperRole": True}, + ) + ) + + self.assertTrue(service.supports_developer_role) + + class TTSServiceFactoryTest(unittest.TestCase): def test_http_tts_keeps_wider_audio_chunk_timeout(self): config = AssistantConfig(