From 281affed673a91f6a4e783ff2c7fe523e9c3fafe Mon Sep 17 00:00:00 2001 From: Andrew Chen <48723787+chuenchen309@users.noreply.github.com> Date: Thu, 16 Jul 2026 09:25:01 +0800 Subject: [PATCH] fix(agents): guard IndexError when Chat.last hits an empty messages list `_run_tools`'s no-op guard checked `not chat.last`, but `Chat.last` indexes `messages[-1]`, which raises IndexError on an empty list instead of behaving falsy. A ChatWorkflow with no messages added (e.g. `ChatWorkflow(...).run()`) hit this IndexError inside the guard's own condition instead of the intended clean no-op. Check `not chat.messages` directly instead. Co-Authored-By: Claude Sonnet 5 --- .../src/giskard/agents/workflow.py | 2 +- .../tests/test_workflow_steps.py | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/libs/giskard-agents/src/giskard/agents/workflow.py b/libs/giskard-agents/src/giskard/agents/workflow.py index 348a542a7d..51b5d3399f 100644 --- a/libs/giskard-agents/src/giskard/agents/workflow.py +++ b/libs/giskard-agents/src/giskard/agents/workflow.py @@ -166,7 +166,7 @@ async def execute( async def _run_tools(self, chat: Chat[Any]) -> AsyncGenerator[ToolMessage, None]: if ( - not chat.last + not chat.messages or not isinstance(chat.last, AssistantMessage) or not chat.last.tool_calls ): diff --git a/libs/giskard-agents/tests/test_workflow_steps.py b/libs/giskard-agents/tests/test_workflow_steps.py index 4f4360b3f5..dcc7f7e05c 100644 --- a/libs/giskard-agents/tests/test_workflow_steps.py +++ b/libs/giskard-agents/tests/test_workflow_steps.py @@ -120,3 +120,29 @@ async def test_run_returns_successful_chat(): assert not chat.failed assert chat.last.content == "World!" + + +async def test_run_with_no_messages_does_not_raise_indexerror(): + """A ChatWorkflow with no messages added must not crash with IndexError. + + ``_run_tools``'s guard checks ``not chat.last`` before checking whether + there is anything to process, but ``Chat.last`` indexes ``messages[-1]``, + which raises ``IndexError`` on an empty list instead of behaving falsy. + """ + gen = MagicMock(spec=BaseGenerator) + gen.complete = AsyncMock( + return_value=CompletionResponse( + choices=[ + Choice( + message=AssistantMessage(content="Hello!"), + finish_reason="stop", + index=0, + ) + ] + ) + ) + + chat = await ChatWorkflow(generator=gen).run() + + assert not chat.failed + assert chat.last.content == "Hello!"