Compare commits

..

8 Commits

Author SHA1 Message Date
Devin AI
db6dbea091 Fix type validation in ToolUsageFinishedEvent to accept any result type
Co-Authored-By: Joe Moura <joao@crewai.com>
2025-03-22 01:06:27 +00:00
Devin AI
7a71a79dae Add test cassettes for tool usage events
Co-Authored-By: Joe Moura <joao@crewai.com>
2025-03-22 01:03:16 +00:00
Devin AI
fb7c94df8c Fix import sorting in test_events.py
Co-Authored-By: Joe Moura <joao@crewai.com>
2025-03-22 01:03:08 +00:00
Devin AI
840772894b Address PR feedback: improve type hints, documentation and test coverage
Co-Authored-By: Joe Moura <joao@crewai.com>
2025-03-22 01:01:37 +00:00
Devin AI
23aeaf67ba Add tool execution result to ToolUsageFinishedEvent
Co-Authored-By: Joe Moura <joao@crewai.com>
2025-03-22 00:55:25 +00:00
Matisse
bb3829a9ed docs: Update model reference in LLM configuration (#2267)
Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>
2025-03-21 15:12:26 -04:00
Fernando Galves
0a116202f0 Update the context window size for Amazon Bedrock FM- llm.py (#2304)
Update the context window size for Amazon Bedrock Foundation Models.

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>
Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com>
2025-03-21 14:48:25 -04:00
Stefano Baccianella
4daa88fa59 As explained in https://github.com/mangiucugna/json_repair?tab=readme-ov-file#performance-considerations we can skip a wasteful json.loads() here and save quite some time (#2397)
Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>
Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com>
2025-03-21 14:25:19 -04:00
8 changed files with 495 additions and 52 deletions

View File

@@ -59,7 +59,7 @@ There are three ways to configure LLMs in CrewAI. Choose the method that best fi
goal: Conduct comprehensive research and analysis
backstory: A dedicated research professional with years of experience
verbose: true
llm: openai/gpt-4o-mini # your model here
llm: openai/gpt-4o-mini # your model here
# (see provider configuration examples below for more)
```
@@ -111,7 +111,7 @@ There are three ways to configure LLMs in CrewAI. Choose the method that best fi
## Provider Configuration Examples
CrewAI supports a multitude of LLM providers, each offering unique features, authentication methods, and model capabilities.
CrewAI supports a multitude of LLM providers, each offering unique features, authentication methods, and model capabilities.
In this section, you'll find detailed examples that help you select, configure, and optimize the LLM that best fits your project's needs.
<AccordionGroup>
@@ -121,7 +121,7 @@ In this section, you'll find detailed examples that help you select, configure,
```toml Code
# Required
OPENAI_API_KEY=sk-...
# Optional
OPENAI_API_BASE=<custom-base-url>
OPENAI_ORGANIZATION=<your-org-id>
@@ -226,7 +226,7 @@ In this section, you'll find detailed examples that help you select, configure,
AZURE_API_KEY=<your-api-key>
AZURE_API_BASE=<your-resource-url>
AZURE_API_VERSION=<api-version>
# Optional
AZURE_AD_TOKEN=<your-azure-ad-token>
AZURE_API_TYPE=<your-azure-api-type>
@@ -289,7 +289,7 @@ In this section, you'll find detailed examples that help you select, configure,
| Mistral 8x7B Instruct | Up to 32k tokens | An MOE LLM that follows instructions, completes requests, and generates creative text. |
</Accordion>
<Accordion title="Amazon SageMaker">
```toml Code
AWS_ACCESS_KEY_ID=<your-access-key>
@@ -474,7 +474,7 @@ In this section, you'll find detailed examples that help you select, configure,
WATSONX_URL=<your-url>
WATSONX_APIKEY=<your-apikey>
WATSONX_PROJECT_ID=<your-project-id>
# Optional
WATSONX_TOKEN=<your-token>
WATSONX_DEPLOYMENT_SPACE_ID=<your-space-id>
@@ -491,7 +491,7 @@ In this section, you'll find detailed examples that help you select, configure,
<Accordion title="Ollama (Local LLMs)">
1. Install Ollama: [ollama.ai](https://ollama.ai/)
2. Run a model: `ollama run llama2`
2. Run a model: `ollama run llama3`
3. Configure:
```python Code
@@ -600,7 +600,7 @@ In this section, you'll find detailed examples that help you select, configure,
```toml Code
OPENROUTER_API_KEY=<your-api-key>
```
Example usage in your CrewAI project:
```python Code
llm = LLM(
@@ -723,7 +723,7 @@ Learn how to get the most out of your LLM configuration:
- Small tasks (up to 4K tokens): Standard models
- Medium tasks (between 4K-32K): Enhanced models
- Large tasks (over 32K): Large context models
```python
# Configure model with appropriate settings
llm = LLM(
@@ -760,11 +760,11 @@ Learn how to get the most out of your LLM configuration:
<Warning>
Most authentication issues can be resolved by checking API key format and environment variable names.
</Warning>
```bash
# OpenAI
OPENAI_API_KEY=sk-...
# Anthropic
ANTHROPIC_API_KEY=sk-ant-...
```
@@ -773,11 +773,11 @@ Learn how to get the most out of your LLM configuration:
<Check>
Always include the provider prefix in model names
</Check>
```python
# Correct
llm = LLM(model="openai/gpt-4")
# Incorrect
llm = LLM(model="gpt-4")
```
@@ -786,5 +786,10 @@ Learn how to get the most out of your LLM configuration:
<Tip>
Use larger context models for extensive tasks
</Tip>
```python
# Large context model
llm = LLM(model="openai/gpt-4o") # 128K tokens
```
</Tab>
</Tabs>

View File

@@ -114,6 +114,60 @@ LLM_CONTEXT_WINDOW_SIZES = {
"Llama-3.2-11B-Vision-Instruct": 16384,
"Meta-Llama-3.2-3B-Instruct": 4096,
"Meta-Llama-3.2-1B-Instruct": 16384,
# bedrock
"us.amazon.nova-pro-v1:0": 300000,
"us.amazon.nova-micro-v1:0": 128000,
"us.amazon.nova-lite-v1:0": 300000,
"us.anthropic.claude-3-5-sonnet-20240620-v1:0": 200000,
"us.anthropic.claude-3-5-haiku-20241022-v1:0": 200000,
"us.anthropic.claude-3-5-sonnet-20241022-v2:0": 200000,
"us.anthropic.claude-3-7-sonnet-20250219-v1:0": 200000,
"us.anthropic.claude-3-sonnet-20240229-v1:0": 200000,
"us.anthropic.claude-3-opus-20240229-v1:0": 200000,
"us.anthropic.claude-3-haiku-20240307-v1:0": 200000,
"us.meta.llama3-2-11b-instruct-v1:0": 128000,
"us.meta.llama3-2-3b-instruct-v1:0": 131000,
"us.meta.llama3-2-90b-instruct-v1:0": 128000,
"us.meta.llama3-2-1b-instruct-v1:0": 131000,
"us.meta.llama3-1-8b-instruct-v1:0": 128000,
"us.meta.llama3-1-70b-instruct-v1:0": 128000,
"us.meta.llama3-3-70b-instruct-v1:0": 128000,
"us.meta.llama3-1-405b-instruct-v1:0": 128000,
"eu.anthropic.claude-3-5-sonnet-20240620-v1:0": 200000,
"eu.anthropic.claude-3-sonnet-20240229-v1:0": 200000,
"eu.anthropic.claude-3-haiku-20240307-v1:0": 200000,
"eu.meta.llama3-2-3b-instruct-v1:0": 131000,
"eu.meta.llama3-2-1b-instruct-v1:0": 131000,
"apac.anthropic.claude-3-5-sonnet-20240620-v1:0": 200000,
"apac.anthropic.claude-3-5-sonnet-20241022-v2:0": 200000,
"apac.anthropic.claude-3-sonnet-20240229-v1:0": 200000,
"apac.anthropic.claude-3-haiku-20240307-v1:0": 200000,
"amazon.nova-pro-v1:0": 300000,
"amazon.nova-micro-v1:0": 128000,
"amazon.nova-lite-v1:0": 300000,
"anthropic.claude-3-5-sonnet-20240620-v1:0": 200000,
"anthropic.claude-3-5-haiku-20241022-v1:0": 200000,
"anthropic.claude-3-5-sonnet-20241022-v2:0": 200000,
"anthropic.claude-3-7-sonnet-20250219-v1:0": 200000,
"anthropic.claude-3-sonnet-20240229-v1:0": 200000,
"anthropic.claude-3-opus-20240229-v1:0": 200000,
"anthropic.claude-3-haiku-20240307-v1:0": 200000,
"anthropic.claude-v2:1": 200000,
"anthropic.claude-v2": 100000,
"anthropic.claude-instant-v1": 100000,
"meta.llama3-1-405b-instruct-v1:0": 128000,
"meta.llama3-1-70b-instruct-v1:0": 128000,
"meta.llama3-1-8b-instruct-v1:0": 128000,
"meta.llama3-70b-instruct-v1:0": 8000,
"meta.llama3-8b-instruct-v1:0": 8000,
"amazon.titan-text-lite-v1": 4000,
"amazon.titan-text-express-v1": 8000,
"cohere.command-text-v14": 4000,
"ai21.j2-mid-v1": 8191,
"ai21.j2-ultra-v1": 8191,
"ai21.jamba-instruct-v1:0": 256000,
"mistral.mistral-7b-instruct-v0:2": 32000,
"mistral.mixtral-8x7b-instruct-v0:1": 32000,
# mistral
"mistral-tiny": 32768,
"mistral-small-latest": 32768,

View File

@@ -244,6 +244,7 @@ class ToolUsage:
tool_calling=calling,
from_cache=from_cache,
started_at=started_at,
result=result, # Pass the result
)
if (
@@ -337,23 +338,11 @@ class ToolUsage:
return "\n--\n".join(descriptions)
def _function_calling(self, tool_string: str):
supports_function_calling = (
self.function_calling_llm.supports_function_calling()
model = (
InstructorToolCalling
if self.function_calling_llm.supports_function_calling()
else ToolCalling
)
if not supports_function_calling:
import warnings
warnings.warn(
"The model you're using doesn't natively support function calling. "
"CrewAI will attempt to use a workaround, but this may be less reliable. "
"Consider using a model with native function calling support for better results.",
UserWarning,
stacklevel=2,
)
model = InstructorToolCalling if supports_function_calling else ToolCalling
converter = Converter(
text=f"Only tools available:\n###\n{self._render()}\n\nReturn a valid schema for the tool, the tool name must be exactly equal one of the options, use this text to inform the valid output schema:\n\n### TEXT \n{tool_string}",
llm=self.function_calling_llm,
@@ -467,7 +456,7 @@ class ToolUsage:
# Attempt 4: Repair JSON
try:
repaired_input = repair_json(tool_input)
repaired_input = repair_json(tool_input, skip_json_loads=True)
self._printer.print(
content=f"Repaired JSON: {repaired_input}", color="blue"
)
@@ -504,8 +493,18 @@ class ToolUsage:
crewai_event_bus.emit(self, ToolUsageErrorEvent(**{**event_data, "error": e}))
def on_tool_use_finished(
self, tool: Any, tool_calling: ToolCalling, from_cache: bool, started_at: float
self, tool: Any, tool_calling: ToolCalling, from_cache: bool, started_at: float,
result: Any = None
) -> None:
"""Handle tool usage completion event.
Args:
tool: The tool that was used
tool_calling: The tool calling information
from_cache: Whether the result was retrieved from cache
started_at: Timestamp when the tool execution started
result: The execution result of the tool
"""
finished_at = time.time()
event_data = self._prepare_event_data(tool, tool_calling)
event_data.update(
@@ -513,6 +512,7 @@ class ToolUsage:
"started_at": datetime.datetime.fromtimestamp(started_at),
"finished_at": datetime.datetime.fromtimestamp(finished_at),
"from_cache": from_cache,
"result": result, # Tool execution result
}
)
crewai_event_bus.emit(self, ToolUsageFinishedEvent(**event_data))

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from typing import Any, Callable, Dict
from typing import Any, Callable, Dict, Optional, Union
from .base_events import CrewEvent
@@ -25,11 +25,16 @@ class ToolUsageStartedEvent(ToolUsageEvent):
class ToolUsageFinishedEvent(ToolUsageEvent):
"""Event emitted when a tool execution is completed"""
"""Event emitted when a tool execution is completed
This event contains the result of the tool execution, allowing listeners
to access the output directly without implementing workarounds.
"""
started_at: datetime
finished_at: datetime
from_cache: bool = False
result: Any = None # Tool execution result
type: str = "tool_usage_finished"

View File

@@ -0,0 +1,92 @@
interactions:
- request:
body: '{"messages": [{"role": "system", "content": "You are base_agent. You are
a helpful assistant that returns structured data\nYour personal goal is: Return
a dictionary result\nYou ONLY have access to the following tools, and should
NEVER make up tools that are not listed here:\n\nTool Name: dict_result\nTool
Arguments: {}\nTool Description: Return a dictionary result\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 [dict_result],
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: Return a dictionary result\n\nThis
is the expected criteria for your final answer: Dictionary with message and
data\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-4o-mini", "stop": ["\nObservation:"]}'
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate
connection:
- keep-alive
content-length:
- '1378'
content-type:
- application/json
cookie:
- __cf_bm=KV5GuGyEdKrxlT_rqmqKhQ9TUxeMz_ccZ5K3Xu.mfLc-1742605207-1.0.1.1-1aSRm5P8hBtB3zVmHWH3LcYPoXvhsezGomR1uazQeGCEWL_uUIGP1x3dMkviSWrjf88WKm1snQNn.Eyy8_qewmd7NT7lqUt0MgRWVgfCOAY;
_cfuvid=1KExsJlCZ0R2Kp3gV2Y71_SiQ0R2D9rAbONnmJq7tjk-1742605207873-0.0.1.1-604800000
host:
- api.openai.com
user-agent:
- OpenAI/Python 1.61.0
x-stainless-arch:
- x64
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- Linux
x-stainless-package-version:
- 1.61.0
x-stainless-raw-response:
- 'true'
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.12.7
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
content: "{\n \"error\": {\n \"message\": \"Incorrect API key provided:
sk-proj-********************************************************************************************************************************************************sLcA.
You can find your API key at https://platform.openai.com/account/api-keys.\",\n
\ \"type\": \"invalid_request_error\",\n \"param\": null,\n \"code\":
\"invalid_api_key\"\n }\n}\n"
headers:
CF-RAY:
- 9241c01b0df3b9d9-SEA
Connection:
- keep-alive
Content-Length:
- '414'
Content-Type:
- application/json; charset=utf-8
Date:
- Sat, 22 Mar 2025 01:00:08 GMT
Server:
- cloudflare
X-Content-Type-Options:
- nosniff
alt-svc:
- h3=":443"; ma=86400
cf-cache-status:
- DYNAMIC
strict-transport-security:
- max-age=31536000; includeSubDomains; preload
vary:
- Origin
x-request-id:
- req_d0eae92c72f0f64514d32384a7f27a9a
http_version: HTTP/1.1
status_code: 401
version: 1

View File

@@ -0,0 +1,91 @@
interactions:
- request:
body: '{"messages": [{"role": "system", "content": "You are base_agent. You are
a helpful assistant that returns None\nYour personal goal is: Return None as
result\nYou ONLY have access to the following tools, and should NEVER make up
tools that are not listed here:\n\nTool Name: none_result\nTool Arguments: {}\nTool
Description: Return None as result\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 [none_result], 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: Return None as result\n\nThis is the expected criteria for
your final answer: None\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-4o-mini", "stop": ["\nObservation:"]}'
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate
connection:
- keep-alive
content-length:
- '1324'
content-type:
- application/json
cookie:
- __cf_bm=KV5GuGyEdKrxlT_rqmqKhQ9TUxeMz_ccZ5K3Xu.mfLc-1742605207-1.0.1.1-1aSRm5P8hBtB3zVmHWH3LcYPoXvhsezGomR1uazQeGCEWL_uUIGP1x3dMkviSWrjf88WKm1snQNn.Eyy8_qewmd7NT7lqUt0MgRWVgfCOAY;
_cfuvid=1KExsJlCZ0R2Kp3gV2Y71_SiQ0R2D9rAbONnmJq7tjk-1742605207873-0.0.1.1-604800000
host:
- api.openai.com
user-agent:
- OpenAI/Python 1.61.0
x-stainless-arch:
- x64
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- Linux
x-stainless-package-version:
- 1.61.0
x-stainless-raw-response:
- 'true'
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.12.7
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
content: "{\n \"error\": {\n \"message\": \"Incorrect API key provided:
sk-proj-********************************************************************************************************************************************************sLcA.
You can find your API key at https://platform.openai.com/account/api-keys.\",\n
\ \"type\": \"invalid_request_error\",\n \"param\": null,\n \"code\":
\"invalid_api_key\"\n }\n}\n"
headers:
CF-RAY:
- 9241c01eea3db9d9-SEA
Connection:
- keep-alive
Content-Length:
- '414'
Content-Type:
- application/json; charset=utf-8
Date:
- Sat, 22 Mar 2025 01:00:09 GMT
Server:
- cloudflare
X-Content-Type-Options:
- nosniff
alt-svc:
- h3=":443"; ma=86400
cf-cache-status:
- DYNAMIC
strict-transport-security:
- max-age=31536000; includeSubDomains; preload
vary:
- Origin
x-request-id:
- req_00b89378d512de492b7c90876d0e3c8a
http_version: HTTP/1.1
status_code: 401
version: 1

View File

@@ -0,0 +1,93 @@
interactions:
- request:
body: '{"messages": [{"role": "system", "content": "You are base_agent. You are
a helpful assistant that just says hi\nYour personal goal is: Just say hi\nYou
ONLY have access to the following tools, and should NEVER make up tools that
are not listed here:\n\nTool Name: say_hi\nTool Arguments: {}\nTool Description:
Say hi\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 [say_hi], 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: Just say
hi\n\nThis is the expected criteria for your final answer: hi\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-4o-mini", "stop": ["\nObservation:"]}'
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate
connection:
- keep-alive
content-length:
- '1277'
content-type:
- application/json
host:
- api.openai.com
user-agent:
- OpenAI/Python 1.61.0
x-stainless-arch:
- x64
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- Linux
x-stainless-package-version:
- 1.61.0
x-stainless-raw-response:
- 'true'
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.12.7
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
content: "{\n \"error\": {\n \"message\": \"Incorrect API key provided:
sk-proj-********************************************************************************************************************************************************sLcA.
You can find your API key at https://platform.openai.com/account/api-keys.\",\n
\ \"type\": \"invalid_request_error\",\n \"param\": null,\n \"code\":
\"invalid_api_key\"\n }\n}\n"
headers:
CF-RAY:
- 9241c014df27b9d9-SEA
Connection:
- keep-alive
Content-Length:
- '414'
Content-Type:
- application/json; charset=utf-8
Date:
- Sat, 22 Mar 2025 01:00:07 GMT
Server:
- cloudflare
Set-Cookie:
- __cf_bm=KV5GuGyEdKrxlT_rqmqKhQ9TUxeMz_ccZ5K3Xu.mfLc-1742605207-1.0.1.1-1aSRm5P8hBtB3zVmHWH3LcYPoXvhsezGomR1uazQeGCEWL_uUIGP1x3dMkviSWrjf88WKm1snQNn.Eyy8_qewmd7NT7lqUt0MgRWVgfCOAY;
path=/; expires=Sat, 22-Mar-25 01:30:07 GMT; domain=.api.openai.com; HttpOnly;
Secure; SameSite=None
- _cfuvid=1KExsJlCZ0R2Kp3gV2Y71_SiQ0R2D9rAbONnmJq7tjk-1742605207873-0.0.1.1-604800000;
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
X-Content-Type-Options:
- nosniff
alt-svc:
- h3=":443"; ma=86400
cf-cache-status:
- DYNAMIC
strict-transport-security:
- max-age=31536000; includeSubDomains; preload
vary:
- Origin
x-request-id:
- req_b2233dbbb60c3ebb5717c05ec5150588
http_version: HTTP/1.1
status_code: 401
version: 1

View File

@@ -12,6 +12,7 @@ from crewai.flow.flow import Flow, listen, start
from crewai.llm import LLM
from crewai.task import Task
from crewai.tools.base_tool import BaseTool
from crewai.tools.tool_calling import ToolCalling
from crewai.utilities.events.agent_events import (
AgentExecutionCompletedEvent,
AgentExecutionErrorEvent,
@@ -329,35 +330,137 @@ class SayHiTool(BaseTool):
return "hi"
@pytest.mark.vcr(filter_headers=["authorization"])
def test_tools_emits_finished_events():
class DictResultTool(BaseTool):
name: str = Field(default="dict_result", description="The name of the tool")
description: str = Field(
default="Return a dictionary result",
description="The description of the tool"
)
def _run(self) -> dict:
return {"message": "success", "data": {"value": 42}}
class NoneResultTool(BaseTool):
name: str = Field(default="none_result", description="The name of the tool")
description: str = Field(
default="Return None as result",
description="The description of the tool"
)
def _run(self) -> None:
return None
def test_tools_emits_finished_events_with_string_result():
received_events = []
@crewai_event_bus.on(ToolUsageFinishedEvent)
def handle_tool_end(source, event):
received_events.append(event)
agent = Agent(
role="base_agent",
goal="Just say hi",
backstory="You are a helpful assistant that just says hi",
tools=[SayHiTool()],
)
task = Task(
description="Just say hi",
expected_output="hi",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task], name="TestCrew")
crew.kickoff()
# Create a mock event with string result
tool = SayHiTool()
tool_calling = ToolCalling(tool_name=tool.name, arguments={}, log="")
event_data = {
"agent_key": "test_agent_key",
"agent_role": "test_agent_role",
"tool_name": tool.name,
"tool_args": {},
"tool_class": tool.__class__.__name__,
"started_at": datetime.now(),
"finished_at": datetime.now(),
"from_cache": False,
"result": "hi"
}
# Emit the event
crewai_event_bus.emit(None, ToolUsageFinishedEvent(**event_data))
# Verify the event was received with the correct result
assert len(received_events) == 1
assert received_events[0].agent_key == agent.key
assert received_events[0].agent_role == agent.role
assert received_events[0].tool_name == SayHiTool().name
assert received_events[0].agent_key == "test_agent_key"
assert received_events[0].agent_role == "test_agent_role"
assert received_events[0].tool_name == tool.name
assert received_events[0].tool_args == {}
assert received_events[0].type == "tool_usage_finished"
assert isinstance(received_events[0].timestamp, datetime)
assert received_events[0].result == "hi"
def test_tools_emits_finished_events_with_dict_result():
received_events = []
@crewai_event_bus.on(ToolUsageFinishedEvent)
def handle_tool_end(source, event):
received_events.append(event)
# Create a mock event with dictionary result
tool = DictResultTool()
tool_calling = ToolCalling(tool_name=tool.name, arguments={}, log="")
dict_result = {"message": "success", "data": {"value": 42}}
event_data = {
"agent_key": "test_agent_key",
"agent_role": "test_agent_role",
"tool_name": tool.name,
"tool_args": {},
"tool_class": tool.__class__.__name__,
"started_at": datetime.now(),
"finished_at": datetime.now(),
"from_cache": False,
"result": dict_result
}
# Emit the event
crewai_event_bus.emit(None, ToolUsageFinishedEvent(**event_data))
# Verify the event was received with the correct result
assert len(received_events) == 1
assert received_events[0].agent_key == "test_agent_key"
assert received_events[0].agent_role == "test_agent_role"
assert received_events[0].tool_name == tool.name
assert received_events[0].tool_args == {}
assert received_events[0].type == "tool_usage_finished"
assert isinstance(received_events[0].timestamp, datetime)
assert isinstance(received_events[0].result, dict)
assert received_events[0].result["message"] == "success"
assert received_events[0].result["data"]["value"] == 42
def test_tools_emits_finished_events_with_none_result():
received_events = []
@crewai_event_bus.on(ToolUsageFinishedEvent)
def handle_tool_end(source, event):
received_events.append(event)
# Create a mock event with None result
tool = NoneResultTool()
tool_calling = ToolCalling(tool_name=tool.name, arguments={}, log="")
event_data = {
"agent_key": "test_agent_key",
"agent_role": "test_agent_role",
"tool_name": tool.name,
"tool_args": {},
"tool_class": tool.__class__.__name__,
"started_at": datetime.now(),
"finished_at": datetime.now(),
"from_cache": False,
"result": None
}
# Emit the event
crewai_event_bus.emit(None, ToolUsageFinishedEvent(**event_data))
# Verify the event was received with the correct result
assert len(received_events) == 1
assert received_events[0].agent_key == "test_agent_key"
assert received_events[0].agent_role == "test_agent_role"
assert received_events[0].tool_name == tool.name
assert received_events[0].tool_args == {}
assert received_events[0].type == "tool_usage_finished"
assert isinstance(received_events[0].timestamp, datetime)
assert received_events[0].result is None
@pytest.mark.vcr(filter_headers=["authorization"])