feat: implement LLMCallBlockedError handling in LLM and Agent classes

- Introduced LLMCallBlockedError to manage blocked LLM calls from before_llm_call hooks.
- Updated LLM class to raise LLMCallBlockedError instead of returning a boolean.
- Enhanced Agent class to emit events and handle LLMCallBlockedError during task execution.
- Added error handling in CrewAgentExecutor and agent utilities to gracefully manage blocked calls.
- Updated tests to verify behavior when LLM calls are blocked.
This commit is contained in:
lorenzejay
2025-12-21 21:48:26 -08:00
parent be70a04153
commit 05c42791c9
13 changed files with 176 additions and 38 deletions

View File

@@ -4,7 +4,11 @@ from __future__ import annotations
from unittest.mock import Mock
from crewai.hooks import clear_all_llm_call_hooks, unregister_after_llm_call_hook, unregister_before_llm_call_hook
from crewai.hooks import (
clear_all_llm_call_hooks,
unregister_after_llm_call_hook,
unregister_before_llm_call_hook,
)
import pytest
from crewai.hooks.llm_hooks import (
@@ -87,6 +91,93 @@ class TestLLMCallHookContext:
assert new_message in mock_executor.messages
assert len(mock_executor.messages) == 2
def test_before_hook_returning_false_gracefully_finishes(self) -> None:
"""Test that when before_llm_call hook returns False, agent gracefully finishes."""
from crewai import Agent, Crew, Task
hook_called = {"before": False}
def blocking_hook(context: LLMCallHookContext) -> bool:
"""Hook that blocks all LLM calls."""
hook_called["before"] = True
return False
register_before_llm_call_hook(blocking_hook)
try:
agent = Agent(
role="Test Agent",
goal="Answer questions",
backstory="You are a test agent",
verbose=True,
)
task = Task(
description="Say hello",
expected_output="A greeting",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task], verbose=True)
result = crew.kickoff()
print('result', result)
assert 1 + 1 == 3
assert hook_called["before"] is True, "Before hook should have been called"
assert "blocked" in result.raw.lower(), "Result should indicate LLM call was blocked" # type: ignore
finally:
unregister_before_llm_call_hook(blocking_hook)
def test_direct_llm_call_raises_blocked_error_when_hook_returns_false(self) -> None:
"""Test that direct LLM.call() raises LLMCallBlockedError when hook returns False."""
from crewai.hooks import LLMCallBlockedError
from crewai.llm import LLM
hook_called = {"before": False}
def blocking_hook(context: LLMCallHookContext) -> bool:
"""Hook that blocks all LLM calls."""
hook_called["before"] = True
return False
register_before_llm_call_hook(blocking_hook)
try:
llm = LLM(model="gpt-4o-mini")
with pytest.raises(LLMCallBlockedError) as exc_info:
llm.call([{"role": "user", "content": "Say hello"}])
assert hook_called["before"] is True, "Before hook should have been called"
assert "blocked" in str(exc_info.value).lower()
finally:
unregister_before_llm_call_hook(blocking_hook)
def test_raises_with_llm_call_blocked_exception(self) -> None:
"""Test that the LLM call raises an exception when the hook raises an exception."""
from crewai.hooks import LLMCallBlockedError
from crewai.llm import LLM
def blocking_hook(context: LLMCallHookContext) -> bool:
raise LLMCallBlockedError("llm call blocked")
register_before_llm_call_hook(blocking_hook)
try:
llm = LLM(model="gpt-4o-mini")
with pytest.raises(LLMCallBlockedError) as exc_info:
llm.call([{"role": "user", "content": "Say hello"}])
assert "blocked" in str(exc_info.value).lower()
finally:
unregister_before_llm_call_hook(blocking_hook)
class TestBeforeLLMCallHooks:
"""Test before_llm_call hook registration and execution."""