mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-16 03:28:30 +00:00
feat: restructure project as UV workspace with crewai in lib/
This commit is contained in:
@@ -1 +0,0 @@
|
||||
"""Tests for tools."""
|
||||
@@ -1,115 +0,0 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages": [{"role": "system", "content": "You are researcher. You''re
|
||||
an expert researcher, specialized in technology\nYour personal goal is: make
|
||||
the best research and analysis on content about AI and AI agents\nTo give my
|
||||
best complete final answer to the task use 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: do you hate AI Agents?\n\nThis is the expect criteria for your final answer:
|
||||
Your best answer to your coworker asking you this, accounting for the context
|
||||
shared.\nyou MUST return the actual complete content as the final answer, not
|
||||
a summary.\n\nThis is the context you''re working with:\nI heard you LOVE them\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"}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1021'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA;
|
||||
_cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.47.0
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.47.0
|
||||
x-stainless-raw-response:
|
||||
- 'true'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.11.7
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
content: "{\n \"id\": \"chatcmpl-AB7WnyWZFoccBH9YB7ghLbR1L8Wqa\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1727213909,\n \"model\": \"gpt-4o-2024-05-13\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal
|
||||
Answer: As an expert researcher specialized in technology, I don't harbor emotions
|
||||
such as hate towards AI agents. Instead, my focus is on understanding, analyzing,
|
||||
and leveraging their potential to advance various fields. AI agents, when designed
|
||||
and implemented effectively, can greatly augment human capabilities, streamline
|
||||
processes, and provide valuable insights that might otherwise be overlooked.
|
||||
My enthusiasm for AI agents stems from their ability to transform industries
|
||||
and improve everyday life, making complex tasks more manageable and enhancing
|
||||
overall efficiency. This passion drives my research and commitment to making
|
||||
meaningful contributions in the realm of AI and AI agents.\",\n \"refusal\":
|
||||
null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n
|
||||
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 199,\n \"completion_tokens\":
|
||||
126,\n \"total_tokens\": 325,\n \"completion_tokens_details\": {\n \"reasoning_tokens\":
|
||||
0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n"
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8c85ebf47e661cf3-GRU
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 24 Sep 2024 21:38:31 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
openai-processing-ms:
|
||||
- '2498'
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
x-ratelimit-limit-requests:
|
||||
- '10000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '30000000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '9999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '29999755'
|
||||
x-ratelimit-reset-requests:
|
||||
- 6ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
x-request-id:
|
||||
- req_b7e2cb0620e45d3d74310d3f0166551f
|
||||
http_version: HTTP/1.1
|
||||
status_code: 200
|
||||
version: 1
|
||||
@@ -1,116 +0,0 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages": [{"role": "system", "content": "You are researcher. You''re
|
||||
an expert researcher, specialized in technology\nYour personal goal is: make
|
||||
the best research and analysis on content about AI and AI agents\nTo give my
|
||||
best complete final answer to the task use 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: do you hate AI Agents?\n\nThis is the expect criteria for your final answer:
|
||||
Your best answer to your coworker asking you this, accounting for the context
|
||||
shared.\nyou MUST return the actual complete content as the final answer, not
|
||||
a summary.\n\nThis is the context you''re working with:\nI heard you LOVE them\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"}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1021'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA;
|
||||
_cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.47.0
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.47.0
|
||||
x-stainless-raw-response:
|
||||
- 'true'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.11.7
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
content: "{\n \"id\": \"chatcmpl-AB7Wy6aW1XM0lWaMyQUNB9qhbCZlH\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1727213920,\n \"model\": \"gpt-4o-2024-05-13\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal
|
||||
Answer: As an expert researcher specializing in technology and AI, I have a
|
||||
deep appreciation for AI agents. These advanced tools have the potential to
|
||||
revolutionize countless industries by improving efficiency, accuracy, and decision-making
|
||||
processes. They can augment human capabilities, handle mundane and repetitive
|
||||
tasks, and even offer insights that might be beyond human reach. While it's
|
||||
crucial to approach AI with a balanced perspective, understanding both its capabilities
|
||||
and limitations, my stance is one of optimism and fascination. Properly developed
|
||||
and ethically managed, AI agents hold immense promise for driving innovation
|
||||
and solving complex problems. So yes, I do love AI agents for their transformative
|
||||
potential and the positive impact they can have on society.\",\n \"refusal\":
|
||||
null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n
|
||||
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 199,\n \"completion_tokens\":
|
||||
146,\n \"total_tokens\": 345,\n \"completion_tokens_details\": {\n \"reasoning_tokens\":
|
||||
0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n"
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8c85ec3c6f3b1cf3-GRU
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 24 Sep 2024 21:38:42 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
openai-processing-ms:
|
||||
- '1675'
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
x-ratelimit-limit-requests:
|
||||
- '10000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '30000000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '9999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '29999755'
|
||||
x-ratelimit-reset-requests:
|
||||
- 6ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
x-request-id:
|
||||
- req_a249567d37ada11bc8857404338b24cc
|
||||
http_version: HTTP/1.1
|
||||
status_code: 200
|
||||
version: 1
|
||||
@@ -1,114 +0,0 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages": [{"role": "system", "content": "You are researcher. You''re
|
||||
an expert researcher, specialized in technology\nYour personal goal is: make
|
||||
the best research and analysis on content about AI and AI agents\nTo give my
|
||||
best complete final answer to the task use 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: do you hate AI Agents?\n\nThis is the expect criteria for your final answer:
|
||||
Your best answer to your coworker asking you this, accounting for the context
|
||||
shared.\nyou MUST return the actual complete content as the final answer, not
|
||||
a summary.\n\nThis is the context you''re working with:\nI heard you LOVE them\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"}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1021'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA;
|
||||
_cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.47.0
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.47.0
|
||||
x-stainless-raw-response:
|
||||
- 'true'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.11.7
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
content: "{\n \"id\": \"chatcmpl-AB7Wq7edXMCGJR1zDd2QoySLdo8mM\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1727213912,\n \"model\": \"gpt-4o-2024-05-13\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal
|
||||
Answer: I don't hate AI agents; on the contrary, I find them fascinating and
|
||||
incredibly useful. Considering the rapid advancements in AI technology, these
|
||||
agents have the potential to revolutionize various industries by automating
|
||||
tasks, improving efficiency, and providing insights that were previously unattainable.
|
||||
My expertise in researching and analyzing AI and AI agents has allowed me to
|
||||
appreciate the intricate design and the vast possibilities they offer. Therefore,
|
||||
it's more accurate to say that I love AI agents for their potential to drive
|
||||
innovation and improve our daily lives.\",\n \"refusal\": null\n },\n
|
||||
\ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n
|
||||
\ \"usage\": {\n \"prompt_tokens\": 199,\n \"completion_tokens\": 116,\n
|
||||
\ \"total_tokens\": 315,\n \"completion_tokens_details\": {\n \"reasoning_tokens\":
|
||||
0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n"
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8c85ec05f8651cf3-GRU
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 24 Sep 2024 21:38:33 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
openai-processing-ms:
|
||||
- '1739'
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
x-ratelimit-limit-requests:
|
||||
- '10000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '30000000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '9999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '29999755'
|
||||
x-ratelimit-reset-requests:
|
||||
- 6ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
x-request-id:
|
||||
- req_d9e1e9458d5539061397a618345c27d4
|
||||
http_version: HTTP/1.1
|
||||
status_code: 200
|
||||
version: 1
|
||||
@@ -1,132 +0,0 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages": [{"role": "system", "content": "You are researcher. You''re
|
||||
an expert researcher, specialized in technology\nYour personal goal is: make
|
||||
the best research and analysis on content about AI and AI agents\nTo give my
|
||||
best complete final answer to the task use 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: share your take on AI Agents\n\nThis is the expect criteria for your final
|
||||
answer: Your best answer to your coworker asking you this, accounting for the
|
||||
context shared.\nyou MUST return the actual complete content as the final answer,
|
||||
not a summary.\n\nThis is the context you''re working with:\nI heard you hate
|
||||
them\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"}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1027'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA;
|
||||
_cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.47.0
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.47.0
|
||||
x-stainless-raw-response:
|
||||
- 'true'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.11.7
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
content: "{\n \"id\": \"chatcmpl-AB7WbKt7If02iTLuH5cJJjeYo9uDi\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1727213897,\n \"model\": \"gpt-4o-2024-05-13\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal
|
||||
Answer: I understand why you might think I dislike AI agents, but my perspective
|
||||
is more nuanced. AI agents, in essence, are incredibly versatile tools designed
|
||||
to perform specific tasks autonomously or semi-autonomously. They harness various
|
||||
artificial intelligence techniques, such as machine learning, natural language
|
||||
processing, and computer vision, to interpret data, understand tasks, and execute
|
||||
them efficiently. \\n\\nFrom a technological standpoint, AI agents have revolutionized
|
||||
numerous industries. In customer service, for instance, AI agents like chatbots
|
||||
and virtual assistants handle customer inquiries 24/7, providing quick and efficient
|
||||
solutions. In healthcare, AI agents can assist in diagnosing diseases, managing
|
||||
patient data, and even predicting outbreaks. The automation capabilities of
|
||||
AI agents also enhance productivity in areas such as logistics, finance, and
|
||||
cybersecurity by identifying patterns and anomalies at speeds far beyond human
|
||||
capabilities.\\n\\nHowever, it's important to acknowledge the potential downsides
|
||||
and challenges associated with AI agents. Ethical considerations are paramount.
|
||||
Issues such as data privacy, security, and biases in AI algorithms need to be
|
||||
carefully managed. There is also the human aspect to consider\u2014over-reliance
|
||||
on AI agents might lead to job displacement in certain sectors, and ensuring
|
||||
a fair transition for affected workers is crucial.\\n\\nMy concerns generally
|
||||
stem from these ethical and societal implications rather than from the technology
|
||||
itself. I advocate for responsible AI development, which includes transparency,
|
||||
fairness, and accountability. By addressing these concerns, we can harness the
|
||||
full potential of AI agents while mitigating the associated risks.\\n\\nSo,
|
||||
to clarify, I don't hate AI agents; I recognize their immense potential and
|
||||
the significant benefits they bring to various fields. However, I am equally
|
||||
aware of the challenges they present and advocate for a balanced approach to
|
||||
their development and deployment.\",\n \"refusal\": null\n },\n
|
||||
\ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n
|
||||
\ \"usage\": {\n \"prompt_tokens\": 200,\n \"completion_tokens\": 359,\n
|
||||
\ \"total_tokens\": 559,\n \"completion_tokens_details\": {\n \"reasoning_tokens\":
|
||||
0\n }\n },\n \"system_fingerprint\": \"fp_3537616b13\"\n}\n"
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8c85ebaa5c061cf3-GRU
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 24 Sep 2024 21:38:22 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
openai-processing-ms:
|
||||
- '4928'
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
x-ratelimit-limit-requests:
|
||||
- '10000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '30000000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '9999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '29999755'
|
||||
x-ratelimit-reset-requests:
|
||||
- 6ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
x-request-id:
|
||||
- req_761796305026b5adfbb5a6237f14e32a
|
||||
http_version: HTTP/1.1
|
||||
status_code: 200
|
||||
version: 1
|
||||
@@ -1,136 +0,0 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages": [{"role": "system", "content": "You are researcher. You''re
|
||||
an expert researcher, specialized in technology\nYour personal goal is: make
|
||||
the best research and analysis on content about AI and AI agents\nTo give my
|
||||
best complete final answer to the task use 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: share your take on AI Agents\n\nThis is the expect criteria for your final
|
||||
answer: Your best answer to your coworker asking you this, accounting for the
|
||||
context shared.\nyou MUST return the actual complete content as the final answer,
|
||||
not a summary.\n\nThis is the context you''re working with:\nI heard you hate
|
||||
them\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"}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1027'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA;
|
||||
_cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.47.0
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.47.0
|
||||
x-stainless-raw-response:
|
||||
- 'true'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.11.7
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
content: "{\n \"id\": \"chatcmpl-AB7Wh4RzroZdiwUNOc4oRRhwfdRzs\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1727213903,\n \"model\": \"gpt-4o-2024-05-13\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal
|
||||
Answer: AI agents are essentially autonomous software programs that perform
|
||||
tasks or provide services on behalf of humans. They're built on complex algorithms
|
||||
and often leverage machine learning and neural networks to adapt and improve
|
||||
over time. \\n\\nIt's important to clarify that I don't \\\"hate\\\" AI agents,
|
||||
but I do approach them with a critical eye for a couple of reasons. AI agents
|
||||
have enormous potential to transform industries, making processes more efficient,
|
||||
providing insightful data analytics, and even learning from user behavior to
|
||||
offer personalized experiences. However, this potential comes with significant
|
||||
challenges and risks:\\n\\n1. **Ethical Concerns**: AI agents operate on data,
|
||||
and the biases present in data can lead to unfair or unethical outcomes. Ensuring
|
||||
that AI operates within ethical boundaries requires rigorous oversight, which
|
||||
is not always in place.\\n\\n2. **Privacy Issues**: AI agents often need access
|
||||
to large amounts of data, raising questions about privacy and data security.
|
||||
If not managed correctly, this can lead to unauthorized data access and potential
|
||||
misuse of sensitive information.\\n\\n3. **Transparency and Accountability**:
|
||||
The decision-making process of AI agents can be opaque, making it difficult
|
||||
to understand how they arrive at specific conclusions or actions. This lack
|
||||
of transparency poses challenges for accountability, especially if something
|
||||
goes wrong.\\n\\n4. **Job Displacement**: As AI agents become more capable,
|
||||
there are valid concerns about their impact on employment. Tasks that were traditionally
|
||||
performed by humans are increasingly being automated, which can lead to job
|
||||
loss in certain sectors.\\n\\n5. **Reliability**: While AI agents can outperform
|
||||
humans in many areas, they are not infallible. They can make mistakes, sometimes
|
||||
with serious consequences. Continuous monitoring and regular updates are essential
|
||||
to maintain their performance and reliability.\\n\\nIn summary, while AI agents
|
||||
offer substantial benefits and opportunities, it's critical to approach their
|
||||
adoption and deployment with careful consideration of the associated risks.
|
||||
Balancing innovation with responsibility is key to leveraging AI agents effectively
|
||||
and ethically. So, rather than \\\"hating\\\" AI agents, I advocate for a balanced,
|
||||
cautious approach that maximizes benefits while mitigating potential downsides.\",\n
|
||||
\ \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\":
|
||||
\"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 200,\n \"completion_tokens\":
|
||||
429,\n \"total_tokens\": 629,\n \"completion_tokens_details\": {\n \"reasoning_tokens\":
|
||||
0\n }\n },\n \"system_fingerprint\": \"fp_3537616b13\"\n}\n"
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8c85ebcdae971cf3-GRU
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 24 Sep 2024 21:38:29 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
openai-processing-ms:
|
||||
- '5730'
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
x-ratelimit-limit-requests:
|
||||
- '10000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '30000000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '9999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '29999755'
|
||||
x-ratelimit-reset-requests:
|
||||
- 6ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
x-request-id:
|
||||
- req_5da5b18b3cee10548a217ba97e133815
|
||||
http_version: HTTP/1.1
|
||||
status_code: 200
|
||||
version: 1
|
||||
@@ -1,137 +0,0 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages": [{"role": "system", "content": "You are researcher. You''re
|
||||
an expert researcher, specialized in technology\nYour personal goal is: make
|
||||
the best research and analysis on content about AI and AI agents\nTo give my
|
||||
best complete final answer to the task use 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: share your take on AI Agents\n\nThis is the expect criteria for your final
|
||||
answer: Your best answer to your coworker asking you this, accounting for the
|
||||
context shared.\nyou MUST return the actual complete content as the final answer,
|
||||
not a summary.\n\nThis is the context you''re working with:\nI heard you hate
|
||||
them\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"}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1027'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA;
|
||||
_cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.47.0
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.47.0
|
||||
x-stainless-raw-response:
|
||||
- 'true'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.11.7
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
content: "{\n \"id\": \"chatcmpl-AB7Wsv05NzccAAGC0CZVg03mE72wi\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1727213914,\n \"model\": \"gpt-4o-2024-05-13\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal
|
||||
Answer: My perspective on AI agents is quite nuanced and not a matter of simple
|
||||
like or dislike. AI agents, depending on their design, deployment, and use cases,
|
||||
can bring about both significant benefits and substantial challenges.\\n\\nOn
|
||||
the positive side, AI agents have the potential to automate mundane tasks, enhance
|
||||
productivity, and provide personalized services in ways that were previously
|
||||
unimaginable. For instance, in customer service, AI agents can handle inquiries
|
||||
24/7, reducing waiting times and improving user satisfaction. In healthcare,
|
||||
they can assist in diagnosing diseases by analyzing vast datasets much faster
|
||||
than humans. These applications demonstrate the transformative power of AI in
|
||||
improving efficiency and delivering better outcomes across various industries.\\n\\nHowever,
|
||||
my reservations stem from several critical concerns. Firstly, there's the issue
|
||||
of reliability and accuracy. Mismanaged or poorly designed AI systems can lead
|
||||
to significant errors, which could be particularly detrimental in high-stakes
|
||||
environments like healthcare or autonomous vehicles. Second, there's a risk
|
||||
of job displacement as AI agents become capable of performing tasks traditionally
|
||||
done by humans. This raises socio-economic concerns that need to be addressed
|
||||
through effective policy-making and upskilling programs.\\n\\nAdditionally,
|
||||
there are ethical and privacy considerations. AI agents often require large
|
||||
amounts of data to function effectively, which can lead to issues concerning
|
||||
consent, data security, and individual privacy rights. The lack of transparency
|
||||
in how these agents make decisions can also pose challenges\u2014this is often
|
||||
referred to as the \\\"black box\\\" problem, where even the developers may
|
||||
not fully understand how specific AI outputs are generated.\\n\\nFinally, the
|
||||
deployment of AI agents by bad actors for malicious purposes, such as deepfakes,
|
||||
misinformation, and hacking, remains a pertinent concern. These potential downsides
|
||||
imply that while AI technology is extremely powerful and promising, it must
|
||||
be developed and implemented with care, consideration, and robust ethical guidelines.\\n\\nSo,
|
||||
in summary, I don't hate AI agents\u2014rather, I approach them critically with
|
||||
a balanced perspective, recognizing both their profound potential and the significant
|
||||
challenges they present. Thoughtful development, responsible deployment, and
|
||||
ethical governance are crucial to harness the benefits while mitigating the
|
||||
risks associated with AI agents.\",\n \"refusal\": null\n },\n \"logprobs\":
|
||||
null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
200,\n \"completion_tokens\": 436,\n \"total_tokens\": 636,\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0\n }\n },\n \"system_fingerprint\": \"fp_3537616b13\"\n}\n"
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8c85ec12ab0d1cf3-GRU
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 24 Sep 2024 21:38:40 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
openai-processing-ms:
|
||||
- '6251'
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
x-ratelimit-limit-requests:
|
||||
- '10000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '30000000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '9999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '29999755'
|
||||
x-ratelimit-reset-requests:
|
||||
- 6ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
x-request-id:
|
||||
- req_50aa23cad48cfb83b754a5a92939638e
|
||||
http_version: HTTP/1.1
|
||||
status_code: 200
|
||||
version: 1
|
||||
@@ -1,133 +0,0 @@
|
||||
"""Test Agent creation and execution basic functionality."""
|
||||
|
||||
import pytest
|
||||
|
||||
from crewai.agent import Agent
|
||||
from crewai.tools.agent_tools.agent_tools import AgentTools
|
||||
|
||||
researcher = Agent(
|
||||
role="researcher",
|
||||
goal="make the best research and analysis on content about AI and AI agents",
|
||||
backstory="You're an expert researcher, specialized in technology",
|
||||
allow_delegation=False,
|
||||
)
|
||||
tools = AgentTools(agents=[researcher]).tools()
|
||||
delegate_tool = tools[0]
|
||||
ask_tool = tools[1]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vcr_config(request) -> dict:
|
||||
return {
|
||||
"cassette_library_dir": "tests/tools/agent_tools/cassettes",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_delegate_work():
|
||||
result = delegate_tool.run(
|
||||
coworker="researcher",
|
||||
task="share your take on AI Agents",
|
||||
context="I heard you hate them",
|
||||
)
|
||||
|
||||
assert (
|
||||
result
|
||||
== "I understand why you might think I dislike AI agents, but my perspective is more nuanced. AI agents, in essence, are incredibly versatile tools designed to perform specific tasks autonomously or semi-autonomously. They harness various artificial intelligence techniques, such as machine learning, natural language processing, and computer vision, to interpret data, understand tasks, and execute them efficiently. \n\nFrom a technological standpoint, AI agents have revolutionized numerous industries. In customer service, for instance, AI agents like chatbots and virtual assistants handle customer inquiries 24/7, providing quick and efficient solutions. In healthcare, AI agents can assist in diagnosing diseases, managing patient data, and even predicting outbreaks. The automation capabilities of AI agents also enhance productivity in areas such as logistics, finance, and cybersecurity by identifying patterns and anomalies at speeds far beyond human capabilities.\n\nHowever, it's important to acknowledge the potential downsides and challenges associated with AI agents. Ethical considerations are paramount. Issues such as data privacy, security, and biases in AI algorithms need to be carefully managed. There is also the human aspect to consider—over-reliance on AI agents might lead to job displacement in certain sectors, and ensuring a fair transition for affected workers is crucial.\n\nMy concerns generally stem from these ethical and societal implications rather than from the technology itself. I advocate for responsible AI development, which includes transparency, fairness, and accountability. By addressing these concerns, we can harness the full potential of AI agents while mitigating the associated risks.\n\nSo, to clarify, I don't hate AI agents; I recognize their immense potential and the significant benefits they bring to various fields. However, I am equally aware of the challenges they present and advocate for a balanced approach to their development and deployment."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_delegate_work_with_wrong_co_worker_variable():
|
||||
result = delegate_tool.run(
|
||||
coworker="researcher",
|
||||
task="share your take on AI Agents",
|
||||
context="I heard you hate them",
|
||||
)
|
||||
|
||||
assert (
|
||||
result
|
||||
== "AI agents are essentially autonomous software programs that perform tasks or provide services on behalf of humans. They're built on complex algorithms and often leverage machine learning and neural networks to adapt and improve over time. \n\nIt's important to clarify that I don't \"hate\" AI agents, but I do approach them with a critical eye for a couple of reasons. AI agents have enormous potential to transform industries, making processes more efficient, providing insightful data analytics, and even learning from user behavior to offer personalized experiences. However, this potential comes with significant challenges and risks:\n\n1. **Ethical Concerns**: AI agents operate on data, and the biases present in data can lead to unfair or unethical outcomes. Ensuring that AI operates within ethical boundaries requires rigorous oversight, which is not always in place.\n\n2. **Privacy Issues**: AI agents often need access to large amounts of data, raising questions about privacy and data security. If not managed correctly, this can lead to unauthorized data access and potential misuse of sensitive information.\n\n3. **Transparency and Accountability**: The decision-making process of AI agents can be opaque, making it difficult to understand how they arrive at specific conclusions or actions. This lack of transparency poses challenges for accountability, especially if something goes wrong.\n\n4. **Job Displacement**: As AI agents become more capable, there are valid concerns about their impact on employment. Tasks that were traditionally performed by humans are increasingly being automated, which can lead to job loss in certain sectors.\n\n5. **Reliability**: While AI agents can outperform humans in many areas, they are not infallible. They can make mistakes, sometimes with serious consequences. Continuous monitoring and regular updates are essential to maintain their performance and reliability.\n\nIn summary, while AI agents offer substantial benefits and opportunities, it's critical to approach their adoption and deployment with careful consideration of the associated risks. Balancing innovation with responsibility is key to leveraging AI agents effectively and ethically. So, rather than \"hating\" AI agents, I advocate for a balanced, cautious approach that maximizes benefits while mitigating potential downsides."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_ask_question():
|
||||
result = ask_tool.run(
|
||||
coworker="researcher",
|
||||
question="do you hate AI Agents?",
|
||||
context="I heard you LOVE them",
|
||||
)
|
||||
|
||||
assert (
|
||||
result
|
||||
== "As an expert researcher specialized in technology, I don't harbor emotions such as hate towards AI agents. Instead, my focus is on understanding, analyzing, and leveraging their potential to advance various fields. AI agents, when designed and implemented effectively, can greatly augment human capabilities, streamline processes, and provide valuable insights that might otherwise be overlooked. My enthusiasm for AI agents stems from their ability to transform industries and improve everyday life, making complex tasks more manageable and enhancing overall efficiency. This passion drives my research and commitment to making meaningful contributions in the realm of AI and AI agents."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_ask_question_with_wrong_co_worker_variable():
|
||||
result = ask_tool.run(
|
||||
coworker="researcher",
|
||||
question="do you hate AI Agents?",
|
||||
context="I heard you LOVE them",
|
||||
)
|
||||
|
||||
assert (
|
||||
result
|
||||
== "I don't hate AI agents; on the contrary, I find them fascinating and incredibly useful. Considering the rapid advancements in AI technology, these agents have the potential to revolutionize various industries by automating tasks, improving efficiency, and providing insights that were previously unattainable. My expertise in researching and analyzing AI and AI agents has allowed me to appreciate the intricate design and the vast possibilities they offer. Therefore, it's more accurate to say that I love AI agents for their potential to drive innovation and improve our daily lives."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_delegate_work_withwith_coworker_as_array():
|
||||
result = delegate_tool.run(
|
||||
coworker="[researcher]",
|
||||
task="share your take on AI Agents",
|
||||
context="I heard you hate them",
|
||||
)
|
||||
|
||||
assert (
|
||||
result
|
||||
== "My perspective on AI agents is quite nuanced and not a matter of simple like or dislike. AI agents, depending on their design, deployment, and use cases, can bring about both significant benefits and substantial challenges.\n\nOn the positive side, AI agents have the potential to automate mundane tasks, enhance productivity, and provide personalized services in ways that were previously unimaginable. For instance, in customer service, AI agents can handle inquiries 24/7, reducing waiting times and improving user satisfaction. In healthcare, they can assist in diagnosing diseases by analyzing vast datasets much faster than humans. These applications demonstrate the transformative power of AI in improving efficiency and delivering better outcomes across various industries.\n\nHowever, my reservations stem from several critical concerns. Firstly, there's the issue of reliability and accuracy. Mismanaged or poorly designed AI systems can lead to significant errors, which could be particularly detrimental in high-stakes environments like healthcare or autonomous vehicles. Second, there's a risk of job displacement as AI agents become capable of performing tasks traditionally done by humans. This raises socio-economic concerns that need to be addressed through effective policy-making and upskilling programs.\n\nAdditionally, there are ethical and privacy considerations. AI agents often require large amounts of data to function effectively, which can lead to issues concerning consent, data security, and individual privacy rights. The lack of transparency in how these agents make decisions can also pose challenges—this is often referred to as the \"black box\" problem, where even the developers may not fully understand how specific AI outputs are generated.\n\nFinally, the deployment of AI agents by bad actors for malicious purposes, such as deepfakes, misinformation, and hacking, remains a pertinent concern. These potential downsides imply that while AI technology is extremely powerful and promising, it must be developed and implemented with care, consideration, and robust ethical guidelines.\n\nSo, in summary, I don't hate AI agents—rather, I approach them critically with a balanced perspective, recognizing both their profound potential and the significant challenges they present. Thoughtful development, responsible deployment, and ethical governance are crucial to harness the benefits while mitigating the risks associated with AI agents."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_ask_question_with_coworker_as_array():
|
||||
result = ask_tool.run(
|
||||
coworker="[researcher]",
|
||||
question="do you hate AI Agents?",
|
||||
context="I heard you LOVE them",
|
||||
)
|
||||
|
||||
assert (
|
||||
result
|
||||
== "As an expert researcher specializing in technology and AI, I have a deep appreciation for AI agents. These advanced tools have the potential to revolutionize countless industries by improving efficiency, accuracy, and decision-making processes. They can augment human capabilities, handle mundane and repetitive tasks, and even offer insights that might be beyond human reach. While it's crucial to approach AI with a balanced perspective, understanding both its capabilities and limitations, my stance is one of optimism and fascination. Properly developed and ethically managed, AI agents hold immense promise for driving innovation and solving complex problems. So yes, I do love AI agents for their transformative potential and the positive impact they can have on society."
|
||||
)
|
||||
|
||||
|
||||
def test_delegate_work_to_wrong_agent():
|
||||
result = ask_tool.run(
|
||||
coworker="writer",
|
||||
question="share your take on AI Agents",
|
||||
context="I heard you hate them",
|
||||
)
|
||||
|
||||
assert (
|
||||
result
|
||||
== "\nError executing tool. coworker mentioned not found, it must be one of the following options:\n- researcher\n"
|
||||
)
|
||||
|
||||
|
||||
def test_ask_question_to_wrong_agent():
|
||||
result = ask_tool.run(
|
||||
coworker="writer",
|
||||
question="do you hate AI Agents?",
|
||||
context="I heard you LOVE them",
|
||||
)
|
||||
|
||||
assert (
|
||||
result
|
||||
== "\nError executing tool. coworker mentioned not found, it must be one of the following options:\n- researcher\n"
|
||||
)
|
||||
@@ -1,233 +0,0 @@
|
||||
import asyncio
|
||||
from typing import Callable
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from crewai.agent import Agent
|
||||
from crewai.crew import Crew
|
||||
from crewai.task import Task
|
||||
from crewai.tools import BaseTool, tool
|
||||
|
||||
|
||||
def test_creating_a_tool_using_annotation():
|
||||
@tool("Name of my tool")
|
||||
def my_tool(question: str) -> str:
|
||||
"""Clear description for what this tool is useful for, your agent will need this information to use it."""
|
||||
return question
|
||||
|
||||
# Assert all the right attributes were defined
|
||||
assert my_tool.name == "Name of my tool"
|
||||
assert (
|
||||
my_tool.description
|
||||
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, your agent will need this information to use it."
|
||||
)
|
||||
assert my_tool.args_schema.model_json_schema()["properties"] == {
|
||||
"question": {"title": "Question", "type": "string"}
|
||||
}
|
||||
assert (
|
||||
my_tool.func("What is the meaning of life?") == "What is the meaning of life?"
|
||||
)
|
||||
|
||||
converted_tool = my_tool.to_structured_tool()
|
||||
assert converted_tool.name == "Name of my tool"
|
||||
|
||||
assert (
|
||||
converted_tool.description
|
||||
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, your agent will need this information to use it."
|
||||
)
|
||||
assert converted_tool.args_schema.model_json_schema()["properties"] == {
|
||||
"question": {"title": "Question", "type": "string"}
|
||||
}
|
||||
assert (
|
||||
converted_tool.func("What is the meaning of life?")
|
||||
== "What is the meaning of life?"
|
||||
)
|
||||
|
||||
|
||||
def test_creating_a_tool_using_baseclass():
|
||||
class MyCustomTool(BaseTool):
|
||||
name: str = "Name of my tool"
|
||||
description: str = "Clear description for what this tool is useful for, your agent will need this information to use it."
|
||||
|
||||
def _run(self, question: str) -> str:
|
||||
return question
|
||||
|
||||
my_tool = MyCustomTool()
|
||||
# Assert all the right attributes were defined
|
||||
assert my_tool.name == "Name of my tool"
|
||||
|
||||
assert (
|
||||
my_tool.description
|
||||
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, your agent will need this information to use it."
|
||||
)
|
||||
assert my_tool.args_schema.model_json_schema()["properties"] == {
|
||||
"question": {"title": "Question", "type": "string"}
|
||||
}
|
||||
assert my_tool.run("What is the meaning of life?") == "What is the meaning of life?"
|
||||
|
||||
converted_tool = my_tool.to_structured_tool()
|
||||
assert converted_tool.name == "Name of my tool"
|
||||
|
||||
assert (
|
||||
converted_tool.description
|
||||
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, your agent will need this information to use it."
|
||||
)
|
||||
assert converted_tool.args_schema.model_json_schema()["properties"] == {
|
||||
"question": {"title": "Question", "type": "string"}
|
||||
}
|
||||
assert (
|
||||
converted_tool._run("What is the meaning of life?")
|
||||
== "What is the meaning of life?"
|
||||
)
|
||||
|
||||
|
||||
def test_setting_cache_function():
|
||||
class MyCustomTool(BaseTool):
|
||||
name: str = "Name of my tool"
|
||||
description: str = "Clear description for what this tool is useful for, your agent will need this information to use it."
|
||||
cache_function: Callable = lambda: False
|
||||
|
||||
def _run(self, question: str) -> str:
|
||||
return question
|
||||
|
||||
my_tool = MyCustomTool()
|
||||
# Assert all the right attributes were defined
|
||||
assert not my_tool.cache_function()
|
||||
|
||||
|
||||
def test_default_cache_function_is_true():
|
||||
class MyCustomTool(BaseTool):
|
||||
name: str = "Name of my tool"
|
||||
description: str = "Clear description for what this tool is useful for, your agent will need this information to use it."
|
||||
|
||||
def _run(self, question: str) -> str:
|
||||
return question
|
||||
|
||||
my_tool = MyCustomTool()
|
||||
# Assert all the right attributes were defined
|
||||
assert my_tool.cache_function()
|
||||
|
||||
|
||||
def test_result_as_answer_in_tool_decorator():
|
||||
@tool("Tool with result as answer", result_as_answer=True)
|
||||
def my_tool_with_result_as_answer(question: str) -> str:
|
||||
"""This tool will return its result as the final answer."""
|
||||
return question
|
||||
|
||||
assert my_tool_with_result_as_answer.result_as_answer is True
|
||||
|
||||
converted_tool = my_tool_with_result_as_answer.to_structured_tool()
|
||||
assert converted_tool.result_as_answer is True
|
||||
|
||||
@tool("Tool with default result_as_answer")
|
||||
def my_tool_with_default(question: str) -> str:
|
||||
"""This tool uses the default result_as_answer value."""
|
||||
return question
|
||||
|
||||
assert my_tool_with_default.result_as_answer is False
|
||||
|
||||
converted_tool = my_tool_with_default.to_structured_tool()
|
||||
assert converted_tool.result_as_answer is False
|
||||
|
||||
|
||||
class SyncTool(BaseTool):
|
||||
"""Test implementation with a synchronous _run method"""
|
||||
|
||||
name: str = "sync_tool"
|
||||
description: str = "A synchronous tool for testing"
|
||||
|
||||
def _run(self, input_text: str) -> str:
|
||||
"""Process input text synchronously."""
|
||||
return f"Processed {input_text} synchronously"
|
||||
|
||||
|
||||
class AsyncTool(BaseTool):
|
||||
"""Test implementation with an asynchronous _run method"""
|
||||
|
||||
name: str = "async_tool"
|
||||
description: str = "An asynchronous tool for testing"
|
||||
|
||||
async def _run(self, input_text: str) -> str:
|
||||
"""Process input text asynchronously."""
|
||||
await asyncio.sleep(0.1) # Simulate async operation
|
||||
return f"Processed {input_text} asynchronously"
|
||||
|
||||
|
||||
def test_sync_run_returns_direct_result():
|
||||
"""Test that _run in a synchronous tool returns a direct result, not a coroutine."""
|
||||
tool = SyncTool()
|
||||
result = tool._run(input_text="hello")
|
||||
|
||||
assert not asyncio.iscoroutine(result)
|
||||
assert result == "Processed hello synchronously"
|
||||
|
||||
run_result = tool.run(input_text="hello")
|
||||
assert run_result == "Processed hello synchronously"
|
||||
|
||||
|
||||
def test_async_run_returns_coroutine():
|
||||
"""Test that _run in an asynchronous tool returns a coroutine object."""
|
||||
tool = AsyncTool()
|
||||
result = tool._run(input_text="hello")
|
||||
|
||||
assert asyncio.iscoroutine(result)
|
||||
result.close() # Clean up the coroutine
|
||||
|
||||
|
||||
def test_run_calls_asyncio_run_for_async_tools():
|
||||
"""Test that asyncio.run is called when using async tools."""
|
||||
async_tool = AsyncTool()
|
||||
|
||||
with patch("asyncio.run") as mock_run:
|
||||
mock_run.return_value = "Processed test asynchronously"
|
||||
async_result = async_tool.run(input_text="test")
|
||||
|
||||
mock_run.assert_called_once()
|
||||
assert async_result == "Processed test asynchronously"
|
||||
|
||||
|
||||
def test_run_does_not_call_asyncio_run_for_sync_tools():
|
||||
"""Test that asyncio.run is NOT called when using sync tools."""
|
||||
sync_tool = SyncTool()
|
||||
|
||||
with patch("asyncio.run") as mock_run:
|
||||
sync_result = sync_tool.run(input_text="test")
|
||||
|
||||
mock_run.assert_not_called()
|
||||
assert sync_result == "Processed test synchronously"
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_max_usage_count_is_respected():
|
||||
class IteratingTool(BaseTool):
|
||||
name: str = "iterating_tool"
|
||||
description: str = "A tool that iterates a given number of times"
|
||||
|
||||
def _run(self, input_text: str):
|
||||
return f"Iteration {input_text}"
|
||||
|
||||
tool = IteratingTool(max_usage_count=5)
|
||||
|
||||
agent = Agent(
|
||||
role="Iterating Agent",
|
||||
goal="Call the iterating tool 5 times",
|
||||
backstory="You are an agent that iterates a given number of times",
|
||||
tools=[tool],
|
||||
)
|
||||
|
||||
task = Task(
|
||||
description="Call the iterating tool 5 times",
|
||||
expected_output="A list of the iterations",
|
||||
agent=agent,
|
||||
)
|
||||
|
||||
crew = Crew(
|
||||
agents=[agent],
|
||||
tasks=[task],
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
crew.kickoff()
|
||||
assert tool.max_usage_count == 5
|
||||
assert tool.current_usage_count == 5
|
||||
@@ -1,376 +0,0 @@
|
||||
import pytest
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from crewai.tools.structured_tool import CrewStructuredTool
|
||||
|
||||
|
||||
# Test fixtures
|
||||
@pytest.fixture
|
||||
def basic_function():
|
||||
def test_func(param1: str, param2: int = 0) -> str:
|
||||
"""Test function with basic params."""
|
||||
return f"{param1} {param2}"
|
||||
|
||||
return test_func
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def schema_class():
|
||||
class TestSchema(BaseModel):
|
||||
param1: str
|
||||
param2: int = Field(default=0)
|
||||
|
||||
return TestSchema
|
||||
|
||||
|
||||
def test_initialization(basic_function, schema_class):
|
||||
"""Test basic initialization of CrewStructuredTool"""
|
||||
tool = CrewStructuredTool(
|
||||
name="test_tool",
|
||||
description="Test tool description",
|
||||
func=basic_function,
|
||||
args_schema=schema_class,
|
||||
)
|
||||
|
||||
assert tool.name == "test_tool"
|
||||
assert tool.description == "Test tool description"
|
||||
assert tool.func == basic_function
|
||||
assert tool.args_schema == schema_class
|
||||
|
||||
|
||||
def test_from_function(basic_function):
|
||||
"""Test creating tool from function"""
|
||||
tool = CrewStructuredTool.from_function(
|
||||
func=basic_function, name="test_tool", description="Test description"
|
||||
)
|
||||
|
||||
assert tool.name == "test_tool"
|
||||
assert tool.description == "Test description"
|
||||
assert tool.func == basic_function
|
||||
assert isinstance(tool.args_schema, type(BaseModel))
|
||||
|
||||
|
||||
def test_validate_function_signature(basic_function, schema_class):
|
||||
"""Test function signature validation"""
|
||||
tool = CrewStructuredTool(
|
||||
name="test_tool",
|
||||
description="Test tool",
|
||||
func=basic_function,
|
||||
args_schema=schema_class,
|
||||
)
|
||||
|
||||
# Should not raise any exceptions
|
||||
tool._validate_function_signature()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ainvoke(basic_function):
|
||||
"""Test asynchronous invocation"""
|
||||
tool = CrewStructuredTool.from_function(func=basic_function, name="test_tool")
|
||||
|
||||
result = await tool.ainvoke(input={"param1": "test"})
|
||||
assert result == "test 0"
|
||||
|
||||
|
||||
def test_parse_args_dict(basic_function):
|
||||
"""Test parsing dictionary arguments"""
|
||||
tool = CrewStructuredTool.from_function(func=basic_function, name="test_tool")
|
||||
|
||||
parsed = tool._parse_args({"param1": "test", "param2": 42})
|
||||
assert parsed["param1"] == "test"
|
||||
assert parsed["param2"] == 42
|
||||
|
||||
|
||||
def test_parse_args_string(basic_function):
|
||||
"""Test parsing string arguments"""
|
||||
tool = CrewStructuredTool.from_function(func=basic_function, name="test_tool")
|
||||
|
||||
parsed = tool._parse_args('{"param1": "test", "param2": 42}')
|
||||
assert parsed["param1"] == "test"
|
||||
assert parsed["param2"] == 42
|
||||
|
||||
|
||||
def test_complex_types():
|
||||
"""Test handling of complex parameter types"""
|
||||
|
||||
def complex_func(nested: dict, items: list) -> str:
|
||||
"""Process complex types."""
|
||||
return f"Processed {len(items)} items with {len(nested)} nested keys"
|
||||
|
||||
tool = CrewStructuredTool.from_function(
|
||||
func=complex_func, name="test_tool", description="Test complex types"
|
||||
)
|
||||
result = tool.invoke({"nested": {"key": "value"}, "items": [1, 2, 3]})
|
||||
assert result == "Processed 3 items with 1 nested keys"
|
||||
|
||||
|
||||
def test_schema_inheritance():
|
||||
"""Test tool creation with inherited schema"""
|
||||
|
||||
def extended_func(base_param: str, extra_param: int) -> str:
|
||||
"""Test function with inherited schema."""
|
||||
return f"{base_param} {extra_param}"
|
||||
|
||||
class BaseSchema(BaseModel):
|
||||
base_param: str
|
||||
|
||||
class ExtendedSchema(BaseSchema):
|
||||
extra_param: int
|
||||
|
||||
tool = CrewStructuredTool.from_function(
|
||||
func=extended_func, name="test_tool", args_schema=ExtendedSchema
|
||||
)
|
||||
|
||||
result = tool.invoke({"base_param": "test", "extra_param": 42})
|
||||
assert result == "test 42"
|
||||
|
||||
|
||||
def test_default_values_in_schema():
|
||||
"""Test handling of default values in schema"""
|
||||
|
||||
def default_func(
|
||||
required_param: str,
|
||||
optional_param: str = "default",
|
||||
nullable_param: int | None = None,
|
||||
) -> str:
|
||||
"""Test function with default values."""
|
||||
return f"{required_param} {optional_param} {nullable_param}"
|
||||
|
||||
tool = CrewStructuredTool.from_function(
|
||||
func=default_func, name="test_tool", description="Test defaults"
|
||||
)
|
||||
|
||||
# Test with minimal parameters
|
||||
result = tool.invoke({"required_param": "test"})
|
||||
assert result == "test default None"
|
||||
|
||||
# Test with all parameters
|
||||
result = tool.invoke(
|
||||
{"required_param": "test", "optional_param": "custom", "nullable_param": 42}
|
||||
)
|
||||
assert result == "test custom 42"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def custom_tool_decorator():
|
||||
from crewai.tools import tool
|
||||
|
||||
@tool("custom_tool", result_as_answer=True)
|
||||
async def custom_tool():
|
||||
"""This is a tool that does something"""
|
||||
return "Hello World from Custom Tool"
|
||||
|
||||
return custom_tool
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def custom_tool():
|
||||
from crewai.tools import BaseTool
|
||||
|
||||
class CustomTool(BaseTool):
|
||||
name: str = "my_tool"
|
||||
description: str = "This is a tool that does something"
|
||||
result_as_answer: bool = True
|
||||
|
||||
async def _run(self):
|
||||
return "Hello World from Custom Tool"
|
||||
|
||||
return CustomTool()
|
||||
|
||||
|
||||
def build_simple_crew(tool):
|
||||
from crewai import Agent, Crew, Task
|
||||
|
||||
agent1 = Agent(
|
||||
role="Simple role",
|
||||
goal="Simple goal",
|
||||
backstory="Simple backstory",
|
||||
tools=[tool],
|
||||
)
|
||||
|
||||
say_hi_task = Task(
|
||||
description="Use the custom tool result as answer.",
|
||||
agent=agent1,
|
||||
expected_output="Use the tool result",
|
||||
)
|
||||
|
||||
return Crew(agents=[agent1], tasks=[say_hi_task])
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_async_tool_using_within_isolated_crew(custom_tool):
|
||||
crew = build_simple_crew(custom_tool)
|
||||
result = crew.kickoff()
|
||||
|
||||
assert result.raw == "Hello World from Custom Tool"
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_async_tool_using_decorator_within_isolated_crew(custom_tool_decorator):
|
||||
crew = build_simple_crew(custom_tool_decorator)
|
||||
result = crew.kickoff()
|
||||
|
||||
assert result.raw == "Hello World from Custom Tool"
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_async_tool_within_flow(custom_tool):
|
||||
from crewai.flow.flow import Flow
|
||||
|
||||
class StructuredExampleFlow(Flow):
|
||||
from crewai.flow.flow import start
|
||||
|
||||
@start()
|
||||
async def start(self):
|
||||
crew = build_simple_crew(custom_tool)
|
||||
return await crew.kickoff_async()
|
||||
|
||||
flow = StructuredExampleFlow()
|
||||
result = flow.kickoff()
|
||||
assert result.raw == "Hello World from Custom Tool"
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_async_tool_using_decorator_within_flow(custom_tool_decorator):
|
||||
from crewai.flow.flow import Flow
|
||||
|
||||
class StructuredExampleFlow(Flow):
|
||||
from crewai.flow.flow import start
|
||||
|
||||
@start()
|
||||
async def start(self):
|
||||
crew = build_simple_crew(custom_tool_decorator)
|
||||
return await crew.kickoff_async()
|
||||
|
||||
flow = StructuredExampleFlow()
|
||||
result = flow.kickoff()
|
||||
assert result.raw == "Hello World from Custom Tool"
|
||||
|
||||
|
||||
def test_structured_tool_invoke_calls_func_only_once():
|
||||
"""Test that CrewStructuredTool.invoke() calls the underlying function exactly once."""
|
||||
call_count = 0
|
||||
call_history = []
|
||||
|
||||
def counting_function(param: str) -> str:
|
||||
"""Function that tracks how many times it's called."""
|
||||
nonlocal call_count
|
||||
call_count += 1
|
||||
call_history.append(f"Call #{call_count} with param: {param}")
|
||||
return f"Result from call #{call_count}: {param}"
|
||||
|
||||
# Create CrewStructuredTool directly
|
||||
tool = CrewStructuredTool.from_function(
|
||||
func=counting_function,
|
||||
name="direct_test_tool",
|
||||
description="Tool to test direct invoke() method",
|
||||
)
|
||||
|
||||
# Call invoke() directly - this is where the bug was
|
||||
result = tool.invoke({"param": "test_value"})
|
||||
|
||||
# Critical assertions that would catch the duplicate execution bug
|
||||
assert call_count == 1, (
|
||||
f"DUPLICATE EXECUTION BUG: Function was called {call_count} times instead of 1. "
|
||||
f"This means CrewStructuredTool.invoke() has duplicate function calls. "
|
||||
f"Call history: {call_history}"
|
||||
)
|
||||
|
||||
assert len(call_history) == 1, (
|
||||
f"Expected 1 call in history, got {len(call_history)}: {call_history}"
|
||||
)
|
||||
|
||||
assert call_history[0] == "Call #1 with param: test_value", (
|
||||
f"Expected 'Call #1 with param: test_value', got: {call_history[0]}"
|
||||
)
|
||||
|
||||
assert result == "Result from call #1: test_value", (
|
||||
f"Expected result from first call, got: {result}"
|
||||
)
|
||||
|
||||
|
||||
def test_structured_tool_invoke_multiple_calls_increment_correctly():
|
||||
"""Test multiple calls to invoke() to ensure each increments correctly."""
|
||||
call_count = 0
|
||||
|
||||
def incrementing_function(value: int) -> int:
|
||||
nonlocal call_count
|
||||
call_count += 1
|
||||
return value + call_count
|
||||
|
||||
tool = CrewStructuredTool.from_function(
|
||||
func=incrementing_function,
|
||||
name="incrementing_tool",
|
||||
description="Tool that increments on each call",
|
||||
)
|
||||
|
||||
result1 = tool.invoke({"value": 10})
|
||||
assert call_count == 1, (
|
||||
f"After first invoke, expected call_count=1, got {call_count}"
|
||||
)
|
||||
assert result1 == 11, f"Expected 11 (10+1), got {result1}"
|
||||
|
||||
result2 = tool.invoke({"value": 20})
|
||||
assert call_count == 2, (
|
||||
f"After second invoke, expected call_count=2, got {call_count}"
|
||||
)
|
||||
assert result2 == 22, f"Expected 22 (20+2), got {result2}"
|
||||
|
||||
result3 = tool.invoke({"value": 30})
|
||||
assert call_count == 3, (
|
||||
f"After third invoke, expected call_count=3, got {call_count}"
|
||||
)
|
||||
assert result3 == 33, f"Expected 33 (30+3), got {result3}"
|
||||
|
||||
|
||||
def test_structured_tool_invoke_with_side_effects():
|
||||
"""Test that side effects only happen once per invoke() call."""
|
||||
side_effects = []
|
||||
|
||||
def side_effect_function(action: str) -> str:
|
||||
side_effects.append(f"SIDE_EFFECT: {action} executed at call")
|
||||
return f"Action {action} completed"
|
||||
|
||||
tool = CrewStructuredTool.from_function(
|
||||
func=side_effect_function,
|
||||
name="side_effect_tool",
|
||||
description="Tool with observable side effects",
|
||||
)
|
||||
|
||||
result = tool.invoke({"action": "write_file"})
|
||||
|
||||
assert len(side_effects) == 1, (
|
||||
f"SIDE EFFECT BUG: Expected 1 side effect, got {len(side_effects)}. "
|
||||
f"This indicates the function was called multiple times. "
|
||||
f"Side effects: {side_effects}"
|
||||
)
|
||||
|
||||
assert side_effects[0] == "SIDE_EFFECT: write_file executed at call"
|
||||
assert result == "Action write_file completed"
|
||||
|
||||
|
||||
def test_structured_tool_invoke_exception_handling():
|
||||
"""Test that exceptions don't cause duplicate execution."""
|
||||
call_count = 0
|
||||
|
||||
def failing_function(should_fail: bool) -> str:
|
||||
nonlocal call_count
|
||||
call_count += 1
|
||||
if should_fail:
|
||||
raise ValueError(f"Intentional failure on call #{call_count}")
|
||||
return f"Success on call #{call_count}"
|
||||
|
||||
tool = CrewStructuredTool.from_function(
|
||||
func=failing_function, name="failing_tool", description="Tool that can fail"
|
||||
)
|
||||
|
||||
result = tool.invoke({"should_fail": False})
|
||||
assert call_count == 1, f"Expected 1 call for success case, got {call_count}"
|
||||
assert result == "Success on call #1"
|
||||
|
||||
call_count = 0
|
||||
|
||||
with pytest.raises(ValueError, match="Intentional failure on call #1"):
|
||||
tool.invoke({"should_fail": True})
|
||||
|
||||
assert call_count == 1
|
||||
@@ -1,734 +0,0 @@
|
||||
import datetime
|
||||
import json
|
||||
import random
|
||||
import time
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from crewai import Agent, Task
|
||||
from crewai.tools import BaseTool
|
||||
from crewai.tools.tool_usage import ToolUsage
|
||||
from crewai.events.event_bus import crewai_event_bus
|
||||
from crewai.events.types.tool_usage_events import (
|
||||
ToolSelectionErrorEvent,
|
||||
ToolUsageFinishedEvent,
|
||||
ToolValidateInputErrorEvent,
|
||||
)
|
||||
|
||||
|
||||
class RandomNumberToolInput(BaseModel):
|
||||
min_value: int = Field(
|
||||
..., description="The minimum value of the range (inclusive)"
|
||||
)
|
||||
max_value: int = Field(
|
||||
..., description="The maximum value of the range (inclusive)"
|
||||
)
|
||||
|
||||
|
||||
class RandomNumberTool(BaseTool):
|
||||
name: str = "Random Number Generator"
|
||||
description: str = "Generates a random number within a specified range"
|
||||
args_schema: type[BaseModel] = RandomNumberToolInput
|
||||
|
||||
def _run(self, min_value: int, max_value: int) -> int:
|
||||
return random.randint(min_value, max_value)
|
||||
|
||||
|
||||
# Example agent and task
|
||||
example_agent = Agent(
|
||||
role="Number Generator",
|
||||
goal="Generate random numbers for various purposes",
|
||||
backstory="You are an AI agent specialized in generating random numbers within specified ranges.",
|
||||
tools=[RandomNumberTool()],
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
example_task = Task(
|
||||
description="Generate a random number between 1 and 100",
|
||||
expected_output="A random number between 1 and 100",
|
||||
agent=example_agent,
|
||||
)
|
||||
|
||||
|
||||
def test_random_number_tool_range():
|
||||
tool = RandomNumberTool()
|
||||
result = tool._run(1, 10)
|
||||
assert 1 <= result <= 10
|
||||
|
||||
|
||||
def test_random_number_tool_invalid_range():
|
||||
tool = RandomNumberTool()
|
||||
with pytest.raises(ValueError):
|
||||
tool._run(10, 1) # min_value > max_value
|
||||
|
||||
|
||||
def test_random_number_tool_schema():
|
||||
tool = RandomNumberTool()
|
||||
|
||||
# Get the schema using model_json_schema()
|
||||
schema = tool.args_schema.model_json_schema()
|
||||
|
||||
# Convert the schema to a string
|
||||
schema_str = json.dumps(schema)
|
||||
|
||||
# Check if the schema string contains the expected fields
|
||||
assert "min_value" in schema_str
|
||||
assert "max_value" in schema_str
|
||||
|
||||
# Parse the schema string back to a dictionary
|
||||
schema_dict = json.loads(schema_str)
|
||||
|
||||
# Check if the schema contains the correct field types
|
||||
assert schema_dict["properties"]["min_value"]["type"] == "integer"
|
||||
assert schema_dict["properties"]["max_value"]["type"] == "integer"
|
||||
|
||||
# Check if the schema contains the field descriptions
|
||||
assert (
|
||||
"minimum value" in schema_dict["properties"]["min_value"]["description"].lower()
|
||||
)
|
||||
assert (
|
||||
"maximum value" in schema_dict["properties"]["max_value"]["description"].lower()
|
||||
)
|
||||
|
||||
|
||||
def test_tool_usage_render():
|
||||
tool = RandomNumberTool()
|
||||
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[tool],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=MagicMock(),
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
rendered = tool_usage._render()
|
||||
|
||||
# Updated checks to match the actual output
|
||||
assert "Tool Name: Random Number Generator" in rendered
|
||||
assert "Tool Arguments:" in rendered
|
||||
assert (
|
||||
"'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}"
|
||||
in rendered
|
||||
)
|
||||
assert (
|
||||
"'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}"
|
||||
in rendered
|
||||
)
|
||||
assert (
|
||||
"Tool Description: Generates a random number within a specified range"
|
||||
in rendered
|
||||
)
|
||||
assert (
|
||||
"Tool Name: Random Number Generator\nTool Arguments: {'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}, 'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}}\nTool Description: Generates a random number within a specified range"
|
||||
in rendered
|
||||
)
|
||||
|
||||
|
||||
def test_validate_tool_input_booleans_and_none():
|
||||
# Create a ToolUsage instance with mocks
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=MagicMock(),
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
# Input with booleans and None
|
||||
tool_input = '{"key1": True, "key2": False, "key3": None}'
|
||||
expected_arguments = {"key1": True, "key2": False, "key3": None}
|
||||
|
||||
arguments = tool_usage._validate_tool_input(tool_input)
|
||||
assert arguments == expected_arguments
|
||||
|
||||
|
||||
def test_validate_tool_input_mixed_types():
|
||||
# Create a ToolUsage instance with mocks
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=MagicMock(),
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
# Input with mixed types
|
||||
tool_input = '{"number": 123, "text": "Some text", "flag": True}'
|
||||
expected_arguments = {"number": 123, "text": "Some text", "flag": True}
|
||||
|
||||
arguments = tool_usage._validate_tool_input(tool_input)
|
||||
assert arguments == expected_arguments
|
||||
|
||||
|
||||
def test_validate_tool_input_single_quotes():
|
||||
# Create a ToolUsage instance with mocks
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=MagicMock(),
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
# Input with single quotes instead of double quotes
|
||||
tool_input = "{'key': 'value', 'flag': True}"
|
||||
expected_arguments = {"key": "value", "flag": True}
|
||||
|
||||
arguments = tool_usage._validate_tool_input(tool_input)
|
||||
assert arguments == expected_arguments
|
||||
|
||||
|
||||
def test_validate_tool_input_invalid_json_repairable():
|
||||
# Create a ToolUsage instance with mocks
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=MagicMock(),
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
# Invalid JSON input that can be repaired
|
||||
tool_input = '{"key": "value", "list": [1, 2, 3,]}'
|
||||
expected_arguments = {"key": "value", "list": [1, 2, 3]}
|
||||
|
||||
arguments = tool_usage._validate_tool_input(tool_input)
|
||||
assert arguments == expected_arguments
|
||||
|
||||
|
||||
def test_validate_tool_input_with_special_characters():
|
||||
# Create a ToolUsage instance with mocks
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=MagicMock(),
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
# Input with special characters
|
||||
tool_input = '{"message": "Hello, world! \u263a", "valid": True}'
|
||||
expected_arguments = {"message": "Hello, world! ☺", "valid": True}
|
||||
|
||||
arguments = tool_usage._validate_tool_input(tool_input)
|
||||
assert arguments == expected_arguments
|
||||
|
||||
|
||||
def test_validate_tool_input_none_input():
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
arguments = tool_usage._validate_tool_input(None)
|
||||
assert arguments == {}
|
||||
|
||||
|
||||
def test_validate_tool_input_valid_json():
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
tool_input = '{"key": "value", "number": 42, "flag": true}'
|
||||
expected_arguments = {"key": "value", "number": 42, "flag": True}
|
||||
|
||||
arguments = tool_usage._validate_tool_input(tool_input)
|
||||
assert arguments == expected_arguments
|
||||
|
||||
|
||||
def test_validate_tool_input_python_dict():
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
tool_input = "{'key': 'value', 'number': 42, 'flag': True}"
|
||||
expected_arguments = {"key": "value", "number": 42, "flag": True}
|
||||
|
||||
arguments = tool_usage._validate_tool_input(tool_input)
|
||||
assert arguments == expected_arguments
|
||||
|
||||
|
||||
def test_validate_tool_input_json5_unquoted_keys():
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
tool_input = "{key: 'value', number: 42, flag: true}"
|
||||
expected_arguments = {"key": "value", "number": 42, "flag": True}
|
||||
|
||||
arguments = tool_usage._validate_tool_input(tool_input)
|
||||
assert arguments == expected_arguments
|
||||
|
||||
|
||||
def test_validate_tool_input_with_trailing_commas():
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
tool_input = '{"key": "value", "number": 42, "flag": true,}'
|
||||
expected_arguments = {"key": "value", "number": 42, "flag": True}
|
||||
|
||||
arguments = tool_usage._validate_tool_input(tool_input)
|
||||
assert arguments == expected_arguments
|
||||
|
||||
|
||||
def test_validate_tool_input_invalid_input():
|
||||
# Create mock agent with proper string values
|
||||
mock_agent = MagicMock()
|
||||
mock_agent.key = "test_agent_key" # Must be a string
|
||||
mock_agent.role = "test_agent_role" # Must be a string
|
||||
mock_agent._original_role = "test_agent_role" # Must be a string
|
||||
mock_agent.i18n = MagicMock()
|
||||
mock_agent.verbose = False
|
||||
|
||||
# Create mock action with proper string value
|
||||
mock_action = MagicMock()
|
||||
mock_action.tool = "test_tool" # Must be a string
|
||||
mock_action.tool_input = "test_input" # Must be a string
|
||||
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=mock_agent,
|
||||
action=mock_action,
|
||||
)
|
||||
|
||||
invalid_inputs = [
|
||||
"Just a string",
|
||||
"['list', 'of', 'values']",
|
||||
"12345",
|
||||
"",
|
||||
]
|
||||
|
||||
for invalid_input in invalid_inputs:
|
||||
with pytest.raises(Exception) as e_info:
|
||||
tool_usage._validate_tool_input(invalid_input)
|
||||
assert (
|
||||
"Tool input must be a valid dictionary in JSON or Python literal format"
|
||||
in str(e_info.value)
|
||||
)
|
||||
|
||||
# Test for None input separately
|
||||
arguments = tool_usage._validate_tool_input(None)
|
||||
assert arguments == {}
|
||||
|
||||
|
||||
def test_validate_tool_input_complex_structure():
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
tool_input = """
|
||||
{
|
||||
"user": {
|
||||
"name": "Alice",
|
||||
"age": 30
|
||||
},
|
||||
"items": [
|
||||
{"id": 1, "value": "Item1"},
|
||||
{"id": 2, "value": "Item2",}
|
||||
],
|
||||
"active": true,
|
||||
}
|
||||
"""
|
||||
expected_arguments = {
|
||||
"user": {"name": "Alice", "age": 30},
|
||||
"items": [
|
||||
{"id": 1, "value": "Item1"},
|
||||
{"id": 2, "value": "Item2"},
|
||||
],
|
||||
"active": True,
|
||||
}
|
||||
|
||||
arguments = tool_usage._validate_tool_input(tool_input)
|
||||
assert arguments == expected_arguments
|
||||
|
||||
|
||||
def test_validate_tool_input_code_content():
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
tool_input = '{"filename": "script.py", "content": "def hello():\\n print(\'Hello, world!\')"}'
|
||||
expected_arguments = {
|
||||
"filename": "script.py",
|
||||
"content": "def hello():\n print('Hello, world!')",
|
||||
}
|
||||
|
||||
arguments = tool_usage._validate_tool_input(tool_input)
|
||||
assert arguments == expected_arguments
|
||||
|
||||
|
||||
def test_validate_tool_input_with_escaped_quotes():
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
tool_input = '{"text": "He said, \\"Hello, world!\\""}'
|
||||
expected_arguments = {"text": 'He said, "Hello, world!"'}
|
||||
|
||||
arguments = tool_usage._validate_tool_input(tool_input)
|
||||
assert arguments == expected_arguments
|
||||
|
||||
|
||||
def test_validate_tool_input_large_json_content():
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
# Simulate a large JSON content
|
||||
tool_input = (
|
||||
'{"data": ' + json.dumps([{"id": i, "value": i * 2} for i in range(1000)]) + "}"
|
||||
)
|
||||
expected_arguments = {"data": [{"id": i, "value": i * 2} for i in range(1000)]}
|
||||
|
||||
arguments = tool_usage._validate_tool_input(tool_input)
|
||||
assert arguments == expected_arguments
|
||||
|
||||
|
||||
def test_tool_selection_error_event_direct():
|
||||
"""Test tool selection error event emission directly from ToolUsage class."""
|
||||
mock_agent = MagicMock()
|
||||
mock_agent.key = "test_key"
|
||||
mock_agent.role = "test_role"
|
||||
mock_agent.i18n = MagicMock()
|
||||
mock_agent.verbose = False
|
||||
|
||||
mock_task = MagicMock()
|
||||
mock_tools_handler = MagicMock()
|
||||
|
||||
class TestTool(BaseTool):
|
||||
name: str = "Test Tool"
|
||||
description: str = "A test tool"
|
||||
|
||||
def _run(self, input: dict) -> str:
|
||||
return "test result"
|
||||
|
||||
test_tool = TestTool()
|
||||
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=mock_tools_handler,
|
||||
tools=[test_tool],
|
||||
task=mock_task,
|
||||
function_calling_llm=None,
|
||||
agent=mock_agent,
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
received_events = []
|
||||
|
||||
@crewai_event_bus.on(ToolSelectionErrorEvent)
|
||||
def event_handler(source, event):
|
||||
received_events.append(event)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
tool_usage._select_tool("Non Existent Tool")
|
||||
assert len(received_events) == 1
|
||||
event = received_events[0]
|
||||
assert isinstance(event, ToolSelectionErrorEvent)
|
||||
assert event.agent_key == "test_key"
|
||||
assert event.agent_role == "test_role"
|
||||
assert event.tool_name == "Non Existent Tool"
|
||||
assert event.tool_args == {}
|
||||
assert "Tool Name: Test Tool" in event.tool_class
|
||||
assert "A test tool" in event.tool_class
|
||||
assert "don't exist" in event.error
|
||||
|
||||
received_events.clear()
|
||||
with pytest.raises(Exception):
|
||||
tool_usage._select_tool("")
|
||||
|
||||
assert len(received_events) == 1
|
||||
event = received_events[0]
|
||||
assert isinstance(event, ToolSelectionErrorEvent)
|
||||
assert event.agent_key == "test_key"
|
||||
assert event.agent_role == "test_role"
|
||||
assert event.tool_name == ""
|
||||
assert event.tool_args == {}
|
||||
assert "Test Tool" in event.tool_class
|
||||
assert "forgot the Action name" in event.error
|
||||
|
||||
|
||||
def test_tool_validate_input_error_event():
|
||||
"""Test tool validation input error event emission from ToolUsage class."""
|
||||
# Mock agent and required components
|
||||
mock_agent = MagicMock()
|
||||
mock_agent.key = "test_key"
|
||||
mock_agent.role = "test_role"
|
||||
mock_agent.verbose = False
|
||||
mock_agent._original_role = "test_role"
|
||||
|
||||
# Mock i18n with error message
|
||||
mock_i18n = MagicMock()
|
||||
mock_i18n.errors.return_value = (
|
||||
"Tool input must be a valid dictionary in JSON or Python literal format"
|
||||
)
|
||||
mock_agent.i18n = mock_i18n
|
||||
|
||||
# Mock task and tools handler
|
||||
mock_task = MagicMock()
|
||||
mock_tools_handler = MagicMock()
|
||||
|
||||
# Mock printer
|
||||
mock_printer = MagicMock()
|
||||
|
||||
# Create test tool
|
||||
class TestTool(BaseTool):
|
||||
name: str = "Test Tool"
|
||||
description: str = "A test tool"
|
||||
|
||||
def _run(self, input: dict) -> str:
|
||||
return "test result"
|
||||
|
||||
test_tool = TestTool()
|
||||
|
||||
# Create ToolUsage instance
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=mock_tools_handler,
|
||||
tools=[test_tool],
|
||||
task=mock_task,
|
||||
function_calling_llm=None,
|
||||
agent=mock_agent,
|
||||
action=MagicMock(tool="test_tool"),
|
||||
)
|
||||
tool_usage._printer = mock_printer
|
||||
|
||||
# Mock all parsing attempts to fail
|
||||
with (
|
||||
patch("json.loads", side_effect=json.JSONDecodeError("Test Error", "", 0)),
|
||||
patch("ast.literal_eval", side_effect=ValueError),
|
||||
patch("json5.loads", side_effect=json.JSONDecodeError("Test Error", "", 0)),
|
||||
patch("json_repair.repair_json", side_effect=Exception("Failed to repair")),
|
||||
):
|
||||
received_events = []
|
||||
|
||||
@crewai_event_bus.on(ToolValidateInputErrorEvent)
|
||||
def event_handler(source, event):
|
||||
received_events.append(event)
|
||||
|
||||
# Test invalid input
|
||||
invalid_input = "invalid json {[}"
|
||||
with pytest.raises(Exception):
|
||||
tool_usage._validate_tool_input(invalid_input)
|
||||
|
||||
# Verify event was emitted
|
||||
assert len(received_events) == 1, "Expected one event to be emitted"
|
||||
event = received_events[0]
|
||||
assert isinstance(event, ToolValidateInputErrorEvent)
|
||||
assert event.agent_key == "test_key"
|
||||
assert event.agent_role == "test_role"
|
||||
assert event.tool_name == "test_tool"
|
||||
assert "must be a valid dictionary" in event.error
|
||||
|
||||
|
||||
def test_tool_usage_finished_event_with_result():
|
||||
"""Test that ToolUsageFinishedEvent is emitted with correct result attributes."""
|
||||
# Create mock agent with proper string values
|
||||
mock_agent = MagicMock()
|
||||
mock_agent.key = "test_agent_key"
|
||||
mock_agent.role = "test_agent_role"
|
||||
mock_agent._original_role = "test_agent_role"
|
||||
mock_agent.i18n = MagicMock()
|
||||
mock_agent.verbose = False
|
||||
|
||||
# Create mock task
|
||||
mock_task = MagicMock()
|
||||
mock_task.delegations = 0
|
||||
mock_task.name = "Test Task"
|
||||
mock_task.description = "A test task for tool usage"
|
||||
mock_task.id = "test-task-id"
|
||||
|
||||
# Create mock tool
|
||||
class TestTool(BaseTool):
|
||||
name: str = "Test Tool"
|
||||
description: str = "A test tool"
|
||||
|
||||
def _run(self, input: dict) -> str:
|
||||
return "test result"
|
||||
|
||||
test_tool = TestTool()
|
||||
|
||||
# Create mock tool calling
|
||||
mock_tool_calling = MagicMock()
|
||||
mock_tool_calling.arguments = {"arg1": "value1"}
|
||||
|
||||
# Create ToolUsage instance
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[test_tool],
|
||||
task=mock_task,
|
||||
function_calling_llm=None,
|
||||
agent=mock_agent,
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
# Track received events
|
||||
received_events = []
|
||||
|
||||
@crewai_event_bus.on(ToolUsageFinishedEvent)
|
||||
def event_handler(source, event):
|
||||
received_events.append(event)
|
||||
|
||||
# Call on_tool_use_finished with test data
|
||||
started_at = time.time()
|
||||
result = "test output result"
|
||||
tool_usage.on_tool_use_finished(
|
||||
tool=test_tool,
|
||||
tool_calling=mock_tool_calling,
|
||||
from_cache=False,
|
||||
started_at=started_at,
|
||||
result=result,
|
||||
)
|
||||
|
||||
# Verify event was emitted
|
||||
assert len(received_events) == 1, "Expected one event to be emitted"
|
||||
event = received_events[0]
|
||||
assert isinstance(event, ToolUsageFinishedEvent)
|
||||
|
||||
# Verify event attributes
|
||||
assert event.agent_key == "test_agent_key"
|
||||
assert event.agent_role == "test_agent_role"
|
||||
assert event.tool_name == "Test Tool"
|
||||
assert event.tool_args == {"arg1": "value1"}
|
||||
assert event.tool_class == "TestTool"
|
||||
assert event.run_attempts == 1 # Default value from ToolUsage
|
||||
assert event.delegations == 0
|
||||
assert event.from_cache is False
|
||||
assert event.output == "test output result"
|
||||
assert isinstance(event.started_at, datetime.datetime)
|
||||
assert isinstance(event.finished_at, datetime.datetime)
|
||||
assert event.type == "tool_usage_finished"
|
||||
|
||||
|
||||
def test_tool_usage_finished_event_with_cached_result():
|
||||
"""Test that ToolUsageFinishedEvent is emitted with correct result attributes when using cached result."""
|
||||
# Create mock agent with proper string values
|
||||
mock_agent = MagicMock()
|
||||
mock_agent.key = "test_agent_key"
|
||||
mock_agent.role = "test_agent_role"
|
||||
mock_agent._original_role = "test_agent_role"
|
||||
mock_agent.i18n = MagicMock()
|
||||
mock_agent.verbose = False
|
||||
|
||||
# Create mock task
|
||||
mock_task = MagicMock()
|
||||
mock_task.delegations = 0
|
||||
mock_task.name = "Test Task"
|
||||
mock_task.description = "A test task for tool usage"
|
||||
mock_task.id = "test-task-id"
|
||||
|
||||
# Create mock tool
|
||||
class TestTool(BaseTool):
|
||||
name: str = "Test Tool"
|
||||
description: str = "A test tool"
|
||||
|
||||
def _run(self, input: dict) -> str:
|
||||
return "test result"
|
||||
|
||||
test_tool = TestTool()
|
||||
|
||||
# Create mock tool calling
|
||||
mock_tool_calling = MagicMock()
|
||||
mock_tool_calling.arguments = {"arg1": "value1"}
|
||||
|
||||
# Create ToolUsage instance
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[test_tool],
|
||||
task=mock_task,
|
||||
function_calling_llm=None,
|
||||
agent=mock_agent,
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
# Track received events
|
||||
received_events = []
|
||||
|
||||
@crewai_event_bus.on(ToolUsageFinishedEvent)
|
||||
def event_handler(source, event):
|
||||
received_events.append(event)
|
||||
|
||||
# Call on_tool_use_finished with test data and from_cache=True
|
||||
started_at = time.time()
|
||||
result = "cached test output result"
|
||||
tool_usage.on_tool_use_finished(
|
||||
tool=test_tool,
|
||||
tool_calling=mock_tool_calling,
|
||||
from_cache=True,
|
||||
started_at=started_at,
|
||||
result=result,
|
||||
)
|
||||
|
||||
# Verify event was emitted
|
||||
assert len(received_events) == 1, "Expected one event to be emitted"
|
||||
event = received_events[0]
|
||||
assert isinstance(event, ToolUsageFinishedEvent)
|
||||
|
||||
# Verify event attributes
|
||||
assert event.agent_key == "test_agent_key"
|
||||
assert event.agent_role == "test_agent_role"
|
||||
assert event.tool_name == "Test Tool"
|
||||
assert event.tool_args == {"arg1": "value1"}
|
||||
assert event.tool_class == "TestTool"
|
||||
assert event.run_attempts == 1 # Default value from ToolUsage
|
||||
assert event.delegations == 0
|
||||
assert event.from_cache is True
|
||||
assert event.output == "cached test output result"
|
||||
assert isinstance(event.started_at, datetime.datetime)
|
||||
assert isinstance(event.finished_at, datetime.datetime)
|
||||
assert event.type == "tool_usage_finished"
|
||||
@@ -1,151 +0,0 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from crewai.tools import BaseTool, tool
|
||||
from crewai.tools.tool_usage import ToolUsage
|
||||
|
||||
|
||||
def test_tool_usage_limit():
|
||||
"""Test that tools respect usage limits."""
|
||||
class LimitedTool(BaseTool):
|
||||
name: str = "Limited Tool"
|
||||
description: str = "A tool with usage limits for testing"
|
||||
max_usage_count: int = 2
|
||||
|
||||
def _run(self, input_text: str) -> str:
|
||||
return f"Processed {input_text}"
|
||||
|
||||
tool = LimitedTool()
|
||||
|
||||
result1 = tool.run(input_text="test1")
|
||||
assert result1 == "Processed test1"
|
||||
assert tool.current_usage_count == 1
|
||||
|
||||
result2 = tool.run(input_text="test2")
|
||||
assert result2 == "Processed test2"
|
||||
assert tool.current_usage_count == 2
|
||||
|
||||
|
||||
def test_unlimited_tool_usage():
|
||||
"""Test that tools without usage limits work normally."""
|
||||
class UnlimitedTool(BaseTool):
|
||||
name: str = "Unlimited Tool"
|
||||
description: str = "A tool without usage limits"
|
||||
|
||||
def _run(self, input_text: str) -> str:
|
||||
return f"Processed {input_text}"
|
||||
|
||||
tool = UnlimitedTool()
|
||||
|
||||
for i in range(5):
|
||||
result = tool.run(input_text=f"test{i}")
|
||||
assert result == f"Processed test{i}"
|
||||
assert tool.current_usage_count == i + 1
|
||||
|
||||
|
||||
def test_tool_decorator_with_usage_limit():
|
||||
"""Test usage limit with @tool decorator."""
|
||||
@tool("Test Tool", max_usage_count=3)
|
||||
def test_tool(input_text: str) -> str:
|
||||
"""A test tool."""
|
||||
return f"Result: {input_text}"
|
||||
|
||||
assert test_tool.max_usage_count == 3
|
||||
assert test_tool.current_usage_count == 0
|
||||
|
||||
result = test_tool.run(input_text="test")
|
||||
assert result == "Result: test"
|
||||
assert test_tool.current_usage_count == 1
|
||||
|
||||
|
||||
def test_default_unlimited_usage():
|
||||
"""Test that tools have unlimited usage by default."""
|
||||
@tool("Default Tool")
|
||||
def default_tool(input_text: str) -> str:
|
||||
"""A default tool."""
|
||||
return f"Result: {input_text}"
|
||||
|
||||
assert default_tool.max_usage_count is None
|
||||
assert default_tool.current_usage_count == 0
|
||||
|
||||
|
||||
def test_invalid_usage_limit():
|
||||
"""Test that negative usage limits raise ValueError."""
|
||||
class ValidTool(BaseTool):
|
||||
name: str = "Valid Tool"
|
||||
description: str = "A tool with valid usage limit"
|
||||
|
||||
def _run(self, input_text: str) -> str:
|
||||
return f"Processed {input_text}"
|
||||
|
||||
with pytest.raises(ValueError, match="max_usage_count must be a positive integer"):
|
||||
ValidTool(max_usage_count=-1)
|
||||
|
||||
|
||||
def test_reset_usage_count():
|
||||
"""Test that reset_usage_count method works correctly."""
|
||||
class LimitedTool(BaseTool):
|
||||
name: str = "Limited Tool"
|
||||
description: str = "A tool with usage limits for testing"
|
||||
max_usage_count: int = 3
|
||||
|
||||
def _run(self, input_text: str) -> str:
|
||||
return f"Processed {input_text}"
|
||||
|
||||
tool = LimitedTool()
|
||||
|
||||
tool.run(input_text="test1")
|
||||
tool.run(input_text="test2")
|
||||
assert tool.current_usage_count == 2
|
||||
|
||||
tool.reset_usage_count()
|
||||
assert tool.current_usage_count == 0
|
||||
|
||||
result = tool.run(input_text="test3")
|
||||
assert result == "Processed test3"
|
||||
assert tool.current_usage_count == 1
|
||||
|
||||
|
||||
def test_tool_usage_with_toolusage_class():
|
||||
"""Test that ToolUsage class correctly enforces usage limits."""
|
||||
class LimitedTool(BaseTool):
|
||||
name: str = "Limited Tool"
|
||||
description: str = "A tool with usage limits for testing"
|
||||
max_usage_count: int = 2
|
||||
|
||||
def _run(self, input_text: str) -> str:
|
||||
return f"Processed {input_text}"
|
||||
|
||||
tool = LimitedTool()
|
||||
|
||||
mock_agent = MagicMock()
|
||||
mock_task = MagicMock()
|
||||
mock_tools_handler = MagicMock()
|
||||
|
||||
tool_usage = ToolUsage(
|
||||
tools=[tool],
|
||||
agent=mock_agent,
|
||||
task=mock_task,
|
||||
tools_handler=mock_tools_handler,
|
||||
function_calling_llm=MagicMock(),
|
||||
)
|
||||
|
||||
tool_usage._check_tool_repeated_usage = MagicMock(return_value=False)
|
||||
tool_usage._format_result = lambda result: result
|
||||
|
||||
mock_calling = MagicMock()
|
||||
mock_calling.tool_name = "Limited Tool"
|
||||
mock_calling.arguments = {"input_text": "test"}
|
||||
|
||||
result1 = tool_usage._check_usage_limit(tool, "Limited Tool")
|
||||
assert result1 is None
|
||||
|
||||
tool.current_usage_count += 1
|
||||
|
||||
result2 = tool_usage._check_usage_limit(tool, "Limited Tool")
|
||||
assert result2 is None
|
||||
|
||||
tool.current_usage_count += 1
|
||||
|
||||
result3 = tool_usage._check_usage_limit(tool, "Limited Tool")
|
||||
assert "has reached its usage limit of 2 times" in result3
|
||||
Reference in New Issue
Block a user