Files
crewAI/tests/crew_test.py
Brandon Hancock (bhancock_ai) 4bcc3b532d Trying out timeouts (#1840)
* Make tests green again

* Add Git validations for publishing tools  (#1381)

This commit prevents tools from being published if the underlying Git
repository is unsynced with origin.

* fix: JSON encoding date objects (#1374)

* Update README  (#1376)

* Change all instaces of crewAI to CrewAI and fix installation step

* Update the  example to use YAML format

* Update  to come after setup and edits

* Remove double tool instance

* docs: correct miswritten command name (#1365)

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Add `--force` option to `crewai tool publish` (#1383)

This commit adds an option to bypass Git remote validations when
publishing tools.

* add plotting to flows documentation (#1394)

* Brandon/cre 288 add telemetry to flows (#1391)

* Telemetry for flows

* store node names

* Brandon/cre 291 flow improvements (#1390)

* Implement joao feedback

* update colors for crew nodes

* clean up

* more linting clean up

* round legend corners

---------

Co-authored-by: João Moura <joaomdmoura@gmail.com>

* quick fixes (#1385)

* quick fixes

* add generic name

---------

Co-authored-by: João Moura <joaomdmoura@gmail.com>

* reduce import time by 6x (#1396)

* reduce import by 6x

* fix linting

* Added version details (#1402)

Co-authored-by: João Moura <joaomdmoura@gmail.com>

* Update twitter logo to x-twiiter (#1403)

* fix task cloning error (#1416)

* Migrate docs from MkDocs to Mintlify (#1423)

* add new mintlify docs

* add favicon.svg

* minor edits

* add github stats

* Fix/logger - fix #1412 (#1413)

* improved logger

* log file looks better

* better lines written to log file

---------

Co-authored-by: João Moura <joaomdmoura@gmail.com>

* fixing tests

* preparing new version

* updating init

* Preparing new version

* Trying to fix linting and other warnings (#1417)

* Trying to fix linting

* fixing more type issues

* clean up ci

* more ci fixes

---------

Co-authored-by: Eduardo Chiarotti <dudumelgaco@hotmail.com>

* Feat/poetry to uv migration (#1406)

* feat: Start migrating to UV

* feat: add uv to flows

* feat: update docs on Poetry -> uv

* feat: update docs and uv.locl

* feat: update tests and github CI

* feat: run ruff format

* feat: update typechecking

* feat: fix type checking

* feat: update python version

* feat: type checking gic

* feat: adapt uv command to run the tool repo

* Adapt tool build command to uv

* feat: update logic to let only projects with crew to be deployed

* feat: add uv to tools

* fix; tests

* fix: remove breakpoint

* fix :test

* feat: add crewai update to migrate from poetry to uv

* fix: tests

* feat: add validation for ˆ character on pyproject

* feat: add run_crew to pyproject if doesnt exist

* feat: add validation for poetry migration

* fix: warning

---------

Co-authored-by: Vinicius Brasil <vini@hey.com>

* fix: training issue (#1433)

* fix: training issue

* fix: output from crew

* fix: message

* Use a slice for the manager request. Make the task use the agent i18n settings (#1446)

* Fix Cache Typo in Documentation (#1441)

* Correct the role for the message being added to the messages list (#1438)

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* fix typo in template file (#1432)

* Adapt Tools CLI to uv (#1455)

* Adapt Tools CLI to UV

* Fix failing test

* use the same i18n as the agent for tool usage (#1440)

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Upgrade docs to mirror change from `Poetry` to `UV` (#1451)

* Update docs to use  instead of

* Add Flows YouTube tutorial & link images

* feat: ADd warning from poetry -> uv (#1458)

* feat/updated CLI to allow for model selection & submitting API keys (#1430)

* updated CLI to allow for submitting API keys

* updated click prompt to remove default number

* removed all unnecessary comments

* feat: implement crew creation CLI command

- refactor code to multiple functions
- Added ability for users to select provider and model when uing crewai create command and ave API key to .env

* refactered select_choice function for early return

* refactored  select_provider to have an ealry return

* cleanup of comments

* refactor/Move functions into utils file, added new provider file and migrated fucntions thre, new constants file + general function refactor

* small comment cleanup

* fix unnecessary deps

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>
Co-authored-by: Brandon Hancock <brandon@brandonhancock.io>

* Fix incorrect parameter name in Vision tool docs page (#1461)

Co-authored-by: João Moura <joaomdmoura@gmail.com>

* Feat/memory base (#1444)

* byom - short/entity memory

* better

* rm uneeded

* fix text

* use context

* rm dep and sync

* type check fix

* fixed test using new cassete

* fixing types

* fixed types

* fix types

* fixed types

* fixing types

* fix type

* cassette update

* just mock the return of short term mem

* remove print

* try catch block

* added docs

* dding error handling here

* preparing new version

* fixing annotations

* fix tasks and agents ordering

* Avoiding exceptions

* feat: add poetry.lock to uv migration (#1468)

* fix tool calling issue (#1467)

* fix tool calling issue

* Update tool type check

* Drop print

* cutting new version

* new verison

* Adapt `crewai tool install <tool>` to uv (#1481)

This commit updates the tool install comamnd to uv's new custom index
feature.

Related: https://github.com/astral-sh/uv/pull/7746/

* fix(docs): typo (#1470)

* drop unneccesary tests (#1484)

* drop uneccesary tests

* fix linting

* simplify flow (#1482)

* simplify flow

* propogate changes

* Update docs and scripts

* Template fix

* make flow kickoff sync

* Clean up docs

* Add Cerebras LLM example configuration to LLM docs (#1488)

* ensure original embedding config works (#1476)

* ensure original embedding config works

* some fixes

* raise error on unsupported provider

* WIP: brandons notes

* fixes

* rm prints

* fixed docs

* fixed run types

* updates to add more docs and correct imports with huggingface embedding server enabled

---------

Co-authored-by: Brandon Hancock <brandon@brandonhancock.io>

* use copy to split testing and training on crews (#1491)

* use copy to split testing and training on crews

* make tests handle new copy functionality on train and test

* fix last test

* fix test

* preparing new verison

* fix/fixed missing API prompt + CLI docs update (#1464)

* updated CLI to allow for submitting API keys

* updated click prompt to remove default number

* removed all unnecessary comments

* feat: implement crew creation CLI command

- refactor code to multiple functions
- Added ability for users to select provider and model when uing crewai create command and ave API key to .env

* refactered select_choice function for early return

* refactored  select_provider to have an ealry return

* cleanup of comments

* refactor/Move functions into utils file, added new provider file and migrated fucntions thre, new constants file + general function refactor

* small comment cleanup

* fix unnecessary deps

* Added docs for new CLI provider + fixed missing API prompt

* Minor doc updates

* allow user to bypass api key entry + incorect number selected logic + ruff formatting

* ruff updates

* Fix spelling mistake

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>
Co-authored-by: Brandon Hancock <brandon@brandonhancock.io>

* chore(readme-fix): fixing step for 'running tests' in the contribution section (#1490)

Co-authored-by: Eduardo Chiarotti <dudumelgaco@hotmail.com>

* support unsafe code execution. add in docker install and running checks. (#1496)

* support unsafe code execution. add in docker install and running checks.

* Update return type

* Fix memory imports for embedding functions (#1497)

* updating crewai version

* new version

* new version

* update plot command (#1504)

* feat: add tomli so we can support 3.10 (#1506)

* feat: add tomli so we can support 3.10

* feat: add validation for poetry data

* Forward install command options to `uv sync` (#1510)

Allow passing additional options from `crewai install` directly to
`uv sync`. This enables commands like `crewai install --locked` to work
as expected by forwarding all flags and options to the underlying uv
command.

* improve tool text description and args (#1512)

* improve tool text descriptoin and args

* fix lint

* Drop print

* add back in docstring

* Improve tooling docs

* Update flow docs to talk about self evaluation example

* Update flow docs to talk about self evaluation example

* Update flows.mdx - Fix link

* Update flows cli to allow you to easily add additional crews to a flow (#1525)

* Update flows cli to allow you to easily add additional crews to a flow

* fix failing test

* adding more error logs to test thats failing

* try again

* Bugfix/flows with multiple starts plus ands breaking (#1531)

* bugfix/flows-with-multiple-starts-plus-ands-breaking

* fix user found issue

* remove prints

* prepare new version

* Added security.md file (#1533)

* Disable telemetry explicitly (#1536)

* Disable telemetry explicitly

* fix linting

* revert parts to og

* Enhance log storage to support more data types (#1530)

* Add llm providers accordion group (#1534)

* add llm providers accordion group

* fix numbering

* Replace .netrc with uv environment variables (#1541)

This commit replaces .netrc with uv environment variables for installing
tools from private repositories. To store credentials, I created a new
and reusable settings file for the CLI in
`$HOME/.config/crewai/settings.json`.

The issue with .netrc files is that they are applied system-wide and are
scoped by hostname, meaning we can't differentiate tool repositories
requests from regular requests to CrewAI's API.

* refactor: Move BaseTool to main package and centralize tool description generation (#1514)

* move base_tool to main package and consolidate tool desscription generation

* update import path

* update tests

* update doc

* add base_tool test

* migrate agent delegation tools to use BaseTool

* update tests

* update import path for tool

* fix lint

* update param signature

* add from_langchain to BaseTool for backwards support of langchain tools

* fix the case where StructuredTool doesn't have func

---------

Co-authored-by: c0dez <li@vitablehealth.com>
Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Update docs  (#1550)

* add llm providers accordion group

* fix numbering

* Fix directory tree & add llms to accordion

* Feat/ibm memory (#1549)

* Everything looks like its working. Waiting for lorenze review.

* Update docs as well.

* clean up for PR

* add inputs to flows (#1553)

* add inputs to flows

* fix flows lint

* Increase providers fetching timeout

* Raise an error if an LLM doesnt return a response (#1548)

* docs update (#1558)

* add llm providers accordion group

* fix numbering

* Fix directory tree & add llms to accordion

* update crewai enterprise link in docs

* Feat/watson in cli (#1535)

* getting cli and .env to work together for different models

* support new models

* clean up prints

* Add support for cerebras

* Fix watson keys

* Fix flows to support cycles and added in test (#1556)

* fix missing config (#1557)

* making sure we don't check for agents that were not used in the crew

* preparing new version

* updating LLM docs

* preparing new version

* curring new version

* preparing new version

* preparing new version

* add missing init

* fix LiteLLM callback replacement

* fix test_agent_usage_metrics_are_captured_for_hierarchical_process

* removing prints

* fix: Step callback issue (#1595)

* fix: Step callback issue

* fix: Add empty thought since its required

* Cached prompt tokens on usage metrics

* do not include cached on total

* Fix crew_train_success test

* feat: Reduce level for Bandit and fix code to adapt (#1604)

* Add support for retrieving user preferences and memories using Mem0 (#1209)

* Integrate Mem0

* Update src/crewai/memory/contextual/contextual_memory.py

Co-authored-by: Deshraj Yadav <deshraj@gatech.edu>

* pending commit for _fetch_user_memories

* update poetry.lock

* fixes mypy issues

* fix mypy checks

* New fixes for user_id

* remove memory_provider

* handle memory_provider

* checks for memory_config

* add mem0 to dependency

* Update pyproject.toml

Co-authored-by: Deshraj Yadav <deshraj@gatech.edu>

* update docs

* update doc

* bump mem0 version

* fix api error msg and mypy issue

* mypy fix

* resolve comments

* fix memory usage without mem0

* mem0 version bump

* lazy import mem0

---------

Co-authored-by: Deshraj Yadav <deshraj@gatech.edu>
Co-authored-by: João Moura <joaomdmoura@gmail.com>
Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* upgrade chroma and adjust embedder function generator (#1607)

* upgrade chroma and adjust embedder function generator

* >= version

* linted

* preparing enw version

* adding before and after crew

* Update CLI Watson supported models + docs (#1628)

* docs: add gh_token documentation to GithubSearchTool

* Move kickoff callbacks to crew's domain

* Cassettes

* Make mypy happy

* Knowledge (#1567)

* initial knowledge

* WIP

* Adding core knowledge sources

* Improve types and better support for file paths

* added additional sources

* fix linting

* update yaml to include optional deps

* adding in lorenze feedback

* ensure embeddings are persisted

* improvements all around Knowledge class

* return this

* properly reset memory

* properly reset memory+knowledge

* consolodation and improvements

* linted

* cleanup rm unused embedder

* fix test

* fix duplicate

* generating cassettes for knowledge test

* updated default embedder

* None embedder to use default on pipeline cloning

* improvements

* fixed text_file_knowledge

* mypysrc fixes

* type check fixes

* added extra cassette

* just mocks

* linted

* mock knowledge query to not spin up db

* linted

* verbose run

* put a flag

* fix

* adding docs

* better docs

* improvements from review

* more docs

* linted

* rm print

* more fixes

* clearer docs

* added docstrings and type hints for cli

---------

Co-authored-by: João Moura <joaomdmoura@gmail.com>
Co-authored-by: Lorenze Jay <lorenzejaytech@gmail.com>

* Updated README.md, fix typo(s) (#1637)

* Update Perplexity example in documentation (#1623)

* Fix threading

* preparing new version

* Log in to Tool Repository on `crewai login` (#1650)

This commit adds an extra step to `crewai login` to ensure users also
log in to Tool Repository, that is, exchanging their Auth0 tokens for a
Tool Repository username and password to be used by UV downloads and API
tool uploads.

* add knowledge to mint.json

* Improve typed task outputs (#1651)

* V1 working

* clean up imports and prints

* more clean up and add tests

* fixing tests

* fix test

* fix linting

* Fix tests

* Fix linting

* add doc string as requested by eduardo

* Update Github actions (#1639)

* actions/checkout@v4

* actions/cache@v4

* actions/setup-python@v5

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* update (#1638)

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* fix spelling issue found by @Jacques-Murray (#1660)

* Update readme for running mypy (#1614)

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Feat/remove langchain (#1654)

* feat: add initial changes from langchain

* feat: remove kwargs of being processed

* feat: remove langchain, update uv.lock and fix type_hint

* feat: change docs

* feat: remove forced requirements for parameter

* feat add tests for new structure tool

* feat: fix tests and adapt code for args

* Feat/remove langchain (#1668)

* feat: add initial changes from langchain

* feat: remove kwargs of being processed

* feat: remove langchain, update uv.lock and fix type_hint

* feat: change docs

* feat: remove forced requirements for parameter

* feat add tests for new structure tool

* feat: fix tests and adapt code for args

* fix tool calling for langchain tools

* doc strings

---------

Co-authored-by: Eduardo Chiarotti <dudumelgaco@hotmail.com>

* added knowledge to agent level (#1655)

* added knowledge to agent level

* linted

* added doc

* added from suggestions

* added test

* fixes from discussion

* fix docs

* fix test

* rm cassette for knowledge_sources test as its a mock and update agent doc string

* fix test

* rm unused

* linted

* Update Agents docs to include two approaches for creating an agent: with and without YAML configuration

* Documentation Improvements: LLM Configuration and Usage (#1684)

* docs: improve tasks documentation clarity and structure

- Add Task Execution Flow section
- Add variable interpolation explanation
- Add Task Dependencies section with examples
- Improve overall document structure and readability
- Update code examples with proper syntax highlighting

* docs: update agent documentation with improved examples and formatting

- Replace DuckDuckGoSearchRun with SerperDevTool
- Update code block formatting to be consistent
- Improve template examples with actual syntax
- Update LLM examples to use current models
- Clean up formatting and remove redundant comments

* docs: enhance LLM documentation with Cerebras provider and formatting improvements

* docs: simplify LLMs documentation title

* docs: improve installation guide clarity and structure

- Add clear Python version requirements with check command
- Simplify installation options to recommended method
- Improve upgrade section clarity for existing users
- Add better visual structure with Notes and Tips
- Update description and formatting

* docs: improve introduction page organization and clarity

- Update organizational analogy in Note section
- Improve table formatting and alignment
- Remove emojis from component table for cleaner look
- Add 'helps you' to make the note more action-oriented

* docs: add enterprise and community cards

- Add Enterprise deployment card in quickstart
- Add community card focused on open source discussions
- Remove deployment reference from community description
- Clean up introduction page cards
- Remove link from Enterprise description text

* Fixes issues with result as answer not properly exiting LLM loop (#1689)

* v1 of fix implemented. Need to confirm with tokens.

* remove print statements

* preparing new version

* fix missing code in flows docs (#1690)

* docs: improve tasks documentation clarity and structure

- Add Task Execution Flow section
- Add variable interpolation explanation
- Add Task Dependencies section with examples
- Improve overall document structure and readability
- Update code examples with proper syntax highlighting

* docs: update agent documentation with improved examples and formatting

- Replace DuckDuckGoSearchRun with SerperDevTool
- Update code block formatting to be consistent
- Improve template examples with actual syntax
- Update LLM examples to use current models
- Clean up formatting and remove redundant comments

* docs: enhance LLM documentation with Cerebras provider and formatting improvements

* docs: simplify LLMs documentation title

* docs: improve installation guide clarity and structure

- Add clear Python version requirements with check command
- Simplify installation options to recommended method
- Improve upgrade section clarity for existing users
- Add better visual structure with Notes and Tips
- Update description and formatting

* docs: improve introduction page organization and clarity

- Update organizational analogy in Note section
- Improve table formatting and alignment
- Remove emojis from component table for cleaner look
- Add 'helps you' to make the note more action-oriented

* docs: add enterprise and community cards

- Add Enterprise deployment card in quickstart
- Add community card focused on open source discussions
- Remove deployment reference from community description
- Clean up introduction page cards
- Remove link from Enterprise description text

* docs: add code snippet to Getting Started section in flows.mdx

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Update reset memories command based on the SDK (#1688)

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Update using langchain tools docs (#1664)

* Update example of how to use LangChain tools with correct syntax

* Use .env

* Add  Code back

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* [FEATURE] Support for custom path in RAGStorage (#1659)

* added path to RAGStorage

* added path to short term and entity memory

* add path for long_term_storage for completeness

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* [Doc]: Add documenation for openlit observability (#1612)

* Create openlit-observability.mdx

* Update doc with images and steps

* Update mkdocs.yml and add OpenLIT guide link

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Fix indentation in llm-connections.mdx code block (#1573)

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Knowledge project directory standard (#1691)

* Knowledge project directory standard

* fixed types

* comment fix

* made base file knowledge source an abstract class

* cleaner validator on model_post_init

* fix type checker

* cleaner refactor

* better template

* Update README.md (#1694)

Corrected the statement which says users can not disable telemetry, but now users can disable by setting the environment variable OTEL_SDK_DISABLED to true.

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Talk about getting structured consistent outputs with tasks.

* remove all references to pipeline and pipeline router (#1661)

* remove all references to pipeline and router

* fix linting

* drop poetry.lock

* docs: add nvidia as provider (#1632)

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* add knowledge demo + improve knowledge docs (#1706)

* Brandon/cre 509 hitl multiple rounds of followup (#1702)

* v1 of HITL working

* Drop print statements

* HITL code more robust. Still needs to be refactored.

* refactor and more clear messages

* Fix type issue

* fix tests

* Fix test again

* Drop extra print

* New docs about yaml crew with decorators. Simplify template crew with… (#1701)

* New docs about yaml crew with decorators. Simplify template crew with links

* Fix spelling issues.

* updating tools

* curting new verson

* Incorporate Stale PRs that have feedback (#1693)

* incorporate #1683

* add in --version flag to cli. closes #1679.

* Fix env issue

* Add in suggestions from @caike to make sure ragstorage doesnt exceed os file limit. Also, included additional checks to support windows.

* remove poetry.lock as pointed out by @sanders41 in #1574.

* Incorporate feedback from crewai reviewer

* Incorporate @lorenzejay feedback

* drop metadata requirement (#1712)

* drop metadata requirement

* fix linting

* Update docs for new knowledge

* more linting

* more linting

* make save_documents private

* update docs to the new way we use knowledge and include clearing memory

* add support for langfuse with litellm (#1721)

* docs: Add quotes to agentops installing command (#1729)

* docs: Add quotes to agentops installing command

* feat: Add ContextualMemory to __init__

* feat: remove import due to circular improt

* feat: update tasks config main template typos

* Fixed output_file not respecting system path (#1726)

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* fix:typo error (#1732)

* Update crew_agent_executor.py

typo error

* Update en.json

typo error

* Fix Knowledge docs Spaceflight News API dead link

* call storage.search in user context search instead of memory.search (#1692)

Co-authored-by: Eduardo Chiarotti <dudumelgaco@hotmail.com>

* Add doc structured tool (#1713)

* Add doc structured tool

* Fix example

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* _execute_tool_and_check_finality 结果给回调参数,这样就可以提前拿到结果信息,去做数据解析判断做预判 (#1716)

Co-authored-by: xiaohan <fuck@qq.com>
Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* format bullet points (#1734)

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Add missing @functools.wraps when wrapping functions and preserve wrapped class name in @CrewBase. (#1560)

* Update annotations.py

* Update utils.py

* Update crew_base.py

* Update utils.py

* Update crew_base.py

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Fix disk I/O error when resetting short-term memory. (#1724)

* Fix disk I/O error when resetting short-term memory.

Reset chromadb client and nullifies references before
removing directory.

* Nit for clarity

* did the same for knowledge_storage

* cleanup

* cleanup order

* Cleanup after the rm of the directories

---------

Co-authored-by: Lorenze Jay <lorenzejaytech@gmail.com>
Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com>

* restrict python version compatibility (#1731)

* drop 3.13

* revert

* Drop test cassette that was causing error

* trying to fix failing test

* adding thiago changes

* resolve final tests

* Drop skip

* Bugfix/restrict python version compatibility (#1736)

* drop 3.13

* revert

* Drop test cassette that was causing error

* trying to fix failing test

* adding thiago changes

* resolve final tests

* Drop skip

* drop pipeline

* Update pyproject.toml and uv.lock to drop crewai-tools as a default requirement (#1711)

* copy googles changes. Fix tests. Improve LLM file (#1737)

* copy googles changes. Fix tests. Improve LLM file

* Fix type issue

* fix:typo error (#1738)

* Update base_agent_tools.py

typo error

* Update main.py

typo error

* Update base_file_knowledge_source.py

typo error

* Update test_main.py

typo error

* Update en.json

* Update prompts.json

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Remove manager_callbacks reference (#1741)

* include event emitter in flows (#1740)

* include event emitter in flows

* Clean up

* Fix linter

* sort imports with isort rules by ruff linter (#1730)

* sort imports

* update

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>
Co-authored-by: Eduardo Chiarotti <dudumelgaco@hotmail.com>

* Added is_auto_end flag in agentops.end session in crew.py (#1320)

When using agentops, we have the option to pass the `skip_auto_end_session` parameter, which is supposed to not end the session if the `end_session` function is called by Crew.

Now the way it works is, the `agentops.end_session` accepts `is_auto_end` flag and crewai should have passed it as `True` (its `False` by default). 

I have changed the code to pass is_auto_end=True

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* NVIDIA Provider : UI changes (#1746)

* docs: add nvidia as provider

* nvidia ui docs changes

* add note for updated list

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Fix small typo in sample tool (#1747)

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Feature/add workflow permissions (#1749)

* fix: Call ChromaDB reset before removing storage directory to fix disk I/O errors

* feat: add workflow permissions to stale.yml

* revert rag_storage.py changes

* revert rag_storage.py changes

---------

Co-authored-by: Matt B <mattb@Matts-MacBook-Pro.local>
Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* remove pkg_resources which was causing issues (#1751)

* apply agent ops changes and resolve merge conflicts (#1748)

* apply agent ops changes and resolve merge conflicts

* Trying to fix tests

* add back in vcr

* update tools

* remove pkg_resources which was causing issues

* Fix tests

* experimenting to see if unique content is an issue with knowledge

* experimenting to see if unique content is an issue with knowledge

* update chromadb which seems to have issues with upsert

* generate new yaml for failing test

* Investigating upsert

* Drop patch

* Update casettes

* Fix duplicate document issue

* more fixes

* add back in vcr

* new cassette for test

---------

Co-authored-by: Lorenze Jay <lorenzejaytech@gmail.com>

* drop print (#1755)

* Fix: CrewJSONEncoder now accepts enums (#1752)

* bugfix: CrewJSONEncoder now accepts enums

* sort imports

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Fix bool and null handling (#1771)

* include 12 but not 13

* change to <13 instead of <=12

* Gemini 2.0 (#1773)

* Update llms.mdx (Gemini 2.0)

- Add Gemini 2.0 flash to Gemini table.
- Add link to 2 hosting paths for Gemini in Tip.
- Change to lower case model slugs vs names, user convenience.
- Add https://artificialanalysis.ai/ as alternate leaderboard.
- Move Gemma to "other" tab.

* Update llm.py (gemini 2.0)

Add setting for Gemini 2.0 context window to llm.py

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* Remove relative import in flow `main.py` template (#1782)

* Add `tool.crewai.type` pyproject attribute in templates (#1789)

* Correcting a small grammatical issue that was bugging me: from _satisfy the expect criteria_ to _satisfies the expected criteria_ (#1783)

Signed-off-by: PJ Hagerty <pjhagerty@gmail.com>
Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>

* feat: Add task guardrails feature (#1742)

* feat: Add task guardrails feature

Add support for custom code guardrails in tasks that validate outputs
before proceeding to the next task. Features include:

- Optional task-level guardrail function
- Pre-next-task execution timing
- Tuple return format (success, data)
- Automatic result/error routing
- Configurable retry mechanism
- Comprehensive documentation and tests

Link to Devin run: https://app.devin.ai/sessions/39f6cfd6c5a24d25a7bd70ce070ed29a

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Add type check for guardrail result and remove unused import

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Remove unnecessary f-string prefix

Co-Authored-By: Joe Moura <joao@crewai.com>

* feat: Add guardrail validation improvements

- Add result/error exclusivity validation in GuardrailResult
- Make return type annotations optional in Task guardrail validator
- Improve error messages for validation failures

Co-Authored-By: Joe Moura <joao@crewai.com>

* docs: Add comprehensive guardrails documentation

- Add type hints and examples
- Add error handling best practices
- Add structured error response patterns
- Document retry mechanisms
- Improve documentation organization

Co-Authored-By: Joe Moura <joao@crewai.com>

* refactor: Update guardrail functions to handle TaskOutput objects

Co-Authored-By: Joe Moura <joao@crewai.com>

* feat: Add task guardrails feature

Add support for custom code guardrails in tasks that validate outputs
before proceeding to the next task. Features include:

- Optional task-level guardrail function
- Pre-next-task execution timing
- Tuple return format (success, data)
- Automatic result/error routing
- Configurable retry mechanism
- Comprehensive documentation and tests

Link to Devin run: https://app.devin.ai/sessions/39f6cfd6c5a24d25a7bd70ce070ed29a

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Add type check for guardrail result and remove unused import

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Remove unnecessary f-string prefix

Co-Authored-By: Joe Moura <joao@crewai.com>

* feat: Add guardrail validation improvements

- Add result/error exclusivity validation in GuardrailResult
- Make return type annotations optional in Task guardrail validator
- Improve error messages for validation failures

Co-Authored-By: Joe Moura <joao@crewai.com>

* docs: Add comprehensive guardrails documentation

- Add type hints and examples
- Add error handling best practices
- Add structured error response patterns
- Document retry mechanisms
- Improve documentation organization

Co-Authored-By: Joe Moura <joao@crewai.com>

* refactor: Update guardrail functions to handle TaskOutput objects

Co-Authored-By: Joe Moura <joao@crewai.com>

* style: Fix import sorting in task guardrails files

Co-Authored-By: Joe Moura <joao@crewai.com>

* fixing docs

* Fixing guardarils implementation

* docs: Enhance guardrail validator docstring with runtime validation rationale

Co-Authored-By: Joe Moura <joao@crewai.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>
Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>
Co-authored-by: João Moura <joaomdmoura@gmail.com>

* feat: Add interpolate_only method and improve error handling (#1791)

* Fixed output_file not respecting system path

* Fixed yaml config is not escaped properly for output requirements

* feat: Add interpolate_only method and improve error handling

- Add interpolate_only method for string interpolation while preserving JSON structure
- Add comprehensive test coverage for interpolate_only
- Add proper type annotation for logger using ClassVar
- Improve error handling and documentation for _save_file method

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Sort imports to fix lint issues

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Reorganize imports using ruff --fix

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Consolidate imports and fix formatting

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Apply ruff automatic import sorting

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Sort imports using ruff --fix

Co-Authored-By: Joe Moura <joao@crewai.com>

---------

Co-authored-by: Frieda (Jingying) Huang <jingyingfhuang@gmail.com>
Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>
Co-authored-by: Frieda Huang <124417784+frieda-huang@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>

* Feat/docling-support (#1763)

* added tool for docling support

* docling support installation

* use file_paths instead of file_path

* fix import

* organized imports

* run_type docs

* needs to be list

* fixed logic

* logged but file_path is backwards compatible

* use file_paths instead of file_path 2

* added test for multiple sources for file_paths

* fix run-types

* enabling local files to work and type cleanup

* linted

* fix test and types

* fixed run types

* fix types

* renamed to CrewDoclingSource

* linted

* added docs

* resolve conflicts

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>
Co-authored-by: Brandon Hancock <brandon@brandonhancock.io>

* removed some redundancies (#1796)

* removed some redundancies

* cleanup

* Feat/joao flow improvement requests (#1795)

* Add in or and and in router

* In the middle of improving plotting

* final plot changes

---------

Co-authored-by: João Moura <joaomdmoura@gmail.com>

* Adding Multimodal Abilities to Crew (#1805)

* initial fix on delegation tools

* fixing tests for delegations and coding

* Refactor prepare tool and adding initial add images logic

* supporting image tool

* fixing linter

* fix linter

* Making sure multimodal feature support i18n

* fix linter and types

* mixxing translations

* fix types and linter

* Revert "fixing linter"

This reverts commit ef323e3487e62ee4f5bce7f86378068a5ac77e16.

* fix linters

* test

* fix

* fix

* fix linter

* fix

* ignore

* type improvements

* chore: removing crewai-tools from dev-dependencies (#1760)

As mentioned in issue #1759, listing crewai-tools as dev-dependencies makes pip install it a required dependency, and not an optional

Co-authored-by: João Moura <joaomdmoura@gmail.com>

* docs: add guide for multimodal agents (#1807)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>

* Portkey Integration with CrewAI (#1233)

* Create Portkey-Observability-and-Guardrails.md

* crewAI update with new changes

* small change

---------

Co-authored-by: siddharthsambharia-portkey <siddhath.s@portkey.ai>
Co-authored-by: João Moura <joaomdmoura@gmail.com>

* fix: Change storage initialization to None for KnowledgeStorage (#1804)

* fix: Change storage initialization to None for KnowledgeStorage

* refactor: Change storage field to optional and improve error handling when saving documents

---------

Co-authored-by: João Moura <joaomdmoura@gmail.com>

* fix: handle optional storage with null checks (#1808)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: João Moura <joaomdmoura@gmail.com>

* docs: update README to highlight Flows (#1809)

* docs: highlight Flows feature in README

Co-Authored-By: Joe Moura <joao@crewai.com>

* docs: enhance README with LangGraph comparison and flows-crews synergy

Co-Authored-By: Joe Moura <joao@crewai.com>

* docs: replace initial Flow example with advanced Flow+Crew example; enhance LangGraph comparison

Co-Authored-By: Joe Moura <joao@crewai.com>

* docs: incorporate key terms and enhance feature descriptions

Co-Authored-By: Joe Moura <joao@crewai.com>

* docs: refine technical language, enhance feature descriptions, fix string interpolation

Co-Authored-By: Joe Moura <joao@crewai.com>

* docs: update README with performance metrics, feature enhancements, and course links

Co-Authored-By: Joe Moura <joao@crewai.com>

* docs: update LangGraph comparison with paragraph and P.S. section

Co-Authored-By: Joe Moura <joao@crewai.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>

* Update README.md

* docs: add agent-specific knowledge documentation and examples (#1811)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>

* fixing file paths for knowledge source

* Fix interpolation for output_file in Task (#1803) (#1814)

* fix: interpolate output_file attribute from YAML

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: add security validation for output_file paths

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: add _original_output_file private attribute to fix type-checker error

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: update interpolate_only to handle None inputs and remove duplicate attribute

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: improve output_file validation and error messages

Co-Authored-By: Joe Moura <joao@crewai.com>

* test: add end-to-end tests for output_file functionality

Co-Authored-By: Joe Moura <joao@crewai.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>

* fix(manager_llm): handle coworker role name case/whitespace properly (#1820)

* fix(manager_llm): handle coworker role name case/whitespace properly

- Add .strip() to agent name and role comparisons in base_agent_tools.py
- Add test case for varied role name cases and whitespace
- Fix issue #1503 with manager LLM delegation

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix(manager_llm): improve error handling and add debug logging

- Add debug logging for better observability
- Add sanitize_agent_name helper method
- Enhance error messages with more context
- Add parameterized tests for edge cases:
  - Embedded quotes
  - Trailing newlines
  - Multiple whitespace
  - Case variations
  - None values
- Improve error handling with specific exceptions

Co-Authored-By: Joe Moura <joao@crewai.com>

* style: fix import sorting in base_agent_tools and test_manager_llm_delegation

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix(manager_llm): improve whitespace normalization in role name matching

Co-Authored-By: Joe Moura <joao@crewai.com>

* style: fix import sorting in base_agent_tools and test_manager_llm_delegation

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix(manager_llm): add error message template for agent tool execution errors

Co-Authored-By: Joe Moura <joao@crewai.com>

* style: fix import sorting in test_manager_llm_delegation.py

Co-Authored-By: Joe Moura <joao@crewai.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>

* fix: add tiktoken as explicit dependency and document Rust requirement (#1826)

* feat: add tiktoken as explicit dependency and document Rust requirement

- Add tiktoken>=0.8.0 as explicit dependency to ensure pre-built wheels are used
- Document Rust compiler requirement as fallback in README.md
- Addresses issue #1824 tiktoken build failure

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: adjust tiktoken version to ~=0.7.0 for dependency compatibility

- Update tiktoken dependency to ~=0.7.0 to resolve conflict with embedchain
- Maintain compatibility with crewai-tools dependency chain
- Addresses CI build failures

Co-Authored-By: Joe Moura <joao@crewai.com>

* docs: add troubleshooting section and make tiktoken optional

Co-Authored-By: Joe Moura <joao@crewai.com>

* Update README.md

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>
Co-authored-by: João Moura <joaomdmoura@gmail.com>

* Docstring, Error Handling, and Type Hints Improvements (#1828)

* docs: add comprehensive docstrings to Flow class and methods

- Added NumPy-style docstrings to all decorator functions
- Added detailed documentation to Flow class methods
- Included parameter types, return types, and examples
- Enhanced documentation clarity and completeness

Co-Authored-By: Joe Moura <joao@crewai.com>

* feat: add secure path handling utilities

- Add path_utils.py with safe path handling functions
- Implement path validation and security checks
- Integrate secure path handling in flow_visualizer.py
- Add path validation in html_template_handler.py
- Add comprehensive error handling for path operations

Co-Authored-By: Joe Moura <joao@crewai.com>

* docs: add comprehensive docstrings and type hints to flow utils (#1819)

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: add type annotations and fix import sorting

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: add type annotations to flow utils and visualization utils

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: resolve import sorting and type annotation issues

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: properly initialize and update edge_smooth variable

Co-Authored-By: Joe Moura <joao@crewai.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>

* feat: add docstring (#1819)

Co-authored-by: João Moura <joaomdmoura@gmail.com>

* fix: Include agent knowledge in planning process (#1818)

* test: Add test demonstrating knowledge not included in planning process

Issue #1703: Add test to verify that agent knowledge sources are not currently
included in the planning process. This test will help validate the fix once
implemented.

- Creates agent with knowledge sources
- Verifies knowledge context missing from planning
- Checks other expected components are present

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Include agent knowledge in planning process

Issue #1703: Integrate agent knowledge sources into planning summaries
- Add agent_knowledge field to task summaries in planning_handler
- Update test to verify knowledge inclusion
- Ensure knowledge context is available during planning phase

The planning agent now has access to agent knowledge when creating
task execution plans, allowing for better informed planning decisions.

Co-Authored-By: Joe Moura <joao@crewai.com>

* style: Fix import sorting in test_knowledge_planning.py

- Reorganize imports according to ruff linting rules
- Fix I001 linting error

Co-Authored-By: Joe Moura <joao@crewai.com>

* test: Update task summary assertions to include knowledge field

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Update ChromaDB mock path and fix knowledge string formatting

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Improve knowledge integration in planning process with error handling

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Update task summary format for empty tools and knowledge

- Change empty tools message to 'agent has no tools'
- Remove agent_knowledge field when empty
- Update test assertions to match new format
- Improve test messages for clarity

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Update string formatting for agent tools in task summary

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Update string formatting for agent tools in task summary

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Update string formatting for agent tools and knowledge in task summary

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Update knowledge field formatting in task summary

Co-Authored-By: Joe Moura <joao@crewai.com>

* style: Fix import sorting in test_planning_handler.py

Co-Authored-By: Joe Moura <joao@crewai.com>

* style: Fix import sorting order in test_planning_handler.py

Co-Authored-By: Joe Moura <joao@crewai.com>

* test: Add ChromaDB mocking to test_create_tasks_summary_with_knowledge_and_tools

Co-Authored-By: Joe Moura <joao@crewai.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>
Co-authored-by: João Moura <joaomdmoura@gmail.com>

* Suppressed userWarnings from litellm pydantic issues (#1833)

* Suppressed userWarnings from litellm pydantic issues

* change litellm version

* Fix failling ollama tasks

* Trying out timeouts

* Trying out timeouts

* trying next crew_test timeout

* trying next crew_test timeout

* timeout in crew_tests

* timeout in crew_tests

* more timeouts

* more timeouts

* crew_test changes werent applied

* crew_test changes werent applied

* revert uv.lock

* revert uv.lock

* add back in crewai tool dependencies and drop litellm version

* add back in crewai tool dependencies and drop litellm version

* tests should work now

* tests should work now

* more test changes

* more test changes

* Reverting uv.lock and pyproject

* Reverting uv.lock and pyproject

* Update llama3 cassettes

* Update llama3 cassettes

* sync packages with uv.lock

* sync packages with uv.lock

* more test fixes

* fix tets

* drop large file

* final clean up

* drop record new episodes

---------

Signed-off-by: PJ Hagerty <pjhagerty@gmail.com>
Co-authored-by: Thiago Moretto <168731+thiagomoretto@users.noreply.github.com>
Co-authored-by: Thiago Moretto <thiago.moretto@gmail.com>
Co-authored-by: Vini Brasil <vini@hey.com>
Co-authored-by: Guilherme de Amorim <ggimenezjr@gmail.com>
Co-authored-by: Tony Kipkemboi <iamtonykipkemboi@gmail.com>
Co-authored-by: Eren Küçüker <66262604+erenkucuker@users.noreply.github.com>
Co-authored-by: João Moura <joaomdmoura@gmail.com>
Co-authored-by: Akesh kumar <155313882+akesh-0909@users.noreply.github.com>
Co-authored-by: Lennex Zinyando <brizdigital@gmail.com>
Co-authored-by: Shahar Yair <shya95@gmail.com>
Co-authored-by: Eduardo Chiarotti <dudumelgaco@hotmail.com>
Co-authored-by: Stephen Hankinson <shankinson@gmail.com>
Co-authored-by: Muhammad Noman Fareed <60171953+shnoman97@users.noreply.github.com>
Co-authored-by: dbubel <50341559+dbubel@users.noreply.github.com>
Co-authored-by: Rip&Tear <84775494+theCyberTech@users.noreply.github.com>
Co-authored-by: Rok Benko <115651717+rokbenko@users.noreply.github.com>
Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com>
Co-authored-by: Sam <sammcj@users.noreply.github.com>
Co-authored-by: Maicon Peixinho <maiconpeixinho@icloud.com>
Co-authored-by: Robin Wang <6220861+MottoX@users.noreply.github.com>
Co-authored-by: C0deZ <c0dezlee@gmail.com>
Co-authored-by: c0dez <li@vitablehealth.com>
Co-authored-by: Gui Vieira <guilherme_vieira@me.com>
Co-authored-by: Dev Khant <devkhant24@gmail.com>
Co-authored-by: Deshraj Yadav <deshraj@gatech.edu>
Co-authored-by: Gui Vieira <gui@crewai.com>
Co-authored-by: Lorenze Jay <lorenzejaytech@gmail.com>
Co-authored-by: Bob Conan <sufssl03@gmail.com>
Co-authored-by: Andy Bromberg <abromberg@users.noreply.github.com>
Co-authored-by: Bowen Liang <bowenliang@apache.org>
Co-authored-by: Ivan Peevski <133036+ipeevski@users.noreply.github.com>
Co-authored-by: Rok Benko <ksjeno@gmail.com>
Co-authored-by: Javier Saldaña <cjaviersaldana@outlook.com>
Co-authored-by: Ola Hungerford <olahungerford@gmail.com>
Co-authored-by: Tom Mahler, PhD <tom@mahler.tech>
Co-authored-by: Patcher <patcher@openlit.io>
Co-authored-by: Feynman Liang <feynman.liang@gmail.com>
Co-authored-by: Stephen <stephen-talari@users.noreply.github.com>
Co-authored-by: Rashmi Pawar <168514198+raspawar@users.noreply.github.com>
Co-authored-by: Frieda Huang <124417784+frieda-huang@users.noreply.github.com>
Co-authored-by: Archkon <180910180+Archkon@users.noreply.github.com>
Co-authored-by: Aviral Jain <avi.aviral140@gmail.com>
Co-authored-by: lgesuellip <102637283+lgesuellip@users.noreply.github.com>
Co-authored-by: fuckqqcom <9391575+fuckqqcom@users.noreply.github.com>
Co-authored-by: xiaohan <fuck@qq.com>
Co-authored-by: Piotr Mardziel <piotrm@gmail.com>
Co-authored-by: Carlos Souza <caike@users.noreply.github.com>
Co-authored-by: Paul Cowgill <pauldavidcowgill@gmail.com>
Co-authored-by: Bowen Liang <liangbowen@gf.com.cn>
Co-authored-by: Anmol Deep <anmol@getaidora.com>
Co-authored-by: André Lago <andrelago.eu@gmail.com>
Co-authored-by: Matt B <mattb@Matts-MacBook-Pro.local>
Co-authored-by: Karan Vaidya <kaavee315@gmail.com>
Co-authored-by: alan blount <alan@zeroasterisk.com>
Co-authored-by: PJ <pjhagerty@gmail.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>
Co-authored-by: Frieda (Jingying) Huang <jingyingfhuang@gmail.com>
Co-authored-by: João Igor <joaoigm@hotmail.com>
Co-authored-by: siddharth Sambharia <siddharth.s@portkey.ai>
Co-authored-by: siddharthsambharia-portkey <siddhath.s@portkey.ai>
Co-authored-by: Erick Amorim <73451993+ericklima-ca@users.noreply.github.com>
Co-authored-by: Marco Vinciguerra <88108002+VinciGit00@users.noreply.github.com>
2025-01-02 16:06:48 -05:00

3340 lines
122 KiB
Python

"""Test Agent creation and execution basic functionality."""
import hashlib
import json
from concurrent.futures import Future
from unittest import mock
from unittest.mock import MagicMock, patch
import instructor
import pydantic_core
import pytest
from crewai.agent import Agent
from crewai.agents.cache import CacheHandler
from crewai.crew import Crew
from crewai.crews.crew_output import CrewOutput
from crewai.memory.contextual.contextual_memory import ContextualMemory
from crewai.process import Process
from crewai.task import Task
from crewai.tasks.conditional_task import ConditionalTask
from crewai.tasks.output_format import OutputFormat
from crewai.tasks.task_output import TaskOutput
from crewai.types.usage_metrics import UsageMetrics
from crewai.utilities import Logger
from crewai.utilities.rpm_controller import RPMController
from crewai.utilities.task_output_storage_handler import TaskOutputStorageHandler
ceo = Agent(
role="CEO",
goal="Make sure the writers in your company produce amazing content.",
backstory="You're an long time CEO of a content creation agency with a Senior Writer on the team. You're now working on a new project and want to make sure the content produced is amazing.",
allow_delegation=True,
)
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, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
allow_delegation=False,
)
writer = Agent(
role="Senior Writer",
goal="Write the best content about AI and AI agents.",
backstory="You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer.",
allow_delegation=False,
)
def test_crew_config_conditional_requirement():
with pytest.raises(ValueError):
Crew(process=Process.sequential)
config = json.dumps(
{
"agents": [
{
"role": "Senior Researcher",
"goal": "Make the best research and analysis on content about AI and AI agents",
"backstory": "You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
},
{
"role": "Senior Writer",
"goal": "Write the best content about AI and AI agents.",
"backstory": "You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer.",
},
],
"tasks": [
{
"description": "Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
"expected_output": "Bullet point list of 5 important events.",
"agent": "Senior Researcher",
},
{
"description": "Write a 1 amazing paragraph highlight for each idea that showcases how good an article about this topic could be, check references if necessary or search for more content but make sure it's unique, interesting and well written. Return the list of ideas with their paragraph and your notes.",
"expected_output": "A 4 paragraph article about AI.",
"agent": "Senior Writer",
},
],
}
)
parsed_config = json.loads(config)
try:
crew = Crew(process=Process.sequential, config=config)
except ValueError:
pytest.fail("Unexpected ValidationError raised")
assert [agent.role for agent in crew.agents] == [
agent["role"] for agent in parsed_config["agents"]
]
assert [task.description for task in crew.tasks] == [
task["description"] for task in parsed_config["tasks"]
]
def test_async_task_cannot_include_sequential_async_tasks_in_context():
task1 = Task(
description="Task 1",
async_execution=True,
expected_output="output",
agent=researcher,
)
task2 = Task(
description="Task 2",
async_execution=True,
expected_output="output",
agent=researcher,
context=[task1],
)
task3 = Task(
description="Task 3",
async_execution=True,
expected_output="output",
agent=researcher,
context=[task2],
)
task4 = Task(
description="Task 4",
expected_output="output",
agent=writer,
)
task5 = Task(
description="Task 5",
async_execution=True,
expected_output="output",
agent=researcher,
context=[task4],
)
# This should raise an error because task2 is async and has task1 in its context without a sync task in between
with pytest.raises(
ValueError,
match="Task 'Task 2' is asynchronous and cannot include other sequential asynchronous tasks in its context.",
):
Crew(tasks=[task1, task2, task3, task4, task5], agents=[researcher, writer])
# This should not raise an error because task5 has a sync task (task4) in its context
try:
Crew(tasks=[task1, task4, task5], agents=[researcher, writer])
except ValueError:
pytest.fail("Unexpected ValidationError raised")
def test_context_no_future_tasks():
task2 = Task(
description="Task 2",
expected_output="output",
agent=researcher,
)
task3 = Task(
description="Task 3",
expected_output="output",
agent=researcher,
context=[task2],
)
task4 = Task(
description="Task 4",
expected_output="output",
agent=researcher,
)
task1 = Task(
description="Task 1",
expected_output="output",
agent=researcher,
context=[task4],
)
# This should raise an error because task1 has a context dependency on a future task (task4)
with pytest.raises(
ValueError,
match="Task 'Task 1' has a context dependency on a future task 'Task 4', which is not allowed.",
):
Crew(tasks=[task1, task2, task3, task4], agents=[researcher, writer])
def test_crew_config_with_wrong_keys():
no_tasks_config = json.dumps(
{
"agents": [
{
"role": "Senior Researcher",
"goal": "Make the best research and analysis on content about AI and AI agents",
"backstory": "You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
}
]
}
)
no_agents_config = json.dumps(
{
"tasks": [
{
"description": "Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
"agent": "Senior Researcher",
}
]
}
)
with pytest.raises(ValueError):
Crew(process=Process.sequential, config='{"wrong_key": "wrong_value"}')
with pytest.raises(ValueError):
Crew(process=Process.sequential, config=no_tasks_config)
with pytest.raises(ValueError):
Crew(process=Process.sequential, config=no_agents_config)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_creation():
tasks = [
Task(
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
expected_output="Bullet point list of 5 important events.",
agent=researcher,
),
Task(
description="Write a 1 amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="A 4 paragraph article about AI.",
agent=writer,
),
]
crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=tasks,
)
result = crew.kickoff()
expected_string_output = "**The Rise of Generalist AI Agents:**\nImagine a future where AI agents are no longer confined to specific tasks like data analytics or speech recognition. The evolution from specialized AI tools to versatile generalist AI agents is comparable to the leap from feature phones to smartphones. This shift heralds significant transformations across diverse industries, from healthcare and finance to customer service. It also raises fascinating ethical considerations around the deployment and control of such powerful technologies. Moreover, this transformation could democratize AI, making sophisticated tools accessible to non-experts and small businesses, thus leveling the playing field in many sectors.\n\n**Ethical Implications of AI in Surveillance:**\nThe advent of advanced AI has significantly boosted surveillance capabilities, presenting a double-edged sword. On one hand, enhanced surveillance can improve public safety and combat crime more effectively. On the other, it raises substantial ethical concerns about privacy invasion and the potential for misuse by authoritarian regimes. Balancing security with privacy is a delicate task, requiring robust legal frameworks and transparent policies. Real-world case studies, from smart city deployments to airport security systems, illustrate both the benefits and the risks of AI-enhanced surveillance, highlighting the need for ethical vigilance and public discourse.\n\n**AI in Creative Industries:**\nAI is breaking new ground in creative fields, transforming how art, music, and content are produced. Far from being mere tools, AI systems are emerging as collaborators, helping artists push the boundaries of creative expression. Noteworthy are AI-generated works that have captured public imagination, like paintings auctioned at prestigious houses or music albums composed by algorithms. The future holds exciting possibilities, as AI may enable novel art forms and interactive experiences previously unimaginable, fostering a symbiotic relationship between human creativity and machine intelligence.\n\n**The Impact of Quantum Computing on AI Development:**\nQuantum computing promises to be a game-changer for AI, offering unprecedented computational power to tackle complex problems. This revolution could significantly enhance AI algorithms, enabling faster and more efficient training and execution. The potential applications are vast, from optimizing supply chains to solving intricate scientific problems and advancing natural language processing. Looking ahead, quantum-enhanced AI might unlock new frontiers, such as real-time data analysis at scales previously thought impossible, pushing the limits of what we can achieve with AI technology.\n\n**AI and Mental Health:**\nThe integration of AI into mental health care is transforming diagnosis and therapy, offering new hope for those in need. AI-driven tools have shown promise in accurately diagnosing conditions and providing personalized treatment plans through data analysis and pattern recognition. Case studies highlight successful interventions where AI has aided mental health professionals, enhancing the effectiveness of traditional therapies. However, this advancement brings ethical concerns, particularly around data privacy and the transparency of AI decision-making processes. As AI continues to evolve, it could play an even more significant role in mental health care, providing early interventions and support on a scale previously unattainable."
assert str(result) == expected_string_output
assert result.raw == expected_string_output
assert isinstance(result, CrewOutput)
assert len(result.tasks_output) == len(tasks)
assert result.raw == expected_string_output
@pytest.mark.vcr(filter_headers=["authorization"])
def test_sync_task_execution():
from unittest.mock import patch
tasks = [
Task(
description="Give me a list of 5 interesting ideas to explore for an article, what makes them unique and interesting.",
expected_output="Bullet point list of 5 important events.",
agent=researcher,
),
Task(
description="Write an amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="A 4 paragraph article about AI.",
agent=writer,
),
]
crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=tasks,
)
mock_task_output = TaskOutput(
description="Mock description", raw="mocked output", agent="mocked agent"
)
# Because we are mocking execute_sync, we never hit the underlying _execute_core
# which sets the output attribute of the task
for task in tasks:
task.output = mock_task_output
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
# Assert that execute_sync was called for each task
assert mock_execute_sync.call_count == len(tasks)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_hierarchical_process():
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
)
crew = Crew(
agents=[researcher, writer],
process=Process.hierarchical,
manager_llm="gpt-4o",
tasks=[task],
)
result = crew.kickoff()
assert (
result.raw
== "Here are the 5 interesting ideas along with a compelling paragraph for each that showcases how good an article on the topic could be:\n\n1. **The Evolution and Future of AI Agents in Everyday Life**:\nThe rapid development of AI agents from rudimentary virtual assistants like Siri and Alexa to today's sophisticated systems marks a significant technological leap. This article will explore the evolving landscape of AI agents, detailing their seamless integration into daily activities ranging from managing smart home devices to streamlining workflows. We will examine the multifaceted benefits these agents bring, such as increased efficiency and personalized user experiences, while also addressing ethical concerns like data privacy and algorithmic bias. Looking ahead, we will forecast the advancements slated for the next decade, including AI agents in personalized health coaching and automated legal consultancy. With more advanced machine learning algorithms, the potential for these AI systems to revolutionize our daily lives is immense.\n\n2. **AI in Healthcare: Revolutionizing Diagnostics and Treatment**:\nArtificial Intelligence is poised to revolutionize the healthcare sector by offering unprecedented improvements in diagnostic accuracy and personalized treatments. This article will delve into the transformative power of AI in healthcare, highlighting real-world applications like AI-driven imaging technologies that aid in early disease detection and predictive analytics that enable personalized patient care plans. We will discuss the ethical challenges, such as data privacy and the implications of AI-driven decision-making in medicine. Through compelling case studies, we will showcase successful AI implementations that have made significant impacts, ultimately painting a picture of a future where AI plays a central role in proactive and precise healthcare delivery.\n\n3. **The Role of AI in Enhancing Cybersecurity**:\nAs cyber threats become increasingly sophisticated, AI stands at the forefront of the battle against cybercrime. This article will discuss the crucial role AI plays in detecting and responding to threats in real-time, its capacity to predict and prevent potential attacks, and the inherent challenges of an AI-dependent cybersecurity framework. We will highlight recent advancements in AI-based security tools and provide case studies where AI has been instrumental in mitigating cyber threats effectively. By examining these elements, we'll underline the potential and limitations of AI in creating a more secure digital environment, showcasing how it can adapt to evolving threats faster than traditional methods.\n\n4. **The Intersection of AI and Autonomous Vehicles: Driving Towards a Safer Future**:\nThe prospect of AI-driven autonomous vehicles promises to redefine transportation. This article will explore the technological underpinnings of self-driving cars, their developmental milestones, and the hurdles they face, including regulatory and ethical challenges. We will discuss the profound implications for various industries and employment sectors, coupled with the benefits such as reduced traffic accidents, improved fuel efficiency, and enhanced mobility for people with disabilities. By detailing these aspects, the article will offer a comprehensive overview of how AI-powered autonomous vehicles are steering us towards a safer, more efficient future.\n\n5. **AI and the Future of Work: Embracing Change in the Workplace**:\nAI is transforming the workplace by automating mundane tasks, enabling advanced data analysis, and fostering creativity and strategic decision-making. This article will explore the profound impact of AI on the job market, addressing concerns about job displacement and the evolution of new roles that demand reskilling. We will provide insights into the necessity for upskilling to keep pace with an AI-driven economy. Through interviews with industry experts and narratives from workers who have experienced AI's impact firsthand, we will present a balanced perspective. The aim is to paint a future where humans and AI work in synergy, driving innovation and productivity in a continuously evolving workplace landscape."
)
def test_manager_llm_requirement_for_hierarchical_process():
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
)
with pytest.raises(pydantic_core._pydantic_core.ValidationError):
Crew(
agents=[researcher, writer],
process=Process.hierarchical,
tasks=[task],
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_manager_agent_delegating_to_assigned_task_agent():
"""
Test that the manager agent delegates to the assigned task agent.
"""
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
agent=researcher,
)
crew = Crew(
agents=[researcher, writer],
process=Process.hierarchical,
manager_llm="gpt-4o",
tasks=[task],
)
mock_task_output = TaskOutput(
description="Mock description", raw="mocked output", agent="mocked agent"
)
# Because we are mocking execute_sync, we never hit the underlying _execute_core
# which sets the output attribute of the task
task.output = mock_task_output
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
# Verify execute_sync was called once
mock_execute_sync.assert_called_once()
# Get the tools argument from the call
_, kwargs = mock_execute_sync.call_args
tools = kwargs["tools"]
# Verify the delegation tools were passed correctly
assert len(tools) == 2
assert any(
"Delegate a specific task to one of the following coworkers: Researcher"
in tool.description
for tool in tools
)
assert any(
"Ask a specific question to one of the following coworkers: Researcher"
in tool.description
for tool in tools
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_manager_agent_delegating_to_all_agents():
"""
Test that the manager agent delegates to all agents when none are specified.
"""
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
)
crew = Crew(
agents=[researcher, writer],
process=Process.hierarchical,
manager_llm="gpt-4o",
tasks=[task],
)
crew.kickoff()
assert crew.manager_agent is not None
assert crew.manager_agent.tools is not None
assert len(crew.manager_agent.tools) == 2
assert (
"Delegate a specific task to one of the following coworkers: Researcher, Senior Writer\n"
in crew.manager_agent.tools[0].description
)
assert (
"Ask a specific question to one of the following coworkers: Researcher, Senior Writer\n"
in crew.manager_agent.tools[1].description
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_manager_agent_delegates_with_varied_role_cases():
"""
Test that the manager agent can delegate to agents regardless of case or whitespace variations in role names.
This test verifies the fix for issue #1503 where role matching was too strict.
"""
# Create agents with varied case and whitespace in roles
researcher_spaced = Agent(
role=" Researcher ", # Extra spaces
goal="Research with spaces in role",
backstory="A researcher with spaces in role name",
allow_delegation=False,
)
writer_caps = Agent(
role="SENIOR WRITER", # All caps
goal="Write with caps in role",
backstory="A writer with caps in role name",
allow_delegation=False,
)
task = Task(
description="Research and write about AI. The researcher should do the research, and the writer should write it up.",
expected_output="A well-researched article about AI.",
agent=researcher_spaced, # Assign to researcher with spaces
)
crew = Crew(
agents=[researcher_spaced, writer_caps],
process=Process.hierarchical,
manager_llm="gpt-4o",
tasks=[task],
)
mock_task_output = TaskOutput(
description="Mock description", raw="mocked output", agent="mocked agent"
)
task.output = mock_task_output
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
# Verify execute_sync was called once
mock_execute_sync.assert_called_once()
# Get the tools argument from the call
_, kwargs = mock_execute_sync.call_args
tools = kwargs["tools"]
# Verify the delegation tools were passed correctly and can handle case/whitespace variations
assert len(tools) == 2
# Check delegation tool descriptions (should work despite case/whitespace differences)
delegation_tool = tools[0]
question_tool = tools[1]
assert (
"Delegate a specific task to one of the following coworkers:"
in delegation_tool.description
)
assert (
" Researcher " in delegation_tool.description
or "SENIOR WRITER" in delegation_tool.description
)
assert (
"Ask a specific question to one of the following coworkers:"
in question_tool.description
)
assert (
" Researcher " in question_tool.description
or "SENIOR WRITER" in question_tool.description
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_with_delegating_agents():
tasks = [
Task(
description="Produce and amazing 1 paragraph draft of an article about AI Agents.",
expected_output="A 4 paragraph article about AI.",
agent=ceo,
)
]
crew = Crew(
agents=[ceo, writer],
process=Process.sequential,
tasks=tasks,
)
result = crew.kickoff()
assert (
result.raw
== "In the rapidly evolving landscape of technology, AI agents have emerged as formidable tools, revolutionizing how we interact with data and automate tasks. These sophisticated systems leverage machine learning and natural language processing to perform a myriad of functions, from virtual personal assistants to complex decision-making companions in industries such as finance, healthcare, and education. By mimicking human intelligence, AI agents can analyze massive data sets at unparalleled speeds, enabling businesses to uncover valuable insights, enhance productivity, and elevate user experiences to unprecedented levels.\n\nOne of the most striking aspects of AI agents is their adaptability; they learn from their interactions and continuously improve their performance over time. This feature is particularly valuable in customer service where AI agents can address inquiries, resolve issues, and provide personalized recommendations without the limitations of human fatigue. Moreover, with intuitive interfaces, AI agents enhance user interactions, making technology more accessible and user-friendly, thereby breaking down barriers that have historically hindered digital engagement.\n\nDespite their immense potential, the deployment of AI agents raises important ethical and practical considerations. Issues related to privacy, data security, and the potential for job displacement necessitate thoughtful dialogue and proactive measures. Striking a balance between technological innovation and societal impact will be crucial as organizations integrate these agents into their operations. Additionally, ensuring transparency in AI decision-making processes is vital to maintain public trust as AI agents become an integral part of daily life.\n\nLooking ahead, the future of AI agents appears bright, with ongoing advancements promising even greater capabilities. As we continue to harness the power of AI, we can expect these agents to play a transformative role in shaping various sectors—streamlining workflows, enabling smarter decision-making, and fostering more personalized experiences. Embracing this technology responsibly can lead to a future where AI agents not only augment human effort but also inspire creativity and efficiency across the board, ultimately redefining our interaction with the digital world."
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_with_delegating_agents_should_not_override_task_tools():
from typing import Type
from pydantic import BaseModel, Field
from crewai.tools import BaseTool
class TestToolInput(BaseModel):
"""Input schema for TestTool."""
query: str = Field(..., description="Query to process")
class TestTool(BaseTool):
name: str = "Test Tool"
description: str = "A test tool that just returns the input"
args_schema: Type[BaseModel] = TestToolInput
def _run(self, query: str) -> str:
return f"Processed: {query}"
# Create a task with the test tool
tasks = [
Task(
description="Produce and amazing 1 paragraph draft of an article about AI Agents.",
expected_output="A 4 paragraph article about AI.",
agent=ceo,
tools=[TestTool()],
)
]
crew = Crew(
agents=[ceo, writer],
process=Process.sequential,
tasks=tasks,
)
mock_task_output = TaskOutput(
description="Mock description", raw="mocked output", agent="mocked agent"
)
# Because we are mocking execute_sync, we never hit the underlying _execute_core
# which sets the output attribute of the task
tasks[0].output = mock_task_output
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
# Execute the task and verify both tools are present
_, kwargs = mock_execute_sync.call_args
tools = kwargs["tools"]
assert any(
isinstance(tool, TestTool) for tool in tools
), "TestTool should be present"
assert any(
"delegate" in tool.name.lower() for tool in tools
), "Delegation tool should be present"
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_with_delegating_agents_should_not_override_agent_tools():
from typing import Type
from pydantic import BaseModel, Field
from crewai.tools import BaseTool
class TestToolInput(BaseModel):
"""Input schema for TestTool."""
query: str = Field(..., description="Query to process")
class TestTool(BaseTool):
name: str = "Test Tool"
description: str = "A test tool that just returns the input"
args_schema: Type[BaseModel] = TestToolInput
def _run(self, query: str) -> str:
return f"Processed: {query}"
new_ceo = ceo.model_copy()
new_ceo.tools = [TestTool()]
# Create a task with the test tool
tasks = [
Task(
description="Produce and amazing 1 paragraph draft of an article about AI Agents.",
expected_output="A 4 paragraph article about AI.",
agent=new_ceo,
)
]
crew = Crew(
agents=[new_ceo, writer],
process=Process.sequential,
tasks=tasks,
)
mock_task_output = TaskOutput(
description="Mock description", raw="mocked output", agent="mocked agent"
)
# Because we are mocking execute_sync, we never hit the underlying _execute_core
# which sets the output attribute of the task
tasks[0].output = mock_task_output
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
# Execute the task and verify both tools are present
_, kwargs = mock_execute_sync.call_args
tools = kwargs["tools"]
assert any(
isinstance(tool, TestTool) for tool in new_ceo.tools
), "TestTool should be present"
assert any(
"delegate" in tool.name.lower() for tool in tools
), "Delegation tool should be present"
@pytest.mark.vcr(filter_headers=["authorization"])
def test_task_tools_override_agent_tools():
from typing import Type
from pydantic import BaseModel, Field
from crewai.tools import BaseTool
class TestToolInput(BaseModel):
"""Input schema for TestTool."""
query: str = Field(..., description="Query to process")
class TestTool(BaseTool):
name: str = "Test Tool"
description: str = "A test tool that just returns the input"
args_schema: Type[BaseModel] = TestToolInput
def _run(self, query: str) -> str:
return f"Processed: {query}"
class AnotherTestTool(BaseTool):
name: str = "Another Test Tool"
description: str = "Another test tool"
args_schema: Type[BaseModel] = TestToolInput
def _run(self, query: str) -> str:
return f"Another processed: {query}"
# Set agent tools
new_researcher = researcher.model_copy()
new_researcher.tools = [TestTool()]
# Create task with different tools
task = Task(
description="Write a test task",
expected_output="Test output",
agent=new_researcher,
tools=[AnotherTestTool()],
)
crew = Crew(agents=[new_researcher], tasks=[task], process=Process.sequential)
crew.kickoff()
# Verify task tools override agent tools
assert len(task.tools) == 1 # AnotherTestTool
assert any(isinstance(tool, AnotherTestTool) for tool in task.tools)
assert not any(isinstance(tool, TestTool) for tool in task.tools)
# Verify agent tools remain unchanged
assert len(new_researcher.tools) == 1
assert isinstance(new_researcher.tools[0], TestTool)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_task_tools_override_agent_tools_with_allow_delegation():
"""
Test that task tools override agent tools while preserving delegation tools when allow_delegation=True
"""
from typing import Type
from pydantic import BaseModel, Field
from crewai.tools import BaseTool
class TestToolInput(BaseModel):
query: str = Field(..., description="Query to process")
class TestTool(BaseTool):
name: str = "Test Tool"
description: str = "A test tool that just returns the input"
args_schema: Type[BaseModel] = TestToolInput
def _run(self, query: str) -> str:
return f"Processed: {query}"
class AnotherTestTool(BaseTool):
name: str = "Another Test Tool"
description: str = "Another test tool"
args_schema: Type[BaseModel] = TestToolInput
def _run(self, query: str) -> str:
return f"Another processed: {query}"
# Set up agents with tools and allow_delegation
researcher_with_delegation = researcher.model_copy()
researcher_with_delegation.allow_delegation = True
researcher_with_delegation.tools = [TestTool()]
writer_for_delegation = writer.model_copy()
# Create a task with different tools
task = Task(
description="Write a test task",
expected_output="Test output",
agent=researcher_with_delegation,
tools=[AnotherTestTool()],
)
crew = Crew(
agents=[researcher_with_delegation, writer_for_delegation],
tasks=[task],
process=Process.sequential,
)
mock_task_output = TaskOutput(
description="Mock description", raw="mocked output", agent="mocked agent"
)
# We mock execute_sync to verify which tools get used at runtime
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
# Inspect the call kwargs to verify the actual tools passed to execution
_, kwargs = mock_execute_sync.call_args
used_tools = kwargs["tools"]
# Confirm AnotherTestTool is present but TestTool is not
assert any(
isinstance(tool, AnotherTestTool) for tool in used_tools
), "AnotherTestTool should be present"
assert not any(
isinstance(tool, TestTool) for tool in used_tools
), "TestTool should not be present among used tools"
# Confirm delegation tool(s) are present
assert any(
"delegate" in tool.name.lower() for tool in used_tools
), "Delegation tool should be present"
# Finally, make sure the agent's original tools remain unchanged
assert len(researcher_with_delegation.tools) == 1
assert isinstance(researcher_with_delegation.tools[0], TestTool)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_verbose_output(capsys):
tasks = [
Task(
description="Research AI advancements.",
expected_output="A full report on AI advancements.",
agent=researcher,
),
Task(
description="Write about AI in healthcare.",
expected_output="A 4 paragraph article about AI.",
agent=writer,
),
]
crew = Crew(
agents=[researcher, writer],
tasks=tasks,
process=Process.sequential,
verbose=True,
)
crew.kickoff()
captured = capsys.readouterr()
expected_strings = [
"\x1b[1m\x1b[95m# Agent:\x1b[00m \x1b[1m\x1b[92mResearcher",
"\x1b[00m\n\x1b[95m## Task:\x1b[00m \x1b[92mResearch AI advancements.",
"\x1b[1m\x1b[95m# Agent:\x1b[00m \x1b[1m\x1b[92mSenior Writer",
"\x1b[95m## Task:\x1b[00m \x1b[92mWrite about AI in healthcare.",
"\n\n\x1b[1m\x1b[95m# Agent:\x1b[00m \x1b[1m\x1b[92mResearcher",
"\x1b[00m\n\x1b[95m## Final Answer:",
"\n\n\x1b[1m\x1b[95m# Agent:\x1b[00m \x1b[1m\x1b[92mSenior Writer",
"\x1b[00m\n\x1b[95m## Final Answer:",
]
for expected_string in expected_strings:
assert expected_string in captured.out
# Now test with verbose set to False
crew.verbose = False
crew._logger = Logger(verbose=False)
crew.kickoff()
captured = capsys.readouterr()
assert captured.out == ""
@pytest.mark.vcr(filter_headers=["authorization"])
def test_cache_hitting_between_agents():
from unittest.mock import call, patch
from crewai.tools import tool
@tool
def multiplier(first_number: int, second_number: int) -> float:
"""Useful for when you need to multiply two numbers together."""
return first_number * second_number
tasks = [
Task(
description="What is 2 tims 6? Return only the number.",
expected_output="the result of multiplication",
tools=[multiplier],
agent=ceo,
),
Task(
description="What is 2 times 6? Return only the number.",
expected_output="the result of multiplication",
tools=[multiplier],
agent=researcher,
),
]
crew = Crew(
agents=[ceo, researcher],
tasks=tasks,
)
with patch.object(CacheHandler, "read") as read:
read.return_value = "12"
crew.kickoff()
assert read.call_count == 2, "read was not called exactly twice"
# Check if read was called with the expected arguments
expected_calls = [
call(tool="multiplier", input={"first_number": 2, "second_number": 6}),
call(tool="multiplier", input={"first_number": 2, "second_number": 6}),
]
read.assert_has_calls(expected_calls, any_order=False)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_api_calls_throttling(capsys):
from unittest.mock import patch
from crewai.tools import tool
@tool
def get_final_answer() -> float:
"""Get the final answer but don't give it yet, just re-use this
tool non-stop."""
return 42
agent = Agent(
role="Very helpful assistant",
goal="Comply with necessary changes",
backstory="You obey orders",
max_iter=2,
allow_delegation=False,
verbose=True,
llm="gpt-4o",
)
task = Task(
description="Don't give a Final Answer unless explicitly told it's time to give the absolute best final answer.",
expected_output="The final answer.",
tools=[get_final_answer],
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task], max_rpm=1, verbose=True)
with patch.object(RPMController, "_wait_for_next_minute") as moveon:
moveon.return_value = True
crew.kickoff()
captured = capsys.readouterr()
assert "Max RPM reached, waiting for next minute to start." in captured.out
moveon.assert_called()
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_kickoff_usage_metrics():
inputs = [
{"topic": "dog"},
{"topic": "cat"},
{"topic": "apple"},
]
agent = Agent(
role="{topic} Researcher",
goal="Express hot takes on {topic}.",
backstory="You have a lot of experience with {topic}.",
)
task = Task(
description="Give me an analysis around {topic}.",
expected_output="1 bullet point about {topic} that's under 15 words.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
results = crew.kickoff_for_each(inputs=inputs)
assert len(results) == len(inputs)
for result in results:
# Assert that all required keys are in usage_metrics and their values are not None
assert result.token_usage.total_tokens > 0
assert result.token_usage.prompt_tokens > 0
assert result.token_usage.completion_tokens > 0
assert result.token_usage.successful_requests > 0
assert result.token_usage.cached_prompt_tokens == 0
def test_agents_rpm_is_never_set_if_crew_max_RPM_is_not_set():
agent = Agent(
role="test role",
goal="test goal",
backstory="test backstory",
allow_delegation=False,
verbose=True,
)
task = Task(
description="just say hi!",
expected_output="your greeting",
agent=agent,
)
Crew(agents=[agent], tasks=[task], verbose=True)
assert agent._rpm_controller is None
@pytest.mark.vcr(filter_headers=["authorization"])
def test_sequential_async_task_execution_completion():
list_ideas = Task(
description="Give me a list of 5 interesting ideas to explore for an article, what makes them unique and interesting.",
expected_output="Bullet point list of 5 important events.",
agent=researcher,
async_execution=True,
)
list_important_history = Task(
description="Research the history of AI and give me the 5 most important events that shaped the technology.",
expected_output="Bullet point list of 5 important events.",
agent=researcher,
)
write_article = Task(
description="Write an article about the history of AI and its most important events.",
expected_output="A 4 paragraph article about AI.",
agent=writer,
context=[list_ideas, list_important_history],
)
sequential_crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=[list_ideas, list_important_history, write_article],
)
sequential_result = sequential_crew.kickoff()
assert sequential_result.raw.startswith(
"The history of artificial intelligence (AI) is marked by several pivotal events that have shaped the field into what it is today."
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_single_task_with_async_execution():
researcher_agent = 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, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
allow_delegation=False,
)
list_ideas = Task(
description="Generate a list of 5 interesting ideas to explore for an article, where each bulletpoint is under 15 words.",
expected_output="Bullet point list of 5 important events. No additional commentary.",
agent=researcher_agent,
async_execution=True,
)
crew = Crew(
agents=[researcher_agent],
process=Process.sequential,
tasks=[list_ideas],
)
result = crew.kickoff()
assert result.raw.startswith(
"- Ethical implications of AI in law enforcement and surveillance."
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_three_task_with_async_execution():
researcher_agent = 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, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
allow_delegation=False,
)
bullet_list = Task(
description="Generate a list of 5 interesting ideas to explore for an article, where each bulletpoint is under 15 words.",
expected_output="Bullet point list of 5 important events. No additional commentary.",
agent=researcher_agent,
async_execution=True,
)
numbered_list = Task(
description="Generate a list of 5 interesting ideas to explore for an article, where each bulletpoint is under 15 words.",
expected_output="Numbered list of 5 important events. No additional commentary.",
agent=researcher_agent,
async_execution=True,
)
letter_list = Task(
description="Generate a list of 5 interesting ideas to explore for an article, where each bulletpoint is under 15 words.",
expected_output="Numbered list using [A), B), C)] list of 5 important events. No additional commentary.",
agent=researcher_agent,
async_execution=True,
)
# Expected result is that we will get an error
# because a crew can end only end with one or less
# async tasks
with pytest.raises(pydantic_core._pydantic_core.ValidationError) as error:
Crew(
agents=[researcher_agent],
process=Process.sequential,
tasks=[bullet_list, numbered_list, letter_list],
)
assert error.value.errors()[0]["type"] == "async_task_count"
assert (
"The crew must end with at most one asynchronous task."
in error.value.errors()[0]["msg"]
)
@pytest.mark.asyncio
@pytest.mark.vcr(filter_headers=["authorization"])
async def test_crew_async_kickoff():
inputs = [
{"topic": "dog"},
{"topic": "cat"},
{"topic": "apple"},
]
agent = Agent(
role="mock agent",
goal="Express hot takes on {topic}.",
backstory="You have a lot of experience with {topic}.",
)
task = Task(
description="Give me an analysis around {topic}.",
expected_output="1 bullet point about {topic} that's under 15 words.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
mock_task_output = (
CrewOutput(
raw="Test output from Crew 1",
tasks_output=[],
token_usage=UsageMetrics(
total_tokens=100,
prompt_tokens=10,
completion_tokens=90,
successful_requests=1,
),
json_dict={"output": "crew1"},
pydantic=None,
),
)
with patch.object(Crew, "kickoff_async", return_value=mock_task_output):
results = await crew.kickoff_for_each_async(inputs=inputs)
assert len(results) == len(inputs)
for result in results:
# Assert that all required keys are in usage_metrics and their values are not None
assert result[0].token_usage.total_tokens > 0 # type: ignore
assert result[0].token_usage.prompt_tokens > 0 # type: ignore
assert result[0].token_usage.completion_tokens > 0 # type: ignore
assert result[0].token_usage.successful_requests > 0 # type: ignore
@pytest.mark.asyncio
@pytest.mark.vcr(filter_headers=["authorization"])
async def test_async_task_execution_call_count():
from unittest.mock import MagicMock, patch
list_ideas = Task(
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
expected_output="Bullet point list of 5 important events.",
agent=researcher,
async_execution=True,
)
list_important_history = Task(
description="Research the history of AI and give me the 5 most important events that shaped the technology.",
expected_output="Bullet point list of 5 important events.",
agent=researcher,
async_execution=True,
)
write_article = Task(
description="Write an article about the history of AI and its most important events.",
expected_output="A 4 paragraph article about AI.",
agent=writer,
)
crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=[list_ideas, list_important_history, write_article],
)
# Create a valid TaskOutput instance to mock the return value
mock_task_output = TaskOutput(
description="Mock description", raw="mocked output", agent="mocked agent"
)
# Create a MagicMock Future instance
mock_future = MagicMock(spec=Future)
mock_future.result.return_value = mock_task_output
# Directly set the output attribute for each task
list_ideas.output = mock_task_output
list_important_history.output = mock_task_output
write_article.output = mock_task_output
with (
patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync,
patch.object(
Task, "execute_async", return_value=mock_future
) as mock_execute_async,
):
crew.kickoff()
assert mock_execute_async.call_count == 2
assert mock_execute_sync.call_count == 1
@pytest.mark.vcr(filter_headers=["authorization"])
def test_kickoff_for_each_single_input():
"""Tests if kickoff_for_each works with a single input."""
inputs = [{"topic": "dog"}]
agent = Agent(
role="{topic} Researcher",
goal="Express hot takes on {topic}.",
backstory="You have a lot of experience with {topic}.",
)
task = Task(
description="Give me an analysis around {topic}.",
expected_output="1 bullet point about {topic} that's under 15 words.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
results = crew.kickoff_for_each(inputs=inputs)
assert len(results) == 1
@pytest.mark.vcr(filter_headers=["authorization"])
def test_kickoff_for_each_multiple_inputs():
"""Tests if kickoff_for_each works with multiple inputs."""
inputs = [
{"topic": "dog"},
{"topic": "cat"},
{"topic": "apple"},
]
agent = Agent(
role="{topic} Researcher",
goal="Express hot takes on {topic}.",
backstory="You have a lot of experience with {topic}.",
)
task = Task(
description="Give me an analysis around {topic}.",
expected_output="1 bullet point about {topic} that's under 15 words.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
results = crew.kickoff_for_each(inputs=inputs)
assert len(results) == len(inputs)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_kickoff_for_each_empty_input():
"""Tests if kickoff_for_each handles an empty input list."""
agent = Agent(
role="{topic} Researcher",
goal="Express hot takes on {topic}.",
backstory="You have a lot of experience with {topic}.",
)
task = Task(
description="Give me an analysis around {topic}.",
expected_output="1 bullet point about {topic} that's under 15 words.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
results = crew.kickoff_for_each(inputs=[])
assert results == []
def test_kickoff_for_each_invalid_input():
"""Tests if kickoff_for_each raises TypeError for invalid input types."""
agent = Agent(
role="{topic} Researcher",
goal="Express hot takes on {topic}.",
backstory="You have a lot of experience with {topic}.",
)
task = Task(
description="Give me an analysis around {topic}.",
expected_output="1 bullet point about {topic} that's under 15 words.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
with pytest.raises(TypeError):
# Pass a string instead of a list
crew.kickoff_for_each("invalid input")
def test_kickoff_for_each_error_handling():
"""Tests error handling in kickoff_for_each when kickoff raises an error."""
from unittest.mock import patch
inputs = [
{"topic": "dog"},
{"topic": "cat"},
{"topic": "apple"},
]
expected_outputs = [
"Dogs are loyal companions and popular pets.",
"Cats are independent and low-maintenance pets.",
"Apples are a rich source of dietary fiber and vitamin C.",
]
agent = Agent(
role="{topic} Researcher",
goal="Express hot takes on {topic}.",
backstory="You have a lot of experience with {topic}.",
)
task = Task(
description="Give me an analysis around {topic}.",
expected_output="1 bullet point about {topic} that's under 15 words.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
with patch.object(Crew, "kickoff") as mock_kickoff:
mock_kickoff.side_effect = expected_outputs[:2] + [
Exception("Simulated kickoff error")
]
with pytest.raises(Exception, match="Simulated kickoff error"):
crew.kickoff_for_each(inputs=inputs)
@pytest.mark.asyncio
async def test_kickoff_async_basic_functionality_and_output():
"""Tests the basic functionality and output of kickoff_async."""
from unittest.mock import patch
inputs = {"topic": "dog"}
agent = Agent(
role="{topic} Researcher",
goal="Express hot takes on {topic}.",
backstory="You have a lot of experience with {topic}.",
)
task = Task(
description="Give me an analysis around {topic}.",
expected_output="1 bullet point about {topic} that's under 15 words.",
agent=agent,
)
# Create the crew
crew = Crew(
agents=[agent],
tasks=[task],
)
expected_output = "This is a sample output from kickoff."
with patch.object(Crew, "kickoff", return_value=expected_output) as mock_kickoff:
result = await crew.kickoff_async(inputs)
assert isinstance(result, str), "Result should be a string"
assert result == expected_output, "Result should match expected output"
mock_kickoff.assert_called_once_with(inputs)
@pytest.mark.asyncio
async def test_async_kickoff_for_each_async_basic_functionality_and_output():
"""Tests the basic functionality and output of kickoff_for_each_async."""
inputs = [
{"topic": "dog"},
{"topic": "cat"},
{"topic": "apple"},
]
# Define expected outputs for each input
expected_outputs = [
"Dogs are loyal companions and popular pets.",
"Cats are independent and low-maintenance pets.",
"Apples are a rich source of dietary fiber and vitamin C.",
]
agent = Agent(
role="{topic} Researcher",
goal="Express hot takes on {topic}.",
backstory="You have a lot of experience with {topic}.",
)
task = Task(
description="Give me an analysis around {topic}.",
expected_output="1 bullet point about {topic} that's under 15 words.",
agent=agent,
)
async def mock_kickoff_async(**kwargs):
input_data = kwargs.get("inputs")
index = [input_["topic"] for input_ in inputs].index(input_data["topic"])
return expected_outputs[index]
with patch.object(
Crew, "kickoff_async", side_effect=mock_kickoff_async
) as mock_kickoff_async:
crew = Crew(agents=[agent], tasks=[task])
results = await crew.kickoff_for_each_async(inputs)
assert len(results) == len(inputs)
assert results == expected_outputs
for input_data in inputs:
mock_kickoff_async.assert_any_call(inputs=input_data)
@pytest.mark.asyncio
async def test_async_kickoff_for_each_async_empty_input():
"""Tests if akickoff_for_each_async handles an empty input list."""
agent = Agent(
role="{topic} Researcher",
goal="Express hot takes on {topic}.",
backstory="You have a lot of experience with {topic}.",
)
task = Task(
description="Give me an analysis around {topic}.",
expected_output="1 bullet point about {topic} that's under 15 words.",
agent=agent,
)
# Create the crew
crew = Crew(
agents=[agent],
tasks=[task],
)
# Call the function we are testing
results = await crew.kickoff_for_each_async([])
# Assertion
assert results == [], "Result should be an empty list when input is empty"
def test_set_agents_step_callback():
from unittest.mock import patch
researcher_agent = 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, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
allow_delegation=False,
)
list_ideas = Task(
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
expected_output="Bullet point list of 5 important events.",
agent=researcher_agent,
async_execution=True,
)
crew = Crew(
agents=[researcher_agent],
process=Process.sequential,
tasks=[list_ideas],
step_callback=lambda: None,
)
with patch.object(Agent, "execute_task") as execute:
execute.return_value = "ok"
crew.kickoff()
assert researcher_agent.step_callback is not None
def test_dont_set_agents_step_callback_if_already_set():
from unittest.mock import patch
def agent_callback(_):
pass
def crew_callback(_):
pass
researcher_agent = 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, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
allow_delegation=False,
step_callback=agent_callback,
)
list_ideas = Task(
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
expected_output="Bullet point list of 5 important events.",
agent=researcher_agent,
async_execution=True,
)
crew = Crew(
agents=[researcher_agent],
process=Process.sequential,
tasks=[list_ideas],
step_callback=crew_callback,
)
with patch.object(Agent, "execute_task") as execute:
execute.return_value = "ok"
crew.kickoff()
assert researcher_agent.step_callback is not crew_callback
assert researcher_agent.step_callback is agent_callback
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_function_calling_llm():
from unittest.mock import patch
from crewai.tools import tool
llm = "gpt-4o"
@tool
def learn_about_AI() -> str:
"""Useful for when you need to learn about AI to write an paragraph about it."""
return "AI is a very broad field."
agent1 = Agent(
role="test role",
goal="test goal",
backstory="test backstory",
tools=[learn_about_AI],
llm="gpt-4o-mini",
function_calling_llm=llm,
)
essay = Task(
description="Write and then review an small paragraph on AI until it's AMAZING",
expected_output="The final paragraph.",
agent=agent1,
)
tasks = [essay]
crew = Crew(agents=[agent1], tasks=tasks)
with patch.object(
instructor, "from_litellm", wraps=instructor.from_litellm
) as mock_from_litellm:
crew.kickoff()
mock_from_litellm.assert_called()
@pytest.mark.vcr(filter_headers=["authorization"])
def test_task_with_no_arguments():
from crewai.tools import tool
@tool
def return_data() -> str:
"Useful to get the sales related data"
return "January: 5, February: 10, March: 15, April: 20, May: 25"
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, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
tools=[return_data],
allow_delegation=False,
)
task = Task(
description="Look at the available data and give me a sense on the total number of sales.",
expected_output="The total number of sales as an integer",
agent=researcher,
)
crew = Crew(agents=[researcher], tasks=[task])
result = crew.kickoff()
assert result.raw == "The total number of sales is 75."
def test_code_execution_flag_adds_code_tool_upon_kickoff():
from crewai_tools import CodeInterpreterTool
programmer = Agent(
role="Programmer",
goal="Write code to solve problems.",
backstory="You're a programmer who loves to solve problems with code.",
allow_delegation=False,
allow_code_execution=True,
)
task = Task(
description="How much is 2 + 2?",
expected_output="The result of the sum as an integer.",
agent=programmer,
)
crew = Crew(agents=[programmer], tasks=[task])
mock_task_output = TaskOutput(
description="Mock description", raw="mocked output", agent="mocked agent"
)
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
# Get the tools that were actually used in execution
_, kwargs = mock_execute_sync.call_args
used_tools = kwargs["tools"]
# Verify that exactly one tool was used and it was a CodeInterpreterTool
assert len(used_tools) == 1, "Should have exactly one tool"
assert isinstance(
used_tools[0], CodeInterpreterTool
), "Tool should be CodeInterpreterTool"
@pytest.mark.vcr(filter_headers=["authorization"])
def test_delegation_is_not_enabled_if_there_are_only_one_agent():
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, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
allow_delegation=True,
)
task = Task(
description="Look at the available data and give me a sense on the total number of sales.",
expected_output="The total number of sales as an integer",
agent=researcher,
)
crew = Crew(agents=[researcher], tasks=[task])
crew.kickoff()
assert task.tools == []
@pytest.mark.vcr(filter_headers=["authorization"])
def test_agents_do_not_get_delegation_tools_with_there_is_only_one_agent():
agent = Agent(
role="Researcher",
goal="Be super empathetic.",
backstory="You're love to sey howdy.",
allow_delegation=False,
)
task = Task(description="say howdy", expected_output="Howdy!", agent=agent)
crew = Crew(agents=[agent], tasks=[task])
result = crew.kickoff()
assert result.raw == "Howdy!"
assert len(agent.tools) == 0
@pytest.mark.vcr(filter_headers=["authorization"])
def test_sequential_crew_creation_tasks_without_agents():
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
# agent=researcher, # not having an agent on the task should throw an error
)
# Expected Output: The sequential crew should fail to create because the task is missing an agent
with pytest.raises(pydantic_core._pydantic_core.ValidationError) as exec_info:
Crew(
tasks=[task],
agents=[researcher],
process=Process.sequential,
)
assert exec_info.value.errors()[0]["type"] == "missing_agent_in_task"
assert (
"Agent is missing in the task with the following description"
in exec_info.value.errors()[0]["msg"]
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_agent_usage_metrics_are_captured_for_hierarchical_process():
agent = Agent(
role="Researcher",
goal="Be super empathetic.",
backstory="You're love to sey howdy.",
allow_delegation=False,
)
task = Task(description="Ask the researched to say hi!", expected_output="Howdy!")
crew = Crew(
agents=[agent], tasks=[task], process=Process.hierarchical, manager_llm="gpt-4o"
)
result = crew.kickoff()
assert result.raw == "Howdy!"
assert result.token_usage == UsageMetrics(
total_tokens=1673,
prompt_tokens=1562,
completion_tokens=111,
successful_requests=3,
cached_prompt_tokens=0,
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_hierarchical_crew_creation_tasks_with_agents():
"""
Agents are not required for tasks in a hierarchical process but sometimes they are still added
This test makes sure that the manager still delegates the task to the agent even if the agent is passed in the task
"""
task = Task(
description="Write one amazing paragraph about AI.",
expected_output="A single paragraph with 4 sentences.",
agent=writer,
)
crew = Crew(
tasks=[task],
agents=[writer, researcher],
process=Process.hierarchical,
manager_llm="gpt-4o",
)
mock_task_output = TaskOutput(
description="Mock description", raw="mocked output", agent="mocked agent"
)
# Because we are mocking execute_sync, we never hit the underlying _execute_core
# which sets the output attribute of the task
task.output = mock_task_output
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
# Verify execute_sync was called once
mock_execute_sync.assert_called_once()
# Get the tools argument from the call
_, kwargs = mock_execute_sync.call_args
tools = kwargs["tools"]
# Verify the delegation tools were passed correctly
assert len(tools) == 2
assert any(
"Delegate a specific task to one of the following coworkers: Senior Writer"
in tool.description
for tool in tools
)
assert any(
"Ask a specific question to one of the following coworkers: Senior Writer"
in tool.description
for tool in tools
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_hierarchical_crew_creation_tasks_with_async_execution():
"""
Tests that async tasks in hierarchical crews are handled correctly with proper delegation tools
"""
task = Task(
description="Write one amazing paragraph about AI.",
expected_output="A single paragraph with 4 sentences.",
agent=writer,
async_execution=True,
)
crew = Crew(
tasks=[task],
agents=[writer, researcher, ceo],
process=Process.hierarchical,
manager_llm="gpt-4o",
)
mock_task_output = TaskOutput(
description="Mock description", raw="mocked output", agent="mocked agent"
)
# Create a mock Future that returns our TaskOutput
mock_future = MagicMock(spec=Future)
mock_future.result.return_value = mock_task_output
# Because we are mocking execute_async, we never hit the underlying _execute_core
# which sets the output attribute of the task
task.output = mock_task_output
with patch.object(
Task, "execute_async", return_value=mock_future
) as mock_execute_async:
crew.kickoff()
# Verify execute_async was called once
mock_execute_async.assert_called_once()
# Get the tools argument from the call
_, kwargs = mock_execute_async.call_args
tools = kwargs["tools"]
# Verify the delegation tools were passed correctly
assert len(tools) == 2
assert any(
"Delegate a specific task to one of the following coworkers: Senior Writer\n"
in tool.description
for tool in tools
)
assert any(
"Ask a specific question to one of the following coworkers: Senior Writer\n"
in tool.description
for tool in tools
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_hierarchical_crew_creation_tasks_with_sync_last():
"""
Agents are not required for tasks in a hierarchical process but sometimes they are still added
This test makes sure that the manager still delegates the task to the agent even if the agent is passed in the task
"""
task = Task(
description="Write one amazing paragraph about AI.",
expected_output="A single paragraph with 4 sentences.",
agent=writer,
async_execution=True,
)
task2 = Task(
description="Write one amazing paragraph about AI.",
expected_output="A single paragraph with 4 sentences.",
async_execution=False,
)
crew = Crew(
tasks=[task, task2],
agents=[writer, researcher, ceo],
process=Process.hierarchical,
manager_llm="gpt-4o",
)
crew.kickoff()
assert crew.manager_agent is not None
assert crew.manager_agent.tools is not None
assert (
"Delegate a specific task to one of the following coworkers: Senior Writer, Researcher, CEO\n"
in crew.manager_agent.tools[0].description
)
def test_crew_inputs_interpolate_both_agents_and_tasks():
agent = Agent(
role="{topic} Researcher",
goal="Express hot takes on {topic}.",
backstory="You have a lot of experience with {topic}.",
)
task = Task(
description="Give me an analysis around {topic}.",
expected_output="{points} bullet points about {topic}.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
inputs = {"topic": "AI", "points": 5}
crew._interpolate_inputs(inputs=inputs) # Manual call for now
assert crew.tasks[0].description == "Give me an analysis around AI."
assert crew.tasks[0].expected_output == "5 bullet points about AI."
assert crew.agents[0].role == "AI Researcher"
assert crew.agents[0].goal == "Express hot takes on AI."
assert crew.agents[0].backstory == "You have a lot of experience with AI."
def test_crew_inputs_interpolate_both_agents_and_tasks_diff():
from unittest.mock import patch
agent = Agent(
role="{topic} Researcher",
goal="Express hot takes on {topic}.",
backstory="You have a lot of experience with {topic}.",
)
task = Task(
description="Give me an analysis around {topic}.",
expected_output="{points} bullet points about {topic}.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
with patch.object(Agent, "execute_task") as execute:
with patch.object(
Agent, "interpolate_inputs", wraps=agent.interpolate_inputs
) as interpolate_agent_inputs:
with patch.object(
Task, "interpolate_inputs", wraps=task.interpolate_inputs
) as interpolate_task_inputs:
execute.return_value = "ok"
crew.kickoff(inputs={"topic": "AI", "points": 5})
interpolate_agent_inputs.assert_called()
interpolate_task_inputs.assert_called()
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_does_not_interpolate_without_inputs():
from unittest.mock import patch
agent = Agent(
role="{topic} Researcher",
goal="Express hot takes on {topic}.",
backstory="You have a lot of experience with {topic}.",
)
task = Task(
description="Give me an analysis around {topic}.",
expected_output="{points} bullet points about {topic}.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
with patch.object(Agent, "interpolate_inputs") as interpolate_agent_inputs:
with patch.object(Task, "interpolate_inputs") as interpolate_task_inputs:
crew.kickoff()
interpolate_agent_inputs.assert_not_called()
interpolate_task_inputs.assert_not_called()
def test_task_callback_on_crew():
from unittest.mock import MagicMock, patch
researcher_agent = 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, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
allow_delegation=False,
)
list_ideas = Task(
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
expected_output="Bullet point list of 5 important events.",
agent=researcher_agent,
async_execution=True,
)
mock_callback = MagicMock()
crew = Crew(
agents=[researcher_agent],
process=Process.sequential,
tasks=[list_ideas],
task_callback=mock_callback,
)
with patch.object(Agent, "execute_task") as execute:
execute.return_value = "ok"
crew.kickoff()
assert list_ideas.callback is not None
mock_callback.assert_called_once()
args, _ = mock_callback.call_args
assert isinstance(args[0], TaskOutput)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_tools_with_custom_caching():
from unittest.mock import patch
from crewai.tools import tool
@tool
def multiplcation_tool(first_number: int, second_number: int) -> int:
"""Useful for when you need to multiply two numbers together."""
return first_number * second_number
def cache_func(args, result):
cache = result % 2 == 0
return cache
multiplcation_tool.cache_function = cache_func
writer1 = Agent(
role="Writer",
goal="You write lessons of math for kids.",
backstory="You're an expert in writing and you love to teach kids but you know nothing of math.",
tools=[multiplcation_tool],
allow_delegation=False,
)
writer2 = Agent(
role="Writer",
goal="You write lessons of math for kids.",
backstory="You're an expert in writing and you love to teach kids but you know nothing of math.",
tools=[multiplcation_tool],
allow_delegation=False,
)
task1 = Task(
description="What is 2 times 6? Return only the number after using the multiplication tool.",
expected_output="the result of multiplication",
agent=writer1,
)
task2 = Task(
description="What is 3 times 1? Return only the number after using the multiplication tool.",
expected_output="the result of multiplication",
agent=writer1,
)
task3 = Task(
description="What is 2 times 6? Return only the number after using the multiplication tool.",
expected_output="the result of multiplication",
agent=writer2,
)
task4 = Task(
description="What is 3 times 1? Return only the number after using the multiplication tool.",
expected_output="the result of multiplication",
agent=writer2,
)
crew = Crew(agents=[writer1, writer2], tasks=[task1, task2, task3, task4])
with patch.object(
CacheHandler, "add", wraps=crew._cache_handler.add
) as add_to_cache:
with patch.object(CacheHandler, "read", wraps=crew._cache_handler.read) as _:
result = crew.kickoff()
add_to_cache.assert_called_once_with(
tool="multiplcation_tool",
input={"first_number": 2, "second_number": 6},
output=12,
)
assert result.raw == "3"
@pytest.mark.vcr(filter_headers=["authorization"])
def test_using_contextual_memory():
from unittest.mock import patch
math_researcher = Agent(
role="Researcher",
goal="You research about math.",
backstory="You're an expert in research and you love to learn new things.",
allow_delegation=False,
)
task1 = Task(
description="Research a topic to teach a kid aged 6 about math.",
expected_output="A topic, explanation, angle, and examples.",
agent=math_researcher,
)
crew = Crew(
agents=[math_researcher],
tasks=[task1],
memory=True,
)
with patch.object(ContextualMemory, "build_context_for_task") as contextual_mem:
crew.kickoff()
contextual_mem.assert_called_once()
@pytest.mark.vcr(filter_headers=["authorization"])
def test_disabled_memory_using_contextual_memory():
from unittest.mock import patch
math_researcher = Agent(
role="Researcher",
goal="You research about math.",
backstory="You're an expert in research and you love to learn new things.",
allow_delegation=False,
)
task1 = Task(
description="Research a topic to teach a kid aged 6 about math.",
expected_output="A topic, explanation, angle, and examples.",
agent=math_researcher,
)
crew = Crew(
agents=[math_researcher],
tasks=[task1],
memory=False,
)
with patch.object(ContextualMemory, "build_context_for_task") as contextual_mem:
crew.kickoff()
contextual_mem.assert_not_called()
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_log_file_output(tmp_path):
test_file = tmp_path / "logs.txt"
tasks = [
Task(
description="Say Hi",
expected_output="The word: Hi",
agent=researcher,
)
]
crew = Crew(agents=[researcher], tasks=tasks, output_log_file=str(test_file))
crew.kickoff()
assert test_file.exists()
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_output_file_end_to_end(tmp_path):
"""Test output file functionality in a full crew context."""
# Create an agent
agent = Agent(
role="Researcher",
goal="Analyze AI topics",
backstory="You have extensive AI research experience.",
allow_delegation=False,
)
# Create a task with dynamic output file path
dynamic_path = tmp_path / "output_{topic}.txt"
task = Task(
description="Explain the advantages of {topic}.",
expected_output="A summary of the main advantages, bullet points recommended.",
agent=agent,
output_file=str(dynamic_path),
)
# Create and run the crew
crew = Crew(
agents=[agent],
tasks=[task],
process=Process.sequential,
)
crew.kickoff(inputs={"topic": "AI"})
# Verify file creation and cleanup
expected_file = tmp_path / "output_AI.txt"
assert expected_file.exists(), f"Output file {expected_file} was not created"
def test_crew_output_file_validation_failures():
"""Test output file validation failures in a crew context."""
agent = Agent(
role="Researcher",
goal="Analyze data",
backstory="You analyze data files.",
allow_delegation=False,
)
# Test path traversal
with pytest.raises(ValueError, match="Path traversal"):
task = Task(
description="Analyze data",
expected_output="Analysis results",
agent=agent,
output_file="../output.txt",
)
Crew(agents=[agent], tasks=[task]).kickoff()
# Test shell special characters
with pytest.raises(ValueError, match="Shell special characters"):
task = Task(
description="Analyze data",
expected_output="Analysis results",
agent=agent,
output_file="output.txt | rm -rf /",
)
Crew(agents=[agent], tasks=[task]).kickoff()
# Test shell expansion
with pytest.raises(ValueError, match="Shell expansion"):
task = Task(
description="Analyze data",
expected_output="Analysis results",
agent=agent,
output_file="~/output.txt",
)
Crew(agents=[agent], tasks=[task]).kickoff()
# Test invalid template variable
with pytest.raises(ValueError, match="Invalid template variable"):
task = Task(
description="Analyze data",
expected_output="Analysis results",
agent=agent,
output_file="{invalid-name}/output.txt",
)
Crew(agents=[agent], tasks=[task]).kickoff()
def test_manager_agent():
from unittest.mock import patch
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
)
manager = Agent(
role="Manager",
goal="Manage the crew and ensure the tasks are completed efficiently.",
backstory="You're an experienced manager, skilled in overseeing complex projects and guiding teams to success. Your role is to coordinate the efforts of the crew members, ensuring that each task is completed on time and to the highest standard.",
allow_delegation=False,
)
crew = Crew(
agents=[researcher, writer],
process=Process.hierarchical,
manager_agent=manager,
tasks=[task],
)
mock_task_output = TaskOutput(
description="Mock description", raw="mocked output", agent="mocked agent"
)
# Because we are mocking execute_sync, we never hit the underlying _execute_core
# which sets the output attribute of the task
task.output = mock_task_output
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
assert manager.allow_delegation is True
mock_execute_sync.assert_called()
def test_manager_agent_in_agents_raises_exception():
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
)
manager = Agent(
role="Manager",
goal="Manage the crew and ensure the tasks are completed efficiently.",
backstory="You're an experienced manager, skilled in overseeing complex projects and guiding teams to success. Your role is to coordinate the efforts of the crew members, ensuring that each task is completed on time and to the highest standard.",
allow_delegation=False,
)
with pytest.raises(pydantic_core._pydantic_core.ValidationError):
Crew(
agents=[researcher, writer, manager],
process=Process.hierarchical,
manager_agent=manager,
tasks=[task],
)
def test_manager_agent_with_tools_raises_exception():
from crewai.tools import tool
@tool
def testing_tool(first_number: int, second_number: int) -> int:
"""Useful for when you need to multiply two numbers together."""
return first_number * second_number
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
)
manager = Agent(
role="Manager",
goal="Manage the crew and ensure the tasks are completed efficiently.",
backstory="You're an experienced manager, skilled in overseeing complex projects and guiding teams to success. Your role is to coordinate the efforts of the crew members, ensuring that each task is completed on time and to the highest standard.",
allow_delegation=False,
tools=[testing_tool],
)
crew = Crew(
agents=[researcher, writer],
process=Process.hierarchical,
manager_agent=manager,
tasks=[task],
)
with pytest.raises(Exception):
crew.kickoff()
@patch("crewai.crew.Crew.kickoff")
@patch("crewai.crew.CrewTrainingHandler")
@patch("crewai.crew.TaskEvaluator")
@patch("crewai.crew.Crew.copy")
def test_crew_train_success(
copy_mock, task_evaluator, crew_training_handler, kickoff_mock
):
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
agent=researcher,
)
crew = Crew(
agents=[researcher, writer],
tasks=[task],
)
# Create a mock for the copied crew
copy_mock.return_value = crew
crew.train(
n_iterations=2, inputs={"topic": "AI"}, filename="trained_agents_data.pkl"
)
# Ensure kickoff is called on the copied crew
kickoff_mock.assert_has_calls(
[mock.call(inputs={"topic": "AI"}), mock.call(inputs={"topic": "AI"})]
)
task_evaluator.assert_has_calls(
[
mock.call(researcher),
mock.call().evaluate_training_data(
training_data=crew_training_handler().load(),
agent_id=str(researcher.id),
),
mock.call().evaluate_training_data().model_dump(),
mock.call(writer),
mock.call().evaluate_training_data(
training_data=crew_training_handler().load(),
agent_id=str(writer.id),
),
mock.call().evaluate_training_data().model_dump(),
]
)
crew_training_handler.assert_any_call("training_data.pkl")
crew_training_handler().load.assert_called()
crew_training_handler.assert_any_call("trained_agents_data.pkl")
crew_training_handler().load.assert_called()
crew_training_handler().save_trained_data.assert_has_calls(
[
mock.call(
agent_id="Researcher",
trained_data=task_evaluator().evaluate_training_data().model_dump(),
),
mock.call(
agent_id="Senior Writer",
trained_data=task_evaluator().evaluate_training_data().model_dump(),
),
]
)
def test_crew_train_error():
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article",
expected_output="5 bullet points with a paragraph for each idea.",
agent=researcher,
)
crew = Crew(
agents=[researcher, writer],
tasks=[task],
)
with pytest.raises(TypeError) as e:
crew.train() # type: ignore purposefully throwing err
assert "train() missing 1 required positional argument: 'n_iterations'" in str(
e
)
def test__setup_for_training():
researcher.allow_delegation = True
writer.allow_delegation = True
agents = [researcher, writer]
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article",
expected_output="5 bullet points with a paragraph for each idea.",
agent=researcher,
)
crew = Crew(
agents=agents,
tasks=[task],
)
assert crew._train is False
assert task.human_input is False
for agent in agents:
assert agent.allow_delegation is True
crew._setup_for_training("trained_agents_data.pkl")
assert crew._train is True
assert task.human_input is True
for agent in agents:
assert agent.allow_delegation is False
@pytest.mark.vcr(filter_headers=["authorization"])
def test_replay_feature():
list_ideas = Task(
description="Generate a list of 5 interesting ideas to explore for an article, where each bulletpoint is under 15 words.",
expected_output="Bullet point list of 5 important events. No additional commentary.",
agent=researcher,
)
write = Task(
description="Write a sentence about the events",
expected_output="A sentence about the events",
agent=writer,
context=[list_ideas],
)
crew = Crew(
agents=[researcher, writer],
tasks=[list_ideas, write],
process=Process.sequential,
)
with patch.object(Task, "execute_sync") as mock_execute_task:
mock_execute_task.return_value = TaskOutput(
description="Mock description",
raw="Mocked output for list of ideas",
agent="Researcher",
json_dict=None,
output_format=OutputFormat.RAW,
pydantic=None,
summary="Mocked output for list of ideas",
)
crew.kickoff()
crew.replay(str(write.id))
# Ensure context was passed correctly
assert mock_execute_task.call_count == 3
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_replay_error():
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article",
expected_output="5 bullet points with a paragraph for each idea.",
agent=researcher,
)
crew = Crew(
agents=[researcher, writer],
tasks=[task],
)
with pytest.raises(TypeError) as e:
crew.replay() # type: ignore purposefully throwing err
assert "task_id is required" in str(e)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_task_db_init():
agent = Agent(
role="Content Writer",
goal="Write engaging content on various topics.",
backstory="You have a background in journalism and creative writing.",
)
task = Task(
description="Write a detailed article about AI in healthcare.",
expected_output="A 1 paragraph article about AI.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
with patch.object(Task, "execute_sync") as mock_execute_task:
mock_execute_task.return_value = TaskOutput(
description="Write about AI in healthcare.",
raw="Artificial Intelligence (AI) is revolutionizing healthcare by enhancing diagnostic accuracy, personalizing treatment plans, and streamlining administrative tasks.",
agent="Content Writer",
json_dict=None,
output_format=OutputFormat.RAW,
pydantic=None,
summary="Write about AI in healthcare...",
)
crew.kickoff()
# Check if this runs without raising an exception
try:
db_handler = TaskOutputStorageHandler()
db_handler.load()
assert True # If we reach this point, no exception was raised
except Exception as e:
pytest.fail(f"An exception was raised: {str(e)}")
@pytest.mark.vcr(filter_headers=["authorization"])
def test_replay_task_with_context():
agent1 = Agent(
role="Researcher",
goal="Research AI advancements.",
backstory="You are an expert in AI research.",
)
agent2 = Agent(
role="Writer",
goal="Write detailed articles on AI.",
backstory="You have a background in journalism and AI.",
)
task1 = Task(
description="Research the latest advancements in AI.",
expected_output="A detailed report on AI advancements.",
agent=agent1,
)
task2 = Task(
description="Summarize the AI advancements report.",
expected_output="A summary of the AI advancements report.",
agent=agent2,
)
task3 = Task(
description="Write an article based on the AI advancements summary.",
expected_output="An article on AI advancements.",
agent=agent2,
)
task4 = Task(
description="Create a presentation based on the AI advancements article.",
expected_output="A presentation on AI advancements.",
agent=agent2,
context=[task1],
)
crew = Crew(
agents=[agent1, agent2],
tasks=[task1, task2, task3, task4],
process=Process.sequential,
)
mock_task_output1 = TaskOutput(
description="Research the latest advancements in AI.",
raw="Detailed report on AI advancements...",
agent="Researcher",
json_dict=None,
output_format=OutputFormat.RAW,
pydantic=None,
summary="Detailed report on AI advancements...",
)
mock_task_output2 = TaskOutput(
description="Summarize the AI advancements report.",
raw="Summary of the AI advancements report...",
agent="Writer",
json_dict=None,
output_format=OutputFormat.RAW,
pydantic=None,
summary="Summary of the AI advancements report...",
)
mock_task_output3 = TaskOutput(
description="Write an article based on the AI advancements summary.",
raw="Article on AI advancements...",
agent="Writer",
json_dict=None,
output_format=OutputFormat.RAW,
pydantic=None,
summary="Article on AI advancements...",
)
mock_task_output4 = TaskOutput(
description="Create a presentation based on the AI advancements article.",
raw="Presentation on AI advancements...",
agent="Writer",
json_dict=None,
output_format=OutputFormat.RAW,
pydantic=None,
summary="Presentation on AI advancements...",
)
with patch.object(Task, "execute_sync") as mock_execute_task:
mock_execute_task.side_effect = [
mock_task_output1,
mock_task_output2,
mock_task_output3,
mock_task_output4,
]
crew.kickoff()
db_handler = TaskOutputStorageHandler()
assert db_handler.load() != []
with patch.object(Task, "execute_sync") as mock_replay_task:
mock_replay_task.return_value = mock_task_output4
replayed_output = crew.replay(str(task4.id))
assert replayed_output.raw == "Presentation on AI advancements..."
db_handler.reset()
@pytest.mark.vcr(filter_headers=["authorization"])
def test_replay_with_context():
agent = Agent(role="test_agent", backstory="Test Description", goal="Test Goal")
task1 = Task(
description="Context Task", expected_output="Say Task Output", agent=agent
)
task2 = Task(
description="Test Task", expected_output="Say Hi", agent=agent, context=[task1]
)
context_output = TaskOutput(
description="Context Task Output",
agent="test_agent",
raw="context raw output",
pydantic=None,
json_dict={},
output_format=OutputFormat.RAW,
)
task1.output = context_output
crew = Crew(agents=[agent], tasks=[task1, task2], process=Process.sequential)
with patch(
"crewai.utilities.task_output_storage_handler.TaskOutputStorageHandler.load",
return_value=[
{
"task_id": str(task1.id),
"output": {
"description": context_output.description,
"summary": context_output.summary,
"raw": context_output.raw,
"pydantic": context_output.pydantic,
"json_dict": context_output.json_dict,
"output_format": context_output.output_format,
"agent": context_output.agent,
},
"inputs": {},
},
{
"task_id": str(task2.id),
"output": {
"description": "Test Task Output",
"summary": None,
"raw": "test raw output",
"pydantic": None,
"json_dict": {},
"output_format": "json",
"agent": "test_agent",
},
"inputs": {},
},
],
):
crew.replay(str(task2.id))
assert crew.tasks[1].context[0].output.raw == "context raw output"
@pytest.mark.vcr(filter_headers=["authorization"])
def test_replay_with_invalid_task_id():
agent = Agent(role="test_agent", backstory="Test Description", goal="Test Goal")
task1 = Task(
description="Context Task", expected_output="Say Task Output", agent=agent
)
task2 = Task(
description="Test Task", expected_output="Say Hi", agent=agent, context=[task1]
)
context_output = TaskOutput(
description="Context Task Output",
agent="test_agent",
raw="context raw output",
pydantic=None,
json_dict={},
output_format=OutputFormat.RAW,
)
task1.output = context_output
crew = Crew(agents=[agent], tasks=[task1, task2], process=Process.sequential)
with patch(
"crewai.utilities.task_output_storage_handler.TaskOutputStorageHandler.load",
return_value=[
{
"task_id": str(task1.id),
"output": {
"description": context_output.description,
"summary": context_output.summary,
"raw": context_output.raw,
"pydantic": context_output.pydantic,
"json_dict": context_output.json_dict,
"output_format": context_output.output_format,
"agent": context_output.agent,
},
"inputs": {},
},
{
"task_id": str(task2.id),
"output": {
"description": "Test Task Output",
"summary": None,
"raw": "test raw output",
"pydantic": None,
"json_dict": {},
"output_format": "json",
"agent": "test_agent",
},
"inputs": {},
},
],
):
with pytest.raises(
ValueError,
match="Task with id bf5b09c9-69bd-4eb8-be12-f9e5bae31c2d not found in the crew's tasks.",
):
crew.replay("bf5b09c9-69bd-4eb8-be12-f9e5bae31c2d")
@pytest.mark.vcr(filter_headers=["authorization"])
@patch.object(Crew, "_interpolate_inputs")
def test_replay_interpolates_inputs_properly(mock_interpolate_inputs):
agent = Agent(role="test_agent", backstory="Test Description", goal="Test Goal")
task1 = Task(description="Context Task", expected_output="Say {name}", agent=agent)
task2 = Task(
description="Test Task",
expected_output="Say Hi to {name}",
agent=agent,
context=[task1],
)
context_output = TaskOutput(
description="Context Task Output",
agent="test_agent",
raw="context raw output",
pydantic=None,
json_dict={},
output_format=OutputFormat.RAW,
)
task1.output = context_output
crew = Crew(agents=[agent], tasks=[task1, task2], process=Process.sequential)
crew.kickoff(inputs={"name": "John"})
with patch(
"crewai.utilities.task_output_storage_handler.TaskOutputStorageHandler.load",
return_value=[
{
"task_id": str(task1.id),
"output": {
"description": context_output.description,
"summary": context_output.summary,
"raw": context_output.raw,
"pydantic": context_output.pydantic,
"json_dict": context_output.json_dict,
"output_format": context_output.output_format,
"agent": context_output.agent,
},
"inputs": {"name": "John"},
},
{
"task_id": str(task2.id),
"output": {
"description": "Test Task Output",
"summary": None,
"raw": "test raw output",
"pydantic": None,
"json_dict": {},
"output_format": "json",
"agent": "test_agent",
},
"inputs": {"name": "John"},
},
],
):
crew.replay(str(task2.id))
assert crew._inputs == {"name": "John"}
assert mock_interpolate_inputs.call_count == 2
@pytest.mark.vcr(filter_headers=["authorization"])
def test_replay_setup_context():
agent = Agent(role="test_agent", backstory="Test Description", goal="Test Goal")
task1 = Task(description="Context Task", expected_output="Say {name}", agent=agent)
task2 = Task(
description="Test Task",
expected_output="Say Hi to {name}",
agent=agent,
)
context_output = TaskOutput(
description="Context Task Output",
agent="test_agent",
raw="context raw output",
pydantic=None,
json_dict={},
output_format=OutputFormat.RAW,
)
task1.output = context_output
crew = Crew(agents=[agent], tasks=[task1, task2], process=Process.sequential)
with patch(
"crewai.utilities.task_output_storage_handler.TaskOutputStorageHandler.load",
return_value=[
{
"task_id": str(task1.id),
"output": {
"description": context_output.description,
"summary": context_output.summary,
"raw": context_output.raw,
"pydantic": context_output.pydantic,
"json_dict": context_output.json_dict,
"output_format": context_output.output_format,
"agent": context_output.agent,
},
"inputs": {"name": "John"},
},
{
"task_id": str(task2.id),
"output": {
"description": "Test Task Output",
"summary": None,
"raw": "test raw output",
"pydantic": None,
"json_dict": {},
"output_format": "json",
"agent": "test_agent",
},
"inputs": {"name": "John"},
},
],
):
crew.replay(str(task2.id))
# Check if the first task's output was set correctly
assert crew.tasks[0].output is not None
assert isinstance(crew.tasks[0].output, TaskOutput)
assert crew.tasks[0].output.description == "Context Task Output"
assert crew.tasks[0].output.agent == "test_agent"
assert crew.tasks[0].output.raw == "context raw output"
assert crew.tasks[0].output.output_format == OutputFormat.RAW
assert crew.tasks[1].prompt_context == "context raw output"
def test_key():
tasks = [
Task(
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
expected_output="Bullet point list of 5 important events.",
agent=researcher,
),
Task(
description="Write a 1 amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="A 4 paragraph article about AI.",
agent=writer,
),
]
crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=tasks,
)
hash = hashlib.md5(
f"{researcher.key}|{writer.key}|{tasks[0].key}|{tasks[1].key}".encode()
).hexdigest()
assert crew.key == hash
def test_key_with_interpolated_inputs():
researcher = Agent(
role="{topic} Researcher",
goal="Make the best research and analysis on content {topic}",
backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
allow_delegation=False,
)
writer = Agent(
role="{topic} Senior Writer",
goal="Write the best content about {topic}",
backstory="You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer.",
allow_delegation=False,
)
tasks = [
Task(
description="Give me a list of 5 interesting ideas about {topic} to explore for an article, what makes them unique and interesting.",
expected_output="Bullet point list of 5 important events.",
agent=researcher,
),
Task(
description="Write a 1 amazing paragraph highlight for each idea of {topic} that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="A 4 paragraph article about AI.",
agent=writer,
),
]
crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=tasks,
)
hash = hashlib.md5(
f"{researcher.key}|{writer.key}|{tasks[0].key}|{tasks[1].key}".encode()
).hexdigest()
assert crew.key == hash
curr_key = crew.key
crew._interpolate_inputs({"topic": "AI"})
assert crew.key == curr_key
def test_conditional_task_requirement_breaks_when_singular_conditional_task():
def condition_fn(output) -> bool:
return output.raw.startswith("Andrew Ng has!!")
task = ConditionalTask(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
condition=condition_fn,
)
with pytest.raises(pydantic_core._pydantic_core.ValidationError):
Crew(
agents=[researcher, writer],
tasks=[task],
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_conditional_task_last_task_when_conditional_is_true():
def condition_fn(output) -> bool:
return True
task1 = Task(
description="Say Hi",
expected_output="Hi",
agent=researcher,
)
task2 = ConditionalTask(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
condition=condition_fn,
agent=writer,
)
crew = Crew(
agents=[researcher, writer],
tasks=[task1, task2],
)
result = crew.kickoff()
assert result.raw.startswith(
"Hi\n\nHere are five interesting ideas for articles focused on AI and AI agents, each accompanied by a compelling paragraph to showcase the potential impact and depth of each topic:"
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_conditional_task_last_task_when_conditional_is_false():
def condition_fn(output) -> bool:
return False
task1 = Task(
description="Say Hi",
expected_output="Hi",
agent=researcher,
)
task2 = ConditionalTask(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
condition=condition_fn,
agent=writer,
)
crew = Crew(
agents=[researcher, writer],
tasks=[task1, task2],
)
result = crew.kickoff()
assert result.raw == "Hi"
def test_conditional_task_requirement_breaks_when_task_async():
def my_condition(context):
return context.get("some_value") > 10
task = ConditionalTask(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
execute_async=True,
condition=my_condition,
agent=researcher,
)
task2 = Task(
description="Say Hi",
expected_output="Hi",
agent=writer,
)
with pytest.raises(pydantic_core._pydantic_core.ValidationError):
Crew(
agents=[researcher, writer],
tasks=[task, task2],
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_conditional_should_skip():
task1 = Task(description="Return hello", expected_output="say hi", agent=researcher)
condition_mock = MagicMock(return_value=False)
task2 = ConditionalTask(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
condition=condition_mock,
agent=writer,
)
crew_met = Crew(
agents=[researcher, writer],
tasks=[task1, task2],
)
with patch.object(Task, "execute_sync") as mock_execute_sync:
mock_execute_sync.return_value = TaskOutput(
description="Task 1 description",
raw="Task 1 output",
agent="Researcher",
)
result = crew_met.kickoff()
assert mock_execute_sync.call_count == 1
assert condition_mock.call_count == 1
assert condition_mock() is False
assert task2.output is None
assert result.raw.startswith("Task 1 output")
@pytest.mark.vcr(filter_headers=["authorization"])
def test_conditional_should_execute():
task1 = Task(description="Return hello", expected_output="say hi", agent=researcher)
condition_mock = MagicMock(
return_value=True
) # should execute this conditional task
task2 = ConditionalTask(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
condition=condition_mock,
agent=writer,
)
crew_met = Crew(
agents=[researcher, writer],
tasks=[task1, task2],
)
with patch.object(Task, "execute_sync") as mock_execute_sync:
mock_execute_sync.return_value = TaskOutput(
description="Task 1 description",
raw="Task 1 output",
agent="Researcher",
)
crew_met.kickoff()
assert condition_mock.call_count == 1
assert condition_mock() is True
assert mock_execute_sync.call_count == 2
@mock.patch("crewai.crew.CrewEvaluator")
@mock.patch("crewai.crew.Crew.copy")
@mock.patch("crewai.crew.Crew.kickoff")
def test_crew_testing_function(kickoff_mock, copy_mock, crew_evaluator):
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
agent=researcher,
)
crew = Crew(
agents=[researcher],
tasks=[task],
)
# Create a mock for the copied crew
copy_mock.return_value = crew
n_iterations = 2
crew.test(n_iterations, openai_model_name="gpt-4o-mini", inputs={"topic": "AI"})
# Ensure kickoff is called on the copied crew
kickoff_mock.assert_has_calls(
[mock.call(inputs={"topic": "AI"}), mock.call(inputs={"topic": "AI"})]
)
crew_evaluator.assert_has_calls(
[
mock.call(crew, "gpt-4o-mini"),
mock.call().set_iteration(1),
mock.call().set_iteration(2),
mock.call().print_crew_evaluation_result(),
]
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_hierarchical_verbose_manager_agent():
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
)
crew = Crew(
agents=[researcher, writer],
tasks=[task],
process=Process.hierarchical,
manager_llm="gpt-4o",
verbose=True,
)
crew.kickoff()
assert crew.manager_agent is not None
assert crew.manager_agent.verbose
@pytest.mark.vcr(filter_headers=["authorization"])
def test_hierarchical_verbose_false_manager_agent():
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
)
crew = Crew(
agents=[researcher, writer],
tasks=[task],
process=Process.hierarchical,
manager_llm="gpt-4o",
verbose=False,
)
crew.kickoff()
assert crew.manager_agent is not None
assert not crew.manager_agent.verbose
def test_task_tools_preserve_code_execution_tools():
"""
Test that task tools don't override code execution tools when allow_code_execution=True
"""
from typing import Type
from crewai_tools import CodeInterpreterTool
from pydantic import BaseModel, Field
from crewai.tools import BaseTool
class TestToolInput(BaseModel):
"""Input schema for TestTool."""
query: str = Field(..., description="Query to process")
class TestTool(BaseTool):
name: str = "Test Tool"
description: str = "A test tool that just returns the input"
args_schema: Type[BaseModel] = TestToolInput
def _run(self, query: str) -> str:
return f"Processed: {query}"
# Create a programmer agent with code execution enabled
programmer = Agent(
role="Programmer",
goal="Write code to solve problems.",
backstory="You're a programmer who loves to solve problems with code.",
allow_delegation=True,
allow_code_execution=True,
)
# Create a code reviewer agent
reviewer = Agent(
role="Code Reviewer",
goal="Review code for bugs and improvements",
backstory="You're an experienced code reviewer who ensures code quality and best practices.",
allow_delegation=True,
allow_code_execution=True,
)
# Create a task with its own tools
task = Task(
description="Write a program to calculate fibonacci numbers.",
expected_output="A working fibonacci calculator.",
agent=programmer,
tools=[TestTool()],
)
crew = Crew(
agents=[programmer, reviewer],
tasks=[task],
process=Process.sequential,
)
mock_task_output = TaskOutput(
description="Mock description", raw="mocked output", agent="mocked agent"
)
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
# Get the tools that were actually used in execution
_, kwargs = mock_execute_sync.call_args
used_tools = kwargs["tools"]
# Verify all expected tools are present
assert any(
isinstance(tool, TestTool) for tool in used_tools
), "Task's TestTool should be present"
assert any(
isinstance(tool, CodeInterpreterTool) for tool in used_tools
), "CodeInterpreterTool should be present"
assert any(
"delegate" in tool.name.lower() for tool in used_tools
), "Delegation tool should be present"
# Verify the total number of tools (TestTool + CodeInterpreter + 2 delegation tools)
assert (
len(used_tools) == 4
), "Should have TestTool, CodeInterpreter, and 2 delegation tools"
@pytest.mark.vcr(filter_headers=["authorization"])
def test_multimodal_flag_adds_multimodal_tools():
"""
Test that an agent with multimodal=True automatically has multimodal tools added to the task execution.
"""
from crewai.tools.agent_tools.add_image_tool import AddImageTool
# Create an agent that supports multimodal
multimodal_agent = Agent(
role="Multimodal Analyst",
goal="Handle multiple media types (text, images, etc.).",
backstory="You're an agent specialized in analyzing text, images, and other media.",
allow_delegation=False,
multimodal=True, # crucial for adding the multimodal tool
)
# Create a dummy task
task = Task(
description="Describe what's in this image and generate relevant metadata.",
expected_output="An image description plus any relevant metadata.",
agent=multimodal_agent,
)
# Define a crew with the multimodal agent
crew = Crew(agents=[multimodal_agent], tasks=[task], process=Process.sequential)
mock_task_output = TaskOutput(
description="Mock description", raw="mocked output", agent="mocked agent"
)
# Mock execute_sync to verify the tools passed at runtime
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
# Get the tools that were actually used in execution
_, kwargs = mock_execute_sync.call_args
used_tools = kwargs["tools"]
# Check that the multimodal tool was added
assert any(
isinstance(tool, AddImageTool) for tool in used_tools
), "AddImageTool should be present when agent is multimodal"
# Verify we have exactly one tool (just the AddImageTool)
assert len(used_tools) == 1, "Should only have the AddImageTool"
@pytest.mark.vcr(filter_headers=["authorization"])
def test_multimodal_agent_image_tool_handling():
"""
Test that multimodal agents properly handle image tools in the CrewAgentExecutor
"""
# Create a multimodal agent
multimodal_agent = Agent(
role="Image Analyst",
goal="Analyze images and provide descriptions",
backstory="You're an expert at analyzing and describing images.",
allow_delegation=False,
multimodal=True,
)
# Create a task that involves image analysis
task = Task(
description="Analyze this image and describe what you see.",
expected_output="A detailed description of the image.",
agent=multimodal_agent,
)
crew = Crew(agents=[multimodal_agent], tasks=[task])
# Mock the image tool response
mock_image_tool_result = {
"role": "user",
"content": [
{"type": "text", "text": "Please analyze this image"},
{
"type": "image_url",
"image_url": {
"url": "https://example.com/test-image.jpg",
},
},
],
}
# Create a mock task output for the final result
mock_task_output = TaskOutput(
description="Mock description",
raw="A detailed analysis of the image",
agent="Image Analyst",
)
with patch.object(Task, "execute_sync") as mock_execute_sync:
# Set up the mock to return our task output
mock_execute_sync.return_value = mock_task_output
# Execute the crew
crew.kickoff()
# Get the tools that were passed to execute_sync
_, kwargs = mock_execute_sync.call_args
tools = kwargs["tools"]
# Verify the AddImageTool is present and properly configured
image_tools = [tool for tool in tools if tool.name == "Add image to content"]
assert len(image_tools) == 1, "Should have exactly one AddImageTool"
# Test the tool's execution
image_tool = image_tools[0]
result = image_tool._run(
image_url="https://example.com/test-image.jpg",
action="Please analyze this image",
)
# Verify the tool returns the expected format
assert result == mock_image_tool_result
assert result["role"] == "user"
assert len(result["content"]) == 2
assert result["content"][0]["type"] == "text"
assert result["content"][1]["type"] == "image_url"
@pytest.mark.vcr(filter_headers=["authorization"])
def test_multimodal_agent_live_image_analysis():
"""
Test that multimodal agents can analyze images through a real API call
"""
# Create a multimodal agent
image_analyst = Agent(
role="Image Analyst",
goal="Analyze images with high attention to detail",
backstory="You're an expert at visual analysis, trained to notice and describe details in images.",
allow_delegation=False,
multimodal=True,
verbose=True,
llm="gpt-4o",
)
# Create a task for image analysis
analyze_image = Task(
description="""
Analyze the provided image and describe what you see in detail.
Focus on main elements, colors, composition, and any notable details.
Image: {image_url}
""",
expected_output="A comprehensive description of the image contents.",
agent=image_analyst,
)
# Create and run the crew
crew = Crew(agents=[image_analyst], tasks=[analyze_image])
# Execute with an image URL
result = crew.kickoff(
inputs={
"image_url": "https://media.istockphoto.com/id/946087016/photo/aerial-view-of-lower-manhattan-new-york.jpg?s=612x612&w=0&k=20&c=viLiMRznQ8v5LzKTt_LvtfPFUVl1oiyiemVdSlm29_k="
}
)
# Verify we got a meaningful response
assert isinstance(result.raw, str)
assert len(result.raw) > 100 # Expecting a detailed analysis
assert "error" not in result.raw.lower() # No error messages in response