diff --git a/engine/core/duplex_pipeline.py b/engine/core/duplex_pipeline.py index 6ee5747..f2d96e2 100644 --- a/engine/core/duplex_pipeline.py +++ b/engine/core/duplex_pipeline.py @@ -955,6 +955,16 @@ class DuplexPipeline: if not self._bot_starts_first(): return + if self._generated_opener_enabled() and self._resolved_tool_schemas(): + # Run generated opener as a normal tool-capable assistant turn. + # Use an empty user input so the opener can be driven by system prompt policy. + if self._current_turn_task and not self._current_turn_task.done(): + logger.info("Skip initial generated opener: assistant turn already in progress") + return + self._current_turn_task = asyncio.create_task(self._handle_turn("")) + logger.info("Initial generated opener started with tool-calling path") + return + greeting_to_speak = self.conversation.greeting if self._generated_opener_enabled(): generated_greeting = await self._generate_runtime_greeting() diff --git a/engine/tests/test_tool_call_flow.py b/engine/tests/test_tool_call_flow.py index 3a34193..f3285be 100644 --- a/engine/tests/test_tool_call_flow.py +++ b/engine/tests/test_tool_call_flow.py @@ -246,6 +246,42 @@ async def test_generated_opener_prompt_uses_system_prompt_only(monkeypatch): assert "额外风格提示" not in user_prompt +@pytest.mark.asyncio +async def test_generated_opener_uses_tool_capable_turn_when_tools_available(monkeypatch): + pipeline, _events = _build_pipeline(monkeypatch, [[LLMStreamEvent(type="done")]]) + pipeline.apply_runtime_overrides( + { + "generatedOpenerEnabled": True, + "tools": [ + { + "type": "function", + "executor": "client", + "function": { + "name": "text_msg_prompt", + "description": "Show a prompt", + "parameters": {"type": "object", "properties": {}}, + }, + } + ], + } + ) + + called: Dict[str, Any] = {} + waiter = asyncio.Event() + + async def _fake_handle_turn(user_text: str) -> None: + called["user_text"] = user_text + waiter.set() + + monkeypatch.setattr(pipeline, "_handle_turn", _fake_handle_turn) + pipeline.conversation.greeting = "fallback greeting" + + await pipeline.emit_initial_greeting() + await asyncio.wait_for(waiter.wait(), timeout=1.0) + + assert called.get("user_text") == "" + + @pytest.mark.asyncio async def test_ws_message_parses_tool_call_results(): msg = parse_client_message(