mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-09 08:08:32 +00:00
* 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>
3340 lines
122 KiB
Python
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
|