mirror of
https://github.com/crewAIInc/crewAI.git
synced 2025-12-28 18:28:30 +00:00
Compare commits
5 Commits
fix/unsafe
...
devin/1742
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db6dbea091 | ||
|
|
7a71a79dae | ||
|
|
fb7c94df8c | ||
|
|
840772894b | ||
|
|
23aeaf67ba |
@@ -244,6 +244,7 @@ class ToolUsage:
|
||||
tool_calling=calling,
|
||||
from_cache=from_cache,
|
||||
started_at=started_at,
|
||||
result=result, # Pass the result
|
||||
)
|
||||
|
||||
if (
|
||||
@@ -492,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(
|
||||
@@ -501,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))
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"])
|
||||
|
||||
Reference in New Issue
Block a user