From c205d2e8de6f7c9fe47d22cc91e5852fdf211fe1 Mon Sep 17 00:00:00 2001 From: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> Date: Wed, 12 Nov 2025 08:38:13 -0800 Subject: [PATCH] feat: implement before and after LLM call hooks in CrewAgentExecutor (#3893) - Added support for before and after LLM call hooks to allow modification of messages and responses during LLM interactions. - Introduced LLMCallHookContext to provide hooks with access to the executor state, enabling in-place modifications of messages. - Updated get_llm_response function to utilize the new hooks, ensuring that modifications persist across iterations. - Enhanced tests to verify the functionality of the hooks and their error handling capabilities, ensuring robust execution flow. --- .../src/crewai/agents/crew_agent_executor.py | 9 + .../src/crewai/utilities/agent_utils.py | 99 +++++- .../src/crewai/utilities/llm_call_hooks.py | 115 +++++++ lib/crewai/tests/agents/test_agent.py | 290 ++++++++++++++++++ ...after_llm_call_hook_modifies_messages.yaml | 126 ++++++++ ..._modifies_messages_for_next_iteration.yaml | 222 ++++++++++++++ ...efore_llm_call_hook_modifies_messages.yaml | 127 ++++++++ ..._hooks_can_modify_executor_attributes.yaml | 262 ++++++++++++++++ .../test_llm_call_hooks_error_handling.yaml | 159 ++++++++++ .../test_llm_call_hooks_with_crew.yaml | 182 +++++++++++ 10 files changed, 1590 insertions(+), 1 deletion(-) create mode 100644 lib/crewai/src/crewai/utilities/llm_call_hooks.py create mode 100644 lib/crewai/tests/cassettes/test_after_llm_call_hook_modifies_messages.yaml create mode 100644 lib/crewai/tests/cassettes/test_after_llm_call_hook_modifies_messages_for_next_iteration.yaml create mode 100644 lib/crewai/tests/cassettes/test_before_llm_call_hook_modifies_messages.yaml create mode 100644 lib/crewai/tests/cassettes/test_llm_call_hooks_can_modify_executor_attributes.yaml create mode 100644 lib/crewai/tests/cassettes/test_llm_call_hooks_error_handling.yaml create mode 100644 lib/crewai/tests/cassettes/test_llm_call_hooks_with_crew.yaml diff --git a/lib/crewai/src/crewai/agents/crew_agent_executor.py b/lib/crewai/src/crewai/agents/crew_agent_executor.py index 5b806658c..d44c984d4 100644 --- a/lib/crewai/src/crewai/agents/crew_agent_executor.py +++ b/lib/crewai/src/crewai/agents/crew_agent_executor.py @@ -38,6 +38,10 @@ from crewai.utilities.agent_utils import ( ) from crewai.utilities.constants import TRAINING_DATA_FILE from crewai.utilities.i18n import I18N, get_i18n +from crewai.utilities.llm_call_hooks import ( + get_after_llm_call_hooks, + get_before_llm_call_hooks, +) from crewai.utilities.printer import Printer from crewai.utilities.tool_utils import execute_tool_and_check_finality from crewai.utilities.training_handler import CrewTrainingHandler @@ -130,6 +134,10 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): self.messages: list[LLMMessage] = [] self.iterations = 0 self.log_error_after = 3 + self.before_llm_call_hooks: list[Callable] = [] + self.after_llm_call_hooks: list[Callable] = [] + self.before_llm_call_hooks.extend(get_before_llm_call_hooks()) + self.after_llm_call_hooks.extend(get_after_llm_call_hooks()) if self.llm: # This may be mutating the shared llm object and needs further evaluation existing_stop = getattr(self.llm, "stop", []) @@ -226,6 +234,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): from_task=self.task, from_agent=self.agent, response_model=self.response_model, + executor_context=self, ) formatted_answer = process_llm_response(answer, self.use_stop_words) # type: ignore[assignment] diff --git a/lib/crewai/src/crewai/utilities/agent_utils.py b/lib/crewai/src/crewai/utilities/agent_utils.py index a6403c315..d9b4f148b 100644 --- a/lib/crewai/src/crewai/utilities/agent_utils.py +++ b/lib/crewai/src/crewai/utilities/agent_utils.py @@ -33,6 +33,7 @@ from crewai.utilities.types import LLMMessage if TYPE_CHECKING: from crewai.agent import Agent + from crewai.agents.crew_agent_executor import CrewAgentExecutor from crewai.lite_agent import LiteAgent from crewai.llm import LLM from crewai.task import Task @@ -236,6 +237,7 @@ def get_llm_response( from_task: Task | None = None, from_agent: Agent | LiteAgent | None = None, response_model: type[BaseModel] | None = None, + executor_context: CrewAgentExecutor | None = None, ) -> str: """Call the LLM and return the response, handling any invalid responses. @@ -247,6 +249,7 @@ def get_llm_response( from_task: Optional task context for the LLM call from_agent: Optional agent context for the LLM call response_model: Optional Pydantic model for structured outputs + executor_context: Optional executor context for hook invocation Returns: The response from the LLM as a string @@ -255,6 +258,11 @@ def get_llm_response( Exception: If an error occurs. ValueError: If the response is None or empty. """ + + if executor_context is not None: + _setup_before_llm_call_hooks(executor_context, printer) + messages = executor_context.messages + try: answer = llm.call( messages, @@ -272,7 +280,7 @@ def get_llm_response( ) raise ValueError("Invalid response from LLM call - None or empty.") - return answer + return _setup_after_llm_call_hooks(executor_context, answer, printer) def process_llm_response( @@ -661,3 +669,92 @@ def load_agent_from_repository(from_repository: str) -> dict[str, Any]: else: attributes[key] = value return attributes + + +def _setup_before_llm_call_hooks( + executor_context: CrewAgentExecutor | None, printer: Printer +) -> None: + """Setup and invoke before_llm_call hooks for the executor context. + + Args: + executor_context: The executor context to setup the hooks for. + printer: Printer instance for error logging. + """ + if executor_context and executor_context.before_llm_call_hooks: + from crewai.utilities.llm_call_hooks import LLMCallHookContext + + original_messages = executor_context.messages + + hook_context = LLMCallHookContext(executor_context) + try: + for hook in executor_context.before_llm_call_hooks: + hook(hook_context) + except Exception as e: + printer.print( + content=f"Error in before_llm_call hook: {e}", + color="yellow", + ) + + if not isinstance(executor_context.messages, list): + printer.print( + content=( + "Warning: before_llm_call hook replaced messages with non-list. " + "Restoring original messages list. Hooks should modify messages in-place, " + "not replace the list (e.g., use context.messages.append() not context.messages = [])." + ), + color="yellow", + ) + if isinstance(original_messages, list): + executor_context.messages = original_messages + else: + executor_context.messages = [] + + +def _setup_after_llm_call_hooks( + executor_context: CrewAgentExecutor | None, + answer: str, + printer: Printer, +) -> str: + """Setup and invoke after_llm_call hooks for the executor context. + + Args: + executor_context: The executor context to setup the hooks for. + answer: The LLM response string. + printer: Printer instance for error logging. + + Returns: + The potentially modified response string. + """ + if executor_context and executor_context.after_llm_call_hooks: + from crewai.utilities.llm_call_hooks import LLMCallHookContext + + original_messages = executor_context.messages + + hook_context = LLMCallHookContext(executor_context, response=answer) + try: + for hook in executor_context.after_llm_call_hooks: + modified_response = hook(hook_context) + if modified_response is not None and isinstance(modified_response, str): + answer = modified_response + + except Exception as e: + printer.print( + content=f"Error in after_llm_call hook: {e}", + color="yellow", + ) + + if not isinstance(executor_context.messages, list): + printer.print( + content=( + "Warning: after_llm_call hook replaced messages with non-list. " + "Restoring original messages list. Hooks should modify messages in-place, " + "not replace the list (e.g., use context.messages.append() not context.messages = [])." + ), + color="yellow", + ) + if isinstance(original_messages, list): + executor_context.messages = original_messages + else: + executor_context.messages = [] + + return answer diff --git a/lib/crewai/src/crewai/utilities/llm_call_hooks.py b/lib/crewai/src/crewai/utilities/llm_call_hooks.py new file mode 100644 index 000000000..bf6b81b36 --- /dev/null +++ b/lib/crewai/src/crewai/utilities/llm_call_hooks.py @@ -0,0 +1,115 @@ +from __future__ import annotations + +from collections.abc import Callable +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from crewai.agents.crew_agent_executor import CrewAgentExecutor + + +class LLMCallHookContext: + """Context object passed to LLM call hooks with full executor access. + + Provides hooks with complete access to the executor state, allowing + modification of messages, responses, and executor attributes. + + Attributes: + executor: Full reference to the CrewAgentExecutor instance + messages: Direct reference to executor.messages (mutable list). + Can be modified in both before_llm_call and after_llm_call hooks. + Modifications in after_llm_call hooks persist to the next iteration, + allowing hooks to modify conversation history for subsequent LLM calls. + IMPORTANT: Modify messages in-place (e.g., append, extend, remove items). + Do NOT replace the list (e.g., context.messages = []), as this will break + the executor. Use context.messages.append() or context.messages.extend() + instead of assignment. + agent: Reference to the agent executing the task + task: Reference to the task being executed + crew: Reference to the crew instance + llm: Reference to the LLM instance + iterations: Current iteration count + response: LLM response string (only set for after_llm_call hooks). + Can be modified by returning a new string from after_llm_call hook. + """ + + def __init__( + self, + executor: CrewAgentExecutor, + response: str | None = None, + ) -> None: + """Initialize hook context with executor reference. + + Args: + executor: The CrewAgentExecutor instance + response: Optional response string (for after_llm_call hooks) + """ + self.executor = executor + self.messages = executor.messages + self.agent = executor.agent + self.task = executor.task + self.crew = executor.crew + self.llm = executor.llm + self.iterations = executor.iterations + self.response = response + + +# Global hook registries (optional convenience feature) +_before_llm_call_hooks: list[Callable[[LLMCallHookContext], None]] = [] +_after_llm_call_hooks: list[Callable[[LLMCallHookContext], str | None]] = [] + + +def register_before_llm_call_hook( + hook: Callable[[LLMCallHookContext], None], +) -> None: + """Register a global before_llm_call hook. + + Global hooks are added to all executors automatically. + This is a convenience function for registering hooks that should + apply to all LLM calls across all executors. + + Args: + hook: Function that receives LLMCallHookContext and can modify + context.messages directly. Should return None. + IMPORTANT: Modify messages in-place (append, extend, remove items). + Do NOT replace the list (context.messages = []), as this will break execution. + """ + _before_llm_call_hooks.append(hook) + + +def register_after_llm_call_hook( + hook: Callable[[LLMCallHookContext], str | None], +) -> None: + """Register a global after_llm_call hook. + + Global hooks are added to all executors automatically. + This is a convenience function for registering hooks that should + apply to all LLM calls across all executors. + + Args: + hook: Function that receives LLMCallHookContext and can modify: + - The response: Return modified response string or None to keep original + - The messages: Modify context.messages directly (mutable reference) + Both modifications are supported and can be used together. + IMPORTANT: Modify messages in-place (append, extend, remove items). + Do NOT replace the list (context.messages = []), as this will break execution. + """ + _after_llm_call_hooks.append(hook) + + +def get_before_llm_call_hooks() -> list[Callable[[LLMCallHookContext], None]]: + """Get all registered global before_llm_call hooks. + + Returns: + List of registered before hooks + """ + return _before_llm_call_hooks.copy() + + +def get_after_llm_call_hooks() -> list[Callable[[LLMCallHookContext], str | None]]: + """Get all registered global after_llm_call hooks. + + Returns: + List of registered after hooks + """ + return _after_llm_call_hooks.copy() diff --git a/lib/crewai/tests/agents/test_agent.py b/lib/crewai/tests/agents/test_agent.py index 4fd1f3b5b..21f08a336 100644 --- a/lib/crewai/tests/agents/test_agent.py +++ b/lib/crewai/tests/agents/test_agent.py @@ -2714,3 +2714,293 @@ def test_agent_without_apps_no_platform_tools(): tools = crew._prepare_tools(agent, task, []) assert tools == [] + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_before_llm_call_hook_modifies_messages(): + """Test that before_llm_call hooks can modify messages.""" + from crewai.utilities.llm_call_hooks import LLMCallHookContext, register_before_llm_call_hook + + hook_called = False + original_message_count = 0 + + def before_hook(context: LLMCallHookContext) -> None: + nonlocal hook_called, original_message_count + hook_called = True + original_message_count = len(context.messages) + context.messages.append({ + "role": "user", + "content": "Additional context: This is a test modification." + }) + + register_before_llm_call_hook(before_hook) + + try: + agent = Agent( + role="Test Agent", + goal="Test goal", + backstory="Test backstory", + allow_delegation=False, + ) + + task = Task( + description="Say hello", + expected_output="A greeting", + agent=agent, + ) + + result = agent.execute_task(task) + + assert hook_called, "before_llm_call hook should have been called" + assert len(agent.agent_executor.messages) > original_message_count + assert result is not None + finally: + pass + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_after_llm_call_hook_modifies_messages_for_next_iteration(): + """Test that after_llm_call hooks can modify messages for the next iteration.""" + from crewai.utilities.llm_call_hooks import LLMCallHookContext, register_after_llm_call_hook + + hook_call_count = 0 + hook_iterations = [] + messages_added_in_iteration_0 = False + test_message_content = "HOOK_ADDED_MESSAGE_FOR_NEXT_ITERATION" + + def after_hook(context: LLMCallHookContext) -> str | None: + nonlocal hook_call_count, hook_iterations, messages_added_in_iteration_0 + hook_call_count += 1 + current_iteration = context.iterations + hook_iterations.append(current_iteration) + + if current_iteration == 0: + messages_before = len(context.messages) + context.messages.append({ + "role": "user", + "content": test_message_content + }) + messages_added_in_iteration_0 = True + assert len(context.messages) == messages_before + 1 + + return None + + register_after_llm_call_hook(after_hook) + + try: + agent = Agent( + role="Test Agent", + goal="Test goal", + backstory="Test backstory", + allow_delegation=False, + max_iter=3, + ) + + task = Task( + description="Count to 3, taking your time", + expected_output="A count", + agent=agent, + ) + + result = agent.execute_task(task) + + assert hook_call_count > 0, "after_llm_call hook should have been called" + assert messages_added_in_iteration_0, "Message should have been added in iteration 0" + + executor_messages = agent.agent_executor.messages + message_contents = [msg.get("content", "") for msg in executor_messages if isinstance(msg, dict)] + assert any(test_message_content in content for content in message_contents), ( + f"Message added by hook in iteration 0 should be present in executor messages. " + f"Messages: {message_contents}" + ) + + assert len(executor_messages) > 2, "Executor should have more than initial messages" + assert result is not None + finally: + pass + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_after_llm_call_hook_modifies_messages(): + """Test that after_llm_call hooks can modify messages for next iteration.""" + from crewai.utilities.llm_call_hooks import LLMCallHookContext, register_after_llm_call_hook + + hook_called = False + messages_before_hook = 0 + + def after_hook(context: LLMCallHookContext) -> str | None: + nonlocal hook_called, messages_before_hook + hook_called = True + messages_before_hook = len(context.messages) + context.messages.append({ + "role": "user", + "content": "Remember: This is iteration 2 context." + }) + return None # Don't modify response + + register_after_llm_call_hook(after_hook) + + try: + agent = Agent( + role="Test Agent", + goal="Test goal", + backstory="Test backstory", + allow_delegation=False, + max_iter=2, + ) + + task = Task( + description="Count to 2", + expected_output="A count", + agent=agent, + ) + + result = agent.execute_task(task) + + assert hook_called, "after_llm_call hook should have been called" + assert len(agent.agent_executor.messages) > messages_before_hook + assert result is not None + finally: + pass + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_llm_call_hooks_with_crew(): + """Test that LLM call hooks work with crew execution.""" + from crewai.utilities.llm_call_hooks import ( + LLMCallHookContext, + register_after_llm_call_hook, + register_before_llm_call_hook, + ) + + before_hook_called = False + after_hook_called = False + + def before_hook(context: LLMCallHookContext) -> None: + nonlocal before_hook_called + before_hook_called = True + assert context.executor is not None + assert context.agent is not None + assert context.task is not None + context.messages.append({ + "role": "system", + "content": "Additional system context from hook." + }) + + def after_hook(context: LLMCallHookContext) -> str | None: + nonlocal after_hook_called + after_hook_called = True + assert context.response is not None + assert len(context.messages) > 0 + return None + + register_before_llm_call_hook(before_hook) + register_after_llm_call_hook(after_hook) + + try: + agent = Agent( + role="Researcher", + goal="Research topics", + backstory="You are a researcher", + allow_delegation=False, + ) + + task = Task( + description="Research AI frameworks", + expected_output="A research summary", + agent=agent, + ) + + crew = Crew(agents=[agent], tasks=[task]) + result = crew.kickoff() + + assert before_hook_called, "before_llm_call hook should have been called" + assert after_hook_called, "after_llm_call hook should have been called" + assert result is not None + assert result.raw is not None + finally: + pass + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_llm_call_hooks_can_modify_executor_attributes(): + """Test that hooks can access and modify executor attributes like tools.""" + from crewai.utilities.llm_call_hooks import LLMCallHookContext, register_before_llm_call_hook + from crewai.tools import tool + + @tool + def test_tool() -> str: + """A test tool.""" + return "test result" + + hook_called = False + original_tools_count = 0 + + def before_hook(context: LLMCallHookContext) -> None: + nonlocal hook_called, original_tools_count + hook_called = True + original_tools_count = len(context.executor.tools) + assert context.executor.max_iter > 0 + assert context.executor.iterations >= 0 + assert context.executor.tools is not None + + register_before_llm_call_hook(before_hook) + + try: + agent = Agent( + role="Test Agent", + goal="Test goal", + backstory="Test backstory", + tools=[test_tool], + allow_delegation=False, + ) + + task = Task( + description="Use the test tool", + expected_output="Tool result", + agent=agent, + ) + + result = agent.execute_task(task) + + assert hook_called, "before_llm_call hook should have been called" + assert original_tools_count >= 0 + assert result is not None + finally: + pass + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_llm_call_hooks_error_handling(): + """Test that hook errors don't break execution.""" + from crewai.utilities.llm_call_hooks import LLMCallHookContext, register_before_llm_call_hook + + hook_called = False + + def error_hook(context: LLMCallHookContext) -> None: + nonlocal hook_called + hook_called = True + raise ValueError("Test hook error") + + register_before_llm_call_hook(error_hook) + + try: + agent = Agent( + role="Test Agent", + goal="Test goal", + backstory="Test backstory", + allow_delegation=False, + ) + + task = Task( + description="Say hello", + expected_output="A greeting", + agent=agent, + ) + + result = agent.execute_task(task) + + assert hook_called, "before_llm_call hook should have been called" + assert result is not None + finally: + pass diff --git a/lib/crewai/tests/cassettes/test_after_llm_call_hook_modifies_messages.yaml b/lib/crewai/tests/cassettes/test_after_llm_call_hook_modifies_messages.yaml new file mode 100644 index 000000000..de4aa44bf --- /dev/null +++ b/lib/crewai/tests/cassettes/test_after_llm_call_hook_modifies_messages.yaml @@ -0,0 +1,126 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are Test Agent. Test backstory\nYour + personal goal is: Test goal\nTo give my best complete final answer to the task + respond using the exact following format:\n\nThought: I now can give a great + answer\nFinal Answer: Your final answer must be the great and the most complete + as possible, it must be outcome described.\n\nI MUST use these formats, my job + depends on it!"},{"role":"user","content":"\nCurrent Task: Count to 2\n\nThis + is the expected criteria for your final answer: A count\nyou MUST return the + actual complete content as the final answer, not a summary.\n\nBegin! This is + VERY important to you, use the tools available and give your best Final Answer, + your job depends on it!\n\nThought:"},{"role":"user","content":"Additional context: + This is a test modification."}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '849' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFJNb5wwEL3zK0Y+QwSI7LLcokqVcujHoR9S2wg5ZsBujceyTdIo2v9e + GTYLaROpFyTmzXt+b2YeEwCmOtYAE5IHMVqdveH09UHKLx+/2eFzkAdZXL8XJPr9h3dFydLIoNuf + KMIT60LQaDUGRWaBhUMeMKoW+11ZH/K8rmZgpA51pA02ZNVFkY3KqKzMy8ssr7KiOtElKYGeNfA9 + AQB4nL/RqOnwN2sgT58qI3rPB2TNuQmAOdKxwrj3ygduAktXUJAJaGbvnyRNgwwNXIOhexDcwKDu + EDgMMQBw4+/R/TBvleEarua/BooUyq2gw37yPKYyk9YbgBtDgcepzFFuTsjxbF7TYB3d+r+orFdG + edk65J5MNOoDWTajxwTgZh7S9Cw3s45GG9pAv3B+rtgdFj22LmeD1icwUOB6W9+nL+i1HQautN+M + mQkuJHYrdd0JnzpFGyDZpP7XzUvaS3Jlhv+RXwEh0AbsWuuwU+J54rXNYbzd19rOU54NM4/uTgls + g0IXN9Fhzye9HBTzDz7g2PbKDOisU8tV9batRFlfFn29K1lyTP4AAAD//wMApumqgWQDAAA= + headers: + CF-RAY: + - 99d044543db94e48-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 11 Nov 2025 19:41:25 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=KLlCOQ_zxXquDvj96O28ObVFEoAbFE8R7zlmuiuXH1M-1762890085-1.0.1.1-UChItG1GnLDHrErY60dUpkbD3lEkSvfkTQpOmEtzd0fjjm_y1pJQiB.VDXVi2pPIMSelir0ZgiVXSh5.hGPb3RjQqbH3pv0Rr_2dQ59OIQ8; + path=/; expires=Tue, 11-Nov-25 20:11:25 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=u.Z6xV9tQd3ucK35BinKtlCkewcI6q_uQicyeEeeR18-1762890085355-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '559' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '735' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999817' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999817' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_bcaa0f8500714ed09f967488b238ce2e + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/test_after_llm_call_hook_modifies_messages_for_next_iteration.yaml b/lib/crewai/tests/cassettes/test_after_llm_call_hook_modifies_messages_for_next_iteration.yaml new file mode 100644 index 000000000..cf47c1930 --- /dev/null +++ b/lib/crewai/tests/cassettes/test_after_llm_call_hook_modifies_messages_for_next_iteration.yaml @@ -0,0 +1,222 @@ +interactions: +- request: + body: '{"trace_id": "aeb82647-004a-4a30-9481-d55f476d5659", "execution_type": + "crew", "user_identifier": null, "execution_context": {"crew_fingerprint": null, + "crew_name": "Unknown Crew", "flow_name": null, "crewai_version": "1.4.1", "privacy_level": + "standard"}, "execution_metadata": {"expected_duration_estimate": 300, "agent_count": + 0, "task_count": 0, "flow_method_count": 0, "execution_started_at": "2025-11-11T19:45:17.648657+00:00"}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Length: + - '434' + Content-Type: + - application/json + User-Agent: + - CrewAI-CLI/1.4.1 + X-Crewai-Version: + - 1.4.1 + method: POST + uri: https://app.crewai.com/crewai_plus/api/v1/tracing/batches + response: + body: + string: '{"error":"bad_credentials","message":"Bad credentials"}' + headers: + Connection: + - keep-alive + Content-Length: + - '55' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 11 Nov 2025 19:45:17 GMT + cache-control: + - no-store + content-security-policy: + - 'default-src ''self'' *.app.crewai.com app.crewai.com; script-src ''self'' + ''unsafe-inline'' *.app.crewai.com app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts + https://www.gstatic.com https://run.pstmn.io https://apis.google.com https://apis.google.com/js/api.js + https://accounts.google.com https://accounts.google.com/gsi/client https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css.map + https://*.google.com https://docs.google.com https://slides.google.com https://js.hs-scripts.com + https://js.sentry-cdn.com https://browser.sentry-cdn.com https://www.googletagmanager.com + https://js-na1.hs-scripts.com https://js.hubspot.com http://js-na1.hs-scripts.com + https://bat.bing.com https://cdn.amplitude.com https://cdn.segment.com https://d1d3n03t5zntha.cloudfront.net/ + https://descriptusercontent.com https://edge.fullstory.com https://googleads.g.doubleclick.net + https://js.hs-analytics.net https://js.hs-banner.com https://js.hsadspixel.net + https://js.hscollectedforms.net https://js.usemessages.com https://snap.licdn.com + https://static.cloudflareinsights.com https://static.reo.dev https://www.google-analytics.com + https://share.descript.com/; style-src ''self'' ''unsafe-inline'' *.app.crewai.com + app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts; img-src ''self'' data: + *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com https://dashboard.tools.crewai.com + https://cdn.jsdelivr.net https://forms.hsforms.com https://track.hubspot.com + https://px.ads.linkedin.com https://px4.ads.linkedin.com https://www.google.com + https://www.google.com.br; font-src ''self'' data: *.app.crewai.com app.crewai.com; + connect-src ''self'' *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com + https://connect.useparagon.com/ https://zeus.useparagon.com/* https://*.useparagon.com/* + https://run.pstmn.io https://connect.tools.crewai.com/ https://*.sentry.io + https://www.google-analytics.com https://edge.fullstory.com https://rs.fullstory.com + https://api.hubspot.com https://forms.hscollectedforms.net https://api.hubapi.com + https://px.ads.linkedin.com https://px4.ads.linkedin.com https://google.com/pagead/form-data/16713662509 + https://google.com/ccm/form-data/16713662509 https://www.google.com/ccm/collect + https://worker-actionkit.tools.crewai.com https://api.reo.dev; frame-src ''self'' + *.app.crewai.com app.crewai.com https://connect.useparagon.com/ https://zeus.tools.crewai.com + https://zeus.useparagon.com/* https://connect.tools.crewai.com/ https://docs.google.com + https://drive.google.com https://slides.google.com https://accounts.google.com + https://*.google.com https://app.hubspot.com/ https://td.doubleclick.net https://www.googletagmanager.com/ + https://www.youtube.com https://share.descript.com' + expires: + - '0' + permissions-policy: + - camera=(), microphone=(self), geolocation=() + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=63072000; includeSubDomains + vary: + - Accept + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-permitted-cross-domain-policies: + - none + x-request-id: + - 48a89b0d-206b-4c1b-aa0d-ecc3b4ab525c + x-runtime: + - '0.088251' + x-xss-protection: + - 1; mode=block + status: + code: 401 + message: Unauthorized +- request: + body: '{"messages":[{"role":"system","content":"You are Test Agent. Test backstory\nYour + personal goal is: Test goal\nTo give my best complete final answer to the task + respond using the exact following format:\n\nThought: I now can give a great + answer\nFinal Answer: Your final answer must be the great and the most complete + as possible, it must be outcome described.\n\nI MUST use these formats, my job + depends on it!"},{"role":"user","content":"\nCurrent Task: Count to 3, taking + your time\n\nThis is the expected criteria for your final answer: A count\nyou + MUST return the actual complete content as the final answer, not a summary.\n\nBegin! + This is VERY important to you, use the tools available and give your best Final + Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '790' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFJNa9wwEL37Vww6r43tOpuNb2nKQgslOSy0NA1mIo9tdWVJSHK2Jex/ + L/J+2Ns20IuE5s0bzXszrxEAEzUrgfEOPe+NjO9Q41atP3/79GG7vX8QD0Xq15svX9/fUd+yRWDo + 5x/E/YmVcN0bSV5odYC5JfQUqmbXy3x1k77LViPQ65pkoLXGx0WSxb1QIs7T/CpOizgrjvROC06O + lfAYAQC8jmdoVNX0k5WQLk6RnpzDllh5TgJgVssQYeiccB6VZ4sJ5Fp5UmPvm04PbedL+AhK74Cj + gla8ECC0QQCgcjuy39VaKJRwO75KuFeUJAlsdnq8OkuUzD+w1AwOg0o1SDkDUCntMbg0Sns6Ivuz + GKlbY/Wz+4PKGqGE6ypL6LQKjTuvDRvRfQTwNJo2XPjAjNW98ZXXWxq/y5ZH09g0rBl6cwS99ihn + 8esTcFGvqsmjkG5mO+PIO6on6jQjHGqhZ0A0U/13N/+qfVAuVPs/5SeAczKe6spYqgW/VDylWQq7 + /Fba2eWxYebIvghOlRdkwyRqanCQhwVj7pfz1FeNUC1ZY8VhyxpTFTxfXWXNapmzaB/9BgAA//8D + AL0LXHV0AwAA + headers: + CF-RAY: + - 99d04a06dc4d1949-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 11 Nov 2025 19:45:18 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=KnsnYxgmlpoHf.5TWnNgU30xb2tc0gK7SC2BbUkud2M-1762890318-1.0.1.1-3KeaQY59x5mY6n8DINELLaH9_b68w7W4ZZ0KeOknBHmQyDwx5qbtDonfYxOjsO_KykjtJLHpB0bsINSNEa9TrjNQHqUWTlRhldfTLenUG44; + path=/; expires=Tue, 11-Nov-25 20:15:18 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=ekC35NRP79GCMP.eTi_odl5.6DIsAeFEXKlanWUZOH4-1762890318589-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '598' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '632' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999827' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999827' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_cb36cbe6c33b42a28675e8c6d9a36fe9 + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/test_before_llm_call_hook_modifies_messages.yaml b/lib/crewai/tests/cassettes/test_before_llm_call_hook_modifies_messages.yaml new file mode 100644 index 000000000..ac13d92ca --- /dev/null +++ b/lib/crewai/tests/cassettes/test_before_llm_call_hook_modifies_messages.yaml @@ -0,0 +1,127 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are Test Agent. Test backstory\nYour + personal goal is: Test goal\nTo give my best complete final answer to the task + respond using the exact following format:\n\nThought: I now can give a great + answer\nFinal Answer: Your final answer must be the great and the most complete + as possible, it must be outcome described.\n\nI MUST use these formats, my job + depends on it!"},{"role":"user","content":"\nCurrent Task: Say hello\n\nThis + is the expected criteria for your final answer: A greeting\nyou MUST return + the actual complete content as the final answer, not a summary.\n\nBegin! This + is VERY important to you, use the tools available and give your best Final Answer, + your job depends on it!\n\nThought:"},{"role":"user","content":"Additional context: + This is a test modification."}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '851' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFJdi9swEHz3r9jqOT5sk+RSvx2lJW1poXDQ0vYwirS21cpaIclJr0f+ + +yE7F/s+Cn0xeGdnNLO7dwkAU5KVwETLg+isTt9w+rr/YESx27+93RaHVm4/ff7y8Vpcffv+ly0i + g3a/UIQH1oWgzmoMiswIC4c8YFTNL9fF5nWWbfIB6EiijrTGhnR5kaedMiotsmKVZss0X57oLSmB + npXwIwEAuBu+0aiR+IeVkC0eKh16zxtk5bkJgDnSscK498oHbgJbTKAgE9AM3q9b6ps2lPAeDB1A + cAON2iNwaGIA4MYf0P0075ThGq6GvxK2qDW9mks6rHvPYy7Taz0DuDEUeJzLEObmhBzP9jU11tHO + P6GyWhnl28oh92SiVR/IsgE9JgA3w5j6R8mZddTZUAX6jcNz+fpy1GPTembo6gQGClzP6pti8YJe + JTFwpf1s0Exw0aKcqNNWeC8VzYBklvq5m5e0x+TKNP8jPwFCoA0oK+tQKvE48dTmMF7vv9rOUx4M + M49urwRWQaGLm5BY816PJ8X8rQ/YVbUyDTrr1HhXta2Wotis8nqzLlhyTO4BAAD//wMAuV0QSWYD + AAA= + headers: + CF-RAY: + - 99d044428f103c35-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 11 Nov 2025 19:41:22 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=jp.mByP87tLw_KZOIh7lXZ9UMACecreCMNwHwtJmUvQ-1762890082-1.0.1.1-D76UWkvWlN8e0zlQpgSlSHjrhx3Rkh_r8bz4XKx8kljJt8s9Okre9bo7M62ewJNFK9O9iuHkADMKeAEwlsc4Hg0MsF2vt2Hu1J0xikSInv0; + path=/; expires=Tue, 11-Nov-25 20:11:22 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=pzTqogdMFPJY2.Yrj49LODdUKbD8UBctCWNyIZVsvK4-1762890082258-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '460' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '478' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999817' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999820' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_3bda51e6d3e34f8cadcc12551dc29ab0 + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/test_llm_call_hooks_can_modify_executor_attributes.yaml b/lib/crewai/tests/cassettes/test_llm_call_hooks_can_modify_executor_attributes.yaml new file mode 100644 index 000000000..6e56ea1db --- /dev/null +++ b/lib/crewai/tests/cassettes/test_llm_call_hooks_can_modify_executor_attributes.yaml @@ -0,0 +1,262 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are Test Agent. Test backstory\nYour + personal goal is: Test goal\nYou ONLY have access to the following tools, and + should NEVER make up tools that are not listed here:\n\nTool Name: test_tool\nTool + Arguments: {}\nTool Description: A test tool.\n\nIMPORTANT: Use the following + format in your response:\n\n```\nThought: you should always think about what + to do\nAction: the action to take, only one name of [test_tool], just the name, + exactly as it''s written.\nAction Input: the input to the action, just a simple + JSON object, enclosed in curly braces, using \" to wrap keys and values.\nObservation: + the result of the action\n```\n\nOnce all necessary information is gathered, + return the following format:\n\n```\nThought: I now know the final answer\nFinal + Answer: the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: Use the test tool\n\nThis is the expected criteria for your final answer: + Tool result\nyou MUST return the actual complete content as the final answer, + not a summary.\n\nBegin! This is VERY important to you, use the tools available + and give your best Final Answer, your job depends on it!\n\nThought:"},{"role":"user","content":"Additional + context: This is a test modification."}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '1311' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA4xTy47bMAy85ysIneMgcbNp1reizwXaXrpAD83CVmTaViqLWolu2gb590LOw94+ + gF504HBGw6F0mAAIXYoMhGokq9aZ5KWkz/vdTn14i/dv9h9f19tXi3dtun789H71U0wjg7Y7VHxh + zRS1ziBrsidYeZSMUXXxfJWub+fzddoDLZVoIq12nCxni6TVVifpPL1J5stksTzTG9IKg8jgywQA + 4NCf0agt8bvIYD69VFoMQdYosmsTgPBkYkXIEHRgaVlMB1CRZbS996IoNva+oa5uOIM7CA11poQu + IHCDwBg4ZyIDTFAj90WPj532WIK2FflWxqGhIt+DlbbSgLRhj362sS9URLNB6FKCO+s6zuBw3Nii + KMb2PFZdkDEj2xkzAqS1xP11fTAPZ+R4jcJQ7Txtw29UUWmrQ5N7lIFsHDswOdGjxwnAQx959yRF + 4Ty1Lnr+iv116Wp10hPDqgf02XkfgomlGbFuL6wnenmJLLUJo6UJJVWD5UAdNiy7UtMImIym/tPN + 37RPk2tb/4/8ACiFjrHMncdSq6cTD20e40/4V9s15d6wCOi/aYU5a/RxEyVWsjOn5ynCj8DY5pW2 + NXrn9emNVi5fqnR9s6jWq1RMjpNfAAAA//8DANALR4WyAwAA + headers: + CF-RAY: + - 99d044470bdeb976-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 11 Nov 2025 19:41:23 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=p01_b1BsQgwR2woMBWf1E0gJMDDl7pvqkEVHpHAsMJA-1762890083-1.0.1.1-u8iYLTTx0lmfSR1.CzuuYiHgt03yVVUMsBD8WgExXWm7ts.grUwM1ifj9p6xIz.HElrnQdfDSBD5Lv045aNr61YcB8WW3Vz33W9N0Gn0P3w; + path=/; expires=Tue, 11-Nov-25 20:11:23 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=2gUmBgxb3VydVYt8.t_P6bY8U_pS.a4KeYpZWDDYM9Q-1762890083295-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '729' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '759' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999707' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999707' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_70c7033dbc5e4ced80d3fdcbcda2c675 + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Test Agent. Test backstory\nYour + personal goal is: Test goal\nYou ONLY have access to the following tools, and + should NEVER make up tools that are not listed here:\n\nTool Name: test_tool\nTool + Arguments: {}\nTool Description: A test tool.\n\nIMPORTANT: Use the following + format in your response:\n\n```\nThought: you should always think about what + to do\nAction: the action to take, only one name of [test_tool], just the name, + exactly as it''s written.\nAction Input: the input to the action, just a simple + JSON object, enclosed in curly braces, using \" to wrap keys and values.\nObservation: + the result of the action\n```\n\nOnce all necessary information is gathered, + return the following format:\n\n```\nThought: I now know the final answer\nFinal + Answer: the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: Use the test tool\n\nThis is the expected criteria for your final answer: + Tool result\nyou MUST return the actual complete content as the final answer, + not a summary.\n\nBegin! This is VERY important to you, use the tools available + and give your best Final Answer, your job depends on it!\n\nThought:"},{"role":"user","content":"Additional + context: This is a test modification."},{"role":"assistant","content":"```\nThought: + I should use the test_tool to get the required information for the final answer.\nAction: + test_tool\nAction Input: {}\n```\nObservation: test result"},{"role":"user","content":"Additional + context: This is a test modification."}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '1584' + content-type: + - application/json + cookie: + - __cf_bm=p01_b1BsQgwR2woMBWf1E0gJMDDl7pvqkEVHpHAsMJA-1762890083-1.0.1.1-u8iYLTTx0lmfSR1.CzuuYiHgt03yVVUMsBD8WgExXWm7ts.grUwM1ifj9p6xIz.HElrnQdfDSBD5Lv045aNr61YcB8WW3Vz33W9N0Gn0P3w; + _cfuvid=2gUmBgxb3VydVYt8.t_P6bY8U_pS.a4KeYpZWDDYM9Q-1762890083295-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFLBbtQwEL3nKyyfN1WS3S5pbhRRKCeEkCpgq8RrTxJTxzb2pC1U++/I + TrtJoUhcLNlv3vN7M/OQEEKloBWhvGfIB6vSN8xc3b+/FG/P3rX8x9X+y8dfHy7O8fYra88/0VVg + mP134PjEOuFmsApQGj3B3AFDCKr5q21RnmVZuY7AYASoQOssppuTPB2klmmRFadptknzzSO9N5KD + pxX5lhBCyEM8g1Et4J5WJFs9vQzgPeuAVsciQqgzKrxQ5r30yDTS1QxyoxF09N40zU5/7s3Y9ViR + S6LNHbkJB/ZAWqmZIkz7O3A7fRFvr+OtIggeiQM/KtzppmmW+g7a0bMQUo9KLQCmtUEWmhSTXT8i + h2MWZTrrzN7/QaWt1NL3tQPmjQ6+PRpLI3pICLmOPRuftYFaZwaLNZobiN+t83LSo/OsZvQIokGm + Fqz1dvWCXi0AmVR+0XXKGe9BzNR5RGwU0iyAZJH6bzcvaU/Jpe7+R34GOAeLIGrrQEj+PPFc5iCs + 8r/Kjl2OhqkHdys51CjBhUkIaNmopv2i/qdHGOpW6g6cdXJastbWG16Up3lbbguaHJLfAAAA//8D + AJW0fwtzAwAA + headers: + CF-RAY: + - 99d0444cbd6db976-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 11 Nov 2025 19:41:23 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '527' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '578' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999655' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999655' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_6b1d84dcdde643cea5160e155ee624db + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/test_llm_call_hooks_error_handling.yaml b/lib/crewai/tests/cassettes/test_llm_call_hooks_error_handling.yaml new file mode 100644 index 000000000..1a77ae4a5 --- /dev/null +++ b/lib/crewai/tests/cassettes/test_llm_call_hooks_error_handling.yaml @@ -0,0 +1,159 @@ +interactions: +- request: + body: '{"name":"llama3.2:3b"}' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '22' + content-type: + - application/json + host: + - localhost:11434 + user-agent: + - litellm/1.78.5 + method: POST + uri: http://localhost:11434/api/show + response: + body: + string: '{"error":"model ''llama3.2:3b'' not found"}' + headers: + Content-Length: + - '41' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 11 Nov 2025 19:41:28 GMT + status: + code: 404 + message: Not Found +- request: + body: '{"messages":[{"role":"system","content":"You are Test Agent. Test backstory\nYour + personal goal is: Test goal\nTo give my best complete final answer to the task + respond using the exact following format:\n\nThought: I now can give a great + answer\nFinal Answer: Your final answer must be the great and the most complete + as possible, it must be outcome described.\n\nI MUST use these formats, my job + depends on it!"},{"role":"user","content":"\nCurrent Task: Say hello\n\nThis + is the expected criteria for your final answer: A greeting\nyou MUST return + the actual complete content as the final answer, not a summary.\n\nBegin! This + is VERY important to you, use the tools available and give your best Final Answer, + your job depends on it!\n\nThought:"},{"role":"user","content":"Additional context: + This is a test modification."}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '851' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFLRbtQwEHzPVyx+vlRJmrte84KOSqgFCSFAqLRUkc/ZJAbHa9lOy6m6 + f0dOrpe0gMRLpHh2Znd29jECYLJiBTDRci86o+ILTten/ccPFzyp398srz9/2/o3X/PN6btN84kt + AoO2P1D4J9aJoM4o9JL0CAuL3GNQTc9W2fo8SdbnA9BRhSrQGuPj/CSNO6llnCXZMk7yOM0P9Jak + QMcKuI0AAB6HbxhUV/iLFZAsnl46dI43yIpjEQCzpMIL485J57n2bDGBgrRHPcz+paW+aX0BV6Dp + AQTX0Mh7BA5NMABcuwe03/VbqbmCzfBXwCUqRa/g8sC4grEN7KgHTxXfvZ63s1j3jgfPuldqBnCt + yfOws8Ho3QHZH60paoylrXtBZbXU0rWlRe5IBxvOk2EDuo8A7oYV9s+2woylzvjS008c2qWrs1GP + TdFNaJYdQE+eqxlrTPGlXlmh51K5WQhMcNFiNVGnxHhfSZoB0cz1n9P8TXt0LnXzP/ITIAQaj1Vp + LFZSPHc8lVkMl/2vsuOWh4GZQ3svBZZeog1JVFjzXo3nxtzOeezKWuoGrbFyvLnalLnI1su0Xq8y + Fu2j3wAAAP//AwDurzwzggMAAA== + headers: + CF-RAY: + - 99d0446e698367ab-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 11 Nov 2025 19:41:30 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=b52crfzdOm5rh4aOc2LfM8aQKFI.ZL9WCZXaPBDdG5k-1762890090-1.0.1.1-T2xhtwX0vuEnMIb8NRgP4w3RRn1N1ZwSjuhKBob1vDLDmN7XhCKkoIg3IrlC9KEyhA65IGa5DWsHfmlRKKxqw6sIPA98BSO6E3wsTRspHw4; + path=/; expires=Tue, 11-Nov-25 20:11:30 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=0TH0Kjp_5t6yhwXKA1wlKBHaczp.TeWhM2A5t6by1sI-1762890090153-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '1049' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '1387' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999817' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999817' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_4b132b998ed941b5b6a85ddbb36e2b65 + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/test_llm_call_hooks_with_crew.yaml b/lib/crewai/tests/cassettes/test_llm_call_hooks_with_crew.yaml new file mode 100644 index 000000000..fe5cc5867 --- /dev/null +++ b/lib/crewai/tests/cassettes/test_llm_call_hooks_with_crew.yaml @@ -0,0 +1,182 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are Researcher. You are a + researcher\nYour personal goal is: Research topics\nTo give my best complete + final answer to the task respond using the exact following format:\n\nThought: + I now can give a great answer\nFinal Answer: Your final answer must be the great + and the most complete as possible, it must be outcome described.\n\nI MUST use + these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: + Research AI frameworks\n\nThis is the expected criteria for your final answer: + A research summary\nyou MUST return the actual complete content as the final + answer, not a summary.\n\nYou MUST follow these instructions: \n - Include specific + examples and real-world case studies to enhance the credibility and depth of + the article ideas.\n - Incorporate mentions of notable companies, projects, + or tools relevant to each topic to provide concrete context.\n - Add diverse + viewpoints such as interviews with experts, users, or thought leaders to enrich + the narrative and lend authority.\n - Address ethical, social, and emotional + considerations explicitly to reflect a balanced and comprehensive analysis.\n + - Enhance the descriptions by including implications for future developments + and the potential impact on society.\n - Use more engaging and vivid language + that draws the reader into each topic''s nuances and importance.\n - Include + notes or summaries that contextualize each set of ideas in terms of relevance + and potential reader engagement.\n - In future tasks, focus on elaborating initial + outlines into more detailed and nuanced article proposals with richer content + and insights.\n\nBegin! This is VERY important to you, use the tools available + and give your best Final Answer, your job depends on it!\n\nThought:"},{"role":"user","content":"Additional + context: This is a test modification."}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '1894' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + 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 + headers: + CF-RAY: + - 99d0447958ce36e8-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 11 Nov 2025 19:41:45 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=dSe1gQEFfPpE4AOsFi3S3RQkzPCQnV1.Ywe__K7cSSU-1762890105-1.0.1.1-I1CSTO8ri4tjbaHdIHQ9YP9c2pa.y9WwMQFRaUztT95T_OAe5V0ndTFN4pO1RiCXh15TUpWmBxRdxIWjcYDMqrDIvKWInLO5aavGFWZ1rys; + path=/; expires=Tue, 11-Nov-25 20:11:45 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=LMf_4EPFZGfTiqcjmjEk7WxOTuX2ukd3Cs_R8170wJ4-1762890105804-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '15065' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '15254' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999560' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999560' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_c49c9fba20ff4f05903eff3c78797ce1 + status: + code: 200 + message: OK +version: 1