From f5d6a3b348369b9fa6cd19849c63fa9b1545a09d Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Sat, 5 Sep 2026 17:15:20 +0800 Subject: [PATCH] fix(agent): tolerate intent annotations on no-argument platform tools --- .../pkg/agent/runner/platform_tools.py | 4 +++ tests/unit_tests/agent/test_platform_tools.py | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/langbot/pkg/agent/runner/platform_tools.py b/src/langbot/pkg/agent/runner/platform_tools.py index 913bb90fb..aee86b287 100644 --- a/src/langbot/pkg/agent/runner/platform_tools.py +++ b/src/langbot/pkg/agent/runner/platform_tools.py @@ -573,6 +573,10 @@ def _normalize_platform_params( if not isinstance(parameters, dict): raise ValueError('parameters must be an object') allowed = set((definition.parameters.get('properties') or {}).keys()) + # Some model responses annotate no-argument calls with a textual intent. + # Keep the original trace, but never pass this annotation to the adapter. + if not allowed and isinstance(parameters.get('_call'), str): + parameters = {key: value for key, value in parameters.items() if key != '_call'} extra = set(parameters) - allowed if extra: raise ValueError(f'Unexpected parameters: {", ".join(sorted(extra))}') diff --git a/tests/unit_tests/agent/test_platform_tools.py b/tests/unit_tests/agent/test_platform_tools.py index ebf4c15bf..ac274a0a7 100644 --- a/tests/unit_tests/agent/test_platform_tools.py +++ b/tests/unit_tests/agent/test_platform_tools.py @@ -111,6 +111,34 @@ async def test_mock_fixture_does_not_mutate_options(): assert fixture['nested']['value'] == 42 +@pytest.mark.asyncio +@pytest.mark.parametrize('mock', [False, True]) +async def test_event_actor_accepts_textual_call_annotation_without_changing_target(mock): + event = _event('group.member_joined') + if mock: + event.delivery.surface = 'webui' + event.delivery.platform_capabilities = {'debug_mock': True} + adapter = SimpleNamespace( + get_supported_apis=lambda: ['get_user_info'], + get_user_info=AsyncMock(return_value={'name': 'Test User'}), + ) + ap = SimpleNamespace( + platform_mgr=SimpleNamespace(get_bot_by_uuid=AsyncMock(return_value=SimpleNamespace(adapter=adapter))) + ) + session = {'authorization': {'bot_id': 'bot-1', 'platform_context': freeze_platform_context(event)}} + parameters = {'_call': 'Get the new member profile'} + await execute_platform_tool(ap, object(), session, 'event_get_actor', parameters) + assert parameters == {'_call': 'Get the new member profile'} + if mock: + ap.platform_mgr.get_bot_by_uuid.assert_not_awaited() + else: + adapter.get_user_info.assert_awaited_once_with(user_id='user-1') + + for invalid in ({'_call': {}}, {'_call': 'intent', 'user_id': 'forged'}): + with pytest.raises(ValueError, match='Unexpected parameters'): + await execute_platform_tool(ap, object(), session, 'event_get_actor', invalid) + + def _event(event_type: str = 'friend.request_received') -> AgentEventEnvelope: return AgentEventEnvelope( event_id='event-1',