Compare commits

..

10 Commits

Author SHA1 Message Date
Lorenze Jay
5c4ee9fa9d more fixes 2025-01-03 16:33:34 -08:00
Lorenze Jay
3c8372be0f fix knowledge docs with correct imports 2025-01-03 16:31:22 -08:00
Gui Vieira
30bd79390a [ENG-227] Record task execution timestamps (#1844) 2025-01-03 13:12:13 -05:00
João Moura
d1e2430aac preparing new version 2025-01-03 12:42:47 -03:00
Marco Vinciguerra
bfe2c44f55 feat: add documentation functions (#1831)
* feat: add docstring

* feat: add new docstring

* fix: linting

---------

Co-authored-by: João Moura <joaomdmoura@gmail.com>
2025-01-02 20:42:08 -03:00
siddharth Sambharia
845951a0db .md to .mdx and mint.json updated (no content changes) (#1836)
Co-authored-by: siddharthsambharia-portkey <siddhath.s@portkey.ai>
Co-authored-by: João Moura <joaomdmoura@gmail.com>
2025-01-02 20:35:37 -03:00
Tony Kipkemboi
c1172a685a Update docs (#1842)
* Update portkey docs

* Add more examples to Knowledge docs + clarify issue with `embedder`

* fix knowledge params and usage instructions
2025-01-02 16:10:31 -05:00
Brandon Hancock (bhancock_ai)
4bcc3b532d Trying out timeouts (#1840)
* Make tests green again

* Add Git validations for publishing tools  (#1381)

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

* fix: JSON encoding date objects (#1374)

* Update README  (#1376)

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

* Update the  example to use YAML format

* Update  to come after setup and edits

* Remove double tool instance

* docs: correct miswritten command name (#1365)

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

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

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

* add plotting to flows documentation (#1394)

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

* Telemetry for flows

* store node names

* Brandon/cre 291 flow improvements (#1390)

* Implement joao feedback

* update colors for crew nodes

* clean up

* more linting clean up

* round legend corners

---------

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

* quick fixes (#1385)

* quick fixes

* add generic name

---------

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

* reduce import time by 6x (#1396)

* reduce import by 6x

* fix linting

* Added version details (#1402)

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

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

* fix task cloning error (#1416)

* Migrate docs from MkDocs to Mintlify (#1423)

* add new mintlify docs

* add favicon.svg

* minor edits

* add github stats

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

* improved logger

* log file looks better

* better lines written to log file

---------

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

* fixing tests

* preparing new version

* updating init

* Preparing new version

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

* Trying to fix linting

* fixing more type issues

* clean up ci

* more ci fixes

---------

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

* Feat/poetry to uv migration (#1406)

* feat: Start migrating to UV

* feat: add uv to flows

* feat: update docs on Poetry -> uv

* feat: update docs and uv.locl

* feat: update tests and github CI

* feat: run ruff format

* feat: update typechecking

* feat: fix type checking

* feat: update python version

* feat: type checking gic

* feat: adapt uv command to run the tool repo

* Adapt tool build command to uv

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

* feat: add uv to tools

* fix; tests

* fix: remove breakpoint

* fix :test

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

* fix: tests

* feat: add validation for ˆ character on pyproject

* feat: add run_crew to pyproject if doesnt exist

* feat: add validation for poetry migration

* fix: warning

---------

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

* fix: training issue (#1433)

* fix: training issue

* fix: output from crew

* fix: message

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

* Fix Cache Typo in Documentation (#1441)

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

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

* fix typo in template file (#1432)

* Adapt Tools CLI to uv (#1455)

* Adapt Tools CLI to UV

* Fix failing test

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

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

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

* Update docs to use  instead of

* Add Flows YouTube tutorial & link images

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

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

* updated CLI to allow for submitting API keys

* updated click prompt to remove default number

* removed all unnecessary comments

* feat: implement crew creation CLI command

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

* refactered select_choice function for early return

* refactored  select_provider to have an ealry return

* cleanup of comments

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

* small comment cleanup

* fix unnecessary deps

---------

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

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

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

* Feat/memory base (#1444)

* byom - short/entity memory

* better

* rm uneeded

* fix text

* use context

* rm dep and sync

* type check fix

* fixed test using new cassete

* fixing types

* fixed types

* fix types

* fixed types

* fixing types

* fix type

* cassette update

* just mock the return of short term mem

* remove print

* try catch block

* added docs

* dding error handling here

* preparing new version

* fixing annotations

* fix tasks and agents ordering

* Avoiding exceptions

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

* fix tool calling issue (#1467)

* fix tool calling issue

* Update tool type check

* Drop print

* cutting new version

* new verison

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

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

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

* fix(docs): typo (#1470)

* drop unneccesary tests (#1484)

* drop uneccesary tests

* fix linting

* simplify flow (#1482)

* simplify flow

* propogate changes

* Update docs and scripts

* Template fix

* make flow kickoff sync

* Clean up docs

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

* ensure original embedding config works (#1476)

* ensure original embedding config works

* some fixes

* raise error on unsupported provider

* WIP: brandons notes

* fixes

* rm prints

* fixed docs

* fixed run types

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

---------

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

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

* use copy to split testing and training on crews

* make tests handle new copy functionality on train and test

* fix last test

* fix test

* preparing new verison

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

* updated CLI to allow for submitting API keys

* updated click prompt to remove default number

* removed all unnecessary comments

* feat: implement crew creation CLI command

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

* refactered select_choice function for early return

* refactored  select_provider to have an ealry return

* cleanup of comments

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

* small comment cleanup

* fix unnecessary deps

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

* Minor doc updates

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

* ruff updates

* Fix spelling mistake

---------

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

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

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

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

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

* Update return type

* Fix memory imports for embedding functions (#1497)

* updating crewai version

* new version

* new version

* update plot command (#1504)

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

* feat: add tomli so we can support 3.10

* feat: add validation for poetry data

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

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

* improve tool text description and args (#1512)

* improve tool text descriptoin and args

* fix lint

* Drop print

* add back in docstring

* Improve tooling docs

* Update flow docs to talk about self evaluation example

* Update flow docs to talk about self evaluation example

* Update flows.mdx - Fix link

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

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

* fix failing test

* adding more error logs to test thats failing

* try again

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

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

* fix user found issue

* remove prints

* prepare new version

* Added security.md file (#1533)

* Disable telemetry explicitly (#1536)

* Disable telemetry explicitly

* fix linting

* revert parts to og

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

* Add llm providers accordion group (#1534)

* add llm providers accordion group

* fix numbering

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

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

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

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

* move base_tool to main package and consolidate tool desscription generation

* update import path

* update tests

* update doc

* add base_tool test

* migrate agent delegation tools to use BaseTool

* update tests

* update import path for tool

* fix lint

* update param signature

* add from_langchain to BaseTool for backwards support of langchain tools

* fix the case where StructuredTool doesn't have func

---------

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

* Update docs  (#1550)

* add llm providers accordion group

* fix numbering

* Fix directory tree & add llms to accordion

* Feat/ibm memory (#1549)

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

* Update docs as well.

* clean up for PR

* add inputs to flows (#1553)

* add inputs to flows

* fix flows lint

* Increase providers fetching timeout

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

* docs update (#1558)

* add llm providers accordion group

* fix numbering

* Fix directory tree & add llms to accordion

* update crewai enterprise link in docs

* Feat/watson in cli (#1535)

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

* support new models

* clean up prints

* Add support for cerebras

* Fix watson keys

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

* fix missing config (#1557)

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

* preparing new version

* updating LLM docs

* preparing new version

* curring new version

* preparing new version

* preparing new version

* add missing init

* fix LiteLLM callback replacement

* fix test_agent_usage_metrics_are_captured_for_hierarchical_process

* removing prints

* fix: Step callback issue (#1595)

* fix: Step callback issue

* fix: Add empty thought since its required

* Cached prompt tokens on usage metrics

* do not include cached on total

* Fix crew_train_success test

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

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

* Integrate Mem0

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

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

* pending commit for _fetch_user_memories

* update poetry.lock

* fixes mypy issues

* fix mypy checks

* New fixes for user_id

* remove memory_provider

* handle memory_provider

* checks for memory_config

* add mem0 to dependency

* Update pyproject.toml

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

* update docs

* update doc

* bump mem0 version

* fix api error msg and mypy issue

* mypy fix

* resolve comments

* fix memory usage without mem0

* mem0 version bump

* lazy import mem0

---------

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

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

* upgrade chroma and adjust embedder function generator

* >= version

* linted

* preparing enw version

* adding before and after crew

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

* docs: add gh_token documentation to GithubSearchTool

* Move kickoff callbacks to crew's domain

* Cassettes

* Make mypy happy

* Knowledge (#1567)

* initial knowledge

* WIP

* Adding core knowledge sources

* Improve types and better support for file paths

* added additional sources

* fix linting

* update yaml to include optional deps

* adding in lorenze feedback

* ensure embeddings are persisted

* improvements all around Knowledge class

* return this

* properly reset memory

* properly reset memory+knowledge

* consolodation and improvements

* linted

* cleanup rm unused embedder

* fix test

* fix duplicate

* generating cassettes for knowledge test

* updated default embedder

* None embedder to use default on pipeline cloning

* improvements

* fixed text_file_knowledge

* mypysrc fixes

* type check fixes

* added extra cassette

* just mocks

* linted

* mock knowledge query to not spin up db

* linted

* verbose run

* put a flag

* fix

* adding docs

* better docs

* improvements from review

* more docs

* linted

* rm print

* more fixes

* clearer docs

* added docstrings and type hints for cli

---------

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

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

* Update Perplexity example in documentation (#1623)

* Fix threading

* preparing new version

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

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

* add knowledge to mint.json

* Improve typed task outputs (#1651)

* V1 working

* clean up imports and prints

* more clean up and add tests

* fixing tests

* fix test

* fix linting

* Fix tests

* Fix linting

* add doc string as requested by eduardo

* Update Github actions (#1639)

* actions/checkout@v4

* actions/cache@v4

* actions/setup-python@v5

---------

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

* update (#1638)

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

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

* Update readme for running mypy (#1614)

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

* Feat/remove langchain (#1654)

* feat: add initial changes from langchain

* feat: remove kwargs of being processed

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

* feat: change docs

* feat: remove forced requirements for parameter

* feat add tests for new structure tool

* feat: fix tests and adapt code for args

* Feat/remove langchain (#1668)

* feat: add initial changes from langchain

* feat: remove kwargs of being processed

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

* feat: change docs

* feat: remove forced requirements for parameter

* feat add tests for new structure tool

* feat: fix tests and adapt code for args

* fix tool calling for langchain tools

* doc strings

---------

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

* added knowledge to agent level (#1655)

* added knowledge to agent level

* linted

* added doc

* added from suggestions

* added test

* fixes from discussion

* fix docs

* fix test

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

* fix test

* rm unused

* linted

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

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

* docs: improve tasks documentation clarity and structure

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

* docs: update agent documentation with improved examples and formatting

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

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

* docs: simplify LLMs documentation title

* docs: improve installation guide clarity and structure

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

* docs: improve introduction page organization and clarity

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

* docs: add enterprise and community cards

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

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

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

* remove print statements

* preparing new version

* fix missing code in flows docs (#1690)

* docs: improve tasks documentation clarity and structure

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

* docs: update agent documentation with improved examples and formatting

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

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

* docs: simplify LLMs documentation title

* docs: improve installation guide clarity and structure

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

* docs: improve introduction page organization and clarity

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

* docs: add enterprise and community cards

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

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

---------

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

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

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

* Update using langchain tools docs (#1664)

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

* Use .env

* Add  Code back

---------

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

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

* added path to RAGStorage

* added path to short term and entity memory

* add path for long_term_storage for completeness

---------

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

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

* Create openlit-observability.mdx

* Update doc with images and steps

* Update mkdocs.yml and add OpenLIT guide link

---------

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

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

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

* Knowledge project directory standard (#1691)

* Knowledge project directory standard

* fixed types

* comment fix

* made base file knowledge source an abstract class

* cleaner validator on model_post_init

* fix type checker

* cleaner refactor

* better template

* Update README.md (#1694)

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

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

* Talk about getting structured consistent outputs with tasks.

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

* remove all references to pipeline and router

* fix linting

* drop poetry.lock

* docs: add nvidia as provider (#1632)

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

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

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

* v1 of HITL working

* Drop print statements

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

* refactor and more clear messages

* Fix type issue

* fix tests

* Fix test again

* Drop extra print

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

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

* Fix spelling issues.

* updating tools

* curting new verson

* Incorporate Stale PRs that have feedback (#1693)

* incorporate #1683

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

* Fix env issue

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

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

* Incorporate feedback from crewai reviewer

* Incorporate @lorenzejay feedback

* drop metadata requirement (#1712)

* drop metadata requirement

* fix linting

* Update docs for new knowledge

* more linting

* more linting

* make save_documents private

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

* add support for langfuse with litellm (#1721)

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

* docs: Add quotes to agentops installing command

* feat: Add ContextualMemory to __init__

* feat: remove import due to circular improt

* feat: update tasks config main template typos

* Fixed output_file not respecting system path (#1726)

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

* fix:typo error (#1732)

* Update crew_agent_executor.py

typo error

* Update en.json

typo error

* Fix Knowledge docs Spaceflight News API dead link

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

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

* Add doc structured tool (#1713)

* Add doc structured tool

* Fix example

---------

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

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

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

* format bullet points (#1734)

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

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

* Update annotations.py

* Update utils.py

* Update crew_base.py

* Update utils.py

* Update crew_base.py

---------

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

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

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

Reset chromadb client and nullifies references before
removing directory.

* Nit for clarity

* did the same for knowledge_storage

* cleanup

* cleanup order

* Cleanup after the rm of the directories

---------

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

* restrict python version compatibility (#1731)

* drop 3.13

* revert

* Drop test cassette that was causing error

* trying to fix failing test

* adding thiago changes

* resolve final tests

* Drop skip

* Bugfix/restrict python version compatibility (#1736)

* drop 3.13

* revert

* Drop test cassette that was causing error

* trying to fix failing test

* adding thiago changes

* resolve final tests

* Drop skip

* drop pipeline

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

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

* copy googles changes. Fix tests. Improve LLM file

* Fix type issue

* fix:typo error (#1738)

* Update base_agent_tools.py

typo error

* Update main.py

typo error

* Update base_file_knowledge_source.py

typo error

* Update test_main.py

typo error

* Update en.json

* Update prompts.json

---------

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

* Remove manager_callbacks reference (#1741)

* include event emitter in flows (#1740)

* include event emitter in flows

* Clean up

* Fix linter

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

* sort imports

* update

---------

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

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

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

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

I have changed the code to pass is_auto_end=True

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

* NVIDIA Provider : UI changes (#1746)

* docs: add nvidia as provider

* nvidia ui docs changes

* add note for updated list

---------

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

* Fix small typo in sample tool (#1747)

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

* Feature/add workflow permissions (#1749)

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

* feat: add workflow permissions to stale.yml

* revert rag_storage.py changes

* revert rag_storage.py changes

---------

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

* remove pkg_resources which was causing issues (#1751)

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

* apply agent ops changes and resolve merge conflicts

* Trying to fix tests

* add back in vcr

* update tools

* remove pkg_resources which was causing issues

* Fix tests

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

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

* update chromadb which seems to have issues with upsert

* generate new yaml for failing test

* Investigating upsert

* Drop patch

* Update casettes

* Fix duplicate document issue

* more fixes

* add back in vcr

* new cassette for test

---------

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

* drop print (#1755)

* Fix: CrewJSONEncoder now accepts enums (#1752)

* bugfix: CrewJSONEncoder now accepts enums

* sort imports

---------

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

* Fix bool and null handling (#1771)

* include 12 but not 13

* change to <13 instead of <=12

* Gemini 2.0 (#1773)

* Update llms.mdx (Gemini 2.0)

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

* Update llm.py (gemini 2.0)

Add setting for Gemini 2.0 context window to llm.py

---------

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

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

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

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

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

* feat: Add task guardrails feature (#1742)

* feat: Add task guardrails feature

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

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

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

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

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

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

* fix: Remove unnecessary f-string prefix

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

* feat: Add guardrail validation improvements

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

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

* docs: Add comprehensive guardrails documentation

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

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

* refactor: Update guardrail functions to handle TaskOutput objects

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

* feat: Add task guardrails feature

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

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

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

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

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

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

* fix: Remove unnecessary f-string prefix

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

* feat: Add guardrail validation improvements

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

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

* docs: Add comprehensive guardrails documentation

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

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

* refactor: Update guardrail functions to handle TaskOutput objects

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

* style: Fix import sorting in task guardrails files

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

* fixing docs

* Fixing guardarils implementation

* docs: Enhance guardrail validator docstring with runtime validation rationale

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

---------

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

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

* Fixed output_file not respecting system path

* Fixed yaml config is not escaped properly for output requirements

* feat: Add interpolate_only method and improve error handling

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

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

* fix: Sort imports to fix lint issues

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

* fix: Reorganize imports using ruff --fix

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

* fix: Consolidate imports and fix formatting

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

* fix: Apply ruff automatic import sorting

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

* fix: Sort imports using ruff --fix

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

---------

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

* Feat/docling-support (#1763)

* added tool for docling support

* docling support installation

* use file_paths instead of file_path

* fix import

* organized imports

* run_type docs

* needs to be list

* fixed logic

* logged but file_path is backwards compatible

* use file_paths instead of file_path 2

* added test for multiple sources for file_paths

* fix run-types

* enabling local files to work and type cleanup

* linted

* fix test and types

* fixed run types

* fix types

* renamed to CrewDoclingSource

* linted

* added docs

* resolve conflicts

---------

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

* removed some redundancies (#1796)

* removed some redundancies

* cleanup

* Feat/joao flow improvement requests (#1795)

* Add in or and and in router

* In the middle of improving plotting

* final plot changes

---------

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

* Adding Multimodal Abilities to Crew (#1805)

* initial fix on delegation tools

* fixing tests for delegations and coding

* Refactor prepare tool and adding initial add images logic

* supporting image tool

* fixing linter

* fix linter

* Making sure multimodal feature support i18n

* fix linter and types

* mixxing translations

* fix types and linter

* Revert "fixing linter"

This reverts commit ef323e3487e62ee4f5bce7f86378068a5ac77e16.

* fix linters

* test

* fix

* fix

* fix linter

* fix

* ignore

* type improvements

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

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

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

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

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

* Portkey Integration with CrewAI (#1233)

* Create Portkey-Observability-and-Guardrails.md

* crewAI update with new changes

* small change

---------

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

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

* fix: Change storage initialization to None for KnowledgeStorage

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

---------

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

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

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

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

* docs: highlight Flows feature in README

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

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

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

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

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

* docs: incorporate key terms and enhance feature descriptions

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

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

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

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

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

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

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

---------

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

* Update README.md

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

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

* fixing file paths for knowledge source

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

* fix: interpolate output_file attribute from YAML

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

* fix: add security validation for output_file paths

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

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

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

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

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

* fix: improve output_file validation and error messages

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

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

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

---------

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

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

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

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

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

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

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

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

* style: fix import sorting in base_agent_tools and test_manager_llm_delegation

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

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

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

* style: fix import sorting in base_agent_tools and test_manager_llm_delegation

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

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

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

* style: fix import sorting in test_manager_llm_delegation.py

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

---------

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

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

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

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

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

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

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

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

* docs: add troubleshooting section and make tiktoken optional

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

* Update README.md

---------

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

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

* docs: add comprehensive docstrings to Flow class and methods

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

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

* feat: add secure path handling utilities

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

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

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

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

* fix: add type annotations and fix import sorting

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

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

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

* fix: resolve import sorting and type annotation issues

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

* fix: properly initialize and update edge_smooth variable

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

---------

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

* feat: add docstring (#1819)

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

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

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

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

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

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

* fix: Include agent knowledge in planning process

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

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

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

* style: Fix import sorting in test_knowledge_planning.py

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

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

* test: Update task summary assertions to include knowledge field

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix: Update knowledge field formatting in task summary

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

* style: Fix import sorting in test_planning_handler.py

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

* style: Fix import sorting order in test_planning_handler.py

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

* test: Add ChromaDB mocking to test_create_tasks_summary_with_knowledge_and_tools

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

---------

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

* Suppressed userWarnings from litellm pydantic issues (#1833)

* Suppressed userWarnings from litellm pydantic issues

* change litellm version

* Fix failling ollama tasks

* Trying out timeouts

* Trying out timeouts

* trying next crew_test timeout

* trying next crew_test timeout

* timeout in crew_tests

* timeout in crew_tests

* more timeouts

* more timeouts

* crew_test changes werent applied

* crew_test changes werent applied

* revert uv.lock

* revert uv.lock

* add back in crewai tool dependencies and drop litellm version

* add back in crewai tool dependencies and drop litellm version

* tests should work now

* tests should work now

* more test changes

* more test changes

* Reverting uv.lock and pyproject

* Reverting uv.lock and pyproject

* Update llama3 cassettes

* Update llama3 cassettes

* sync packages with uv.lock

* sync packages with uv.lock

* more test fixes

* fix tets

* drop large file

* final clean up

* drop record new episodes

---------

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

* change litellm version

* Fix failling ollama tasks
2024-12-31 18:40:51 -03:00
devin-ai-integration[bot]
4469461b38 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>
2024-12-31 01:56:38 -03:00
73 changed files with 1677 additions and 7510 deletions

1
.gitignore vendored
View File

@@ -21,3 +21,4 @@ crew_tasks_output.json
.mypy_cache
.ruff_cache
.venv
agentops.log

View File

@@ -138,7 +138,7 @@ print("---- Final Output ----")
print(final_output)
````
``` text Output
```text Output
---- Final Output ----
Second method received: Output from first_method
````

View File

@@ -4,8 +4,6 @@ description: What is knowledge in CrewAI and how to use it.
icon: book
---
# Using Knowledge in CrewAI
## What is Knowledge?
Knowledge in CrewAI is a powerful system that allows AI agents to access and utilize external information sources during their tasks.
@@ -36,7 +34,20 @@ CrewAI supports various types of knowledge sources out of the box:
</Card>
</CardGroup>
## Quick Start
## Supported Knowledge Parameters
| Parameter | Type | Required | Description |
| :--------------------------- | :---------------------------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sources` | **List[BaseKnowledgeSource]** | Yes | List of knowledge sources that provide content to be stored and queried. Can include PDF, CSV, Excel, JSON, text files, or string content. |
| `collection_name` | **str** | No | Name of the collection where the knowledge will be stored. Used to identify different sets of knowledge. Defaults to "knowledge" if not provided. |
| `storage` | **Optional[KnowledgeStorage]** | No | Custom storage configuration for managing how the knowledge is stored and retrieved. If not provided, a default storage will be created. |
## Quickstart Example
<Tip>
For file-Based Knowledge Sources, make sure to place your files in a `knowledge` directory at the root of your project.
Also, use relative paths from the `knowledge` directory when creating the source.
</Tip>
Here's an example using string-based knowledge:
@@ -80,7 +91,8 @@ result = crew.kickoff(inputs={"question": "What city does John live in and how o
```
Here's another example with the `CrewDoclingSource`
Here's another example with the `CrewDoclingSource`. The CrewDoclingSource is actually quite versatile and can handle multiple file formats including TXT, PDF, DOCX, HTML, and more.
```python Code
from crewai import LLM, Agent, Crew, Process, Task
from crewai.knowledge.source.crew_docling_source import CrewDoclingSource
@@ -128,39 +140,217 @@ result = crew.kickoff(
)
```
## More Examples
Here are examples of how to use different types of knowledge sources:
### Text File Knowledge Source
```python
from crewai.knowledge.source.crew_docling_source import CrewDoclingSource
# Create a text file knowledge source
text_source = CrewDoclingSource(
file_paths=["document.txt", "another.txt"]
)
# Create crew with text file source on agents or crew level
agent = Agent(
...
knowledge_sources=[text_source]
)
crew = Crew(
...
knowledge_sources=[text_source]
)
```
### PDF Knowledge Source
```python
from crewai.knowledge.source.pdf_knowledge_source import PDFKnowledgeSource
# Create a PDF knowledge source
pdf_source = PDFKnowledgeSource(
file_paths=["document.pdf", "another.pdf"]
)
# Create crew with PDF knowledge source on agents or crew level
agent = Agent(
...
knowledge_sources=[pdf_source]
)
crew = Crew(
...
knowledge_sources=[pdf_source]
)
```
### CSV Knowledge Source
```python
from crewai.knowledge.source.csv_knowledge_source import CSVKnowledgeSource
# Create a CSV knowledge source
csv_source = CSVKnowledgeSource(
file_paths=["data.csv"]
)
# Create crew with CSV knowledge source or on agent level
agent = Agent(
...
knowledge_sources=[csv_source]
)
crew = Crew(
...
knowledge_sources=[csv_source]
)
```
### Excel Knowledge Source
```python
from crewai.knowledge.source.excel_knowledge_source import ExcelKnowledgeSource
# Create an Excel knowledge source
excel_source = ExcelKnowledgeSource(
file_paths=["spreadsheet.xlsx"]
)
# Create crew with Excel knowledge source on agents or crew level
agent = Agent(
...
knowledge_sources=[excel_source]
)
crew = Crew(
...
knowledge_sources=[excel_source]
)
```
### JSON Knowledge Source
```python
from crewai.knowledge.source.json_knowledge_source import JSONKnowledgeSource
# Create a JSON knowledge source
json_source = JSONKnowledgeSource(
file_paths=["data.json"]
)
# Create crew with JSON knowledge source on agents or crew level
agent = Agent(
...
knowledge_sources=[json_source]
)
crew = Crew(
...
knowledge_sources=[json_source]
)
```
## Knowledge Configuration
### Chunking Configuration
Control how content is split for processing by setting the chunk size and overlap.
Knowledge sources automatically chunk content for better processing.
You can configure chunking behavior in your knowledge sources:
```python Code
knowledge_source = StringKnowledgeSource(
content="Long content...",
chunk_size=4000, # Characters per chunk (default)
chunk_overlap=200 # Overlap between chunks (default)
```python
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
source = StringKnowledgeSource(
content="Your content here",
chunk_size=4000, # Maximum size of each chunk (default: 4000)
chunk_overlap=200 # Overlap between chunks (default: 200)
)
```
## Embedder Configuration
The chunking configuration helps in:
- Breaking down large documents into manageable pieces
- Maintaining context through chunk overlap
- Optimizing retrieval accuracy
You can also configure the embedder for the knowledge store. This is useful if you want to use a different embedder for the knowledge store than the one used for the agents.
### Embeddings Configuration
```python Code
...
You can also configure the embedder for the knowledge store.
This is useful if you want to use a different embedder for the knowledge store than the one used for the agents.
The `embedder` parameter supports various embedding model providers that include:
- `openai`: OpenAI's embedding models
- `google`: Google's text embedding models
- `azure`: Azure OpenAI embeddings
- `ollama`: Local embeddings with Ollama
- `vertexai`: Google Cloud VertexAI embeddings
- `cohere`: Cohere's embedding models
- `bedrock`: AWS Bedrock embeddings
- `huggingface`: Hugging Face models
- `watson`: IBM Watson embeddings
Here's an example of how to configure the embedder for the knowledge store using Google's `text-embedding-004` model:
<CodeGroup>
```python Example
from crewai import Agent, Task, Crew, Process, LLM
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
import os
# Get the GEMINI API key
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
# Create a knowledge source
content = "Users name is John. He is 30 years old and lives in San Francisco."
string_source = StringKnowledgeSource(
content="Users name is John. He is 30 years old and lives in San Francisco.",
content=content,
)
# Create an LLM with a temperature of 0 to ensure deterministic outputs
gemini_llm = LLM(
model="gemini/gemini-1.5-pro-002",
api_key=GEMINI_API_KEY,
temperature=0,
)
# Create an agent with the knowledge store
agent = Agent(
role="About User",
goal="You know everything about the user.",
backstory="""You are a master at understanding people and their preferences.""",
verbose=True,
allow_delegation=False,
llm=gemini_llm,
)
task = Task(
description="Answer the following questions about the user: {question}",
expected_output="An answer to the question.",
agent=agent,
)
crew = Crew(
...
agents=[agent],
tasks=[task],
verbose=True,
process=Process.sequential,
knowledge_sources=[string_source],
embedder={
"provider": "openai",
"config": {"model": "text-embedding-3-small"},
},
"provider": "google",
"config": {
"model": "models/text-embedding-004",
"api_key": GEMINI_API_KEY,
}
}
)
```
result = crew.kickoff(inputs={"question": "What city does John live in and how old is he?"})
```
```text Output
# Agent: About User
## Task: Answer the following questions about the user: What city does John live in and how old is he?
# Agent: About User
## Final Answer:
John is 30 years old and lives in San Francisco.
```
</CodeGroup>
## Clearing Knowledge
If you need to clear the knowledge stored in CrewAI, you can use the `crewai reset-memories` command with the `--knowledge` option.

View File

@@ -0,0 +1,202 @@
---
title: Portkey Observability and Guardrails
description: How to use Portkey with CrewAI
icon: key
---
<img src="https://raw.githubusercontent.com/siddharthsambharia-portkey/Portkey-Product-Images/main/Portkey-CrewAI.png" alt="Portkey CrewAI Header Image" width="70%" />
[Portkey](https://portkey.ai/?utm_source=crewai&utm_medium=crewai&utm_campaign=crewai) is a 2-line upgrade to make your CrewAI agents reliable, cost-efficient, and fast.
Portkey adds 4 core production capabilities to any CrewAI agent:
1. Routing to **200+ LLMs**
2. Making each LLM call more robust
3. Full-stack tracing & cost, performance analytics
4. Real-time guardrails to enforce behavior
## Getting Started
<Steps>
<Step title="Install CrewAI and Portkey">
```bash
pip install -qU crewai portkey-ai
```
</Step>
<Step title="Configure the LLM Client">
To build CrewAI Agents with Portkey, you'll need two keys:
- **Portkey API Key**: Sign up on the [Portkey app](https://app.portkey.ai/?utm_source=crewai&utm_medium=crewai&utm_campaign=crewai) and copy your API key
- **Virtual Key**: Virtual Keys securely manage your LLM API keys in one place. Store your LLM provider API keys securely in Portkey's vault
```python
from crewai import LLM
from portkey_ai import createHeaders, PORTKEY_GATEWAY_URL
gpt_llm = LLM(
model="gpt-4",
base_url=PORTKEY_GATEWAY_URL,
api_key="dummy", # We are using Virtual key
extra_headers=createHeaders(
api_key="YOUR_PORTKEY_API_KEY",
virtual_key="YOUR_VIRTUAL_KEY", # Enter your Virtual key from Portkey
)
)
```
</Step>
<Step title="Create and Run Your First Agent">
```python
from crewai import Agent, Task, Crew
# Define your agents with roles and goals
coder = Agent(
role='Software developer',
goal='Write clear, concise code on demand',
backstory='An expert coder with a keen eye for software trends.',
llm=gpt_llm
)
# Create tasks for your agents
task1 = Task(
description="Define the HTML for making a simple website with heading- Hello World! Portkey is working!",
expected_output="A clear and concise HTML code",
agent=coder
)
# Instantiate your crew
crew = Crew(
agents=[coder],
tasks=[task1],
)
result = crew.kickoff()
print(result)
```
</Step>
</Steps>
## Key Features
| Feature | Description |
|:--------|:------------|
| 🌐 Multi-LLM Support | Access OpenAI, Anthropic, Gemini, Azure, and 250+ providers through a unified interface |
| 🛡️ Production Reliability | Implement retries, timeouts, load balancing, and fallbacks |
| 📊 Advanced Observability | Track 40+ metrics including costs, tokens, latency, and custom metadata |
| 🔍 Comprehensive Logging | Debug with detailed execution traces and function call logs |
| 🚧 Security Controls | Set budget limits and implement role-based access control |
| 🔄 Performance Analytics | Capture and analyze feedback for continuous improvement |
| 💾 Intelligent Caching | Reduce costs and latency with semantic or simple caching |
## Production Features with Portkey Configs
All features mentioned below are through Portkey's Config system. Portkey's Config system allows you to define routing strategies using simple JSON objects in your LLM API calls. You can create and manage Configs directly in your code or through the Portkey Dashboard. Each Config has a unique ID for easy reference.
<Frame>
<img src="https://raw.githubusercontent.com/Portkey-AI/docs-core/refs/heads/main/images/libraries/libraries-3.avif"/>
</Frame>
### 1. Use 250+ LLMs
Access various LLMs like Anthropic, Gemini, Mistral, Azure OpenAI, and more with minimal code changes. Switch between providers or use them together seamlessly. [Learn more about Universal API](https://portkey.ai/docs/product/ai-gateway/universal-api)
Easily switch between different LLM providers:
```python
# Anthropic Configuration
anthropic_llm = LLM(
model="claude-3-5-sonnet-latest",
base_url=PORTKEY_GATEWAY_URL,
api_key="dummy",
extra_headers=createHeaders(
api_key="YOUR_PORTKEY_API_KEY",
virtual_key="YOUR_ANTHROPIC_VIRTUAL_KEY", #You don't need provider when using Virtual keys
trace_id="anthropic_agent"
)
)
# Azure OpenAI Configuration
azure_llm = LLM(
model="gpt-4",
base_url=PORTKEY_GATEWAY_URL,
api_key="dummy",
extra_headers=createHeaders(
api_key="YOUR_PORTKEY_API_KEY",
virtual_key="YOUR_AZURE_VIRTUAL_KEY", #You don't need provider when using Virtual keys
trace_id="azure_agent"
)
)
```
### 2. Caching
Improve response times and reduce costs with two powerful caching modes:
- **Simple Cache**: Perfect for exact matches
- **Semantic Cache**: Matches responses for requests that are semantically similar
[Learn more about Caching](https://portkey.ai/docs/product/ai-gateway/cache-simple-and-semantic)
```py
config = {
"cache": {
"mode": "semantic", # or "simple" for exact matching
}
}
```
### 3. Production Reliability
Portkey provides comprehensive reliability features:
- **Automatic Retries**: Handle temporary failures gracefully
- **Request Timeouts**: Prevent hanging operations
- **Conditional Routing**: Route requests based on specific conditions
- **Fallbacks**: Set up automatic provider failovers
- **Load Balancing**: Distribute requests efficiently
[Learn more about Reliability Features](https://portkey.ai/docs/product/ai-gateway/)
### 4. Metrics
Agent runs are complex. Portkey automatically logs **40+ comprehensive metrics** for your AI agents, including cost, tokens used, latency, etc. Whether you need a broad overview or granular insights into your agent runs, Portkey's customizable filters provide the metrics you need.
- Cost per agent interaction
- Response times and latency
- Token usage and efficiency
- Success/failure rates
- Cache hit rates
<img src="https://github.com/siddharthsambharia-portkey/Portkey-Product-Images/blob/main/Portkey-Dashboard.png?raw=true" width="70%" alt="Portkey Dashboard" />
### 5. Detailed Logging
Logs are essential for understanding agent behavior, diagnosing issues, and improving performance. They provide a detailed record of agent activities and tool use, which is crucial for debugging and optimizing processes.
Access a dedicated section to view records of agent executions, including parameters, outcomes, function calls, and errors. Filter logs based on multiple parameters such as trace ID, model, tokens used, and metadata.
<details>
<summary><b>Traces</b></summary>
<img src="https://raw.githubusercontent.com/siddharthsambharia-portkey/Portkey-Product-Images/main/Portkey-Traces.png" alt="Portkey Traces" width="70%" />
</details>
<details>
<summary><b>Logs</b></summary>
<img src="https://raw.githubusercontent.com/siddharthsambharia-portkey/Portkey-Product-Images/main/Portkey-Logs.png" alt="Portkey Logs" width="70%" />
</details>
### 6. Enterprise Security Features
- Set budget limit and rate limts per Virtual Key (disposable API keys)
- Implement role-based access control
- Track system changes with audit logs
- Configure data retention policies
For detailed information on creating and managing Configs, visit the [Portkey documentation](https://docs.portkey.ai/product/ai-gateway/configs).
## Resources
- [📘 Portkey Documentation](https://docs.portkey.ai)
- [📊 Portkey Dashboard](https://app.portkey.ai/?utm_source=crewai&utm_medium=crewai&utm_campaign=crewai)
- [🐦 Twitter](https://twitter.com/portkeyai)
- [💬 Discord Community](https://discord.gg/DD7vgKK299)

View File

@@ -100,7 +100,8 @@
"how-to/conditional-tasks",
"how-to/agentops-observability",
"how-to/langtrace-observability",
"how-to/openlit-observability"
"how-to/openlit-observability",
"how-to/portkey-observability"
]
},
{

View File

@@ -1,222 +0,0 @@
---
title: BraveSearchTool
description: A tool for performing web searches using the Brave Search API
icon: search
---
## BraveSearchTool
The BraveSearchTool enables web searches using the Brave Search API, providing customizable result counts, country-specific searches, and rate-limited operations. It formats search results with titles, URLs, and snippets for easy consumption.
## Installation
```bash
pip install 'crewai[tools]'
```
## Authentication
Set up your Brave Search API key:
```bash
export BRAVE_API_KEY='your-brave-api-key'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import BraveSearchTool
# Basic initialization
search_tool = BraveSearchTool()
# Advanced initialization with custom parameters
search_tool = BraveSearchTool(
country="US", # Country-specific search
n_results=5, # Number of results to return
save_file=True # Save results to file
)
# Create an agent with the tool
researcher = Agent(
role='Web Researcher',
goal='Search and analyze web content',
backstory='Expert at finding relevant information online.',
tools=[search_tool],
verbose=True
)
```
## Input Schema
```python
class BraveSearchToolSchema(BaseModel):
search_query: str = Field(
description="Mandatory search query you want to use to search the internet"
)
```
## Function Signature
```python
def __init__(
self,
country: Optional[str] = "",
n_results: int = 10,
save_file: bool = False,
*args,
**kwargs
):
"""
Initialize the Brave search tool.
Args:
country (Optional[str]): Country code for region-specific search
n_results (int): Number of results to return (default: 10)
save_file (bool): Whether to save results to file (default: False)
"""
def _run(
self,
**kwargs: Any
) -> str:
"""
Execute web search using Brave Search API.
Args:
search_query (str): Query to search
save_file (bool, optional): Override save_file setting
n_results (int, optional): Override n_results setting
Returns:
str: Formatted search results with titles, URLs, and snippets
"""
```
## Best Practices
1. API Authentication:
- Securely store BRAVE_API_KEY
- Keep API key confidential
- Handle authentication errors
2. Rate Limiting:
- Tool automatically handles rate limiting
- Minimum 1-second interval between requests
- Consider implementing additional rate limits
3. Search Optimization:
- Use specific search queries
- Adjust result count based on needs
- Consider regional search requirements
4. Error Handling:
- Handle API request failures
- Manage parsing errors
- Monitor rate limit errors
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import BraveSearchTool
# Initialize tool with custom configuration
search_tool = BraveSearchTool(
country="GB", # UK-specific search
n_results=3, # Limit to 3 results
save_file=True # Save results to file
)
# Create agent
researcher = Agent(
role='Web Researcher',
goal='Research latest AI developments',
backstory='Expert at finding and analyzing tech news.',
tools=[search_tool]
)
# Define task
research_task = Task(
description="""Find the latest news about artificial
intelligence developments in quantum computing.""",
agent=researcher
)
# The tool will use:
# {
# "search_query": "latest quantum computing AI developments"
# }
# Create crew
crew = Crew(
agents=[researcher],
tasks=[research_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Country-Specific Search
```python
# Initialize tools for different regions
us_search = BraveSearchTool(country="US")
uk_search = BraveSearchTool(country="GB")
jp_search = BraveSearchTool(country="JP")
# Compare results across regions
us_results = us_search.run(
search_query="local news"
)
uk_results = uk_search.run(
search_query="local news"
)
jp_results = jp_search.run(
search_query="local news"
)
```
### Result Management
```python
# Save results to file
archival_search = BraveSearchTool(
save_file=True,
n_results=20
)
# Search and save
results = archival_search.run(
search_query="historical events 2023"
)
# Results saved to search_results_YYYY-MM-DD_HH-MM-SS.txt
```
### Error Handling Example
```python
try:
search_tool = BraveSearchTool()
results = search_tool.run(
search_query="important topic"
)
print(results)
except ValueError as e: # API key missing
print(f"Authentication error: {str(e)}")
except Exception as e:
print(f"Search error: {str(e)}")
```
## Notes
- Requires Brave Search API key
- Implements automatic rate limiting
- Supports country-specific searches
- Customizable result count
- Optional file saving feature
- Thread-safe operations
- Efficient result formatting
- Handles API errors gracefully
- Supports parallel searches
- Maintains search context

View File

@@ -1,164 +0,0 @@
---
title: CodeDocsSearchTool
description: A semantic search tool for code documentation websites using RAG capabilities
icon: book-open
---
## CodeDocsSearchTool
The CodeDocsSearchTool is a specialized Retrieval-Augmented Generation (RAG) tool that enables semantic search within code documentation websites. It inherits from the base RagTool class and provides both fixed and dynamic documentation URL searching capabilities.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import CodeDocsSearchTool
# Method 1: Dynamic documentation URL
docs_search = CodeDocsSearchTool()
# Method 2: Fixed documentation URL
fixed_docs_search = CodeDocsSearchTool(
docs_url="https://docs.example.com"
)
# Create an agent with the tool
researcher = Agent(
role='Documentation Researcher',
goal='Search through code documentation semantically',
backstory='Expert at finding relevant information in technical documentation.',
tools=[docs_search],
verbose=True
)
```
## Input Schema
The tool supports two input schemas depending on initialization:
### Dynamic URL Schema
```python
class CodeDocsSearchToolSchema(BaseModel):
search_query: str # The semantic search query
docs_url: str # URL of the documentation site to search
```
### Fixed URL Schema
```python
class FixedCodeDocsSearchToolSchema(BaseModel):
search_query: str # The semantic search query
```
## Function Signature
```python
def __init__(self, docs_url: Optional[str] = None, **kwargs):
"""
Initialize the documentation search tool.
Args:
docs_url (Optional[str]): Fixed URL to a documentation site. If provided,
the tool will only search this documentation.
**kwargs: Additional arguments passed to the parent RagTool
"""
def _run(self, search_query: str, **kwargs: Any) -> Any:
"""
Perform semantic search on the documentation site.
Args:
search_query (str): The semantic search query
**kwargs: Additional arguments (including 'docs_url' for dynamic mode)
Returns:
str: Relevant documentation passages based on semantic search
"""
```
## Best Practices
1. Choose initialization method based on use case:
- Use fixed URL when repeatedly searching the same documentation
- Use dynamic URL when searching different documentation sites
2. Write clear, semantic search queries
3. Ensure documentation sites are accessible
4. Consider documentation structure and size
5. Handle potential URL access errors in agent prompts
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import CodeDocsSearchTool
# Example 1: Fixed documentation search
api_docs_search = CodeDocsSearchTool(
docs_url="https://api.example.com/docs"
)
# Example 2: Dynamic documentation search
flexible_docs_search = CodeDocsSearchTool()
# Create agents
api_analyst = Agent(
role='API Documentation Analyst',
goal='Find relevant API endpoints and usage examples',
backstory='Expert at analyzing API documentation.',
tools=[api_docs_search]
)
docs_researcher = Agent(
role='Documentation Researcher',
goal='Search through various documentation sites',
backstory='Specialist in finding information across multiple docs.',
tools=[flexible_docs_search]
)
# Define tasks
fixed_search_task = Task(
description="""Find all authentication-related endpoints
in the API documentation.""",
agent=api_analyst
)
# The agent will use:
# {
# "search_query": "authentication endpoints and methods"
# }
dynamic_search_task = Task(
description="""Search through the Python documentation at
docs.python.org for information about async/await.""",
agent=docs_researcher
)
# The agent will use:
# {
# "search_query": "async await syntax and usage",
# "docs_url": "https://docs.python.org"
# }
# Create crew
crew = Crew(
agents=[api_analyst, docs_researcher],
tasks=[fixed_search_task, dynamic_search_task]
)
# Execute
result = crew.kickoff()
```
## Notes
- Inherits from RagTool for semantic search capabilities
- Supports both fixed and dynamic documentation URLs
- Uses embeddings for semantic search
- Thread-safe operations
- Automatically handles documentation loading and embedding
- Optimized for technical documentation search

View File

@@ -1,224 +0,0 @@
---
title: CodeInterpreterTool
description: A tool for secure Python code execution in isolated Docker environments
icon: code
---
## CodeInterpreterTool
The CodeInterpreterTool provides secure Python code execution capabilities using Docker containers. It supports dynamic library installation and offers both safe (Docker-based) and unsafe (direct) execution modes.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import CodeInterpreterTool
# Initialize the tool
code_tool = CodeInterpreterTool()
# Create an agent with the tool
programmer = Agent(
role='Code Executor',
goal='Execute and analyze Python code',
backstory='Expert at writing and executing Python code.',
tools=[code_tool],
verbose=True
)
```
## Input Schema
```python
class CodeInterpreterSchema(BaseModel):
code: str = Field(
description="Python3 code used to be interpreted in the Docker container. ALWAYS PRINT the final result and the output of the code"
)
libraries_used: List[str] = Field(
description="List of libraries used in the code with proper installing names separated by commas. Example: numpy,pandas,beautifulsoup4"
)
```
## Function Signature
```python
def __init__(
self,
code: Optional[str] = None,
user_dockerfile_path: Optional[str] = None,
user_docker_base_url: Optional[str] = None,
unsafe_mode: bool = False,
**kwargs
):
"""
Initialize the code interpreter tool.
Args:
code (Optional[str]): Default code to execute
user_dockerfile_path (Optional[str]): Custom Dockerfile path
user_docker_base_url (Optional[str]): Custom Docker daemon URL
unsafe_mode (bool): Enable direct code execution
**kwargs: Additional arguments for base tool
"""
def _run(
self,
code: str,
libraries_used: List[str],
**kwargs: Any
) -> str:
"""
Execute Python code in Docker container or directly.
Args:
code (str): Python code to execute
libraries_used (List[str]): Required libraries
**kwargs: Additional arguments
Returns:
str: Execution output or error message
"""
```
## Best Practices
1. Security Considerations:
- Use Docker mode by default
- Validate input code
- Control library access
- Monitor execution time
2. Docker Configuration:
- Use custom Dockerfile when needed
- Handle container lifecycle
- Manage resource limits
- Clean up after execution
3. Library Management:
- Specify exact versions
- Use trusted packages
- Handle dependencies
- Verify installations
4. Error Handling:
- Catch execution errors
- Handle timeouts
- Manage Docker errors
- Provide clear messages
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import CodeInterpreterTool
# Initialize tool
code_tool = CodeInterpreterTool()
# Create agent
programmer = Agent(
role='Code Executor',
goal='Execute data analysis code',
backstory='Expert Python programmer specializing in data analysis.',
tools=[code_tool]
)
# Define task
analysis_task = Task(
description="""Analyze the dataset using pandas and
create a summary visualization with matplotlib.""",
agent=programmer
)
# The tool will use:
# {
# "code": """
# import pandas as pd
# import matplotlib.pyplot as plt
#
# # Load and analyze data
# df = pd.read_csv('data.csv')
# summary = df.describe()
#
# # Create visualization
# plt.figure(figsize=(10, 6))
# df['column'].hist()
# plt.savefig('output.png')
#
# print(summary)
# """,
# "libraries_used": "pandas,matplotlib"
# }
# Create crew
crew = Crew(
agents=[programmer],
tasks=[analysis_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Custom Docker Configuration
```python
# Use custom Dockerfile
tool = CodeInterpreterTool(
user_dockerfile_path="/path/to/Dockerfile"
)
# Use custom Docker daemon
tool = CodeInterpreterTool(
user_docker_base_url="tcp://remote-docker:2375"
)
```
### Direct Execution Mode
```python
# Enable unsafe mode (not recommended)
tool = CodeInterpreterTool(unsafe_mode=True)
# Execute code directly
result = tool.run(
code="print('Hello, World!')",
libraries_used=[]
)
```
### Error Handling Example
```python
try:
code_tool = CodeInterpreterTool()
result = code_tool.run(
code="""
import numpy as np
arr = np.array([1, 2, 3])
print(f"Array mean: {arr.mean()}")
""",
libraries_used=["numpy"]
)
print(result)
except Exception as e:
print(f"Error executing code: {str(e)}")
```
## Notes
- Inherits from BaseTool
- Docker-based isolation
- Dynamic library installation
- Secure code execution
- Custom Docker support
- Comprehensive error handling
- Resource management
- Container cleanup
- Library dependency handling
- Execution output capture

View File

@@ -1,207 +0,0 @@
---
title: CSVSearchTool
description: A tool for semantic search within CSV files using RAG capabilities
icon: table
---
## CSVSearchTool
The CSVSearchTool enables semantic search capabilities for CSV files using Retrieval-Augmented Generation (RAG). It can process CSV files either specified during initialization or at runtime, making it flexible for various use cases.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import CSVSearchTool
# Method 1: Initialize with specific CSV file
csv_tool = CSVSearchTool(csv="path/to/data.csv")
# Method 2: Initialize without CSV (specify at runtime)
flexible_csv_tool = CSVSearchTool()
# Create an agent with the tool
data_analyst = Agent(
role='Data Analyst',
goal='Search and analyze CSV data semantically',
backstory='Expert at analyzing and extracting insights from CSV data.',
tools=[csv_tool],
verbose=True
)
```
## Input Schema
### Fixed CSV Schema (when CSV path provided during initialization)
```python
class FixedCSVSearchToolSchema(BaseModel):
search_query: str = Field(
description="Mandatory search query you want to use to search the CSV's content"
)
```
### Flexible CSV Schema (when CSV path provided at runtime)
```python
class CSVSearchToolSchema(FixedCSVSearchToolSchema):
csv: str = Field(
description="Mandatory csv path you want to search"
)
```
## Function Signature
```python
def __init__(
self,
csv: Optional[str] = None,
**kwargs
):
"""
Initialize the CSV search tool.
Args:
csv (Optional[str]): Path to CSV file (optional)
**kwargs: Additional arguments for RAG tool configuration
"""
def _run(
self,
search_query: str,
**kwargs: Any
) -> str:
"""
Execute semantic search on CSV content.
Args:
search_query (str): Query to search in the CSV
**kwargs: Additional arguments including csv path if not initialized
Returns:
str: Relevant content from the CSV matching the query
"""
```
## Best Practices
1. CSV File Handling:
- Ensure CSV files are properly formatted
- Use absolute paths for reliability
- Verify file permissions before processing
2. Search Optimization:
- Use specific, focused search queries
- Consider column names and data structure
- Test with sample queries first
3. Performance Considerations:
- Pre-initialize with CSV for repeated searches
- Handle large CSV files appropriately
- Monitor memory usage with big datasets
4. Error Handling:
- Verify CSV file existence
- Handle malformed CSV data
- Manage file access permissions
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import CSVSearchTool
# Initialize tool with specific CSV
csv_tool = CSVSearchTool(csv="/path/to/sales_data.csv")
# Create agent
analyst = Agent(
role='Data Analyst',
goal='Extract insights from sales data',
backstory='Expert at analyzing sales data and trends.',
tools=[csv_tool]
)
# Define task
analysis_task = Task(
description="""Find all sales records from the CSV
that relate to product returns in Q4 2023.""",
agent=analyst
)
# The tool will use:
# {
# "search_query": "product returns Q4 2023"
# }
# Create crew
crew = Crew(
agents=[analyst],
tasks=[analysis_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Dynamic CSV Selection
```python
# Initialize without CSV
flexible_tool = CSVSearchTool()
# Search different CSVs
result1 = flexible_tool.run(
search_query="revenue 2023",
csv="/path/to/finance.csv"
)
result2 = flexible_tool.run(
search_query="customer feedback",
csv="/path/to/surveys.csv"
)
```
### Multiple CSV Analysis
```python
# Create tools for different CSVs
sales_tool = CSVSearchTool(csv="/path/to/sales.csv")
inventory_tool = CSVSearchTool(csv="/path/to/inventory.csv")
# Create agent with multiple tools
analyst = Agent(
role='Business Analyst',
goal='Cross-reference sales and inventory data',
tools=[sales_tool, inventory_tool]
)
```
### Error Handling Example
```python
try:
csv_tool = CSVSearchTool(csv="/path/to/data.csv")
result = csv_tool.run(
search_query="important metrics"
)
print(result)
except Exception as e:
print(f"Error processing CSV: {str(e)}")
```
## Notes
- Inherits from RagTool for semantic search
- Supports dynamic CSV file specification
- Uses embedchain for data processing
- Maintains search context across queries
- Thread-safe operations
- Efficient semantic search capabilities
- Supports various CSV formats
- Handles large datasets effectively
- Preserves CSV structure in search
- Enables natural language queries

View File

@@ -1,217 +0,0 @@
---
title: Directory Read Tool
description: A tool for recursively listing directory contents
---
# Directory Read Tool
The Directory Read Tool provides functionality to recursively list all files within a directory. It supports both fixed and dynamic directory path modes, allowing you to specify the directory at initialization or runtime.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage
You can use the Directory Read Tool in two ways:
### 1. Fixed Directory Path
Initialize the tool with a specific directory path:
```python
from crewai import Agent
from crewai_tools import DirectoryReadTool
# Initialize with a fixed directory
tool = DirectoryReadTool(directory="/path/to/your/directory")
# Create an agent with the tool
agent = Agent(
role='File System Analyst',
goal='Analyze directory contents',
backstory='I help analyze and organize file systems',
tools=[tool]
)
# Use in a task
task = Task(
description="List all files in the project directory",
agent=agent
)
```
### 2. Dynamic Directory Path
Initialize the tool without a specific directory path to provide it at runtime:
```python
from crewai import Agent
from crewai_tools import DirectoryReadTool
# Initialize without a fixed directory
tool = DirectoryReadTool()
# Create an agent with the tool
agent = Agent(
role='File System Explorer',
goal='Explore different directories',
backstory='I analyze various directory structures',
tools=[tool]
)
# Use in a task with dynamic directory path
task = Task(
description="List all files in the specified directory",
agent=agent,
context={
"directory": "/path/to/explore"
}
)
```
## Input Schema
### Fixed Directory Mode
```python
class FixedDirectoryReadToolSchema(BaseModel):
pass # No additional parameters needed when directory is fixed
```
### Dynamic Directory Mode
```python
class DirectoryReadToolSchema(BaseModel):
directory: str # The path to the directory to list contents
```
## Function Signatures
```python
def __init__(self, directory: Optional[str] = None, **kwargs):
"""
Initialize the Directory Read Tool.
Args:
directory (Optional[str]): Path to the directory (optional)
**kwargs: Additional arguments passed to BaseTool
"""
def _run(
self,
**kwargs: Any,
) -> str:
"""
Execute the directory listing.
Args:
**kwargs: Arguments including 'directory' for dynamic mode
Returns:
str: A formatted string containing all file paths in the directory
"""
```
## Best Practices
1. **Path Handling**:
- Use absolute paths to avoid path resolution issues
- Handle trailing slashes appropriately
- Verify directory existence before listing
2. **Performance Considerations**:
- Be mindful of directory size when listing large directories
- Consider implementing pagination for large directories
- Handle symlinks appropriately
3. **Error Handling**:
- Handle directory not found errors gracefully
- Manage permission issues appropriately
- Validate input parameters before processing
## Example Integration
Here's a complete example showing how to integrate the Directory Read Tool with CrewAI:
```python
from crewai import Agent, Task, Crew
from crewai_tools import DirectoryReadTool
# Initialize the tool
dir_tool = DirectoryReadTool()
# Create an agent with the tool
file_analyst = Agent(
role='File System Analyst',
goal='Analyze and report on directory structures',
backstory='I am an expert at analyzing file system organization',
tools=[dir_tool]
)
# Create tasks
analysis_task = Task(
description="""
Analyze the project directory structure:
1. List all files recursively
2. Identify key file types
3. Report on directory organization
Provide a comprehensive analysis of the findings.
""",
agent=file_analyst,
context={
"directory": "/path/to/project"
}
)
# Create and run the crew
crew = Crew(
agents=[file_analyst],
tasks=[analysis_task]
)
result = crew.kickoff()
```
## Error Handling
The tool handles various error scenarios:
1. **Directory Not Found**:
```python
try:
tool = DirectoryReadTool(directory="/nonexistent/path")
except FileNotFoundError:
print("Directory not found. Please verify the path.")
```
2. **Permission Issues**:
```python
try:
tool = DirectoryReadTool(directory="/restricted/path")
except PermissionError:
print("Insufficient permissions to access the directory.")
```
3. **Invalid Path**:
```python
try:
result = tool._run(directory="invalid/path")
except ValueError:
print("Invalid directory path provided.")
```
## Output Format
The tool returns a formatted string containing all file paths in the directory:
```
File paths:
- /path/to/directory/file1.txt
- /path/to/directory/subdirectory/file2.txt
- /path/to/directory/subdirectory/file3.py
```
Each file path is listed on a new line with a hyphen prefix, making it easy to parse and read the output.

View File

@@ -1,214 +0,0 @@
---
title: DirectorySearchTool
description: A tool for semantic search within directory contents using RAG capabilities
icon: folder-search
---
## DirectorySearchTool
The DirectorySearchTool enables semantic search capabilities for directory contents using Retrieval-Augmented Generation (RAG). It processes files recursively within a directory and allows searching through their contents using natural language queries.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import DirectorySearchTool
# Method 1: Initialize with specific directory
dir_tool = DirectorySearchTool(directory="/path/to/documents")
# Method 2: Initialize without directory (specify at runtime)
flexible_dir_tool = DirectorySearchTool()
# Create an agent with the tool
researcher = Agent(
role='Directory Researcher',
goal='Search and analyze directory contents',
backstory='Expert at finding relevant information in document collections.',
tools=[dir_tool],
verbose=True
)
```
## Input Schema
### Fixed Directory Schema (when path provided during initialization)
```python
class FixedDirectorySearchToolSchema(BaseModel):
search_query: str = Field(
description="Mandatory search query you want to use to search the directory's content"
)
```
### Flexible Directory Schema (when path provided at runtime)
```python
class DirectorySearchToolSchema(FixedDirectorySearchToolSchema):
directory: str = Field(
description="Mandatory directory you want to search"
)
```
## Function Signature
```python
def __init__(
self,
directory: Optional[str] = None,
**kwargs
):
"""
Initialize the directory search tool.
Args:
directory (Optional[str]): Path to directory (optional)
**kwargs: Additional arguments for RAG tool configuration
"""
def _run(
self,
search_query: str,
**kwargs: Any
) -> str:
"""
Execute semantic search on directory contents.
Args:
search_query (str): Query to search in the directory
**kwargs: Additional arguments including directory if not initialized
Returns:
str: Relevant content from the directory matching the query
"""
```
## Best Practices
1. Directory Management:
- Use absolute paths
- Verify directory existence
- Handle permissions properly
2. Search Optimization:
- Use specific queries
- Consider file types
- Test with sample queries
3. Performance Considerations:
- Pre-initialize for repeated searches
- Handle large directories
- Monitor processing time
4. Error Handling:
- Verify directory access
- Handle missing files
- Manage permissions
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import DirectorySearchTool
# Initialize tool with specific directory
dir_tool = DirectorySearchTool(
directory="/path/to/documents"
)
# Create agent
researcher = Agent(
role='Directory Researcher',
goal='Extract insights from document collections',
backstory='Expert at analyzing document collections.',
tools=[dir_tool]
)
# Define task
research_task = Task(
description="""Find all mentions of machine learning
applications from the directory contents.""",
agent=researcher
)
# The tool will use:
# {
# "search_query": "machine learning applications"
# }
# Create crew
crew = Crew(
agents=[researcher],
tasks=[research_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Dynamic Directory Selection
```python
# Initialize without directory path
flexible_tool = DirectorySearchTool()
# Search different directories
docs_results = flexible_tool.run(
search_query="technical specifications",
directory="/path/to/docs"
)
reports_results = flexible_tool.run(
search_query="financial metrics",
directory="/path/to/reports"
)
```
### Multiple Directory Analysis
```python
# Create tools for different directories
docs_tool = DirectorySearchTool(
directory="/path/to/docs"
)
reports_tool = DirectorySearchTool(
directory="/path/to/reports"
)
# Create agent with multiple tools
analyst = Agent(
role='Content Analyst',
goal='Cross-reference multiple document collections',
tools=[docs_tool, reports_tool]
)
```
### Error Handling Example
```python
try:
dir_tool = DirectorySearchTool()
results = dir_tool.run(
search_query="key concepts",
directory="/path/to/documents"
)
print(results)
except Exception as e:
print(f"Error processing directory: {str(e)}")
```
## Notes
- Inherits from RagTool
- Uses DirectoryLoader
- Supports recursive search
- Dynamic directory specification
- Efficient content retrieval
- Thread-safe operations
- Maintains search context
- Processes multiple file types
- Handles nested directories
- Memory-efficient processing

View File

@@ -1,224 +0,0 @@
---
title: DOCXSearchTool
description: A tool for semantic search within DOCX documents using RAG capabilities
icon: file-text
---
## DOCXSearchTool
The DOCXSearchTool enables semantic search capabilities for Microsoft Word (DOCX) documents using Retrieval-Augmented Generation (RAG). It supports both fixed and dynamic document selection modes.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import DOCXSearchTool
# Method 1: Fixed document (specified at initialization)
fixed_tool = DOCXSearchTool(
docx="path/to/document.docx"
)
# Method 2: Dynamic document (specified at runtime)
dynamic_tool = DOCXSearchTool()
# Create an agent with the tool
researcher = Agent(
role='Document Researcher',
goal='Search and analyze document contents',
backstory='Expert at finding relevant information in documents.',
tools=[fixed_tool], # or [dynamic_tool]
verbose=True
)
```
## Input Schema
### Fixed Document Mode
```python
class FixedDOCXSearchToolSchema(BaseModel):
search_query: str = Field(
description="Mandatory search query you want to use to search the DOCX's content"
)
```
### Dynamic Document Mode
```python
class DOCXSearchToolSchema(BaseModel):
docx: str = Field(
description="Mandatory docx path you want to search"
)
search_query: str = Field(
description="Mandatory search query you want to use to search the DOCX's content"
)
```
## Function Signature
```python
def __init__(
self,
docx: Optional[str] = None,
**kwargs
):
"""
Initialize the DOCX search tool.
Args:
docx (Optional[str]): Path to DOCX file (optional for dynamic mode)
**kwargs: Additional arguments for RAG tool configuration
"""
def _run(
self,
search_query: str,
docx: Optional[str] = None,
**kwargs: Any
) -> str:
"""
Execute semantic search on document contents.
Args:
search_query (str): Query to search in the document
docx (Optional[str]): Document path (required for dynamic mode)
**kwargs: Additional arguments
Returns:
str: Relevant content from the document matching the query
"""
```
## Best Practices
1. Document Handling:
- Use absolute file paths
- Verify file existence
- Handle large documents
- Monitor memory usage
2. Query Optimization:
- Structure queries clearly
- Consider document size
- Handle formatting
- Monitor performance
3. Error Handling:
- Check file access
- Validate file format
- Handle corrupted files
- Log issues
4. Mode Selection:
- Choose fixed mode for static documents
- Use dynamic mode for runtime selection
- Consider memory implications
- Manage document lifecycle
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import DOCXSearchTool
# Initialize tool
docx_tool = DOCXSearchTool(
docx="reports/annual_report_2023.docx"
)
# Create agent
researcher = Agent(
role='Document Analyst',
goal='Extract insights from annual report',
backstory='Expert at analyzing business documents.',
tools=[docx_tool]
)
# Define task
analysis_task = Task(
description="""Find all mentions of revenue growth
and market expansion.""",
agent=researcher
)
# Create crew
crew = Crew(
agents=[researcher],
tasks=[analysis_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Multiple Document Analysis
```python
# Create tools for different documents
report_tool = DOCXSearchTool(
docx="reports/annual_report.docx"
)
policy_tool = DOCXSearchTool(
docx="policies/compliance.docx"
)
# Create agent with multiple tools
analyst = Agent(
role='Document Analyst',
goal='Cross-reference reports and policies',
tools=[report_tool, policy_tool]
)
```
### Dynamic Document Loading
```python
# Initialize dynamic tool
dynamic_tool = DOCXSearchTool()
# Use with different documents
result1 = dynamic_tool.run(
docx="document1.docx",
search_query="project timeline"
)
result2 = dynamic_tool.run(
docx="document2.docx",
search_query="budget allocation"
)
```
### Error Handling Example
```python
try:
docx_tool = DOCXSearchTool(
docx="reports/quarterly_report.docx"
)
results = docx_tool.run(
search_query="Q3 performance metrics"
)
print(results)
except FileNotFoundError as e:
print(f"Document not found: {str(e)}")
except Exception as e:
print(f"Error processing document: {str(e)}")
```
## Notes
- Inherits from RagTool
- Supports fixed/dynamic modes
- Document path validation
- Memory management
- Performance optimization
- Error handling
- Search capabilities
- Content extraction
- Format handling
- Security features

View File

@@ -1,193 +0,0 @@
---
title: FileReadTool
description: A tool for reading file contents with flexible path specification
icon: file-text
---
## FileReadTool
The FileReadTool provides functionality to read file contents with support for both fixed and dynamic file path specification. It includes comprehensive error handling for common file operations and maintains clear descriptions of its configured state.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import FileReadTool
# Method 1: Initialize with specific file
reader = FileReadTool(file_path="/path/to/data.txt")
# Method 2: Initialize without file (specify at runtime)
flexible_reader = FileReadTool()
# Create an agent with the tool
file_processor = Agent(
role='File Processor',
goal='Read and process file contents',
backstory='Expert at handling file operations and content processing.',
tools=[reader],
verbose=True
)
```
## Input Schema
```python
class FileReadToolSchema(BaseModel):
file_path: str = Field(
description="Mandatory file full path to read the file"
)
```
## Function Signature
```python
def __init__(
self,
file_path: Optional[str] = None,
**kwargs: Any
) -> None:
"""
Initialize the file read tool.
Args:
file_path (Optional[str]): Path to file to read (optional)
**kwargs: Additional arguments passed to BaseTool
"""
def _run(
self,
**kwargs: Any
) -> str:
"""
Read and return file contents.
Args:
file_path (str, optional): Override default file path
**kwargs: Additional arguments
Returns:
str: File contents or error message
"""
```
## Best Practices
1. File Path Management:
- Use absolute paths for reliability
- Verify file existence before operations
- Handle path resolution properly
2. Error Handling:
- Check for file existence
- Handle permission issues
- Manage encoding errors
- Process file access failures
3. Performance Considerations:
- Close files after reading
- Handle large files appropriately
- Consider memory constraints
4. Security Practices:
- Validate file paths
- Check file permissions
- Avoid path traversal issues
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import FileReadTool
# Initialize tool with specific file
reader = FileReadTool(file_path="/path/to/config.txt")
# Create agent
processor = Agent(
role='File Processor',
goal='Process configuration files',
backstory='Expert at reading and analyzing configuration files.',
tools=[reader]
)
# Define task
read_task = Task(
description="""Read and analyze the contents of
the configuration file.""",
agent=processor
)
# The tool will use the default file path
# Create crew
crew = Crew(
agents=[processor],
tasks=[read_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Dynamic File Selection
```python
# Initialize without file path
flexible_reader = FileReadTool()
# Read different files
config_content = flexible_reader.run(
file_path="/path/to/config.txt"
)
log_content = flexible_reader.run(
file_path="/path/to/logs.txt"
)
```
### Multiple File Processing
```python
# Create tools for different files
config_reader = FileReadTool(file_path="/path/to/config.txt")
log_reader = FileReadTool(file_path="/path/to/logs.txt")
# Create agent with multiple tools
processor = Agent(
role='File Analyst',
goal='Analyze multiple file types',
tools=[config_reader, log_reader]
)
```
### Error Handling Example
```python
try:
reader = FileReadTool()
content = reader.run(
file_path="/path/to/file.txt"
)
print(content)
except Exception as e:
print(f"Error reading file: {str(e)}")
```
## Notes
- Inherits from BaseTool
- Supports fixed or dynamic file paths
- Comprehensive error handling
- Thread-safe operations
- Clear error messages
- Flexible path specification
- Maintains tool description
- Handles common file errors
- Supports various file types
- Memory-efficient operations

View File

@@ -1,141 +0,0 @@
---
title: FileWriterTool
description: A tool for writing content to files with support for various file formats.
icon: file-pen
---
## FileWriterTool
The FileWriterTool provides agents with the capability to write content to files, supporting various file formats and ensuring proper file handling.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import FileWriterTool
# Initialize the tool
file_writer = FileWriterTool()
# Create an agent with the tool
writer_agent = Agent(
role='Content Writer',
goal='Write and save content to files',
backstory='Expert at creating and managing file content.',
tools=[file_writer],
verbose=True
)
# Use in a task
task = Task(
description='Write a report and save it to report.txt',
agent=writer_agent
)
```
## Tool Attributes
| Attribute | Type | Description |
| :-------- | :--- | :---------- |
| name | str | "File Writer Tool" |
| description | str | "A tool that writes content to a file." |
## Input Schema
```python
class FileWriterToolInput(BaseModel):
filename: str # Name of the file to write
directory: str = "./" # Optional directory path, defaults to current directory
overwrite: str = "False" # Whether to overwrite existing file ("True"/"False")
content: str # Content to write to the file
```
## Function Signature
```python
def _run(self, **kwargs: Any) -> str:
"""
Write content to a file with specified parameters.
Args:
filename (str): Name of the file to write
content (str): Content to write to the file
directory (str, optional): Directory path. Defaults to "./".
overwrite (str, optional): Whether to overwrite existing file. Defaults to "False".
Returns:
str: Success message with filepath or error message
"""
```
## Error Handling
The tool includes error handling for common file operations:
- FileExistsError: When file exists and overwrite is not allowed
- KeyError: When required parameters are missing
- Directory Creation: Automatically creates directories if they don't exist
- General Exceptions: Catches and reports any other file operation errors
## Best Practices
1. Always provide absolute file paths
2. Ensure proper file permissions
3. Handle potential errors in your agent prompts
4. Verify file contents after writing
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import FileWriterTool
# Initialize tool
file_writer = FileWriterTool()
# Create agent
writer = Agent(
role='Technical Writer',
goal='Create and save technical documentation',
backstory='Expert technical writer with experience in documentation.',
tools=[file_writer]
)
# Define task
writing_task = Task(
description="""Write a technical guide about Python best practices and save it
to the docs directory. The file should be named 'python_guide.md'.
Include sections on code style, documentation, and testing.
If a file already exists, overwrite it.""",
agent=writer
)
# The agent can use the tool with these parameters:
# {
# "filename": "python_guide.md",
# "directory": "docs",
# "overwrite": "True",
# "content": "# Python Best Practices\n\n## Code Style\n..."
# }
# Create crew
crew = Crew(
agents=[writer],
tasks=[writing_task]
)
# Execute
result = crew.kickoff()
```
## Notes
- The tool automatically creates directories in the file path if they don't exist
- Supports various file formats (txt, md, json, etc.)
- Returns descriptive error messages for better debugging
- Thread-safe file operations

View File

@@ -1,181 +0,0 @@
---
title: FirecrawlCrawlWebsiteTool
description: A web crawling tool powered by Firecrawl API for comprehensive website content extraction
icon: spider-web
---
## FirecrawlCrawlWebsiteTool
The FirecrawlCrawlWebsiteTool provides website crawling capabilities using the Firecrawl API. It allows for customizable crawling with options for polling intervals, idempotency, and URL parameters.
## Installation
```bash
pip install 'crewai[tools]'
pip install firecrawl-py # Required dependency
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import FirecrawlCrawlWebsiteTool
# Method 1: Using environment variable
# export FIRECRAWL_API_KEY='your-api-key'
crawler = FirecrawlCrawlWebsiteTool()
# Method 2: Providing API key directly
crawler = FirecrawlCrawlWebsiteTool(
api_key="your-firecrawl-api-key"
)
# Method 3: With custom configuration
crawler = FirecrawlCrawlWebsiteTool(
api_key="your-firecrawl-api-key",
url="https://example.com", # Base URL
poll_interval=5, # Custom polling interval
idempotency_key="unique-key"
)
# Create an agent with the tool
researcher = Agent(
role='Web Crawler',
goal='Extract and analyze website content',
backstory='Expert at crawling and analyzing web content.',
tools=[crawler],
verbose=True
)
```
## Input Schema
```python
class FirecrawlCrawlWebsiteToolSchema(BaseModel):
url: str = Field(description="Website URL")
```
## Function Signature
```python
def __init__(
self,
api_key: Optional[str] = None,
url: Optional[str] = None,
params: Optional[Dict[str, Any]] = None,
poll_interval: Optional[int] = 2,
idempotency_key: Optional[str] = None,
**kwargs
):
"""
Initialize the website crawling tool.
Args:
api_key (Optional[str]): Firecrawl API key. If not provided, checks FIRECRAWL_API_KEY env var
url (Optional[str]): Base URL to crawl. Can be overridden in _run
params (Optional[Dict[str, Any]]): Additional parameters for FirecrawlApp
poll_interval (Optional[int]): Poll interval for FirecrawlApp
idempotency_key (Optional[str]): Idempotency key for FirecrawlApp
**kwargs: Additional arguments for tool creation
"""
def _run(self, url: str) -> Any:
"""
Crawl a website using Firecrawl.
Args:
url (str): Website URL to crawl (overrides constructor URL if provided)
Returns:
Any: Crawled website content from Firecrawl API
"""
```
## Best Practices
1. Set up API authentication:
- Use environment variable: `export FIRECRAWL_API_KEY='your-api-key'`
- Or provide directly in constructor
2. Configure crawling parameters:
- Set appropriate poll intervals
- Use idempotency keys for retry safety
- Customize URL parameters as needed
3. Handle rate limits and quotas
4. Consider website robots.txt policies
5. Handle potential crawling errors in agent prompts
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import FirecrawlCrawlWebsiteTool
# Initialize crawler with configuration
crawler = FirecrawlCrawlWebsiteTool(
api_key="your-firecrawl-api-key",
poll_interval=5,
params={
"max_depth": 3,
"follow_links": True
}
)
# Create agent
web_analyst = Agent(
role='Web Content Analyst',
goal='Extract and analyze website content comprehensively',
backstory='Expert at web crawling and content analysis.',
tools=[crawler]
)
# Define task
crawl_task = Task(
description="""Crawl the documentation website at docs.example.com
and extract all API-related content.""",
agent=web_analyst
)
# The agent will use:
# {
# "url": "https://docs.example.com"
# }
# Create crew
crew = Crew(
agents=[web_analyst],
tasks=[crawl_task]
)
# Execute
result = crew.kickoff()
```
## Configuration Options
### URL Parameters
```python
params = {
"max_depth": 3, # Maximum crawl depth
"follow_links": True, # Follow internal links
"exclude_patterns": [], # URL patterns to exclude
"include_patterns": [] # URL patterns to include
}
```
### Polling Configuration
```python
crawler = FirecrawlCrawlWebsiteTool(
poll_interval=5, # Poll every 5 seconds
idempotency_key="unique-key-123" # For retry safety
)
```
## Notes
- Requires valid Firecrawl API key
- Supports both environment variable and direct API key configuration
- Configurable polling intervals for crawl status
- Idempotency support for safe retries
- Thread-safe operations
- Customizable crawling parameters
- Respects robots.txt by default

View File

@@ -1,154 +0,0 @@
---
title: FirecrawlSearchTool
description: A web search tool powered by Firecrawl API for comprehensive web search capabilities
icon: magnifying-glass-chart
---
## FirecrawlSearchTool
The FirecrawlSearchTool provides web search capabilities using the Firecrawl API. It allows for customizable search queries with options for result formatting and search parameters.
## Installation
```bash
pip install 'crewai[tools]'
pip install firecrawl-py # Required dependency
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import FirecrawlSearchTool
# Initialize the tool with your API key
search_tool = FirecrawlSearchTool(api_key="your-firecrawl-api-key")
# Create an agent with the tool
researcher = Agent(
role='Web Researcher',
goal='Find relevant information across the web',
backstory='Expert at web research and information gathering.',
tools=[search_tool],
verbose=True
)
```
## Input Schema
```python
class FirecrawlSearchToolSchema(BaseModel):
query: str = Field(description="Search query")
page_options: Optional[Dict[str, Any]] = Field(
default=None,
description="Options for result formatting"
)
search_options: Optional[Dict[str, Any]] = Field(
default=None,
description="Options for searching"
)
```
## Function Signature
```python
def __init__(self, api_key: Optional[str] = None, **kwargs):
"""
Initialize the Firecrawl search tool.
Args:
api_key (Optional[str]): Firecrawl API key
**kwargs: Additional arguments for tool creation
"""
def _run(
self,
query: str,
page_options: Optional[Dict[str, Any]] = None,
result_options: Optional[Dict[str, Any]] = None,
) -> Any:
"""
Perform a web search using Firecrawl.
Args:
query (str): Search query string
page_options (Optional[Dict[str, Any]]): Options for result formatting
result_options (Optional[Dict[str, Any]]): Options for search results
Returns:
Any: Search results from Firecrawl API
"""
```
## Best Practices
1. Always provide a valid API key
2. Use specific, focused search queries
3. Customize page and result options for better results
4. Handle potential API errors in agent prompts
5. Consider rate limits and usage quotas
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import FirecrawlSearchTool
# Initialize tool with API key
search_tool = FirecrawlSearchTool(api_key="your-firecrawl-api-key")
# Create agent
researcher = Agent(
role='Market Researcher',
goal='Research market trends and competitor analysis',
backstory='Expert market analyst with deep research skills.',
tools=[search_tool]
)
# Define task
research_task = Task(
description="""Research the latest developments in electric vehicles,
focusing on market leaders and emerging technologies. Format the results
in a structured way.""",
agent=researcher
)
# The agent will use:
# {
# "query": "electric vehicle market leaders emerging technologies",
# "page_options": {
# "format": "structured",
# "maxLength": 1000
# },
# "result_options": {
# "limit": 5,
# "sortBy": "relevance"
# }
# }
# Create crew
crew = Crew(
agents=[researcher],
tasks=[research_task]
)
# Execute
result = crew.kickoff()
```
## Error Handling
The tool includes error handling for:
- Missing API key
- Missing firecrawl-py package
- API request failures
- Invalid options parameters
## Notes
- Requires valid Firecrawl API key
- Supports customizable search parameters
- Provides structured web search results
- Thread-safe operations
- Efficient for large-scale web searches
- Handles rate limiting automatically

View File

@@ -1,233 +0,0 @@
---
title: GithubSearchTool
description: A tool for semantic search within GitHub repositories using RAG capabilities
icon: github
---
## GithubSearchTool
The GithubSearchTool enables semantic search capabilities for GitHub repositories using Retrieval-Augmented Generation (RAG). It processes various content types including code, repository information, pull requests, and issues, allowing natural language queries across repository content.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import GithubSearchTool
# Method 1: Initialize with specific repository
github_tool = GithubSearchTool(
github_repo="owner/repo",
gh_token="your_github_token",
content_types=["code", "pr", "issue"]
)
# Method 2: Initialize without repository (specify at runtime)
flexible_github_tool = GithubSearchTool(
gh_token="your_github_token",
content_types=["code", "repo"]
)
# Create an agent with the tool
researcher = Agent(
role='GitHub Researcher',
goal='Search and analyze repository contents',
backstory='Expert at finding relevant information in GitHub repositories.',
tools=[github_tool],
verbose=True
)
```
## Input Schema
### Fixed Repository Schema (when repo provided during initialization)
```python
class FixedGithubSearchToolSchema(BaseModel):
search_query: str = Field(
description="Mandatory search query you want to use to search the github repo's content"
)
```
### Flexible Repository Schema (when repo provided at runtime)
```python
class GithubSearchToolSchema(FixedGithubSearchToolSchema):
github_repo: str = Field(
description="Mandatory github you want to search"
)
content_types: List[str] = Field(
description="Mandatory content types you want to be included search, options: [code, repo, pr, issue]"
)
```
## Function Signature
```python
def __init__(
self,
github_repo: Optional[str] = None,
gh_token: str,
content_types: List[str],
**kwargs
):
"""
Initialize the GitHub search tool.
Args:
github_repo (Optional[str]): Repository to search (optional)
gh_token (str): GitHub authentication token
content_types (List[str]): Content types to search
**kwargs: Additional arguments for RAG tool configuration
"""
def _run(
self,
search_query: str,
**kwargs: Any
) -> str:
"""
Execute semantic search on repository contents.
Args:
search_query (str): Query to search in the repository
**kwargs: Additional arguments including github_repo and content_types if not initialized
Returns:
str: Relevant content from the repository matching the query
"""
```
## Best Practices
1. Authentication:
- Secure token management
- Use environment variables
- Handle token expiration
2. Search Optimization:
- Target specific content types
- Use focused queries
- Consider rate limits
3. Performance Considerations:
- Pre-initialize for repeated searches
- Handle large repositories
- Monitor API usage
4. Error Handling:
- Verify repository access
- Handle API limits
- Manage authentication errors
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import GithubSearchTool
# Initialize tool with specific repository
github_tool = GithubSearchTool(
github_repo="owner/repo",
gh_token="your_github_token",
content_types=["code", "pr", "issue"]
)
# Create agent
researcher = Agent(
role='GitHub Researcher',
goal='Extract insights from repository content',
backstory='Expert at analyzing GitHub repositories.',
tools=[github_tool]
)
# Define task
research_task = Task(
description="""Find all implementations of
machine learning algorithms in the codebase.""",
agent=researcher
)
# The tool will use:
# {
# "search_query": "machine learning implementation"
# }
# Create crew
crew = Crew(
agents=[researcher],
tasks=[research_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Dynamic Repository Selection
```python
# Initialize without repository
flexible_tool = GithubSearchTool(
gh_token="your_github_token",
content_types=["code", "repo"]
)
# Search different repositories
backend_results = flexible_tool.run(
search_query="authentication implementation",
github_repo="owner/backend-repo"
)
frontend_results = flexible_tool.run(
search_query="component architecture",
github_repo="owner/frontend-repo"
)
```
### Multiple Content Type Analysis
```python
# Create tool with multiple content types
multi_tool = GithubSearchTool(
github_repo="owner/repo",
gh_token="your_github_token",
content_types=["code", "pr", "issue", "repo"]
)
# Search across all content types
results = multi_tool.run(
search_query="feature implementation status"
)
```
### Error Handling Example
```python
try:
github_tool = GithubSearchTool(
gh_token="your_github_token",
content_types=["code"]
)
results = github_tool.run(
search_query="api endpoints",
github_repo="owner/repo"
)
print(results)
except Exception as e:
print(f"Error searching repository: {str(e)}")
```
## Notes
- Inherits from RagTool
- Uses GithubLoader
- Requires authentication
- Supports multiple content types
- Dynamic repository specification
- Efficient content retrieval
- Thread-safe operations
- Maintains search context
- Handles API rate limits
- Memory-efficient processing

View File

@@ -1,220 +0,0 @@
---
title: JinaScrapeWebsiteTool
description: A tool for scraping website content using Jina.ai's reader service with markdown output
icon: globe
---
## JinaScrapeWebsiteTool
The JinaScrapeWebsiteTool provides website content scraping capabilities using Jina.ai's reader service. It converts web content into clean markdown format and supports both fixed and dynamic URL modes with optional authentication.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import JinaScrapeWebsiteTool
# Method 1: Fixed URL (specified at initialization)
fixed_tool = JinaScrapeWebsiteTool(
website_url="https://example.com",
api_key="your-jina-api-key" # Optional
)
# Method 2: Dynamic URL (specified at runtime)
dynamic_tool = JinaScrapeWebsiteTool(
api_key="your-jina-api-key" # Optional
)
# Create an agent with the tool
researcher = Agent(
role='Web Content Researcher',
goal='Extract and analyze website content',
backstory='Expert at gathering and processing web information.',
tools=[fixed_tool], # or [dynamic_tool]
verbose=True
)
```
## Input Schema
```python
class JinaScrapeWebsiteToolInput(BaseModel):
website_url: str = Field(
description="Mandatory website url to read the file"
)
```
## Function Signature
```python
def __init__(
self,
website_url: Optional[str] = None,
api_key: Optional[str] = None,
custom_headers: Optional[dict] = None,
**kwargs
):
"""
Initialize the website scraping tool.
Args:
website_url (Optional[str]): URL to scrape (optional for dynamic mode)
api_key (Optional[str]): Jina.ai API key for authentication
custom_headers (Optional[dict]): Custom HTTP headers
**kwargs: Additional arguments for base tool
"""
def _run(
self,
website_url: Optional[str] = None
) -> str:
"""
Execute website scraping.
Args:
website_url (Optional[str]): URL to scrape (required for dynamic mode)
Returns:
str: Markdown-formatted website content
"""
```
## Best Practices
1. URL Handling:
- Use complete URLs
- Validate URL format
- Handle redirects
- Monitor timeouts
2. Authentication:
- Secure API key storage
- Use environment variables
- Manage headers properly
- Handle auth errors
3. Content Processing:
- Handle large pages
- Process markdown output
- Manage encoding
- Handle errors
4. Mode Selection:
- Choose fixed mode for static sites
- Use dynamic mode for variable URLs
- Consider caching
- Manage timeouts
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import JinaScrapeWebsiteTool
import os
# Initialize tool with API key
scraper_tool = JinaScrapeWebsiteTool(
api_key=os.getenv('JINA_API_KEY'),
custom_headers={
'User-Agent': 'CrewAI Bot 1.0'
}
)
# Create agent
researcher = Agent(
role='Web Content Analyst',
goal='Extract and analyze website content',
backstory='Expert at processing web information.',
tools=[scraper_tool]
)
# Define task
analysis_task = Task(
description="""Analyze the content of
https://example.com/blog for key insights.""",
agent=researcher
)
# Create crew
crew = Crew(
agents=[researcher],
tasks=[analysis_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Multiple Site Analysis
```python
# Initialize tool
scraper = JinaScrapeWebsiteTool(
api_key=os.getenv('JINA_API_KEY')
)
# Analyze multiple sites
results = []
sites = [
"https://site1.com",
"https://site2.com",
"https://site3.com"
]
for site in sites:
content = scraper.run(
website_url=site
)
results.append(content)
```
### Custom Headers Configuration
```python
# Initialize with custom headers
tool = JinaScrapeWebsiteTool(
custom_headers={
'User-Agent': 'Custom Bot 1.0',
'Accept-Language': 'en-US,en;q=0.9',
'Accept': 'text/html,application/xhtml+xml'
}
)
# Use the tool
content = tool.run(
website_url="https://example.com"
)
```
### Error Handling Example
```python
try:
scraper = JinaScrapeWebsiteTool()
content = scraper.run(
website_url="https://example.com"
)
print(content)
except requests.exceptions.RequestException as e:
print(f"Error accessing website: {str(e)}")
except Exception as e:
print(f"Error processing content: {str(e)}")
```
## Notes
- Uses Jina.ai reader service
- Markdown output format
- API key authentication
- Custom headers support
- Error handling
- Timeout management
- Content processing
- URL validation
- Redirect handling
- Response formatting

View File

@@ -1,224 +0,0 @@
---
title: JSONSearchTool
description: A tool for semantic search within JSON files using RAG capabilities
icon: braces
---
## JSONSearchTool
The JSONSearchTool enables semantic search capabilities for JSON files using Retrieval-Augmented Generation (RAG). It supports both fixed and dynamic file path modes, allowing flexible usage patterns.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import JSONSearchTool
# Method 1: Fixed path (specified at initialization)
fixed_tool = JSONSearchTool(
json_path="path/to/data.json"
)
# Method 2: Dynamic path (specified at runtime)
dynamic_tool = JSONSearchTool()
# Create an agent with the tool
researcher = Agent(
role='JSON Data Researcher',
goal='Search and analyze JSON data',
backstory='Expert at finding relevant information in JSON files.',
tools=[fixed_tool], # or [dynamic_tool]
verbose=True
)
```
## Input Schema
### Fixed Path Mode
```python
class FixedJSONSearchToolSchema(BaseModel):
search_query: str = Field(
description="Mandatory search query you want to use to search the JSON's content"
)
```
### Dynamic Path Mode
```python
class JSONSearchToolSchema(BaseModel):
json_path: str = Field(
description="Mandatory json path you want to search"
)
search_query: str = Field(
description="Mandatory search query you want to use to search the JSON's content"
)
```
## Function Signature
```python
def __init__(
self,
json_path: Optional[str] = None,
**kwargs
):
"""
Initialize the JSON search tool.
Args:
json_path (Optional[str]): Path to JSON file (optional for dynamic mode)
**kwargs: Additional arguments for RAG tool configuration
"""
def _run(
self,
search_query: str,
**kwargs: Any
) -> str:
"""
Execute semantic search on JSON contents.
Args:
search_query (str): Query to search in the JSON
**kwargs: Additional arguments
Returns:
str: Relevant content from the JSON matching the query
"""
```
## Best Practices
1. File Handling:
- Use absolute file paths
- Verify file existence
- Handle large JSON files
- Monitor memory usage
2. Query Optimization:
- Structure queries clearly
- Consider JSON structure
- Handle nested data
- Monitor performance
3. Error Handling:
- Check file access
- Validate JSON format
- Handle malformed JSON
- Log issues
4. Mode Selection:
- Choose fixed mode for static files
- Use dynamic mode for runtime selection
- Consider caching
- Manage file lifecycle
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import JSONSearchTool
# Initialize tool
json_tool = JSONSearchTool(
json_path="data/config.json"
)
# Create agent
researcher = Agent(
role='JSON Data Analyst',
goal='Extract insights from JSON configuration',
backstory='Expert at analyzing JSON data structures.',
tools=[json_tool]
)
# Define task
analysis_task = Task(
description="""Find all configuration settings
related to security.""",
agent=researcher
)
# Create crew
crew = Crew(
agents=[researcher],
tasks=[analysis_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Multiple File Analysis
```python
# Create tools for different JSON files
config_tool = JSONSearchTool(
json_path="config/settings.json"
)
data_tool = JSONSearchTool(
json_path="data/records.json"
)
# Create agent with multiple tools
analyst = Agent(
role='JSON Data Analyst',
goal='Cross-reference configuration and data',
tools=[config_tool, data_tool]
)
```
### Dynamic File Loading
```python
# Initialize dynamic tool
dynamic_tool = JSONSearchTool()
# Use with different JSON files
result1 = dynamic_tool.run(
json_path="file1.json",
search_query="security settings"
)
result2 = dynamic_tool.run(
json_path="file2.json",
search_query="user preferences"
)
```
### Error Handling Example
```python
try:
json_tool = JSONSearchTool(
json_path="config/settings.json"
)
results = json_tool.run(
search_query="encryption settings"
)
print(results)
except FileNotFoundError as e:
print(f"JSON file not found: {str(e)}")
except ValueError as e:
print(f"Invalid JSON format: {str(e)}")
except Exception as e:
print(f"Error processing JSON: {str(e)}")
```
## Notes
- Inherits from RagTool
- Supports fixed/dynamic modes
- JSON path validation
- Memory management
- Performance optimization
- Error handling
- Search capabilities
- Content extraction
- Format validation
- Security features

View File

@@ -1,184 +0,0 @@
---
title: LinkupSearchTool
description: A search tool powered by Linkup API for retrieving contextual information
icon: search
---
## LinkupSearchTool
The LinkupSearchTool provides search capabilities using the Linkup API. It allows for customizable search depth and output formatting, returning structured results with contextual information.
## Installation
```bash
pip install 'crewai[tools]'
pip install linkup # Required dependency
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import LinkupSearchTool
# Initialize the tool with your API key
search_tool = LinkupSearchTool(api_key="your-linkup-api-key")
# Create an agent with the tool
researcher = Agent(
role='Information Researcher',
goal='Find relevant contextual information',
backstory='Expert at retrieving and analyzing contextual data.',
tools=[search_tool],
verbose=True
)
```
## Function Signature
```python
def __init__(self, api_key: str):
"""
Initialize the Linkup search tool.
Args:
api_key (str): Linkup API key for authentication
"""
def _run(
self,
query: str,
depth: str = "standard",
output_type: str = "searchResults"
) -> dict:
"""
Perform a search using the Linkup API.
Args:
query (str): The search query
depth (str): Search depth ("standard" by default)
output_type (str): Desired result type ("searchResults" by default)
Returns:
dict: {
"success": bool,
"results": List[Dict] | None,
"error": str | None
}
On success, results contains list of:
{
"name": str,
"url": str,
"content": str
}
"""
```
## Best Practices
1. Always provide a valid API key
2. Use specific, focused search queries
3. Choose appropriate search depth based on needs
4. Handle potential API errors in agent prompts
5. Process structured results effectively
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import LinkupSearchTool
# Initialize tool with API key
search_tool = LinkupSearchTool(api_key="your-linkup-api-key")
# Create agent
researcher = Agent(
role='Context Researcher',
goal='Find detailed contextual information about topics',
backstory='Expert at discovering and analyzing contextual data.',
tools=[search_tool]
)
# Define task
research_task = Task(
description="""Research the latest developments in quantum computing,
focusing on recent breakthroughs and applications. Use standard depth
for comprehensive results.""",
agent=researcher
)
# The tool will use:
# query: "quantum computing recent breakthroughs applications"
# depth: "standard"
# output_type: "searchResults"
# Create crew
crew = Crew(
agents=[researcher],
tasks=[research_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Search Depth Options
```python
# Quick surface-level search
results = search_tool._run(
query="quantum computing",
depth="basic"
)
# Standard comprehensive search
results = search_tool._run(
query="quantum computing",
depth="standard"
)
# Deep detailed search
results = search_tool._run(
query="quantum computing",
depth="deep"
)
```
### Output Type Options
```python
# Default search results
results = search_tool._run(
query="quantum computing",
output_type="searchResults"
)
# Custom output format
results = search_tool._run(
query="quantum computing",
output_type="customFormat"
)
```
### Error Handling
```python
results = search_tool._run(query="quantum computing")
if results["success"]:
for result in results["results"]:
print(f"Name: {result['name']}")
print(f"URL: {result['url']}")
print(f"Content: {result['content']}")
else:
print(f"Error: {results['error']}")
```
## Notes
- Requires valid Linkup API key
- Returns structured search results
- Supports multiple search depths
- Configurable output formats
- Built-in error handling
- Thread-safe operations
- Efficient for contextual searches

View File

@@ -1,192 +0,0 @@
---
title: LlamaIndexTool
description: A wrapper tool for integrating LlamaIndex tools and query engines with CrewAI
icon: link
---
## LlamaIndexTool
The LlamaIndexTool serves as a bridge between CrewAI and LlamaIndex, allowing you to use LlamaIndex tools and query engines within your CrewAI agents. It supports both direct tool wrapping and query engine integration.
## Installation
```bash
pip install 'crewai[tools]'
pip install llama-index # Required for LlamaIndex integration
```
## Usage Examples
### Using with LlamaIndex Tools
```python
from crewai import Agent
from crewai_tools import LlamaIndexTool
from llama_index.core.tools import BaseTool as LlamaBaseTool
from pydantic import BaseModel, Field
# Create a LlamaIndex tool
class CustomLlamaSchema(BaseModel):
query: str = Field(..., description="Query to process")
class CustomLlamaTool(LlamaBaseTool):
name = "Custom Llama Tool"
description = "A custom LlamaIndex tool"
def __call__(self, query: str) -> str:
return f"Processed: {query}"
# Wrap the LlamaIndex tool
llama_tool = CustomLlamaTool()
wrapped_tool = LlamaIndexTool.from_tool(llama_tool)
# Create an agent with the tool
agent = Agent(
role='LlamaIndex Integration Agent',
goal='Process queries using LlamaIndex tools',
backstory='Specialist in integrating LlamaIndex capabilities.',
tools=[wrapped_tool]
)
```
### Using with Query Engines
```python
from crewai import Agent
from crewai_tools import LlamaIndexTool
from llama_index.core import VectorStoreIndex, Document
# Create a query engine
documents = [Document(text="Sample document content")]
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
# Create the tool
query_tool = LlamaIndexTool.from_query_engine(
query_engine,
name="Document Search",
description="Search through indexed documents"
)
# Create an agent with the tool
agent = Agent(
role='Document Researcher',
goal='Find relevant information in documents',
backstory='Expert at searching through document collections.',
tools=[query_tool]
)
```
## Tool Creation Methods
### From LlamaIndex Tool
```python
@classmethod
def from_tool(cls, tool: Any, **kwargs: Any) -> "LlamaIndexTool":
"""
Create a CrewAI tool from a LlamaIndex tool.
Args:
tool (LlamaBaseTool): A LlamaIndex tool to wrap
**kwargs: Additional arguments for tool creation
Returns:
LlamaIndexTool: A CrewAI-compatible tool wrapper
Raises:
ValueError: If tool is not a LlamaBaseTool or lacks fn_schema
"""
```
### From Query Engine
```python
@classmethod
def from_query_engine(
cls,
query_engine: Any,
name: Optional[str] = None,
description: Optional[str] = None,
return_direct: bool = False,
**kwargs: Any
) -> "LlamaIndexTool":
"""
Create a CrewAI tool from a LlamaIndex query engine.
Args:
query_engine (BaseQueryEngine): The query engine to wrap
name (Optional[str]): Custom name for the tool
description (Optional[str]): Custom description
return_direct (bool): Whether to return query engine response directly
**kwargs: Additional arguments for tool creation
Returns:
LlamaIndexTool: A CrewAI-compatible tool wrapper
Raises:
ValueError: If query_engine is not a BaseQueryEngine
"""
```
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import LlamaIndexTool
from llama_index.core import VectorStoreIndex, Document
from llama_index.core.tools import QueryEngineTool
# Create documents and index
documents = [
Document(text="AI is a technology that simulates human intelligence."),
Document(text="Machine learning is a subset of AI.")
]
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
# Create the tool
search_tool = LlamaIndexTool.from_query_engine(
query_engine,
name="AI Knowledge Base",
description="Search through AI-related documents"
)
# Create agent
researcher = Agent(
role='AI Researcher',
goal='Research AI concepts',
backstory='Expert at finding and explaining AI concepts.',
tools=[search_tool]
)
# Define task
research_task = Task(
description="""Find and explain what AI is and its relationship
with machine learning.""",
agent=researcher
)
# The agent will use:
# {
# "query": "What is AI and how does it relate to machine learning?"
# }
# Create crew
crew = Crew(
agents=[researcher],
tasks=[research_task]
)
# Execute
result = crew.kickoff()
```
## Notes
- Automatically adapts LlamaIndex tool schemas for CrewAI compatibility
- Renames 'input' parameter to 'query' for better integration
- Supports both direct tool wrapping and query engine integration
- Handles schema validation and error resolution
- Thread-safe operations
- Compatible with all LlamaIndex tool types and query engines

View File

@@ -1,209 +0,0 @@
---
title: MDX Search Tool
description: A tool for semantic searching within MDX files using RAG capabilities
---
# MDX Search Tool
The MDX Search Tool enables semantic searching within MDX (Markdown with JSX) files using Retrieval-Augmented Generation (RAG) capabilities. It supports both fixed and dynamic file path modes, allowing you to specify the MDX file at initialization or runtime.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage
You can use the MDX Search Tool in two ways:
### 1. Fixed MDX File Path
Initialize the tool with a specific MDX file path:
```python
from crewai import Agent
from crewai_tools import MDXSearchTool
# Initialize with a fixed MDX file
tool = MDXSearchTool(mdx="/path/to/your/document.mdx")
# Create an agent with the tool
agent = Agent(
role='Technical Writer',
goal='Search through MDX documentation',
backstory='I help find relevant information in MDX documentation',
tools=[tool]
)
# Use in a task
task = Task(
description="Find information about API endpoints in the documentation",
agent=agent
)
```
### 2. Dynamic MDX File Path
Initialize the tool without a specific file path to provide it at runtime:
```python
from crewai import Agent
from crewai_tools import MDXSearchTool
# Initialize without a fixed MDX file
tool = MDXSearchTool()
# Create an agent with the tool
agent = Agent(
role='Documentation Analyst',
goal='Search through various MDX files',
backstory='I analyze different MDX documentation files',
tools=[tool]
)
# Use in a task with dynamic file path
task = Task(
description="Search for 'authentication' in the API documentation",
agent=agent,
context={
"mdx": "/path/to/api-docs.mdx",
"search_query": "authentication"
}
)
```
## Input Schema
### Fixed MDX File Mode
```python
class FixedMDXSearchToolSchema(BaseModel):
search_query: str # The search query to find content in the MDX file
```
### Dynamic MDX File Mode
```python
class MDXSearchToolSchema(BaseModel):
search_query: str # The search query to find content in the MDX file
mdx: str # The path to the MDX file to search
```
## Function Signatures
```python
def __init__(self, mdx: Optional[str] = None, **kwargs):
"""
Initialize the MDX Search Tool.
Args:
mdx (Optional[str]): Path to the MDX file (optional)
**kwargs: Additional arguments passed to RagTool
"""
def _run(
self,
search_query: str,
**kwargs: Any,
) -> str:
"""
Execute the search on the MDX file.
Args:
search_query (str): The query to search for
**kwargs: Additional arguments including 'mdx' for dynamic mode
Returns:
str: The search results from the MDX content
"""
```
## Best Practices
1. **File Path Handling**:
- Use absolute paths to avoid path resolution issues
- Verify file existence before searching
- Handle file permissions appropriately
2. **Query Optimization**:
- Use specific, focused search queries
- Consider context when formulating queries
- Break down complex searches into smaller queries
3. **Error Handling**:
- Handle file not found errors gracefully
- Manage permission issues appropriately
- Validate input parameters before processing
## Example Integration
Here's a complete example showing how to integrate the MDX Search Tool with CrewAI:
```python
from crewai import Agent, Task, Crew
from crewai_tools import MDXSearchTool
# Initialize the tool
mdx_tool = MDXSearchTool()
# Create an agent with the tool
researcher = Agent(
role='Documentation Researcher',
goal='Find and analyze information in MDX documentation',
backstory='I am an expert at finding relevant information in documentation',
tools=[mdx_tool]
)
# Create tasks
search_task = Task(
description="""
Search through the API documentation for information about authentication methods.
Look for:
1. Authentication endpoints
2. Security best practices
3. Token handling
Provide a comprehensive summary of the findings.
""",
agent=researcher,
context={
"mdx": "/path/to/api-docs.mdx",
"search_query": "authentication security tokens"
}
)
# Create and run the crew
crew = Crew(
agents=[researcher],
tasks=[search_task]
)
result = crew.kickoff()
```
## Error Handling
The tool handles various error scenarios:
1. **File Not Found**:
```python
try:
tool = MDXSearchTool(mdx="/path/to/nonexistent.mdx")
except FileNotFoundError:
print("MDX file not found. Please verify the file path.")
```
2. **Permission Issues**:
```python
try:
tool = MDXSearchTool(mdx="/restricted/docs.mdx")
except PermissionError:
print("Insufficient permissions to access the MDX file.")
```
3. **Invalid Content**:
```python
try:
result = tool._run(search_query="query", mdx="/path/to/invalid.mdx")
except ValueError:
print("Invalid MDX content or format.")
```

View File

@@ -1,217 +0,0 @@
---
title: MySQLSearchTool
description: A tool for semantic search within MySQL database tables using RAG capabilities
icon: database
---
## MySQLSearchTool
The MySQLSearchTool enables semantic search capabilities for MySQL database tables using Retrieval-Augmented Generation (RAG). It processes table contents and allows natural language queries to search through the data.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import MySQLSearchTool
# Initialize the tool
mysql_tool = MySQLSearchTool(
table_name="users",
db_uri="mysql://user:pass@localhost:3306/database"
)
# Create an agent with the tool
researcher = Agent(
role='Database Researcher',
goal='Search and analyze database contents',
backstory='Expert at finding relevant information in databases.',
tools=[mysql_tool],
verbose=True
)
```
## Input Schema
```python
class MySQLSearchToolSchema(BaseModel):
search_query: str = Field(
description="Mandatory semantic search query you want to use to search the database's content"
)
```
## Function Signature
```python
def __init__(
self,
table_name: str,
db_uri: str,
**kwargs
):
"""
Initialize the MySQL search tool.
Args:
table_name (str): Name of the table to search
db_uri (str): Database connection URI
**kwargs: Additional arguments for RAG tool configuration
"""
def _run(
self,
search_query: str,
**kwargs: Any
) -> str:
"""
Execute semantic search on table contents.
Args:
search_query (str): Query to search in the table
**kwargs: Additional arguments
Returns:
str: Relevant content from the table matching the query
"""
```
## Best Practices
1. Database Connection:
- Use secure connection URIs
- Handle authentication properly
- Manage connection lifecycle
- Monitor timeouts
2. Query Optimization:
- Structure queries clearly
- Consider table size
- Handle large datasets
- Monitor performance
3. Security Considerations:
- Protect credentials
- Use environment variables
- Limit table access
- Validate inputs
4. Error Handling:
- Handle connection errors
- Manage query timeouts
- Provide clear messages
- Log issues
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import MySQLSearchTool
# Initialize tool
mysql_tool = MySQLSearchTool(
table_name="customers",
db_uri="mysql://user:pass@localhost:3306/crm"
)
# Create agent
researcher = Agent(
role='Database Analyst',
goal='Extract customer insights from database',
backstory='Expert at analyzing customer data.',
tools=[mysql_tool]
)
# Define task
analysis_task = Task(
description="""Find all premium customers
with recent purchases.""",
agent=researcher
)
# The tool will use:
# {
# "search_query": "premium customers recent purchases"
# }
# Create crew
crew = Crew(
agents=[researcher],
tasks=[analysis_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Multiple Table Analysis
```python
# Create tools for different tables
customers_tool = MySQLSearchTool(
table_name="customers",
db_uri="mysql://user:pass@localhost:3306/crm"
)
orders_tool = MySQLSearchTool(
table_name="orders",
db_uri="mysql://user:pass@localhost:3306/crm"
)
# Create agent with multiple tools
analyst = Agent(
role='Data Analyst',
goal='Cross-reference customer and order data',
tools=[customers_tool, orders_tool]
)
```
### Secure Connection Configuration
```python
import os
# Use environment variables for credentials
db_uri = (
f"mysql://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}"
f"@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}"
f"/{os.getenv('DB_NAME')}"
)
tool = MySQLSearchTool(
table_name="sensitive_data",
db_uri=db_uri
)
```
### Error Handling Example
```python
try:
mysql_tool = MySQLSearchTool(
table_name="users",
db_uri="mysql://user:pass@localhost:3306/app"
)
results = mysql_tool.run(
search_query="active users in California"
)
print(results)
except Exception as e:
print(f"Error querying database: {str(e)}")
```
## Notes
- Inherits from RagTool
- Uses MySQLLoader
- Requires database URI
- Table-specific search
- Semantic query support
- Connection management
- Error handling
- Performance optimization
- Security features
- Memory efficiency

View File

@@ -1,208 +0,0 @@
---
title: PDFSearchTool
description: A tool for semantic search within PDF documents using RAG capabilities
icon: file-search
---
## PDFSearchTool
The PDFSearchTool enables semantic search capabilities for PDF documents using Retrieval-Augmented Generation (RAG). It leverages embedchain's PDFEmbedchainAdapter for efficient PDF processing and supports both fixed and dynamic PDF path specification.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import PDFSearchTool
# Method 1: Initialize with specific PDF
pdf_tool = PDFSearchTool(pdf="/path/to/document.pdf")
# Method 2: Initialize without PDF (specify at runtime)
flexible_pdf_tool = PDFSearchTool()
# Create an agent with the tool
researcher = Agent(
role='PDF Researcher',
goal='Search and analyze PDF documents',
backstory='Expert at finding relevant information in PDFs.',
tools=[pdf_tool],
verbose=True
)
```
## Input Schema
### Fixed PDF Schema (when PDF path provided during initialization)
```python
class FixedPDFSearchToolSchema(BaseModel):
query: str = Field(
description="Mandatory query you want to use to search the PDF's content"
)
```
### Flexible PDF Schema (when PDF path provided at runtime)
```python
class PDFSearchToolSchema(FixedPDFSearchToolSchema):
pdf: str = Field(
description="Mandatory pdf path you want to search"
)
```
## Function Signature
```python
def __init__(
self,
pdf: Optional[str] = None,
**kwargs
):
"""
Initialize the PDF search tool.
Args:
pdf (Optional[str]): Path to PDF file (optional)
**kwargs: Additional arguments for RAG tool configuration
"""
def _run(
self,
query: str,
**kwargs: Any
) -> str:
"""
Execute semantic search on PDF content.
Args:
query (str): Search query for the PDF
**kwargs: Additional arguments including pdf path if not initialized
Returns:
str: Relevant content from the PDF matching the query
"""
```
## Best Practices
1. PDF File Handling:
- Use absolute paths for reliability
- Verify PDF file existence
- Handle large PDFs appropriately
2. Search Optimization:
- Use specific, focused queries
- Consider document structure
- Test with sample queries first
3. Performance Considerations:
- Pre-initialize with PDF for repeated searches
- Handle large documents efficiently
- Monitor memory usage
4. Error Handling:
- Verify PDF file existence
- Handle malformed PDFs
- Manage file access permissions
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import PDFSearchTool
# Initialize tool with specific PDF
pdf_tool = PDFSearchTool(pdf="/path/to/research.pdf")
# Create agent
researcher = Agent(
role='PDF Researcher',
goal='Extract insights from research papers',
backstory='Expert at analyzing research documents.',
tools=[pdf_tool]
)
# Define task
research_task = Task(
description="""Find all mentions of machine learning
applications in healthcare from the PDF.""",
agent=researcher
)
# The tool will use:
# {
# "query": "machine learning applications healthcare"
# }
# Create crew
crew = Crew(
agents=[researcher],
tasks=[research_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Dynamic PDF Selection
```python
# Initialize without PDF
flexible_tool = PDFSearchTool()
# Search different PDFs
research_results = flexible_tool.run(
query="quantum computing",
pdf="/path/to/research.pdf"
)
report_results = flexible_tool.run(
query="financial metrics",
pdf="/path/to/report.pdf"
)
```
### Multiple PDF Analysis
```python
# Create tools for different PDFs
research_tool = PDFSearchTool(pdf="/path/to/research.pdf")
report_tool = PDFSearchTool(pdf="/path/to/report.pdf")
# Create agent with multiple tools
analyst = Agent(
role='Document Analyst',
goal='Cross-reference multiple documents',
tools=[research_tool, report_tool]
)
```
### Error Handling Example
```python
try:
pdf_tool = PDFSearchTool()
results = pdf_tool.run(
query="important findings",
pdf="/path/to/document.pdf"
)
print(results)
except Exception as e:
print(f"Error processing PDF: {str(e)}")
```
## Notes
- Inherits from RagTool
- Uses PDFEmbedchainAdapter
- Supports semantic search
- Dynamic PDF specification
- Efficient content retrieval
- Thread-safe operations
- Maintains search context
- Handles large documents
- Supports various PDF formats
- Memory-efficient processing

View File

@@ -1,234 +0,0 @@
---
title: PDFTextWritingTool
description: A tool for adding text to specific positions in PDF documents with custom font support
icon: file-pdf
---
## PDFTextWritingTool
The PDFTextWritingTool allows you to add text to specific positions in PDF documents with support for custom fonts, colors, and positioning. It's particularly useful for adding annotations, watermarks, or any text overlay to existing PDFs.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import PDFTextWritingTool
# Basic initialization
pdf_tool = PDFTextWritingTool()
# Create an agent with the tool
document_processor = Agent(
role='Document Processor',
goal='Add text annotations to PDF documents',
backstory='Expert at PDF document processing and text manipulation.',
tools=[pdf_tool],
verbose=True
)
```
## Input Schema
```python
class PDFTextWritingToolSchema(BaseModel):
pdf_path: str = Field(
description="Path to the PDF file to modify"
)
text: str = Field(
description="Text to add to the PDF"
)
position: tuple = Field(
description="Tuple of (x, y) coordinates for text placement"
)
font_size: int = Field(
default=12,
description="Font size of the text"
)
font_color: str = Field(
default="0 0 0 rg",
description="RGB color code for the text"
)
font_name: Optional[str] = Field(
default="F1",
description="Font name for standard fonts"
)
font_file: Optional[str] = Field(
default=None,
description="Path to a .ttf font file for custom font usage"
)
page_number: int = Field(
default=0,
description="Page number to add text to"
)
```
## Function Signature
```python
def run(
self,
pdf_path: str,
text: str,
position: tuple,
font_size: int,
font_color: str,
font_name: str = "F1",
font_file: Optional[str] = None,
page_number: int = 0,
**kwargs
) -> str:
"""
Add text to a specific position in a PDF document.
Args:
pdf_path (str): Path to the PDF file to modify
text (str): Text to add to the PDF
position (tuple): (x, y) coordinates for text placement
font_size (int): Font size of the text
font_color (str): RGB color code for the text (e.g., "0 0 0 rg" for black)
font_name (str, optional): Font name for standard fonts (default: "F1")
font_file (str, optional): Path to a .ttf font file for custom font
page_number (int, optional): Page number to add text to (default: 0)
Returns:
str: Success message with output file path
"""
```
## Best Practices
1. File Handling:
- Ensure PDF files exist before processing
- Use absolute paths for reliability
- Handle file permissions appropriately
2. Text Positioning:
- Use appropriate coordinates based on PDF dimensions
- Consider page orientation and margins
- Test positioning with small changes first
3. Font Usage:
- Verify custom font files exist
- Use standard fonts when possible
- Test font rendering before production use
4. Error Handling:
- Check page numbers are valid
- Verify font file accessibility
- Handle file writing permissions
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import PDFTextWritingTool
# Initialize tool
pdf_tool = PDFTextWritingTool()
# Create agent
document_processor = Agent(
role='Document Processor',
goal='Process and annotate PDF documents',
backstory='Expert at PDF manipulation and text placement.',
tools=[pdf_tool]
)
# Define task
annotation_task = Task(
description="""Add a watermark saying 'CONFIDENTIAL' to
the center of the first page of the document at
'/path/to/document.pdf'.""",
agent=document_processor
)
# The tool will use:
# {
# "pdf_path": "/path/to/document.pdf",
# "text": "CONFIDENTIAL",
# "position": (300, 400),
# "font_size": 24,
# "font_color": "1 0 0 rg", # Red color
# "page_number": 0
# }
# Create crew
crew = Crew(
agents=[document_processor],
tasks=[annotation_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Custom Font Example
```python
# Using a custom font
result = pdf_tool.run(
pdf_path="/path/to/input.pdf",
text="Custom Font Text",
position=(100, 500),
font_size=16,
font_color="0 0 1 rg", # Blue color
font_file="/path/to/custom_font.ttf",
page_number=0
)
```
### Multiple Text Elements
```python
# Add multiple text elements
positions = [(100, 700), (100, 650), (100, 600)]
texts = ["Header", "Subheader", "Body Text"]
font_sizes = [18, 14, 12]
for text, position, size in zip(texts, positions, font_sizes):
pdf_tool.run(
pdf_path="/path/to/input.pdf",
text=text,
position=position,
font_size=size,
font_color="0 0 0 rg" # Black color
)
```
### Color Text Example
```python
# Add colored text
colors = {
"red": "1 0 0 rg",
"green": "0 1 0 rg",
"blue": "0 0 1 rg"
}
for y_pos, (color_name, color_code) in enumerate(colors.items()):
pdf_tool.run(
pdf_path="/path/to/input.pdf",
text=f"This text is {color_name}",
position=(100, 700 - y_pos * 50),
font_size=14,
font_color=color_code
)
```
## Notes
- Supports custom TrueType fonts (.ttf)
- Allows RGB color specifications
- Handles multi-page PDFs
- Preserves original PDF content
- Supports text positioning with x,y coordinates
- Maintains PDF structure and metadata
- Creates new output file for safety
- Thread-safe operations
- Efficient PDF manipulation
- Supports various text attributes

View File

@@ -1,181 +0,0 @@
---
title: PGSearchTool
description: A RAG-based semantic search tool for PostgreSQL database content
icon: database-search
---
## PGSearchTool
The PGSearchTool provides semantic search capabilities for PostgreSQL database content using RAG (Retrieval-Augmented Generation). It allows for natural language queries over database table content by leveraging embeddings and semantic search.
## Installation
```bash
pip install 'crewai[tools]'
pip install embedchain # Required dependency
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import PGSearchTool
# Initialize the tool with database configuration
search_tool = PGSearchTool(
db_uri="postgresql://user:password@localhost:5432/dbname",
table_name="your_table"
)
# Create an agent with the tool
researcher = Agent(
role='Database Researcher',
goal='Find relevant information in database content',
backstory='Expert at searching and analyzing database content.',
tools=[search_tool],
verbose=True
)
```
## Input Schema
```python
class PGSearchToolSchema(BaseModel):
search_query: str = Field(
description="Mandatory semantic search query for searching the database's content"
)
```
## Function Signature
```python
def __init__(self, table_name: str, **kwargs):
"""
Initialize the PostgreSQL search tool.
Args:
table_name (str): Name of the table to search
db_uri (str): PostgreSQL database URI (required in kwargs)
**kwargs: Additional arguments for RagTool initialization
"""
def _run(
self,
search_query: str,
**kwargs: Any
) -> Any:
"""
Perform semantic search on database content.
Args:
search_query (str): Semantic search query
**kwargs: Additional search parameters
Returns:
Any: Relevant database content based on semantic search
"""
```
## Best Practices
1. Secure database credentials:
```python
# Use environment variables for sensitive data
import os
db_uri = (
f"postgresql://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}"
f"@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
)
```
2. Optimize table selection
3. Use specific semantic queries
4. Handle database connection errors
5. Consider table size and query performance
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import PGSearchTool
# Initialize tool with database configuration
db_search = PGSearchTool(
db_uri="postgresql://user:password@localhost:5432/dbname",
table_name="customer_feedback"
)
# Create agent
analyst = Agent(
role='Database Analyst',
goal='Analyze customer feedback data',
backstory='Expert at finding insights in customer feedback.',
tools=[db_search]
)
# Define task
analysis_task = Task(
description="""Find all customer feedback related to product usability
and ease of use. Focus on common patterns and issues.""",
agent=analyst
)
# The tool will use:
# {
# "search_query": "product usability feedback ease of use issues"
# }
# Create crew
crew = Crew(
agents=[analyst],
tasks=[analysis_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Multiple Table Search
```python
# Create tools for different tables
customer_search = PGSearchTool(
db_uri="postgresql://user:password@localhost:5432/dbname",
table_name="customers"
)
orders_search = PGSearchTool(
db_uri="postgresql://user:password@localhost:5432/dbname",
table_name="orders"
)
# Use both tools in an agent
analyst = Agent(
role='Multi-table Analyst',
goal='Analyze customer and order data',
tools=[customer_search, orders_search]
)
```
### Error Handling
```python
try:
results = search_tool._run(
search_query="customer satisfaction ratings"
)
# Process results
except Exception as e:
print(f"Database search error: {str(e)}")
```
## Notes
- Inherits from RagTool for semantic search
- Uses embedchain's PostgresLoader
- Requires valid PostgreSQL connection
- Supports semantic natural language queries
- Thread-safe operations
- Efficient for large tables
- Handles connection pooling automatically

View File

@@ -1,282 +0,0 @@
---
title: RagTool
description: Base class for Retrieval-Augmented Generation (RAG) tools with flexible adapter support
icon: database
---
## RagTool
The RagTool serves as the base class for all Retrieval-Augmented Generation (RAG) tools in the CrewAI ecosystem. It provides a flexible adapter-based architecture for implementing knowledge base functionality with semantic search capabilities.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import RagTool
from crewai_tools.adapters import EmbedchainAdapter
from embedchain import App
# Create custom adapter
class CustomAdapter(RagTool.Adapter):
def query(self, question: str) -> str:
# Implement custom query logic
return "Answer based on knowledge base"
def add(self, *args, **kwargs) -> None:
# Implement custom add logic
pass
# Method 1: Use default EmbedchainAdapter
rag_tool = RagTool(
name="Custom Knowledge Base",
description="Specialized knowledge base for domain data",
summarize=True
)
# Method 2: Use custom adapter
custom_tool = RagTool(
name="Custom Knowledge Base",
adapter=CustomAdapter(),
summarize=False
)
# Create an agent with the tool
researcher = Agent(
role='Knowledge Base Researcher',
goal='Search and analyze knowledge base content',
backstory='Expert at finding relevant information in specialized datasets.',
tools=[rag_tool],
verbose=True
)
```
## Adapter Interface
```python
class Adapter(BaseModel, ABC):
@abstractmethod
def query(self, question: str) -> str:
"""
Query the knowledge base with a question.
Args:
question (str): Query to search in knowledge base
Returns:
str: Answer based on knowledge base content
"""
@abstractmethod
def add(self, *args: Any, **kwargs: Any) -> None:
"""
Add content to the knowledge base.
Args:
*args: Variable length argument list
**kwargs: Arbitrary keyword arguments
"""
```
## Function Signature
```python
def __init__(
self,
name: str = "Knowledge base",
description: str = "A knowledge base that can be used to answer questions.",
summarize: bool = False,
adapter: Optional[Adapter] = None,
config: Optional[dict[str, Any]] = None,
**kwargs
):
"""
Initialize the RAG tool.
Args:
name (str): Tool name
description (str): Tool description
summarize (bool): Enable answer summarization
adapter (Optional[Adapter]): Custom adapter implementation
config (Optional[dict]): Configuration for default adapter
**kwargs: Additional arguments for base tool
"""
def _run(
self,
query: str,
**kwargs: Any
) -> str:
"""
Execute query against knowledge base.
Args:
query (str): Question to ask
**kwargs: Additional arguments
Returns:
str: Answer from knowledge base
"""
```
## Best Practices
1. Adapter Implementation:
- Define clear interfaces
- Handle edge cases
- Implement error handling
- Document behavior
2. Knowledge Base Management:
- Organize content logically
- Update content regularly
- Monitor performance
- Handle large datasets
3. Query Optimization:
- Structure queries clearly
- Consider context
- Handle ambiguity
- Validate inputs
4. Error Handling:
- Handle missing data
- Manage timeouts
- Provide clear messages
- Log issues
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import RagTool
from embedchain import App
# Initialize tool with custom configuration
rag_tool = RagTool(
name="Technical Documentation KB",
description="Knowledge base for technical documentation",
summarize=True,
config={
"collection_name": "tech_docs",
"chunking": {
"chunk_size": 500,
"chunk_overlap": 50
}
}
)
# Add content to knowledge base
rag_tool.add(
"Technical documentation content here...",
data_type="text"
)
# Create agent
researcher = Agent(
role='Documentation Expert',
goal='Extract technical information from documentation',
backstory='Expert at analyzing technical documentation.',
tools=[rag_tool]
)
# Define task
research_task = Task(
description="""Find all mentions of API endpoints
and their authentication requirements.""",
agent=researcher
)
# Create crew
crew = Crew(
agents=[researcher],
tasks=[research_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Custom Adapter Implementation
```python
from typing import Any
from pydantic import BaseModel
from abc import ABC, abstractmethod
class SpecializedAdapter(RagTool.Adapter):
def __init__(self, config: dict):
self.config = config
self.knowledge_base = {}
def query(self, question: str) -> str:
# Implement specialized query logic
return self._process_query(question)
def add(self, content: str, **kwargs: Any) -> None:
# Implement specialized content addition
self._process_content(content, **kwargs)
# Use custom adapter
specialized_tool = RagTool(
name="Specialized KB",
adapter=SpecializedAdapter(config={"mode": "advanced"})
)
```
### Configuration Management
```python
# Configure default EmbedchainAdapter
config = {
"collection_name": "custom_collection",
"embedding": {
"model": "sentence-transformers/all-mpnet-base-v2",
"dimensions": 768
},
"chunking": {
"chunk_size": 1000,
"chunk_overlap": 100
}
}
tool = RagTool(config=config)
```
### Error Handling Example
```python
try:
rag_tool = RagTool()
# Add content
rag_tool.add(
"Documentation content...",
data_type="text"
)
# Query content
result = rag_tool.run(
query="What are the system requirements?"
)
print(result)
except Exception as e:
print(f"Error using knowledge base: {str(e)}")
```
## Notes
- Base class for RAG tools
- Flexible adapter pattern
- Default EmbedchainAdapter
- Custom adapter support
- Content management
- Query processing
- Error handling
- Configuration options
- Performance optimization
- Memory management

View File

@@ -1,229 +0,0 @@
---
title: SerpApi Google Search Tool
description: A tool for performing Google searches using the SerpApi service
---
# SerpApi Google Search Tool
The SerpApi Google Search Tool enables performing Google searches using the SerpApi service. It provides location-aware search capabilities with comprehensive result filtering.
## Installation
```bash
pip install 'crewai[tools]'
pip install serpapi
```
## Prerequisites
You need a SerpApi API key to use this tool. You can get one from [SerpApi's website](https://serpapi.com/manage-api-key).
Set your API key as an environment variable:
```bash
export SERPAPI_API_KEY="your_api_key_here"
```
## Usage
Here's how to use the SerpApi Google Search Tool:
```python
from crewai import Agent
from crewai_tools import SerpApiGoogleSearchTool
# Initialize the tool
search_tool = SerpApiGoogleSearchTool()
# Create an agent with the tool
search_agent = Agent(
role='Web Researcher',
goal='Find accurate information online',
backstory='I help research and analyze online information',
tools=[search_tool]
)
# Use in a task
task = Task(
description="Research recent AI developments",
agent=search_agent,
context={
"search_query": "latest artificial intelligence breakthroughs 2024",
"location": "United States" # Optional
}
)
```
## Input Schema
```python
class SerpApiGoogleSearchToolSchema(BaseModel):
search_query: str # The search query for Google Search
location: Optional[str] = None # Optional location for localized results
```
## Function Signatures
### Base Tool Initialization
```python
def __init__(self, **kwargs):
"""
Initialize the SerpApi tool with API credentials.
Raises:
ImportError: If serpapi package is not installed
ValueError: If SERPAPI_API_KEY environment variable is not set
"""
```
### Search Execution
```python
def _run(
self,
**kwargs: Any,
) -> dict:
"""
Execute the Google search.
Args:
search_query (str): The search query
location (Optional[str]): Optional location for results
Returns:
dict: Filtered search results from Google
Raises:
HTTPError: If the API request fails
"""
```
## Best Practices
1. **API Key Management**:
- Store the API key securely in environment variables
- Never hardcode the API key in your code
- Verify API key validity before making requests
2. **Search Optimization**:
- Use specific, targeted search queries
- Include relevant keywords and time frames
- Leverage location parameter for regional results
3. **Error Handling**:
- Handle API rate limits gracefully
- Implement retry logic for failed requests
- Validate input parameters before making requests
## Example Integration
Here's a complete example showing how to integrate the SerpApi Google Search Tool with CrewAI:
```python
from crewai import Agent, Task, Crew
from crewai_tools import SerpApiGoogleSearchTool
# Initialize the tool
search_tool = SerpApiGoogleSearchTool()
# Create an agent with the tool
researcher = Agent(
role='Research Analyst',
goal='Find and analyze current information',
backstory="""I am an expert at finding and analyzing
information from various online sources.""",
tools=[search_tool]
)
# Create tasks
research_task = Task(
description="""
Research the following topic:
1. Latest developments in quantum computing
2. Focus on practical applications
3. Include major company announcements
Provide a comprehensive analysis of the findings.
""",
agent=researcher,
context={
"search_query": "quantum computing breakthroughs applications companies",
"location": "United States"
}
)
# Create and run the crew
crew = Crew(
agents=[researcher],
tasks=[research_task]
)
result = crew.kickoff()
```
## Error Handling
The tool handles various error scenarios:
1. **Missing API Key**:
```python
try:
tool = SerpApiGoogleSearchTool()
except ValueError as e:
print("API key not found. Set SERPAPI_API_KEY environment variable.")
```
2. **API Request Errors**:
```python
try:
results = tool._run(
search_query="quantum computing",
location="United States"
)
except HTTPError as e:
print(f"API request failed: {str(e)}")
```
3. **Invalid Parameters**:
```python
try:
results = tool._run(
search_query="", # Empty query
location="Invalid Location"
)
except ValueError as e:
print("Invalid search parameters provided.")
```
## Response Format
The tool returns a filtered dictionary containing Google search results. Example response structure:
```python
{
"organic_results": [
{
"title": "Page Title",
"link": "https://...",
"snippet": "Page description or excerpt...",
"position": 1
}
# Additional results...
],
"knowledge_graph": {
"title": "Topic Title",
"description": "Topic description...",
"source": {
"name": "Source Name",
"link": "https://..."
}
},
"related_questions": [
{
"question": "Related question?",
"answer": "Answer to related question..."
}
# Additional related questions...
]
}
```
The response is automatically filtered to remove metadata and unnecessary fields, focusing on the most relevant search information. Fields like search metadata, parameters, and pagination are omitted for clarity.

View File

@@ -1,225 +0,0 @@
---
title: SerpApi Google Shopping Tool
description: A tool for searching Google Shopping using the SerpApi service
---
# SerpApi Google Shopping Tool
The SerpApi Google Shopping Tool enables searching Google Shopping results using the SerpApi service. It provides location-aware shopping search capabilities with comprehensive result filtering.
## Installation
```bash
pip install 'crewai[tools]'
pip install serpapi
```
## Prerequisites
You need a SerpApi API key to use this tool. You can get one from [SerpApi's website](https://serpapi.com/manage-api-key).
Set your API key as an environment variable:
```bash
export SERPAPI_API_KEY="your_api_key_here"
```
## Usage
Here's how to use the SerpApi Google Shopping Tool:
```python
from crewai import Agent
from crewai_tools import SerpApiGoogleShoppingTool
# Initialize the tool
shopping_tool = SerpApiGoogleShoppingTool()
# Create an agent with the tool
shopping_agent = Agent(
role='Shopping Researcher',
goal='Find the best shopping deals',
backstory='I help find and analyze shopping options',
tools=[shopping_tool]
)
# Use in a task
task = Task(
description="Find best deals for gaming laptops",
agent=shopping_agent,
context={
"search_query": "gaming laptop deals",
"location": "United States" # Optional
}
)
```
## Input Schema
```python
class SerpApiGoogleShoppingToolSchema(BaseModel):
search_query: str # The search query for Google Shopping
location: Optional[str] = None # Optional location for localized results
```
## Function Signatures
### Base Tool Initialization
```python
def __init__(self, **kwargs):
"""
Initialize the SerpApi tool with API credentials.
Raises:
ImportError: If serpapi package is not installed
ValueError: If SERPAPI_API_KEY environment variable is not set
"""
```
### Search Execution
```python
def _run(
self,
**kwargs: Any,
) -> dict:
"""
Execute the Google Shopping search.
Args:
search_query (str): The search query for Google Shopping
location (Optional[str]): Optional location for results
Returns:
dict: Filtered search results from Google Shopping
Raises:
HTTPError: If the API request fails
"""
```
## Best Practices
1. **API Key Management**:
- Store the API key securely in environment variables
- Never hardcode the API key in your code
- Verify API key validity before making requests
2. **Search Optimization**:
- Use specific, targeted search queries
- Include relevant product details in queries
- Leverage location parameter for regional pricing
3. **Error Handling**:
- Handle API rate limits gracefully
- Implement retry logic for failed requests
- Validate input parameters before making requests
## Example Integration
Here's a complete example showing how to integrate the SerpApi Google Shopping Tool with CrewAI:
```python
from crewai import Agent, Task, Crew
from crewai_tools import SerpApiGoogleShoppingTool
# Initialize the tool
shopping_tool = SerpApiGoogleShoppingTool()
# Create an agent with the tool
researcher = Agent(
role='Shopping Analyst',
goal='Find and analyze the best shopping deals',
backstory="""I am an expert at finding the best shopping deals
and analyzing product offerings across different regions.""",
tools=[shopping_tool]
)
# Create tasks
search_task = Task(
description="""
Research gaming laptops with the following criteria:
1. Price range: $800-$1500
2. Released in the last year
3. Compare prices across different retailers
Provide a comprehensive analysis of the findings.
""",
agent=researcher,
context={
"search_query": "gaming laptop RTX 4060 2023",
"location": "United States"
}
)
# Create and run the crew
crew = Crew(
agents=[researcher],
tasks=[search_task]
)
result = crew.kickoff()
```
## Error Handling
The tool handles various error scenarios:
1. **Missing API Key**:
```python
try:
tool = SerpApiGoogleShoppingTool()
except ValueError as e:
print("API key not found. Set SERPAPI_API_KEY environment variable.")
```
2. **API Request Errors**:
```python
try:
results = tool._run(
search_query="gaming laptop",
location="United States"
)
except HTTPError as e:
print(f"API request failed: {str(e)}")
```
3. **Invalid Parameters**:
```python
try:
results = tool._run(
search_query="", # Empty query
location="Invalid Location"
)
except ValueError as e:
print("Invalid search parameters provided.")
```
## Response Format
The tool returns a filtered dictionary containing Google Shopping results. Example response structure:
```python
{
"shopping_results": [
{
"title": "Product Title",
"price": "$999.99",
"link": "https://...",
"source": "Retailer Name",
"rating": 4.5,
"reviews": 123,
"thumbnail": "https://..."
}
# Additional results...
],
"organic_results": [
{
"title": "Related Product",
"link": "https://...",
"snippet": "Product description..."
}
# Additional organic results...
]
}
```
The response is automatically filtered to remove metadata and unnecessary fields, focusing on the most relevant shopping information.

View File

@@ -1,184 +0,0 @@
---
title: SerplyJobSearchTool
description: A tool for searching US job postings using the Serply API
icon: briefcase
---
## SerplyJobSearchTool
The SerplyJobSearchTool provides job search capabilities using the Serply API. It allows for searching job postings in the US market, returning structured information about positions, employers, locations, and remote work status.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import SerplyJobSearchTool
# Set environment variable
# export SERPLY_API_KEY='your-api-key'
# Initialize the tool
search_tool = SerplyJobSearchTool()
# Create an agent with the tool
job_researcher = Agent(
role='Job Market Researcher',
goal='Find relevant job opportunities',
backstory='Expert at analyzing job market trends and opportunities.',
tools=[search_tool],
verbose=True
)
```
## Input Schema
```python
class SerplyJobSearchToolSchema(BaseModel):
search_query: str = Field(
description="Mandatory search query for fetching job postings"
)
```
## Function Signature
```python
def __init__(self, **kwargs):
"""
Initialize the job search tool.
Args:
**kwargs: Additional arguments for RagTool initialization
Note:
Requires SERPLY_API_KEY environment variable
"""
def _run(
self,
**kwargs: Any
) -> str:
"""
Perform job search using Serply API.
Args:
search_query (str): Job search query
**kwargs: Additional search parameters
Returns:
str: Formatted string containing job listings with details:
- Position
- Employer
- Location
- Link
- Highlights
- Remote/Hybrid status
"""
```
## Best Practices
1. Set up API authentication:
```bash
export SERPLY_API_KEY='your-serply-api-key'
```
2. Use specific search queries
3. Handle potential API errors
4. Process structured results effectively
5. Consider rate limits and quotas
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import SerplyJobSearchTool
# Initialize tool
job_search = SerplyJobSearchTool()
# Create agent
recruiter = Agent(
role='Technical Recruiter',
goal='Find relevant job opportunities in tech',
backstory='Expert at identifying promising tech positions.',
tools=[job_search]
)
# Define task
search_task = Task(
description="""Search for senior software engineer positions
with remote work options in the US. Focus on positions
requiring Python expertise.""",
agent=recruiter
)
# The tool will use:
# {
# "search_query": "senior software engineer python remote"
# }
# Create crew
crew = Crew(
agents=[recruiter],
tasks=[search_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Handling Search Results
```python
# Example of processing structured results
results = search_tool._run(
search_query="machine learning engineer"
)
# Results format:
"""
Search results:
Position: Senior Machine Learning Engineer
Employer: TechCorp Inc
Location: San Francisco, CA
Link: https://example.com/job/123
Highlights: Python, TensorFlow, 5+ years experience
Is Remote: True
Is Hybrid: False
---
Position: ML Engineer
...
"""
```
### Error Handling
```python
try:
results = search_tool._run(
search_query="data scientist"
)
if not results:
print("No jobs found")
else:
print(results)
except Exception as e:
print(f"Job search error: {str(e)}")
```
## Notes
- Requires valid Serply API key
- Currently supports US job market only
- Returns structured job information
- Includes remote/hybrid status
- Thread-safe operations
- Efficient job search capabilities
- Handles API rate limiting automatically
- Provides detailed job highlights

View File

@@ -1,209 +0,0 @@
---
title: SerplyNewsSearchTool
description: A news article search tool powered by Serply API with configurable search parameters
icon: newspaper
---
## SerplyNewsSearchTool
The SerplyNewsSearchTool provides news article search capabilities using the Serply API. It allows for customizable search parameters including result limits and proxy location for region-specific news results.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import SerplyNewsSearchTool
# Set environment variable
# export SERPLY_API_KEY='your-api-key'
# Basic initialization
news_tool = SerplyNewsSearchTool()
# Advanced initialization with custom parameters
news_tool = SerplyNewsSearchTool(
limit=20, # Return 20 results
proxy_location="FR" # Search from France
)
# Create an agent with the tool
news_researcher = Agent(
role='News Researcher',
goal='Find relevant news articles',
backstory='Expert at news research and information gathering.',
tools=[news_tool],
verbose=True
)
```
## Input Schema
```python
class SerplyNewsSearchToolSchema(BaseModel):
search_query: str = Field(
description="Mandatory search query for fetching news articles"
)
```
## Function Signature
```python
def __init__(
self,
limit: Optional[int] = 10,
proxy_location: Optional[str] = "US",
**kwargs
):
"""
Initialize the news search tool.
Args:
limit (int): Maximum number of results [10-100] (default: 10)
proxy_location (str): Region for local news results (default: "US")
Options: US, CA, IE, GB, FR, DE, SE, IN, JP, KR, SG, AU, BR
**kwargs: Additional arguments for tool creation
"""
def _run(
self,
**kwargs: Any
) -> str:
"""
Perform news search using Serply API.
Args:
search_query (str): News search query
Returns:
str: Formatted string containing news results:
- Title
- Link
- Source
- Published Date
"""
```
## Best Practices
1. Set up API authentication:
```bash
export SERPLY_API_KEY='your-serply-api-key'
```
2. Configure search parameters appropriately:
- Set reasonable result limits
- Select relevant proxy location for regional news
- Consider time sensitivity of news content
3. Handle potential API errors
4. Process structured results effectively
5. Consider rate limits and quotas
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import SerplyNewsSearchTool
# Initialize tool with custom configuration
news_tool = SerplyNewsSearchTool(
limit=15, # 15 results
proxy_location="US" # US news sources
)
# Create agent
news_analyst = Agent(
role='News Analyst',
goal='Research breaking news and developments',
backstory='Expert at analyzing news trends and developments.',
tools=[news_tool]
)
# Define task
news_task = Task(
description="""Research the latest developments in renewable
energy technology and investments, focusing on major
announcements and industry trends.""",
agent=news_analyst
)
# The tool will use:
# {
# "search_query": "renewable energy technology investments news"
# }
# Create crew
crew = Crew(
agents=[news_analyst],
tasks=[news_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Regional News Configuration
```python
# French news sources
fr_news = SerplyNewsSearchTool(
proxy_location="FR",
limit=20
)
# Japanese news sources
jp_news = SerplyNewsSearchTool(
proxy_location="JP",
limit=20
)
```
### Result Processing
```python
# Get news results
try:
results = news_tool._run(
search_query="renewable energy investments"
)
print(results)
except Exception as e:
print(f"News search error: {str(e)}")
```
### Multiple Region Search
```python
# Search across multiple regions
regions = ["US", "GB", "DE"]
all_results = []
for region in regions:
regional_tool = SerplyNewsSearchTool(
proxy_location=region,
limit=5
)
results = regional_tool._run(
search_query="global tech innovations"
)
all_results.append(f"Results from {region}:\n{results}")
combined_results = "\n\n".join(all_results)
```
## Notes
- Requires valid Serply API key
- Supports multiple regions for news sources
- Configurable result limits (10-100)
- Returns structured news article data
- Thread-safe operations
- Efficient news search capabilities
- Handles API rate limiting automatically
- Includes source attribution and publication dates
- Follows redirects for final article URLs

View File

@@ -1,209 +0,0 @@
---
title: SerplyScholarSearchTool
description: A scholarly literature search tool powered by Serply API with configurable search parameters
icon: book
---
## SerplyScholarSearchTool
The SerplyScholarSearchTool provides scholarly literature search capabilities using the Serply API. It allows for customizable search parameters including language and proxy location for region-specific academic results.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import SerplyScholarSearchTool
# Set environment variable
# export SERPLY_API_KEY='your-api-key'
# Basic initialization
scholar_tool = SerplyScholarSearchTool()
# Advanced initialization with custom parameters
scholar_tool = SerplyScholarSearchTool(
hl="fr", # French language results
proxy_location="FR" # Search from France
)
# Create an agent with the tool
academic_researcher = Agent(
role='Academic Researcher',
goal='Find relevant scholarly literature',
backstory='Expert at academic research and literature review.',
tools=[scholar_tool],
verbose=True
)
```
## Input Schema
```python
class SerplyScholarSearchToolSchema(BaseModel):
search_query: str = Field(
description="Mandatory search query for fetching scholarly literature"
)
```
## Function Signature
```python
def __init__(
self,
hl: str = "us",
proxy_location: Optional[str] = "US",
**kwargs
):
"""
Initialize the scholar search tool.
Args:
hl (str): Host language code for results (default: "us")
Reference: https://developers.google.com/custom-search/docs/xml_results?hl=en#wsInterfaceLanguages
proxy_location (str): Region for local results (default: "US")
Options: US, CA, IE, GB, FR, DE, SE, IN, JP, KR, SG, AU, BR
**kwargs: Additional arguments for tool creation
"""
def _run(
self,
**kwargs: Any
) -> str:
"""
Perform scholarly literature search using Serply API.
Args:
search_query (str): Academic search query
Returns:
str: Formatted string containing scholarly results:
- Title
- Link
- Description
- Citation
- Authors
"""
```
## Best Practices
1. Set up API authentication:
```bash
export SERPLY_API_KEY='your-serply-api-key'
```
2. Configure search parameters appropriately:
- Use relevant language codes
- Select appropriate proxy location
- Provide specific academic search terms
3. Handle potential API errors
4. Process structured results effectively
5. Consider rate limits and quotas
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import SerplyScholarSearchTool
# Initialize tool with custom configuration
scholar_tool = SerplyScholarSearchTool(
hl="en", # English results
proxy_location="US" # US academic sources
)
# Create agent
researcher = Agent(
role='Academic Researcher',
goal='Research recent academic publications',
backstory='Expert at analyzing academic literature and research trends.',
tools=[scholar_tool]
)
# Define task
research_task = Task(
description="""Research recent academic publications on
machine learning applications in healthcare, focusing on
peer-reviewed articles from the last two years.""",
agent=researcher
)
# The tool will use:
# {
# "search_query": "machine learning healthcare applications"
# }
# Create crew
crew = Crew(
agents=[researcher],
tasks=[research_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Language and Region Configuration
```python
# French academic sources
fr_scholar = SerplyScholarSearchTool(
hl="fr",
proxy_location="FR"
)
# German academic sources
de_scholar = SerplyScholarSearchTool(
hl="de",
proxy_location="DE"
)
```
### Result Processing
```python
try:
results = scholar_tool._run(
search_query="machine learning healthcare applications"
)
print(results)
except Exception as e:
print(f"Scholar search error: {str(e)}")
```
### Citation Analysis
```python
# Extract and analyze citations
def analyze_citations(results):
citations = []
for result in results.split("---"):
if "Cite:" in result:
citation = result.split("Cite:")[1].split("\n")[0].strip()
citations.append(citation)
return citations
results = scholar_tool._run(
search_query="artificial intelligence ethics"
)
citations = analyze_citations(results)
```
## Notes
- Requires valid Serply API key
- Supports multiple languages and regions
- Returns structured academic article data
- Includes citation information
- Lists all authors of publications
- Thread-safe operations
- Efficient scholarly search capabilities
- Handles API rate limiting automatically
- Supports both direct and document links
- Provides comprehensive article metadata

View File

@@ -1,213 +0,0 @@
---
title: SerplyWebSearchTool
description: A Google search tool powered by Serply API with configurable search parameters
icon: search
---
## SerplyWebSearchTool
The SerplyWebSearchTool provides Google search capabilities using the Serply API. It allows for customizable search parameters including language, result limits, device type, and proxy location for region-specific results.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import SerplyWebSearchTool
# Set environment variable
# export SERPLY_API_KEY='your-api-key'
# Basic initialization
search_tool = SerplyWebSearchTool()
# Advanced initialization with custom parameters
search_tool = SerplyWebSearchTool(
hl="fr", # French language results
limit=20, # Return 20 results
device_type="mobile", # Mobile search results
proxy_location="FR" # Search from France
)
# Create an agent with the tool
researcher = Agent(
role='Web Researcher',
goal='Find relevant information online',
backstory='Expert at web research and information gathering.',
tools=[search_tool],
verbose=True
)
```
## Input Schema
```python
class SerplyWebSearchToolSchema(BaseModel):
search_query: str = Field(
description="Mandatory search query for Google search"
)
```
## Function Signature
```python
def __init__(
self,
hl: str = "us",
limit: int = 10,
device_type: str = "desktop",
proxy_location: str = "US",
**kwargs
):
"""
Initialize the Google search tool.
Args:
hl (str): Host language code for results (default: "us")
Reference: https://developers.google.com/custom-search/docs/xml_results?hl=en#wsInterfaceLanguages
limit (int): Maximum number of results [10-100] (default: 10)
device_type (str): "desktop" or "mobile" results (default: "desktop")
proxy_location (str): Region for local results (default: "US")
Options: US, CA, IE, GB, FR, DE, SE, IN, JP, KR, SG, AU, BR
**kwargs: Additional arguments for tool creation
"""
def _run(
self,
**kwargs: Any
) -> str:
"""
Perform Google search using Serply API.
Args:
search_query (str): Search query
Returns:
str: Formatted string containing search results:
- Title
- Link
- Description
"""
```
## Best Practices
1. Set up API authentication:
```bash
export SERPLY_API_KEY='your-serply-api-key'
```
2. Configure search parameters appropriately:
- Use relevant language codes
- Set reasonable result limits
- Choose appropriate device type
- Select relevant proxy location
3. Handle potential API errors
4. Process structured results effectively
5. Consider rate limits and quotas
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import SerplyWebSearchTool
# Initialize tool with custom configuration
search_tool = SerplyWebSearchTool(
hl="en", # English results
limit=15, # 15 results
device_type="desktop",
proxy_location="US"
)
# Create agent
researcher = Agent(
role='Web Researcher',
goal='Research emerging technology trends',
backstory='Expert at finding and analyzing tech trends.',
tools=[search_tool]
)
# Define task
research_task = Task(
description="""Research the latest developments in artificial
intelligence and machine learning, focusing on practical
applications in business.""",
agent=researcher
)
# The tool will use:
# {
# "search_query": "latest AI ML developments business applications"
# }
# Create crew
crew = Crew(
agents=[researcher],
tasks=[research_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Language and Region Configuration
```python
# French search from France
fr_search = SerplyWebSearchTool(
hl="fr",
proxy_location="FR"
)
# Japanese search from Japan
jp_search = SerplyWebSearchTool(
hl="ja",
proxy_location="JP"
)
```
### Device-Specific Results
```python
# Mobile results
mobile_search = SerplyWebSearchTool(
device_type="mobile",
limit=20
)
# Desktop results
desktop_search = SerplyWebSearchTool(
device_type="desktop",
limit=20
)
```
### Error Handling
```python
try:
results = search_tool._run(
search_query="artificial intelligence trends"
)
print(results)
except Exception as e:
print(f"Search error: {str(e)}")
```
## Notes
- Requires valid Serply API key
- Supports multiple languages and regions
- Configurable result limits (10-100)
- Device-specific search results
- Thread-safe operations
- Efficient search capabilities
- Handles API rate limiting automatically
- Returns structured search results

View File

@@ -1,201 +0,0 @@
---
title: SerplyWebpageToMarkdownTool
description: A tool for converting web pages to markdown format using Serply API
icon: markdown
---
## SerplyWebpageToMarkdownTool
The SerplyWebpageToMarkdownTool converts web pages to markdown format using the Serply API, making it easier for LLMs to process and understand web content. It supports configurable proxy locations for region-specific access.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import SerplyWebpageToMarkdownTool
# Set environment variable
# export SERPLY_API_KEY='your-api-key'
# Basic initialization
markdown_tool = SerplyWebpageToMarkdownTool()
# Advanced initialization with custom parameters
markdown_tool = SerplyWebpageToMarkdownTool(
proxy_location="FR" # Access from France
)
# Create an agent with the tool
web_processor = Agent(
role='Web Content Processor',
goal='Convert web content to markdown format',
backstory='Expert at processing and formatting web content.',
tools=[markdown_tool],
verbose=True
)
```
## Input Schema
```python
class SerplyWebpageToMarkdownToolSchema(BaseModel):
url: str = Field(
description="Mandatory URL of the webpage to convert to markdown"
)
```
## Function Signature
```python
def __init__(
self,
proxy_location: Optional[str] = "US",
**kwargs
):
"""
Initialize the webpage to markdown conversion tool.
Args:
proxy_location (str): Region for accessing the webpage (default: "US")
Options: US, CA, IE, GB, FR, DE, SE, IN, JP, KR, SG, AU, BR
**kwargs: Additional arguments for tool creation
"""
def _run(
self,
**kwargs: Any
) -> str:
"""
Convert webpage to markdown using Serply API.
Args:
url (str): URL of the webpage to convert
Returns:
str: Markdown formatted content of the webpage
"""
```
## Best Practices
1. Set up API authentication:
```bash
export SERPLY_API_KEY='your-serply-api-key'
```
2. Configure proxy location appropriately:
- Select relevant region for access
- Consider content accessibility
- Handle region-specific content
3. Handle potential API errors
4. Process markdown output effectively
5. Consider rate limits and quotas
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import SerplyWebpageToMarkdownTool
# Initialize tool with custom configuration
markdown_tool = SerplyWebpageToMarkdownTool(
proxy_location="US" # US access point
)
# Create agent
processor = Agent(
role='Content Processor',
goal='Convert web content to structured markdown',
backstory='Expert at processing web content into structured formats.',
tools=[markdown_tool]
)
# Define task
conversion_task = Task(
description="""Convert the documentation page at
https://example.com/docs into markdown format for
further processing.""",
agent=processor
)
# The tool will use:
# {
# "url": "https://example.com/docs"
# }
# Create crew
crew = Crew(
agents=[processor],
tasks=[conversion_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Regional Access Configuration
```python
# European access points
fr_processor = SerplyWebpageToMarkdownTool(
proxy_location="FR"
)
de_processor = SerplyWebpageToMarkdownTool(
proxy_location="DE"
)
```
### Error Handling
```python
try:
markdown_content = markdown_tool._run(
url="https://example.com/page"
)
print(markdown_content)
except Exception as e:
print(f"Conversion error: {str(e)}")
```
### Content Processing
```python
# Process multiple pages
urls = [
"https://example.com/page1",
"https://example.com/page2",
"https://example.com/page3"
]
markdown_contents = []
for url in urls:
try:
content = markdown_tool._run(url=url)
markdown_contents.append(content)
except Exception as e:
print(f"Error processing {url}: {str(e)}")
continue
# Combine contents
combined_markdown = "\n\n---\n\n".join(markdown_contents)
```
## Notes
- Requires valid Serply API key
- Supports multiple proxy locations
- Returns markdown-formatted content
- Simplifies web content for LLM processing
- Thread-safe operations
- Efficient content conversion
- Handles API rate limiting automatically
- Preserves content structure in markdown
- Supports various webpage formats
- Makes web content more accessible to AI agents

View File

@@ -1,158 +0,0 @@
---
title: TXTSearchTool
description: A semantic search tool for text files using RAG capabilities
icon: magnifying-glass-document
---
## TXTSearchTool
The TXTSearchTool is a specialized Retrieval-Augmented Generation (RAG) tool that enables semantic search within text files. It inherits from the base RagTool class and provides both fixed and dynamic text file searching capabilities.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import TXTSearchTool
# Method 1: Dynamic file path
txt_search = TXTSearchTool()
# Method 2: Fixed file path
fixed_txt_search = TXTSearchTool(txt="path/to/fixed/document.txt")
# Create an agent with the tool
researcher = Agent(
role='Research Assistant',
goal='Search through text documents semantically',
backstory='Expert at finding relevant information in documents using semantic search.',
tools=[txt_search],
verbose=True
)
```
## Input Schema
The tool supports two input schemas depending on initialization:
### Dynamic File Path Schema
```python
class TXTSearchToolSchema(BaseModel):
search_query: str # The semantic search query
txt: str # Path to the text file to search
```
### Fixed File Path Schema
```python
class FixedTXTSearchToolSchema(BaseModel):
search_query: str # The semantic search query
```
## Function Signature
```python
def __init__(self, txt: Optional[str] = None, **kwargs):
"""
Initialize the TXT search tool.
Args:
txt (Optional[str]): Fixed path to a text file. If provided, the tool will only search this file.
**kwargs: Additional arguments passed to the parent RagTool
"""
def _run(self, search_query: str, **kwargs: Any) -> Any:
"""
Perform semantic search on the text file.
Args:
search_query (str): The semantic search query
**kwargs: Additional arguments (including 'txt' for dynamic file path)
Returns:
str: Relevant text passages based on semantic search
"""
```
## Best Practices
1. Choose initialization method based on use case:
- Use fixed file path when repeatedly searching the same document
- Use dynamic file path when searching different documents
2. Write clear, semantic search queries
3. Handle potential file access errors in agent prompts
4. Consider memory usage for large text files
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import TXTSearchTool
# Example 1: Fixed document search
documentation_search = TXTSearchTool(txt="api_documentation.txt")
# Example 2: Dynamic document search
flexible_search = TXTSearchTool()
# Create agents
doc_analyst = Agent(
role='Documentation Analyst',
goal='Find relevant API documentation sections',
backstory='Expert at analyzing technical documentation.',
tools=[documentation_search]
)
file_analyst = Agent(
role='File Analyst',
goal='Search through various text files',
backstory='Specialist in finding information across multiple documents.',
tools=[flexible_search]
)
# Define tasks
fixed_search_task = Task(
description="""Find all API endpoints related to user authentication
in the documentation.""",
agent=doc_analyst
)
# The agent will use:
# {
# "search_query": "user authentication API endpoints"
# }
dynamic_search_task = Task(
description="""Search through the logs.txt file for any database
connection errors.""",
agent=file_analyst
)
# The agent will use:
# {
# "search_query": "database connection errors",
# "txt": "logs.txt"
# }
# Create crew
crew = Crew(
agents=[doc_analyst, file_analyst],
tasks=[fixed_search_task, dynamic_search_task]
)
# Execute
result = crew.kickoff()
```
## Notes
- Inherits from RagTool for semantic search capabilities
- Supports both fixed and dynamic text file paths
- Uses embeddings for semantic search
- Optimized for text file analysis
- Thread-safe operations
- Automatically handles file loading and embedding

View File

@@ -1,159 +0,0 @@
---
title: YoutubeChannelSearchTool
description: A semantic search tool for YouTube channel content using RAG capabilities
icon: youtube
---
## YoutubeChannelSearchTool
The YoutubeChannelSearchTool is a specialized Retrieval-Augmented Generation (RAG) tool that enables semantic search within YouTube channel content. It inherits from the base RagTool class and provides both fixed and dynamic YouTube channel searching capabilities.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import YoutubeChannelSearchTool
# Method 1: Dynamic channel handle
youtube_search = YoutubeChannelSearchTool()
# Method 2: Fixed channel handle
fixed_channel_search = YoutubeChannelSearchTool(youtube_channel_handle="@example_channel")
# Create an agent with the tool
researcher = Agent(
role='Content Researcher',
goal='Search through YouTube channel content semantically',
backstory='Expert at finding relevant information in YouTube content.',
tools=[youtube_search],
verbose=True
)
```
## Input Schema
The tool supports two input schemas depending on initialization:
### Dynamic Channel Schema
```python
class YoutubeChannelSearchToolSchema(BaseModel):
search_query: str # The semantic search query
youtube_channel_handle: str # YouTube channel handle (with or without @)
```
### Fixed Channel Schema
```python
class FixedYoutubeChannelSearchToolSchema(BaseModel):
search_query: str # The semantic search query
```
## Function Signature
```python
def __init__(self, youtube_channel_handle: Optional[str] = None, **kwargs):
"""
Initialize the YouTube channel search tool.
Args:
youtube_channel_handle (Optional[str]): Fixed channel handle. If provided,
the tool will only search this channel.
**kwargs: Additional arguments passed to the parent RagTool
"""
def _run(self, search_query: str, **kwargs: Any) -> Any:
"""
Perform semantic search on the YouTube channel content.
Args:
search_query (str): The semantic search query
**kwargs: Additional arguments (including 'youtube_channel_handle' for dynamic mode)
Returns:
str: Relevant content from the YouTube channel based on semantic search
"""
```
## Best Practices
1. Choose initialization method based on use case:
- Use fixed channel handle when repeatedly searching the same channel
- Use dynamic handle when searching different channels
2. Write clear, semantic search queries
3. Channel handles can be provided with or without '@' prefix
4. Consider content availability and channel size
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import YoutubeChannelSearchTool
# Example 1: Fixed channel search
tech_channel_search = YoutubeChannelSearchTool(youtube_channel_handle="@TechChannel")
# Example 2: Dynamic channel search
flexible_search = YoutubeChannelSearchTool()
# Create agents
tech_analyst = Agent(
role='Tech Content Analyst',
goal='Find relevant tech tutorials and explanations',
backstory='Expert at analyzing technical YouTube content.',
tools=[tech_channel_search]
)
content_researcher = Agent(
role='Content Researcher',
goal='Search across multiple YouTube channels',
backstory='Specialist in finding information across various channels.',
tools=[flexible_search]
)
# Define tasks
fixed_search_task = Task(
description="""Find all tutorials related to machine learning
basics in the channel.""",
agent=tech_analyst
)
# The agent will use:
# {
# "search_query": "machine learning basics tutorial"
# }
dynamic_search_task = Task(
description="""Search through the @AIResearch channel for
content about neural networks.""",
agent=content_researcher
)
# The agent will use:
# {
# "search_query": "neural networks explanation",
# "youtube_channel_handle": "@AIResearch"
# }
# Create crew
crew = Crew(
agents=[tech_analyst, content_researcher],
tasks=[fixed_search_task, dynamic_search_task]
)
# Execute
result = crew.kickoff()
```
## Notes
- Inherits from RagTool for semantic search capabilities
- Supports both fixed and dynamic YouTube channel handles
- Automatically adds '@' prefix to channel handles if missing
- Uses embeddings for semantic search
- Thread-safe operations
- Automatically handles YouTube content loading and embedding

View File

@@ -1,216 +0,0 @@
---
title: YoutubeVideoSearchTool
description: A tool for semantic search within YouTube video content using RAG capabilities
icon: video
---
## YoutubeVideoSearchTool
The YoutubeVideoSearchTool enables semantic search capabilities for YouTube video content using Retrieval-Augmented Generation (RAG). It processes video content and allows searching through transcripts and metadata using natural language queries.
## Installation
```bash
pip install 'crewai[tools]'
```
## Usage Example
```python
from crewai import Agent
from crewai_tools import YoutubeVideoSearchTool
# Method 1: Initialize with specific video
video_tool = YoutubeVideoSearchTool(
youtube_video_url="https://www.youtube.com/watch?v=example"
)
# Method 2: Initialize without video (specify at runtime)
flexible_video_tool = YoutubeVideoSearchTool()
# Create an agent with the tool
researcher = Agent(
role='Video Researcher',
goal='Search and analyze video content',
backstory='Expert at finding relevant information in videos.',
tools=[video_tool],
verbose=True
)
```
## Input Schema
### Fixed Video Schema (when URL provided during initialization)
```python
class FixedYoutubeVideoSearchToolSchema(BaseModel):
search_query: str = Field(
description="Mandatory search query you want to use to search the Youtube Video content"
)
```
### Flexible Video Schema (when URL provided at runtime)
```python
class YoutubeVideoSearchToolSchema(FixedYoutubeVideoSearchToolSchema):
youtube_video_url: str = Field(
description="Mandatory youtube_video_url path you want to search"
)
```
## Function Signature
```python
def __init__(
self,
youtube_video_url: Optional[str] = None,
**kwargs
):
"""
Initialize the YouTube video search tool.
Args:
youtube_video_url (Optional[str]): URL of YouTube video (optional)
**kwargs: Additional arguments for RAG tool configuration
"""
def _run(
self,
search_query: str,
**kwargs: Any
) -> str:
"""
Execute semantic search on video content.
Args:
search_query (str): Query to search in the video
**kwargs: Additional arguments including youtube_video_url if not initialized
Returns:
str: Relevant content from the video matching the query
"""
```
## Best Practices
1. Video URL Management:
- Use complete YouTube URLs
- Verify video accessibility
- Handle region restrictions
2. Search Optimization:
- Use specific, focused queries
- Consider video context
- Test with sample queries first
3. Performance Considerations:
- Pre-initialize for repeated searches
- Handle long videos appropriately
- Monitor processing time
4. Error Handling:
- Verify video availability
- Handle unavailable videos
- Manage API limitations
## Integration Example
```python
from crewai import Agent, Task, Crew
from crewai_tools import YoutubeVideoSearchTool
# Initialize tool with specific video
video_tool = YoutubeVideoSearchTool(
youtube_video_url="https://www.youtube.com/watch?v=example"
)
# Create agent
researcher = Agent(
role='Video Researcher',
goal='Extract insights from video content',
backstory='Expert at analyzing video content.',
tools=[video_tool]
)
# Define task
research_task = Task(
description="""Find all mentions of machine learning
applications from the video content.""",
agent=researcher
)
# The tool will use:
# {
# "search_query": "machine learning applications"
# }
# Create crew
crew = Crew(
agents=[researcher],
tasks=[research_task]
)
# Execute
result = crew.kickoff()
```
## Advanced Usage
### Dynamic Video Selection
```python
# Initialize without video URL
flexible_tool = YoutubeVideoSearchTool()
# Search different videos
tech_results = flexible_tool.run(
search_query="quantum computing",
youtube_video_url="https://youtube.com/watch?v=tech123"
)
science_results = flexible_tool.run(
search_query="particle physics",
youtube_video_url="https://youtube.com/watch?v=science456"
)
```
### Multiple Video Analysis
```python
# Create tools for different videos
tech_tool = YoutubeVideoSearchTool(
youtube_video_url="https://youtube.com/watch?v=tech123"
)
science_tool = YoutubeVideoSearchTool(
youtube_video_url="https://youtube.com/watch?v=science456"
)
# Create agent with multiple tools
analyst = Agent(
role='Content Analyst',
goal='Cross-reference multiple videos',
tools=[tech_tool, science_tool]
)
```
### Error Handling Example
```python
try:
video_tool = YoutubeVideoSearchTool()
results = video_tool.run(
search_query="key concepts",
youtube_video_url="https://youtube.com/watch?v=example"
)
print(results)
except Exception as e:
print(f"Error processing video: {str(e)}")
```
## Notes
- Inherits from RagTool
- Uses embedchain for processing
- Supports semantic search
- Dynamic video specification
- Efficient content retrieval
- Thread-safe operations
- Maintains search context
- Handles video transcripts
- Processes video metadata
- Memory-efficient processing

View File

@@ -13,25 +13,25 @@ dependencies = [
"openai>=1.13.3",
"litellm>=1.44.22",
"instructor>=1.3.3",
# Text Processing
"pdfplumber>=0.11.4",
"regex>=2024.9.11",
# Telemetry and Monitoring
"opentelemetry-api>=1.22.0",
"opentelemetry-sdk>=1.22.0",
"opentelemetry-exporter-otlp-proto-http>=1.22.0",
# Data Handling
"chromadb>=0.5.23",
"openpyxl>=3.1.5",
"pyvis>=0.3.2",
# Authentication and Security
"auth0-python>=4.7.1",
"python-dotenv>=1.0.0",
# Configuration and Utils
"click>=8.1.7",
"appdirs>=1.4.4",
@@ -40,7 +40,7 @@ dependencies = [
"uv>=0.4.25",
"tomli-w>=1.1.0",
"tomli>=2.0.2",
"blinker>=1.9.0",
"blinker>=1.9.0"
]
[project.urls]
@@ -49,7 +49,7 @@ Documentation = "https://docs.crewai.com"
Repository = "https://github.com/crewAIInc/crewAI"
[project.optional-dependencies]
tools = ["crewai-tools>=0.17.0"]
tools = ["crewai-tools>=0.25.5"]
embeddings = [
"tiktoken~=0.7.0"
]

View File

@@ -726,11 +726,7 @@ class Crew(BaseModel):
# Determine which tools to use - task tools take precedence over agent tools
tools_for_task = task.tools or agent_to_use.tools or []
tools_for_task = self._prepare_tools(
agent_to_use,
task,
tools_for_task
)
tools_for_task = self._prepare_tools(agent_to_use, task, tools_for_task)
self._log_task_start(task, agent_to_use.role)
@@ -797,14 +793,18 @@ class Crew(BaseModel):
return skipped_task_output
return None
def _prepare_tools(self, agent: BaseAgent, task: Task, tools: List[Tool]) -> List[Tool]:
def _prepare_tools(
self, agent: BaseAgent, task: Task, tools: List[Tool]
) -> List[Tool]:
# Add delegation tools if agent allows delegation
if agent.allow_delegation:
if self.process == Process.hierarchical:
if self.manager_agent:
tools = self._update_manager_tools(task, tools)
else:
raise ValueError("Manager agent is required for hierarchical process.")
raise ValueError(
"Manager agent is required for hierarchical process."
)
elif agent and agent.allow_delegation:
tools = self._add_delegation_tools(task, tools)
@@ -823,7 +823,9 @@ class Crew(BaseModel):
return self.manager_agent
return task.agent
def _merge_tools(self, existing_tools: List[Tool], new_tools: List[Tool]) -> List[Tool]:
def _merge_tools(
self, existing_tools: List[Tool], new_tools: List[Tool]
) -> List[Tool]:
"""Merge new tools into existing tools list, avoiding duplicates by tool name."""
if not new_tools:
return existing_tools
@@ -839,7 +841,9 @@ class Crew(BaseModel):
return tools
def _inject_delegation_tools(self, tools: List[Tool], task_agent: BaseAgent, agents: List[BaseAgent]):
def _inject_delegation_tools(
self, tools: List[Tool], task_agent: BaseAgent, agents: List[BaseAgent]
):
delegation_tools = task_agent.get_delegation_tools(agents)
return self._merge_tools(tools, delegation_tools)
@@ -856,7 +860,9 @@ class Crew(BaseModel):
if len(self.agents) > 1 and len(agents_for_delegation) > 0 and task.agent:
if not tools:
tools = []
tools = self._inject_delegation_tools(tools, task.agent, agents_for_delegation)
tools = self._inject_delegation_tools(
tools, task.agent, agents_for_delegation
)
return tools
def _log_task_start(self, task: Task, role: str = "None"):
@@ -870,7 +876,9 @@ class Crew(BaseModel):
if task.agent:
tools = self._inject_delegation_tools(tools, task.agent, [task.agent])
else:
tools = self._inject_delegation_tools(tools, self.manager_agent, self.agents)
tools = self._inject_delegation_tools(
tools, self.manager_agent, self.agents
)
return tools
def _get_context(self, task: Task, task_outputs: List[TaskOutput]):

View File

@@ -2,11 +2,16 @@ from pathlib import Path
from typing import Iterator, List, Optional, Union
from urllib.parse import urlparse
from docling.datamodel.base_models import InputFormat
from docling.document_converter import DocumentConverter
from docling.exceptions import ConversionError
from docling_core.transforms.chunker.hierarchical_chunker import HierarchicalChunker
from docling_core.types.doc.document import DoclingDocument
try:
from docling.datamodel.base_models import InputFormat
from docling.document_converter import DocumentConverter
from docling.exceptions import ConversionError
from docling_core.transforms.chunker.hierarchical_chunker import HierarchicalChunker
from docling_core.types.doc.document import DoclingDocument
DOCLING_AVAILABLE = True
except ImportError:
DOCLING_AVAILABLE = False
from pydantic import Field
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
@@ -19,6 +24,14 @@ class CrewDoclingSource(BaseKnowledgeSource):
This will auto support PDF, DOCX, and TXT, XLSX, Images, and HTML files without any additional dependencies and follows the docling package as the source of truth.
"""
def __init__(self, *args, **kwargs):
if not DOCLING_AVAILABLE:
raise ImportError(
"The docling package is required to use CrewDoclingSource. "
"Please install it using: uv add docling"
)
super().__init__(*args, **kwargs)
_logger: Logger = Logger(verbose=True)
file_path: Optional[List[Union[Path, str]]] = Field(default=None)

View File

@@ -6,8 +6,10 @@ import warnings
from contextlib import contextmanager
from typing import Any, Dict, List, Optional, Union
import litellm
from litellm import get_supported_openai_params
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
import litellm
from litellm import get_supported_openai_params
from crewai.utilities.exceptions.context_window_exceeding_exception import (
LLMContextLengthExceededException,
@@ -138,7 +140,7 @@ class LLM:
self.kwargs = kwargs
litellm.drop_params = True
litellm.set_verbose = False
self.set_callbacks(callbacks)
self.set_env_callbacks()

View File

@@ -4,18 +4,23 @@ from typing import Callable
from crewai import Crew
from crewai.project.utils import memoize
"""Decorators for defining crew components and their behaviors."""
def before_kickoff(func):
"""Marks a method to execute before crew kickoff."""
func.is_before_kickoff = True
return func
def after_kickoff(func):
"""Marks a method to execute after crew kickoff."""
func.is_after_kickoff = True
return func
def task(func):
"""Marks a method as a crew task."""
func.is_task = True
@wraps(func)
@@ -29,43 +34,51 @@ def task(func):
def agent(func):
"""Marks a method as a crew agent."""
func.is_agent = True
func = memoize(func)
return func
def llm(func):
"""Marks a method as an LLM provider."""
func.is_llm = True
func = memoize(func)
return func
def output_json(cls):
"""Marks a class as JSON output format."""
cls.is_output_json = True
return cls
def output_pydantic(cls):
"""Marks a class as Pydantic output format."""
cls.is_output_pydantic = True
return cls
def tool(func):
"""Marks a method as a crew tool."""
func.is_tool = True
return memoize(func)
def callback(func):
"""Marks a method as a crew callback."""
func.is_callback = True
return memoize(func)
def cache_handler(func):
"""Marks a method as a cache handler."""
func.is_cache_handler = True
return memoize(func)
def crew(func) -> Callable[..., Crew]:
"""Marks a method as the main crew execution point."""
@wraps(func)
def wrapper(self, *args, **kwargs) -> Crew:

View File

@@ -9,8 +9,10 @@ load_dotenv()
T = TypeVar("T", bound=type)
"""Base decorator for creating crew classes with configuration and function management."""
def CrewBase(cls: T) -> T:
"""Wraps a class with crew functionality and configuration management."""
class WrappedClass(cls): # type: ignore
is_crew_class: bool = True # type: ignore
@@ -216,5 +218,5 @@ def CrewBase(cls: T) -> T:
# Include base class (qual)name in the wrapper class (qual)name.
WrappedClass.__name__ = CrewBase.__name__ + "(" + cls.__name__ + ")"
WrappedClass.__qualname__ = CrewBase.__qualname__ + "(" + cls.__name__ + ")"
return cast(T, WrappedClass)

View File

@@ -127,38 +127,41 @@ class Task(BaseModel):
processed_by_agents: Set[str] = Field(default_factory=set)
guardrail: Optional[Callable[[TaskOutput], Tuple[bool, Any]]] = Field(
default=None,
description="Function to validate task output before proceeding to next task"
description="Function to validate task output before proceeding to next task",
)
max_retries: int = Field(
default=3,
description="Maximum number of retries when guardrail fails"
default=3, description="Maximum number of retries when guardrail fails"
)
retry_count: int = Field(
default=0,
description="Current number of retries"
retry_count: int = Field(default=0, description="Current number of retries")
start_time: Optional[datetime.datetime] = Field(
default=None, description="Start time of the task execution"
)
end_time: Optional[datetime.datetime] = Field(
default=None, description="End time of the task execution"
)
@field_validator("guardrail")
@classmethod
def validate_guardrail_function(cls, v: Optional[Callable]) -> Optional[Callable]:
"""Validate that the guardrail function has the correct signature and behavior.
While type hints provide static checking, this validator ensures runtime safety by:
1. Verifying the function accepts exactly one parameter (the TaskOutput)
2. Checking return type annotations match Tuple[bool, Any] if present
3. Providing clear, immediate error messages for debugging
This runtime validation is crucial because:
- Type hints are optional and can be ignored at runtime
- Function signatures need immediate validation before task execution
- Clear error messages help users debug guardrail implementation issues
Args:
v: The guardrail function to validate
Returns:
The validated guardrail function
Raises:
ValueError: If the function signature is invalid or return annotation
doesn't match Tuple[bool, Any]
@@ -171,8 +174,13 @@ class Task(BaseModel):
# Check return annotation if present, but don't require it
return_annotation = sig.return_annotation
if return_annotation != inspect.Signature.empty:
if not (return_annotation == Tuple[bool, Any] or str(return_annotation) == 'Tuple[bool, Any]'):
raise ValueError("If return type is annotated, it must be Tuple[bool, Any]")
if not (
return_annotation == Tuple[bool, Any]
or str(return_annotation) == "Tuple[bool, Any]"
):
raise ValueError(
"If return type is annotated, it must be Tuple[bool, Any]"
)
return v
_telemetry: Telemetry = PrivateAttr(default_factory=Telemetry)
@@ -181,7 +189,6 @@ class Task(BaseModel):
_original_expected_output: Optional[str] = PrivateAttr(default=None)
_original_output_file: Optional[str] = PrivateAttr(default=None)
_thread: Optional[threading.Thread] = PrivateAttr(default=None)
_execution_time: Optional[float] = PrivateAttr(default=None)
@model_validator(mode="before")
@classmethod
@@ -206,25 +213,19 @@ class Task(BaseModel):
"may_not_set_field", "This field is not to be set by the user.", {}
)
def _set_start_execution_time(self) -> float:
return datetime.datetime.now().timestamp()
def _set_end_execution_time(self, start_time: float) -> None:
self._execution_time = datetime.datetime.now().timestamp() - start_time
@field_validator("output_file")
@classmethod
def output_file_validation(cls, value: Optional[str]) -> Optional[str]:
"""Validate the output file path.
Args:
value: The output file path to validate. Can be None or a string.
If the path contains template variables (e.g. {var}), leading slashes are preserved.
For regular paths, leading slashes are stripped.
Returns:
The validated and potentially modified path, or None if no path was provided.
Raises:
ValueError: If the path contains invalid characters, path traversal attempts,
or other security concerns.
@@ -234,18 +235,24 @@ class Task(BaseModel):
# Basic security checks
if ".." in value:
raise ValueError("Path traversal attempts are not allowed in output_file paths")
raise ValueError(
"Path traversal attempts are not allowed in output_file paths"
)
# Check for shell expansion first
if value.startswith('~') or value.startswith('$'):
raise ValueError("Shell expansion characters are not allowed in output_file paths")
if value.startswith("~") or value.startswith("$"):
raise ValueError(
"Shell expansion characters are not allowed in output_file paths"
)
# Then check other shell special characters
if any(char in value for char in ['|', '>', '<', '&', ';']):
raise ValueError("Shell special characters are not allowed in output_file paths")
if any(char in value for char in ["|", ">", "<", "&", ";"]):
raise ValueError(
"Shell special characters are not allowed in output_file paths"
)
# Don't strip leading slash if it's a template path with variables
if "{" in value or "}" in value:
if "{" in value or "}" in value:
# Validate template variable format
template_vars = [part.split("}")[0] for part in value.split("{")[1:]]
for var in template_vars:
@@ -302,6 +309,12 @@ class Task(BaseModel):
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
@property
def execution_duration(self) -> float | None:
if not self.start_time or not self.end_time:
return None
return (self.end_time - self.start_time).total_seconds()
def execute_async(
self,
agent: BaseAgent | None = None,
@@ -342,7 +355,7 @@ class Task(BaseModel):
f"The task '{self.description}' has no agent assigned, therefore it can't be executed directly and should be executed in a Crew using a specific process that support that, like hierarchical."
)
start_time = self._set_start_execution_time()
self.start_time = datetime.datetime.now()
self._execution_span = self._telemetry.task_started(crew=agent.crew, task=self)
self.prompt_context = context
@@ -392,15 +405,17 @@ class Task(BaseModel):
if isinstance(guardrail_result.result, str):
task_output.raw = guardrail_result.result
pydantic_output, json_output = self._export_output(guardrail_result.result)
pydantic_output, json_output = self._export_output(
guardrail_result.result
)
task_output.pydantic = pydantic_output
task_output.json_dict = json_output
elif isinstance(guardrail_result.result, TaskOutput):
task_output = guardrail_result.result
self.output = task_output
self.end_time = datetime.datetime.now()
self._set_end_execution_time(start_time)
if self.callback:
self.callback(self.output)
@@ -412,7 +427,9 @@ class Task(BaseModel):
content = (
json_output
if json_output
else pydantic_output.model_dump_json() if pydantic_output else result
else pydantic_output.model_dump_json()
if pydantic_output
else result
)
self._save_file(content)
@@ -434,11 +451,11 @@ class Task(BaseModel):
def interpolate_inputs(self, inputs: Dict[str, Union[str, int, float]]) -> None:
"""Interpolate inputs into the task description, expected output, and output file path.
Args:
inputs: Dictionary mapping template variables to their values.
Supported value types are strings, integers, and floats.
Raises:
ValueError: If a required template variable is missing from inputs.
"""
@@ -455,7 +472,9 @@ class Task(BaseModel):
try:
self.description = self._original_description.format(**inputs)
except KeyError as e:
raise ValueError(f"Missing required template variable '{e.args[0]}' in description") from e
raise ValueError(
f"Missing required template variable '{e.args[0]}' in description"
) from e
except ValueError as e:
raise ValueError(f"Error interpolating description: {str(e)}") from e
@@ -472,22 +491,26 @@ class Task(BaseModel):
input_string=self._original_output_file, inputs=inputs
)
except (KeyError, ValueError) as e:
raise ValueError(f"Error interpolating output_file path: {str(e)}") from e
raise ValueError(
f"Error interpolating output_file path: {str(e)}"
) from e
def interpolate_only(self, input_string: Optional[str], inputs: Dict[str, Union[str, int, float]]) -> str:
def interpolate_only(
self, input_string: Optional[str], inputs: Dict[str, Union[str, int, float]]
) -> str:
"""Interpolate placeholders (e.g., {key}) in a string while leaving JSON untouched.
Args:
input_string: The string containing template variables to interpolate.
Can be None or empty, in which case an empty string is returned.
inputs: Dictionary mapping template variables to their values.
Supported value types are strings, integers, and floats.
If input_string is empty or has no placeholders, inputs can be empty.
Returns:
The interpolated string with all template variables replaced with their values.
Empty string if input_string is None or empty.
Raises:
ValueError: If a required template variable is missing from inputs.
KeyError: If a template variable is not found in the inputs dictionary.
@@ -497,13 +520,17 @@ class Task(BaseModel):
if "{" not in input_string and "}" not in input_string:
return input_string
if not inputs:
raise ValueError("Inputs dictionary cannot be empty when interpolating variables")
raise ValueError(
"Inputs dictionary cannot be empty when interpolating variables"
)
try:
# Validate input types
for key, value in inputs.items():
if not isinstance(value, (str, int, float)):
raise ValueError(f"Value for key '{key}' must be a string, integer, or float, got {type(value).__name__}")
raise ValueError(
f"Value for key '{key}' must be a string, integer, or float, got {type(value).__name__}"
)
escaped_string = input_string.replace("{", "{{").replace("}", "}}")
@@ -512,7 +539,9 @@ class Task(BaseModel):
return escaped_string.format(**inputs)
except KeyError as e:
raise KeyError(f"Template variable '{e.args[0]}' not found in inputs dictionary") from e
raise KeyError(
f"Template variable '{e.args[0]}' not found in inputs dictionary"
) from e
except ValueError as e:
raise ValueError(f"Error during string interpolation: {str(e)}") from e
@@ -597,10 +626,10 @@ class Task(BaseModel):
def _save_file(self, result: Any) -> None:
"""Save task output to a file.
Args:
result: The result to save to the file. Can be a dict or any stringifiable object.
Raises:
ValueError: If output_file is not set
RuntimeError: If there is an error writing to the file
@@ -618,6 +647,7 @@ class Task(BaseModel):
with resolved_path.open("w", encoding="utf-8") as file:
if isinstance(result, dict):
import json
json.dump(result, file, ensure_ascii=False, indent=2)
else:
file.write(str(result))

View File

@@ -169,7 +169,7 @@ class ToolUsage:
if calling.arguments:
try:
acceptable_args = tool.args_schema.schema()["properties"].keys() # type: ignore # Item "None" of "type[BaseModel] | None" has no attribute "schema"
acceptable_args = tool.args_schema.model_json_schema()["properties"].keys() # type: ignore
arguments = {
k: v
for k, v in calling.arguments.items()

View File

@@ -1,3 +1,5 @@
"""JSON encoder for handling CrewAI specific types."""
import json
from datetime import date, datetime
from decimal import Decimal
@@ -8,6 +10,7 @@ from pydantic import BaseModel
class CrewJSONEncoder(json.JSONEncoder):
"""Custom JSON encoder for CrewAI objects and special types."""
def default(self, obj):
if isinstance(obj, BaseModel):
return self._handle_pydantic_model(obj)

View File

@@ -6,9 +6,10 @@ from pydantic import BaseModel, ValidationError
from crewai.agents.parser import OutputParserException
"""Parser for converting text outputs into Pydantic models."""
class CrewPydanticOutputParser:
"""Parses the text into pydantic models"""
"""Parses text outputs into specified Pydantic models."""
pydantic_object: Type[BaseModel]

View File

@@ -180,12 +180,12 @@ class CrewEvaluator:
self._test_result_span = self._telemetry.individual_test_result_span(
self.crew,
evaluation_result.pydantic.quality,
current_task._execution_time,
current_task.execution_duration,
self.openai_model_name,
)
self.tasks_scores[self.iteration].append(evaluation_result.pydantic.quality)
self.run_execution_times[self.iteration].append(
current_task._execution_time
current_task.execution_duration
)
else:
raise ValueError("Evaluation result is not in the expected format")

View File

@@ -4,8 +4,10 @@ from typing import Dict, Optional, Union
from pydantic import BaseModel, Field, PrivateAttr, model_validator
"""Internationalization support for CrewAI prompts and messages."""
class I18N(BaseModel):
"""Handles loading and retrieving internationalized prompts."""
_prompts: Dict[str, Dict[str, str]] = PrivateAttr()
prompt_file: Optional[str] = Field(
default=None,

View File

@@ -1,3 +1,4 @@
import warnings
from typing import Any, Optional, Type
@@ -25,9 +26,10 @@ class InternalInstructor:
if self.agent and not self.llm:
self.llm = self.agent.function_calling_llm or self.agent.llm
# Lazy import
import instructor
from litellm import completion
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
import instructor
from litellm import completion
self._client = instructor.from_litellm(
completion,

View File

@@ -3,8 +3,10 @@ from pathlib import Path
import appdirs
"""Path management utilities for CrewAI storage and configuration."""
def db_storage_path():
"""Returns the path for database storage."""
app_name = get_project_directory_name()
app_author = "CrewAI"
@@ -14,6 +16,7 @@ def db_storage_path():
def get_project_directory_name():
"""Returns the current project directory name."""
project_directory_name = os.environ.get("CREWAI_STORAGE_DIR")
if project_directory_name:

View File

@@ -1,3 +1,5 @@
import json
import logging
from typing import Any, List, Optional
from pydantic import BaseModel, Field
@@ -5,8 +7,11 @@ from pydantic import BaseModel, Field
from crewai.agent import Agent
from crewai.task import Task
"""Handles planning and coordination of crew tasks."""
logger = logging.getLogger(__name__)
class PlanPerTask(BaseModel):
"""Represents a plan for a specific task."""
task: str = Field(..., description="The task for which the plan is created")
plan: str = Field(
...,
@@ -15,6 +20,7 @@ class PlanPerTask(BaseModel):
class PlannerTaskPydanticOutput(BaseModel):
"""Output format for task planning results."""
list_of_plans_per_task: List[PlanPerTask] = Field(
...,
description="Step by step plan on how the agents can execute their tasks using the available tools with mastery",
@@ -22,6 +28,7 @@ class PlannerTaskPydanticOutput(BaseModel):
class CrewPlanner:
"""Plans and coordinates the execution of crew tasks."""
def __init__(self, tasks: List[Task], planning_agent_llm: Optional[Any] = None):
self.tasks = tasks
@@ -68,19 +75,39 @@ class CrewPlanner:
output_pydantic=PlannerTaskPydanticOutput,
)
def _get_agent_knowledge(self, task: Task) -> List[str]:
"""
Safely retrieve knowledge source content from the task's agent.
Args:
task: The task containing an agent with potential knowledge sources
Returns:
List[str]: A list of knowledge source strings
"""
try:
if task.agent and task.agent.knowledge_sources:
return [source.content for source in task.agent.knowledge_sources]
except AttributeError:
logger.warning("Error accessing agent knowledge sources")
return []
def _create_tasks_summary(self) -> str:
"""Creates a summary of all tasks."""
tasks_summary = []
for idx, task in enumerate(self.tasks):
tasks_summary.append(
f"""
knowledge_list = self._get_agent_knowledge(task)
task_summary = f"""
Task Number {idx + 1} - {task.description}
"task_description": {task.description}
"task_expected_output": {task.expected_output}
"agent": {task.agent.role if task.agent else "None"}
"agent_goal": {task.agent.goal if task.agent else "None"}
"task_tools": {task.tools}
"agent_tools": {task.agent.tools if task.agent else "None"}
"""
)
"agent_tools": %s%s""" % (
f"[{', '.join(str(tool) for tool in task.agent.tools)}]" if task.agent and task.agent.tools else '"agent has no tools"',
f',\n "agent_knowledge": "[\\"{knowledge_list[0]}\\"]"' if knowledge_list and str(knowledge_list) != "None" else ""
)
tasks_summary.append(task_summary)
return " ".join(tasks_summary)

View File

@@ -1,7 +1,11 @@
"""Utility for colored console output."""
from typing import Optional
class Printer:
"""Handles colored console output formatting."""
def print(self, content: str, color: Optional[str] = None):
if color == "purple":
self._print_purple(content)

View File

@@ -6,8 +6,10 @@ from pydantic import BaseModel, Field, PrivateAttr, model_validator
from crewai.utilities.logger import Logger
"""Controls request rate limiting for API calls."""
class RPMController(BaseModel):
"""Manages requests per minute limiting."""
max_rpm: Optional[int] = Field(default=None)
logger: Logger = Field(default_factory=lambda: Logger(verbose=False))
_current_rpm: int = PrivateAttr(default=0)

View File

@@ -8,8 +8,10 @@ from crewai.memory.storage.kickoff_task_outputs_storage import (
)
from crewai.task import Task
"""Handles storage and retrieval of task execution outputs."""
class ExecutionLog(BaseModel):
"""Represents a log entry for task execution."""
task_id: str
expected_output: Optional[str] = None
output: Dict[str, Any]
@@ -22,6 +24,8 @@ class ExecutionLog(BaseModel):
return getattr(self, key)
"""Manages storage and retrieval of task outputs."""
class TaskOutputStorageHandler:
def __init__(self) -> None:
self.storage = KickoffTaskOutputsSQLiteStorage()

View File

@@ -1,3 +1,5 @@
import warnings
from litellm.integrations.custom_logger import CustomLogger
from litellm.types.utils import Usage
@@ -12,11 +14,13 @@ class TokenCalcHandler(CustomLogger):
if self.token_cost_process is None:
return
usage: Usage = response_obj["usage"]
self.token_cost_process.sum_successful_requests(1)
self.token_cost_process.sum_prompt_tokens(usage.prompt_tokens)
self.token_cost_process.sum_completion_tokens(usage.completion_tokens)
if usage.prompt_tokens_details:
self.token_cost_process.sum_cached_prompt_tokens(
usage.prompt_tokens_details.cached_tokens
)
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
usage: Usage = response_obj["usage"]
self.token_cost_process.sum_successful_requests(1)
self.token_cost_process.sum_prompt_tokens(usage.prompt_tokens)
self.token_cost_process.sum_completion_tokens(usage.completion_tokens)
if usage.prompt_tokens_details:
self.token_cost_process.sum_cached_prompt_tokens(
usage.prompt_tokens_details.cached_tokens
)

View File

@@ -1445,44 +1445,43 @@ def test_llm_call_with_all_attributes():
@pytest.mark.vcr(filter_headers=["authorization"])
def test_agent_with_ollama_gemma():
def test_agent_with_ollama_llama3():
agent = Agent(
role="test role",
goal="test goal",
backstory="test backstory",
llm=LLM(
model="ollama/gemma2:latest",
base_url="http://localhost:8080",
),
llm=LLM(model="ollama/llama3.2:3b", base_url="http://localhost:11434"),
)
assert isinstance(agent.llm, LLM)
assert agent.llm.model == "ollama/gemma2:latest"
assert agent.llm.base_url == "http://localhost:8080"
assert agent.llm.model == "ollama/llama3.2:3b"
assert agent.llm.base_url == "http://localhost:11434"
task = "Respond in 20 words. Who are you?"
task = "Respond in 20 words. Which model are you?"
response = agent.llm.call([{"role": "user", "content": task}])
assert response
assert len(response.split()) <= 25 # Allow a little flexibility in word count
assert "Gemma" in response or "AI" in response or "language model" in response
assert "Llama3" in response or "AI" in response or "language model" in response
@pytest.mark.vcr(filter_headers=["authorization"])
def test_llm_call_with_ollama_gemma():
def test_llm_call_with_ollama_llama3():
llm = LLM(
model="ollama/gemma2:latest",
base_url="http://localhost:8080",
model="ollama/llama3.2:3b",
base_url="http://localhost:11434",
temperature=0.7,
max_tokens=30,
)
messages = [{"role": "user", "content": "Respond in 20 words. Who are you?"}]
messages = [
{"role": "user", "content": "Respond in 20 words. Which model are you?"}
]
response = llm.call(messages)
assert response
assert len(response.split()) <= 25 # Allow a little flexibility in word count
assert "Gemma" in response or "AI" in response or "language model" in response
assert "Llama3" in response or "AI" in response or "language model" in response
@pytest.mark.vcr(filter_headers=["authorization"])
@@ -1578,7 +1577,7 @@ def test_agent_execute_task_with_ollama():
role="test role",
goal="test goal",
backstory="test backstory",
llm=LLM(model="ollama/gemma2:latest", base_url="http://localhost:8080"),
llm=LLM(model="ollama/llama3.2:3b", base_url="http://localhost:11434"),
)
task = Task(

View File

@@ -1,42 +1,6 @@
interactions:
- request:
body: !!binary |
CrcCCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSjgIKEgoQY3Jld2FpLnRl
bGVtZXRyeRJoChA/Q8UW5bidCRtKvri5fOaNEgh5qLzvLvZJkioQVG9vbCBVc2FnZSBFcnJvcjAB
OYjFVQr1TPgXQXCXhwr1TPgXShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuNjEuMHoCGAGFAQABAAAS
jQEKEChQTWQ07t26ELkZmP5RresSCHEivRGBpsP7KgpUb29sIFVzYWdlMAE5sKkbC/VM+BdB8MIc
C/VM+BdKGgoOY3Jld2FpX3ZlcnNpb24SCAoGMC42MS4wShkKCXRvb2xfbmFtZRIMCgpkdW1teV90
b29sSg4KCGF0dGVtcHRzEgIYAXoCGAGFAQABAAA=
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '314'
Content-Type:
- application/x-protobuf
User-Agent:
- OTel-OTLP-Exporter-Python/1.27.0
method: POST
uri: https://telemetry.crewai.com:4319/v1/traces
response:
body:
string: "\n\0"
headers:
Content-Length:
- '2'
Content-Type:
- application/x-protobuf
Date:
- Tue, 24 Sep 2024 21:57:54 GMT
status:
code: 200
message: OK
- request:
body: '{"model": "gemma2:latest", "prompt": "### System:\nYou are test role. test
body: '{"model": "llama3.2:3b", "prompt": "### System:\nYou are test role. test
backstory\nYour personal goal is: test goal\nTo give my best complete final
answer to the task use the exact following format:\n\nThought: I now can give
a great answer\nFinal Answer: Your final answer must be the great and the most
@@ -46,7 +10,7 @@ interactions:
explanation of AI\nyou MUST return the actual complete content as the final
answer, not a summary.\n\nBegin! This is VERY important to you, use the tools
available and give your best Final Answer, your job depends on it!\n\nThought:\n\n",
"options": {}, "stream": false}'
"options": {"stop": ["\nObservation:"]}, "stream": false}'
headers:
Accept:
- '*/*'
@@ -55,26 +19,26 @@ interactions:
Connection:
- keep-alive
Content-Length:
- '815'
- '839'
Content-Type:
- application/json
User-Agent:
- python-requests/2.31.0
- python-requests/2.32.3
method: POST
uri: http://localhost:8080/api/generate
uri: http://localhost:11434/api/generate
response:
body:
string: '{"model":"gemma2:latest","created_at":"2024-09-24T21:57:55.835715Z","response":"Thought:
I can explain AI in one sentence. \n\nFinal Answer: Artificial intelligence
(AI) is the ability of computer systems to perform tasks that typically require
human intelligence, such as learning, problem-solving, and decision-making. \n","done":true,"done_reason":"stop","context":[106,1645,108,6176,1479,235292,108,2045,708,2121,4731,235265,2121,135147,108,6922,3749,6789,603,235292,2121,6789,108,1469,2734,970,1963,3407,2048,3448,577,573,6911,1281,573,5463,2412,5920,235292,109,65366,235292,590,1490,798,2734,476,1775,3448,108,11263,10358,235292,3883,2048,3448,2004,614,573,1775,578,573,1546,3407,685,3077,235269,665,2004,614,17526,6547,235265,109,235285,44472,1281,1450,32808,235269,970,3356,12014,611,665,235341,109,6176,4926,235292,109,6846,12297,235292,36576,1212,16481,603,575,974,13060,109,1596,603,573,5246,12830,604,861,2048,3448,235292,586,974,235290,47366,15844,576,16481,108,4747,44472,2203,573,5579,3407,3381,685,573,2048,3448,235269,780,476,13367,235265,109,12694,235341,1417,603,50471,2845,577,692,235269,1281,573,8112,2506,578,2734,861,1963,14124,10358,235269,861,3356,12014,611,665,235341,109,65366,235292,109,107,108,106,2516,108,65366,235292,590,798,10200,16481,575,974,13060,235265,235248,109,11263,10358,235292,42456,17273,591,11716,235275,603,573,7374,576,6875,5188,577,3114,13333,674,15976,2817,3515,17273,235269,1582,685,6044,235269,3210,235290,60495,235269,578,4530,235290,14577,235265,139,108],"total_duration":3370959792,"load_duration":20611750,"prompt_eval_count":173,"prompt_eval_duration":688036000,"eval_count":51,"eval_duration":2660291000}'
string: '{"model":"llama3.2:3b","created_at":"2025-01-02T20:05:52.24992Z","response":"Final
Answer: Artificial Intelligence (AI) refers to the development of computer
systems capable of performing tasks that typically require human intelligence,
such as learning, problem-solving, decision-making, and perception.","done":true,"done_reason":"stop","context":[128006,9125,128007,271,38766,1303,33025,2696,25,6790,220,2366,18,271,128009,128006,882,128007,271,14711,744,512,2675,527,1296,3560,13,1296,93371,198,7927,4443,5915,374,25,1296,5915,198,1271,3041,856,1888,4686,1620,4320,311,279,3465,1005,279,4839,2768,3645,1473,85269,25,358,1457,649,3041,264,2294,4320,198,19918,22559,25,4718,1620,4320,2011,387,279,2294,323,279,1455,4686,439,3284,11,433,2011,387,15632,7633,382,40,28832,1005,1521,20447,11,856,2683,14117,389,433,2268,14711,2724,1473,5520,5546,25,83017,1148,15592,374,304,832,11914,271,2028,374,279,1755,13186,369,701,1620,4320,25,362,832,1355,18886,16540,315,15592,198,9514,28832,471,279,5150,4686,2262,439,279,1620,4320,11,539,264,12399,382,11382,0,1115,374,48174,3062,311,499,11,1005,279,7526,2561,323,3041,701,1888,13321,22559,11,701,2683,14117,389,433,2268,85269,1473,128009,128006,78191,128007,271,19918,22559,25,59294,22107,320,15836,8,19813,311,279,4500,315,6500,6067,13171,315,16785,9256,430,11383,1397,3823,11478,11,1778,439,6975,11,3575,99246,11,5597,28846,11,323,21063,13],"total_duration":1461909875,"load_duration":39886208,"prompt_eval_count":181,"prompt_eval_duration":701000000,"eval_count":39,"eval_duration":719000000}'
headers:
Content-Length:
- '1662'
- '1537'
Content-Type:
- application/json; charset=utf-8
Date:
- Tue, 24 Sep 2024 21:57:55 GMT
- Thu, 02 Jan 2025 20:05:52 GMT
status:
code: 200
message: OK

View File

@@ -1,397 +0,0 @@
interactions:
- request:
body: !!binary |
CumTAQokCiIKDHNlcnZpY2UubmFtZRISChBjcmV3QUktdGVsZW1ldHJ5Er+TAQoSChBjcmV3YWku
dGVsZW1ldHJ5EqoHChDvqD2QZooz9BkEwtbWjp4OEgjxh72KACHvZSoMQ3JldyBDcmVhdGVkMAE5
qMhNnvBM+BdBcO9PnvBM+BdKGgoOY3Jld2FpX3ZlcnNpb24SCAoGMC42MS4wShoKDnB5dGhvbl92
ZXJzaW9uEggKBjMuMTEuN0ouCghjcmV3X2tleRIiCiBkNTUxMTNiZTRhYTQxYmE2NDNkMzI2MDQy
YjJmMDNmMUoxCgdjcmV3X2lkEiYKJGY4YTA1OTA1LTk0OGEtNDQ0YS04NmJmLTJiNTNiNDkyYjgy
MkocCgxjcmV3X3Byb2Nlc3MSDAoKc2VxdWVudGlhbEoRCgtjcmV3X21lbW9yeRICEABKGgoUY3Jl
d19udW1iZXJfb2ZfdGFza3MSAhgBShsKFWNyZXdfbnVtYmVyX29mX2FnZW50cxICGAFKxwIKC2Ny
ZXdfYWdlbnRzErcCCrQCW3sia2V5IjogImUxNDhlNTMyMDI5MzQ5OWY4Y2ViZWE4MjZlNzI1ODJi
IiwgImlkIjogIjg1MGJjNWUwLTk4NTctNDhkOC1iNWZlLTJmZjk2OWExYTU3YiIsICJyb2xlIjog
InRlc3Qgcm9sZSIsICJ2ZXJib3NlPyI6IHRydWUsICJtYXhfaXRlciI6IDQsICJtYXhfcnBtIjog
MTAsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00byIsICJkZWxlZ2F0
aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1h
eF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfV1KkAIKCmNyZXdfdGFza3MSgQIK
/gFbeyJrZXkiOiAiNGEzMWI4NTEzM2EzYTI5NGM2ODUzZGE3NTdkNGJhZTciLCAiaWQiOiAiOTc1
ZDgwMjItMWJkMS00NjBlLTg2NmEtYjJmZGNiYjA4ZDliIiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBm
YWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJ0ZXN0IHJvbGUiLCAi
YWdlbnRfa2V5IjogImUxNDhlNTMyMDI5MzQ5OWY4Y2ViZWE4MjZlNzI1ODJiIiwgInRvb2xzX25h
bWVzIjogWyJnZXRfZmluYWxfYW5zd2VyIl19XXoCGAGFAQABAAASjgIKEP9UYSAOFQbZquSppN1j
IeUSCAgZmXUoJKFmKgxUYXNrIENyZWF0ZWQwATloPV+e8Ez4F0GYsl+e8Ez4F0ouCghjcmV3X2tl
eRIiCiBkNTUxMTNiZTRhYTQxYmE2NDNkMzI2MDQyYjJmMDNmMUoxCgdjcmV3X2lkEiYKJGY4YTA1
OTA1LTk0OGEtNDQ0YS04NmJmLTJiNTNiNDkyYjgyMkouCgh0YXNrX2tleRIiCiA0YTMxYjg1MTMz
YTNhMjk0YzY4NTNkYTc1N2Q0YmFlN0oxCgd0YXNrX2lkEiYKJDk3NWQ4MDIyLTFiZDEtNDYwZS04
NjZhLWIyZmRjYmIwOGQ5YnoCGAGFAQABAAASkwEKEEfiywgqgiUXE3KoUbrnHDQSCGmv+iM7Wc1Z
KgpUb29sIFVzYWdlMAE5kOybnvBM+BdBIM+cnvBM+BdKGgoOY3Jld2FpX3ZlcnNpb24SCAoGMC42
MS4wSh8KCXRvb2xfbmFtZRISChBnZXRfZmluYWxfYW5zd2VySg4KCGF0dGVtcHRzEgIYAXoCGAGF
AQABAAASkwEKEH7AHXpfmvwIkA45HB8YyY0SCAFRC+uJpsEZKgpUb29sIFVzYWdlMAE56PLdnvBM
+BdBYFbfnvBM+BdKGgoOY3Jld2FpX3ZlcnNpb24SCAoGMC42MS4wSh8KCXRvb2xfbmFtZRISChBn
ZXRfZmluYWxfYW5zd2VySg4KCGF0dGVtcHRzEgIYAXoCGAGFAQABAAASkwEKEIDKKEbYU4lcJF+a
WsAVZwESCI+/La7oL86MKgpUb29sIFVzYWdlMAE5yIkgn/BM+BdBWGwhn/BM+BdKGgoOY3Jld2Fp
X3ZlcnNpb24SCAoGMC42MS4wSh8KCXRvb2xfbmFtZRISChBnZXRfZmluYWxfYW5zd2VySg4KCGF0
dGVtcHRzEgIYAXoCGAGFAQABAAASnAEKEMTZ2IhpLz6J2hJhHBQ8/M4SCEuWz+vjzYifKhNUb29s
IFJlcGVhdGVkIFVzYWdlMAE5mAVhn/BM+BdBKOhhn/BM+BdKGgoOY3Jld2FpX3ZlcnNpb24SCAoG
MC42MS4wSh8KCXRvb2xfbmFtZRISChBnZXRfZmluYWxfYW5zd2VySg4KCGF0dGVtcHRzEgIYAXoC
GAGFAQABAAASkAIKED8C+t95p855kLcXs5Nnt/sSCM4XAhL6u8O8Kg5UYXNrIEV4ZWN1dGlvbjAB
OdD8X57wTPgXQUgno5/wTPgXSi4KCGNyZXdfa2V5EiIKIGQ1NTExM2JlNGFhNDFiYTY0M2QzMjYw
NDJiMmYwM2YxSjEKB2NyZXdfaWQSJgokZjhhMDU5MDUtOTQ4YS00NDRhLTg2YmYtMmI1M2I0OTJi
ODIySi4KCHRhc2tfa2V5EiIKIDRhMzFiODUxMzNhM2EyOTRjNjg1M2RhNzU3ZDRiYWU3SjEKB3Rh
c2tfaWQSJgokOTc1ZDgwMjItMWJkMS00NjBlLTg2NmEtYjJmZGNiYjA4ZDliegIYAYUBAAEAABLO
CwoQFlnZCfbZ3Dj0L9TAE5LrLBIIoFr7BZErFNgqDENyZXcgQ3JlYXRlZDABOVhDDaDwTPgXQSg/
D6DwTPgXShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuNjEuMEoaCg5weXRob25fdmVyc2lvbhIICgYz
LjExLjdKLgoIY3Jld19rZXkSIgogOTRjMzBkNmMzYjJhYzhmYjk0YjJkY2ZjNTcyZDBmNTlKMQoH
Y3Jld19pZBImCiQyMzM2MzRjNi1lNmQ2LTQ5ZTYtODhhZS1lYWUxYTM5YjBlMGZKHAoMY3Jld19w
cm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdfbnVtYmVyX29m
X3Rhc2tzEgIYAkobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSv4ECgtjcmV3X2FnZW50cxLu
BArrBFt7ImtleSI6ICJlMTQ4ZTUzMjAyOTM0OTlmOGNlYmVhODI2ZTcyNTgyYiIsICJpZCI6ICI0
MjAzZjIyYi0wNWM3LTRiNjUtODBjMS1kM2Y0YmFlNzZhNDYiLCAicm9sZSI6ICJ0ZXN0IHJvbGUi
LCAidmVyYm9zZT8iOiB0cnVlLCAibWF4X2l0ZXIiOiAyLCAibWF4X3JwbSI6IDEwLCAiZnVuY3Rp
b25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8iLCAiZGVsZWdhdGlvbl9lbmFibGVk
PyI6IGZhbHNlLCAiYWxsb3dfY29kZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGlt
aXQiOiAyLCAidG9vbHNfbmFtZXMiOiBbXX0sIHsia2V5IjogImU3ZThlZWE4ODZiY2I4ZjEwNDVh
YmVlY2YxNDI1ZGI3IiwgImlkIjogImZjOTZjOTQ1LTY4ZDUtNDIxMy05NmNkLTNmYTAwNmUyZTYz
MCIsICJyb2xlIjogInRlc3Qgcm9sZTIiLCAidmVyYm9zZT8iOiB0cnVlLCAibWF4X2l0ZXIiOiAx
LCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdw
dC00byIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlv
bj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfV1K/QMK
CmNyZXdfdGFza3MS7gMK6wNbeyJrZXkiOiAiMzIyZGRhZTNiYzgwYzFkNDViODVmYTc3NTZkYjg2
NjUiLCAiaWQiOiAiOTVjYTg4NDItNmExMi00MGQ5LWIwZDItNGI0MzYxYmJlNTZkIiwgImFzeW5j
X2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6
ICJ0ZXN0IHJvbGUiLCAiYWdlbnRfa2V5IjogImUxNDhlNTMyMDI5MzQ5OWY4Y2ViZWE4MjZlNzI1
ODJiIiwgInRvb2xzX25hbWVzIjogW119LCB7ImtleSI6ICI1ZTljYTdkNjRiNDIwNWJiN2M0N2Uw
YjNmY2I1ZDIxZiIsICJpZCI6ICI5NzI5MTg2Yy1kN2JlLTRkYjQtYTk0ZS02OWU5OTk2NTI3MDAi
LCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2Vu
dF9yb2xlIjogInRlc3Qgcm9sZTIiLCAiYWdlbnRfa2V5IjogImU3ZThlZWE4ODZiY2I4ZjEwNDVh
YmVlY2YxNDI1ZGI3IiwgInRvb2xzX25hbWVzIjogWyJnZXRfZmluYWxfYW5zd2VyIl19XXoCGAGF
AQABAAASjgIKEC/YM2OukRrSg+ZAev4VhGESCOQ5RvzSS5IEKgxUYXNrIENyZWF0ZWQwATmQJx6g
8Ez4F0EgjR6g8Ez4F0ouCghjcmV3X2tleRIiCiA5NGMzMGQ2YzNiMmFjOGZiOTRiMmRjZmM1NzJk
MGY1OUoxCgdjcmV3X2lkEiYKJDIzMzYzNGM2LWU2ZDYtNDllNi04OGFlLWVhZTFhMzliMGUwZkou
Cgh0YXNrX2tleRIiCiAzMjJkZGFlM2JjODBjMWQ0NWI4NWZhNzc1NmRiODY2NUoxCgd0YXNrX2lk
EiYKJDk1Y2E4ODQyLTZhMTItNDBkOS1iMGQyLTRiNDM2MWJiZTU2ZHoCGAGFAQABAAASkAIKEHqZ
L8s3clXQyVTemNcTCcQSCA0tzK95agRQKg5UYXNrIEV4ZWN1dGlvbjABOQC8HqDwTPgXQdgNSqDw
TPgXSi4KCGNyZXdfa2V5EiIKIDk0YzMwZDZjM2IyYWM4ZmI5NGIyZGNmYzU3MmQwZjU5SjEKB2Ny
ZXdfaWQSJgokMjMzNjM0YzYtZTZkNi00OWU2LTg4YWUtZWFlMWEzOWIwZTBmSi4KCHRhc2tfa2V5
EiIKIDMyMmRkYWUzYmM4MGMxZDQ1Yjg1ZmE3NzU2ZGI4NjY1SjEKB3Rhc2tfaWQSJgokOTVjYTg4
NDItNmExMi00MGQ5LWIwZDItNGI0MzYxYmJlNTZkegIYAYUBAAEAABKOAgoQjhKzodMUmQ8NWtdy
Uj99whIIBsGtAymZibwqDFRhc2sgQ3JlYXRlZDABOXjVVaDwTPgXQXhSVqDwTPgXSi4KCGNyZXdf
a2V5EiIKIDk0YzMwZDZjM2IyYWM4ZmI5NGIyZGNmYzU3MmQwZjU5SjEKB2NyZXdfaWQSJgokMjMz
NjM0YzYtZTZkNi00OWU2LTg4YWUtZWFlMWEzOWIwZTBmSi4KCHRhc2tfa2V5EiIKIDVlOWNhN2Q2
NGI0MjA1YmI3YzQ3ZTBiM2ZjYjVkMjFmSjEKB3Rhc2tfaWQSJgokOTcyOTE4NmMtZDdiZS00ZGI0
LWE5NGUtNjllOTk5NjUyNzAwegIYAYUBAAEAABKTAQoQx5IUsjAFMGNUaz5MHy20OBIIzl2tr25P
LL8qClRvb2wgVXNhZ2UwATkgt5Sg8Ez4F0GwFpag8Ez4F0oaCg5jcmV3YWlfdmVyc2lvbhIICgYw
LjYxLjBKHwoJdG9vbF9uYW1lEhIKEGdldF9maW5hbF9hbnN3ZXJKDgoIYXR0ZW1wdHMSAhgBegIY
AYUBAAEAABKQAgoQEkfcfCrzTYIM6GQXhknlexIIa/oxeT78OL8qDlRhc2sgRXhlY3V0aW9uMAE5
WIFWoPBM+BdBuL/GoPBM+BdKLgoIY3Jld19rZXkSIgogOTRjMzBkNmMzYjJhYzhmYjk0YjJkY2Zj
NTcyZDBmNTlKMQoHY3Jld19pZBImCiQyMzM2MzRjNi1lNmQ2LTQ5ZTYtODhhZS1lYWUxYTM5YjBl
MGZKLgoIdGFza19rZXkSIgogNWU5Y2E3ZDY0YjQyMDViYjdjNDdlMGIzZmNiNWQyMWZKMQoHdGFz
a19pZBImCiQ5NzI5MTg2Yy1kN2JlLTRkYjQtYTk0ZS02OWU5OTk2NTI3MDB6AhgBhQEAAQAAEqwH
ChDrKBdEe+Z5276g9fgg6VzjEgiJfnDwsv1SrCoMQ3JldyBDcmVhdGVkMAE5MLQYofBM+BdBQFIa
ofBM+BdKGgoOY3Jld2FpX3ZlcnNpb24SCAoGMC42MS4wShoKDnB5dGhvbl92ZXJzaW9uEggKBjMu
MTEuN0ouCghjcmV3X2tleRIiCiA3M2FhYzI4NWU2NzQ2NjY3Zjc1MTQ3NjcwMDAzNDExMEoxCgdj
cmV3X2lkEiYKJDg0NDY0YjhlLTRiZjctNDRiYy05MmUxLWE4ZDE1NGZlNWZkN0ocCgxjcmV3X3By
b2Nlc3MSDAoKc2VxdWVudGlhbEoRCgtjcmV3X21lbW9yeRICEABKGgoUY3Jld19udW1iZXJfb2Zf
dGFza3MSAhgBShsKFWNyZXdfbnVtYmVyX29mX2FnZW50cxICGAFKyQIKC2NyZXdfYWdlbnRzErkC
CrYCW3sia2V5IjogImUxNDhlNTMyMDI5MzQ5OWY4Y2ViZWE4MjZlNzI1ODJiIiwgImlkIjogIjk4
YmIwNGYxLTBhZGMtNGZiNC04YzM2LWM3M2Q1MzQ1ZGRhZCIsICJyb2xlIjogInRlc3Qgcm9sZSIs
ICJ2ZXJib3NlPyI6IHRydWUsICJtYXhfaXRlciI6IDEsICJtYXhfcnBtIjogbnVsbCwgImZ1bmN0
aW9uX2NhbGxpbmdfbGxtIjogIiIsICJsbG0iOiAiZ3B0LTRvIiwgImRlbGVnYXRpb25fZW5hYmxl
ZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xp
bWl0IjogMiwgInRvb2xzX25hbWVzIjogW119XUqQAgoKY3Jld190YXNrcxKBAgr+AVt7ImtleSI6
ICJmN2E5ZjdiYjFhZWU0YjZlZjJjNTI2ZDBhOGMyZjJhYyIsICJpZCI6ICIxZjRhYzJhYS03YmQ4
LTQ1NWQtODgyMC1jMzZmMjJjMDY4MzciLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVt
YW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogInRlc3Qgcm9sZSIsICJhZ2VudF9rZXki
OiAiZTE0OGU1MzIwMjkzNDk5ZjhjZWJlYTgyNmU3MjU4MmIiLCAidG9vbHNfbmFtZXMiOiBbImdl
dF9maW5hbF9hbnN3ZXIiXX1degIYAYUBAAEAABKOAgoQ0/vrakH7zD0uSvmVBUV8lxIIYe4YKcYG
hNgqDFRhc2sgQ3JlYXRlZDABOdBXKqHwTPgXQcCtKqHwTPgXSi4KCGNyZXdfa2V5EiIKIDczYWFj
Mjg1ZTY3NDY2NjdmNzUxNDc2NzAwMDM0MTEwSjEKB2NyZXdfaWQSJgokODQ0NjRiOGUtNGJmNy00
NGJjLTkyZTEtYThkMTU0ZmU1ZmQ3Si4KCHRhc2tfa2V5EiIKIGY3YTlmN2JiMWFlZTRiNmVmMmM1
MjZkMGE4YzJmMmFjSjEKB3Rhc2tfaWQSJgokMWY0YWMyYWEtN2JkOC00NTVkLTg4MjAtYzM2ZjIy
YzA2ODM3egIYAYUBAAEAABKkAQoQ5GDzHNlSdlcVDdxsI3abfRIIhYu8fZS3iA4qClRvb2wgVXNh
Z2UwATnIi2eh8Ez4F0FYbmih8Ez4F0oaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjYxLjBKHwoJdG9v
bF9uYW1lEhIKEGdldF9maW5hbF9hbnN3ZXJKDgoIYXR0ZW1wdHMSAhgBSg8KA2xsbRIICgZncHQt
NG96AhgBhQEAAQAAEpACChAy85Jfr/EEIe1THU8koXoYEgjlkNn7xfysjioOVGFzayBFeGVjdXRp
b24wATm42Cqh8Ez4F0GgxZah8Ez4F0ouCghjcmV3X2tleRIiCiA3M2FhYzI4NWU2NzQ2NjY3Zjc1
MTQ3NjcwMDAzNDExMEoxCgdjcmV3X2lkEiYKJDg0NDY0YjhlLTRiZjctNDRiYy05MmUxLWE4ZDE1
NGZlNWZkN0ouCgh0YXNrX2tleRIiCiBmN2E5ZjdiYjFhZWU0YjZlZjJjNTI2ZDBhOGMyZjJhY0ox
Cgd0YXNrX2lkEiYKJDFmNGFjMmFhLTdiZDgtNDU1ZC04ODIwLWMzNmYyMmMwNjgzN3oCGAGFAQAB
AAASrAcKEG0ZVq5Ww+/A0wOY3HmKgq4SCMe0ooxqjqBlKgxDcmV3IENyZWF0ZWQwATlwmISi8Ez4
F0HYUYai8Ez4F0oaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjYxLjBKGgoOcHl0aG9uX3ZlcnNpb24S
CAoGMy4xMS43Si4KCGNyZXdfa2V5EiIKIGQ1NTExM2JlNGFhNDFiYTY0M2QzMjYwNDJiMmYwM2Yx
SjEKB2NyZXdfaWQSJgokNzkyMWVlYmItMWI4NS00MzNjLWIxMDAtZDU4MmMyOTg5MzBkShwKDGNy
ZXdfcHJvY2VzcxIMCgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3X251bWJl
cl9vZl90YXNrcxICGAFKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAUrJAgoLY3Jld19hZ2Vu
dHMSuQIKtgJbeyJrZXkiOiAiZTE0OGU1MzIwMjkzNDk5ZjhjZWJlYTgyNmU3MjU4MmIiLCAiaWQi
OiAiZmRiZDI1MWYtYzUwOC00YmFhLTkwNjctN2U5YzQ2ZGZiZTJhIiwgInJvbGUiOiAidGVzdCBy
b2xlIiwgInZlcmJvc2U/IjogdHJ1ZSwgIm1heF9pdGVyIjogNiwgIm1heF9ycG0iOiBudWxsLCAi
ZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8iLCAiZGVsZWdhdGlvbl9l
bmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29kZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0
cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMiOiBbXX1dSpACCgpjcmV3X3Rhc2tzEoECCv4BW3si
a2V5IjogIjRhMzFiODUxMzNhM2EyOTRjNjg1M2RhNzU3ZDRiYWU3IiwgImlkIjogIjA2YWFmM2Y1
LTE5ODctNDAxYS05Yzk0LWY3ZjM1YmQzMDg3OSIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2Us
ICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAidGVzdCByb2xlIiwgImFnZW50
X2tleSI6ICJlMTQ4ZTUzMjAyOTM0OTlmOGNlYmVhODI2ZTcyNTgyYiIsICJ0b29sc19uYW1lcyI6
IFsiZ2V0X2ZpbmFsX2Fuc3dlciJdfV16AhgBhQEAAQAAEo4CChDT+zPZHwfacDilkzaZJ9uGEgip
Kr5r62JB+ioMVGFzayBDcmVhdGVkMAE56KeTovBM+BdB8PmTovBM+BdKLgoIY3Jld19rZXkSIgog
ZDU1MTEzYmU0YWE0MWJhNjQzZDMyNjA0MmIyZjAzZjFKMQoHY3Jld19pZBImCiQ3OTIxZWViYi0x
Yjg1LTQzM2MtYjEwMC1kNTgyYzI5ODkzMGRKLgoIdGFza19rZXkSIgogNGEzMWI4NTEzM2EzYTI5
NGM2ODUzZGE3NTdkNGJhZTdKMQoHdGFza19pZBImCiQwNmFhZjNmNS0xOTg3LTQwMWEtOWM5NC1m
N2YzNWJkMzA4Nzl6AhgBhQEAAQAAEpMBChCl85ZcL2Fa0N5QTl6EsIfnEghyDo3bxT+AkyoKVG9v
bCBVc2FnZTABOVBA2aLwTPgXQYAy2qLwTPgXShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuNjEuMEof
Cgl0b29sX25hbWUSEgoQZ2V0X2ZpbmFsX2Fuc3dlckoOCghhdHRlbXB0cxICGAF6AhgBhQEAAQAA
EpwBChB22uwKhaur9zmeoeEMaRKzEgjrtSEzMbRdIioTVG9vbCBSZXBlYXRlZCBVc2FnZTABOQga
C6PwTPgXQaDRC6PwTPgXShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuNjEuMEofCgl0b29sX25hbWUS
EgoQZ2V0X2ZpbmFsX2Fuc3dlckoOCghhdHRlbXB0cxICGAF6AhgBhQEAAQAAEpMBChArAfcRpE+W
02oszyzccbaWEghTAO9J3zq/kyoKVG9vbCBVc2FnZTABORBRTqPwTPgXQegnT6PwTPgXShoKDmNy
ZXdhaV92ZXJzaW9uEggKBjAuNjEuMEofCgl0b29sX25hbWUSEgoQZ2V0X2ZpbmFsX2Fuc3dlckoO
CghhdHRlbXB0cxICGAF6AhgBhQEAAQAAEpwBChBdtM3p3aqT7wTGaXi6el/4Egie6lFQpa+AfioT
VG9vbCBSZXBlYXRlZCBVc2FnZTABOdBg2KPwTPgXQehW2aPwTPgXShoKDmNyZXdhaV92ZXJzaW9u
EggKBjAuNjEuMEofCgl0b29sX25hbWUSEgoQZ2V0X2ZpbmFsX2Fuc3dlckoOCghhdHRlbXB0cxIC
GAF6AhgBhQEAAQAAEpMBChDq4OuaUKkNoi6jlMyahPJpEgg1MFDHktBxNSoKVG9vbCBVc2FnZTAB
ORD/K6TwTPgXQZgMLaTwTPgXShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuNjEuMEofCgl0b29sX25h
bWUSEgoQZ2V0X2ZpbmFsX2Fuc3dlckoOCghhdHRlbXB0cxICGAF6AhgBhQEAAQAAEpACChBhvTmu
QWP+bx9JMmGpt+w5Egh1J17yki7s8ioOVGFzayBFeGVjdXRpb24wATnoJJSi8Ez4F0HwNX6k8Ez4
F0ouCghjcmV3X2tleRIiCiBkNTUxMTNiZTRhYTQxYmE2NDNkMzI2MDQyYjJmMDNmMUoxCgdjcmV3
X2lkEiYKJDc5MjFlZWJiLTFiODUtNDMzYy1iMTAwLWQ1ODJjMjk4OTMwZEouCgh0YXNrX2tleRIi
CiA0YTMxYjg1MTMzYTNhMjk0YzY4NTNkYTc1N2Q0YmFlN0oxCgd0YXNrX2lkEiYKJDA2YWFmM2Y1
LTE5ODctNDAxYS05Yzk0LWY3ZjM1YmQzMDg3OXoCGAGFAQABAAASrg0KEOJZEqiJ7LTTX/J+tuLR
stQSCHKjy4tIcmKEKgxDcmV3IENyZWF0ZWQwATmIEuGk8Ez4F0FYDuOk8Ez4F0oaCg5jcmV3YWlf
dmVyc2lvbhIICgYwLjYxLjBKGgoOcHl0aG9uX3ZlcnNpb24SCAoGMy4xMS43Si4KCGNyZXdfa2V5
EiIKIDExMWI4NzJkOGYwY2Y3MDNmMmVmZWYwNGNmM2FjNzk4SjEKB2NyZXdfaWQSJgokYWFiYmU5
MmQtYjg3NC00NTZmLWE0NzAtM2FmMDc4ZTdjYThlShwKDGNyZXdfcHJvY2VzcxIMCgpzZXF1ZW50
aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3X251bWJlcl9vZl90YXNrcxICGANKGwoVY3Jl
d19udW1iZXJfb2ZfYWdlbnRzEgIYAkqEBQoLY3Jld19hZ2VudHMS9AQK8QRbeyJrZXkiOiAiZTE0
OGU1MzIwMjkzNDk5ZjhjZWJlYTgyNmU3MjU4MmIiLCAiaWQiOiAiZmYzOTE0OGEtZWI2NS00Nzkx
LWI3MTMtM2Q4ZmE1YWQ5NTJlIiwgInJvbGUiOiAidGVzdCByb2xlIiwgInZlcmJvc2U/IjogZmFs
c2UsICJtYXhfaXRlciI6IDE1LCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xs
bSI6ICIiLCAibGxtIjogImdwdC00byIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJh
bGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29s
c19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiZTdlOGVlYTg4NmJjYjhmMTA0NWFiZWVjZjE0MjVkYjci
LCAiaWQiOiAiYzYyNDJmNDMtNmQ2Mi00N2U4LTliYmMtNjM0ZDQwYWI4YTQ2IiwgInJvbGUiOiAi
dGVzdCByb2xlMiIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAxNSwgIm1heF9ycG0i
OiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8iLCAiZGVs
ZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29kZV9leGVjdXRpb24/IjogZmFsc2Us
ICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMiOiBbXX1dStcFCgpjcmV3X3Rhc2tz
EsgFCsUFW3sia2V5IjogIjMyMmRkYWUzYmM4MGMxZDQ1Yjg1ZmE3NzU2ZGI4NjY1IiwgImlkIjog
IjRmZDZhZDdiLTFjNWMtNDE1ZC1hMWQ4LTgwYzExZGNjMTY4NiIsICJhc3luY19leGVjdXRpb24/
IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAidGVzdCByb2xl
IiwgImFnZW50X2tleSI6ICJlMTQ4ZTUzMjAyOTM0OTlmOGNlYmVhODI2ZTcyNTgyYiIsICJ0b29s
c19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiY2M0ODc2ZjZlNTg4ZTcxMzQ5YmJkM2E2NTg4OGMzZTki
LCAiaWQiOiAiOTFlYWFhMWMtMWI4ZC00MDcxLTk2ZmQtM2QxZWVkMjhjMzZjIiwgImFzeW5jX2V4
ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJ0
ZXN0IHJvbGUiLCAiYWdlbnRfa2V5IjogImUxNDhlNTMyMDI5MzQ5OWY4Y2ViZWE4MjZlNzI1ODJi
IiwgInRvb2xzX25hbWVzIjogW119LCB7ImtleSI6ICJlMGIxM2UxMGQ3YTE0NmRjYzRjNDg4ZmNm
OGQ3NDhhMCIsICJpZCI6ICI4NjExZjhjZS1jNDVlLTQ2OTgtYWEyMS1jMGJkNzdhOGY2ZWYiLCAi
YXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9y
b2xlIjogInRlc3Qgcm9sZTIiLCAiYWdlbnRfa2V5IjogImU3ZThlZWE4ODZiY2I4ZjEwNDVhYmVl
Y2YxNDI1ZGI3IiwgInRvb2xzX25hbWVzIjogW119XXoCGAGFAQABAAASjgIKEMbX6YsWK7RRf4L1
NBRKD6cSCFLJiNmspsyjKgxUYXNrIENyZWF0ZWQwATnonPGk8Ez4F0EotvKk8Ez4F0ouCghjcmV3
X2tleRIiCiAxMTFiODcyZDhmMGNmNzAzZjJlZmVmMDRjZjNhYzc5OEoxCgdjcmV3X2lkEiYKJGFh
YmJlOTJkLWI4NzQtNDU2Zi1hNDcwLTNhZjA3OGU3Y2E4ZUouCgh0YXNrX2tleRIiCiAzMjJkZGFl
M2JjODBjMWQ0NWI4NWZhNzc1NmRiODY2NUoxCgd0YXNrX2lkEiYKJDRmZDZhZDdiLTFjNWMtNDE1
ZC1hMWQ4LTgwYzExZGNjMTY4NnoCGAGFAQABAAASkAIKEM9JnUNanFbE9AtnSxqA7H8SCBWlG0WJ
sMgKKg5UYXNrIEV4ZWN1dGlvbjABOfDo8qTwTPgXQWhEH6XwTPgXSi4KCGNyZXdfa2V5EiIKIDEx
MWI4NzJkOGYwY2Y3MDNmMmVmZWYwNGNmM2FjNzk4SjEKB2NyZXdfaWQSJgokYWFiYmU5MmQtYjg3
NC00NTZmLWE0NzAtM2FmMDc4ZTdjYThlSi4KCHRhc2tfa2V5EiIKIDMyMmRkYWUzYmM4MGMxZDQ1
Yjg1ZmE3NzU2ZGI4NjY1SjEKB3Rhc2tfaWQSJgokNGZkNmFkN2ItMWM1Yy00MTVkLWExZDgtODBj
MTFkY2MxNjg2egIYAYUBAAEAABKOAgoQaQALCJNe5ByN4Wu7FE0kABIIYW/UfVfnYscqDFRhc2sg
Q3JlYXRlZDABOWhzLKXwTPgXQSD8LKXwTPgXSi4KCGNyZXdfa2V5EiIKIDExMWI4NzJkOGYwY2Y3
MDNmMmVmZWYwNGNmM2FjNzk4SjEKB2NyZXdfaWQSJgokYWFiYmU5MmQtYjg3NC00NTZmLWE0NzAt
M2FmMDc4ZTdjYThlSi4KCHRhc2tfa2V5EiIKIGNjNDg3NmY2ZTU4OGU3MTM0OWJiZDNhNjU4ODhj
M2U5SjEKB3Rhc2tfaWQSJgokOTFlYWFhMWMtMWI4ZC00MDcxLTk2ZmQtM2QxZWVkMjhjMzZjegIY
AYUBAAEAABKQAgoQpPfkgFlpIsR/eN2zn+x3MRIILoWF4/HvceAqDlRhc2sgRXhlY3V0aW9uMAE5
GCctpfBM+BdBQLNapfBM+BdKLgoIY3Jld19rZXkSIgogMTExYjg3MmQ4ZjBjZjcwM2YyZWZlZjA0
Y2YzYWM3OThKMQoHY3Jld19pZBImCiRhYWJiZTkyZC1iODc0LTQ1NmYtYTQ3MC0zYWYwNzhlN2Nh
OGVKLgoIdGFza19rZXkSIgogY2M0ODc2ZjZlNTg4ZTcxMzQ5YmJkM2E2NTg4OGMzZTlKMQoHdGFz
a19pZBImCiQ5MWVhYWExYy0xYjhkLTQwNzEtOTZmZC0zZDFlZWQyOGMzNmN6AhgBhQEAAQAAEo4C
ChCdvXmXZRltDxEwZx2XkhWhEghoKdomHHhLGSoMVGFzayBDcmVhdGVkMAE54HpmpfBM+BdB4Pdm
pfBM+BdKLgoIY3Jld19rZXkSIgogMTExYjg3MmQ4ZjBjZjcwM2YyZWZlZjA0Y2YzYWM3OThKMQoH
Y3Jld19pZBImCiRhYWJiZTkyZC1iODc0LTQ1NmYtYTQ3MC0zYWYwNzhlN2NhOGVKLgoIdGFza19r
ZXkSIgogZTBiMTNlMTBkN2ExNDZkY2M0YzQ4OGZjZjhkNzQ4YTBKMQoHdGFza19pZBImCiQ4NjEx
ZjhjZS1jNDVlLTQ2OTgtYWEyMS1jMGJkNzdhOGY2ZWZ6AhgBhQEAAQAAEpACChAIvs/XQL53haTt
NV8fk6geEgicgSOcpcYulyoOVGFzayBFeGVjdXRpb24wATnYImel8Ez4F0Gw5ZSl8Ez4F0ouCghj
cmV3X2tleRIiCiAxMTFiODcyZDhmMGNmNzAzZjJlZmVmMDRjZjNhYzc5OEoxCgdjcmV3X2lkEiYK
JGFhYmJlOTJkLWI4NzQtNDU2Zi1hNDcwLTNhZjA3OGU3Y2E4ZUouCgh0YXNrX2tleRIiCiBlMGIx
M2UxMGQ3YTE0NmRjYzRjNDg4ZmNmOGQ3NDhhMEoxCgd0YXNrX2lkEiYKJDg2MTFmOGNlLWM0NWUt
NDY5OC1hYTIxLWMwYmQ3N2E4ZjZlZnoCGAGFAQABAAASvAcKEARTPn0s+U/k8GclUc+5rRoSCHF3
KCh8OS0FKgxDcmV3IENyZWF0ZWQwATlo+Pul8Ez4F0EQ0f2l8Ez4F0oaCg5jcmV3YWlfdmVyc2lv
bhIICgYwLjYxLjBKGgoOcHl0aG9uX3ZlcnNpb24SCAoGMy4xMS43Si4KCGNyZXdfa2V5EiIKIDQ5
NGYzNjU3MjM3YWQ4YTMwMzViMmYxYmVlY2RjNjc3SjEKB2NyZXdfaWQSJgokOWMwNzg3NWUtMTMz
Mi00MmMzLWFhZTEtZjNjMjc1YTQyNjYwShwKDGNyZXdfcHJvY2VzcxIMCgpzZXF1ZW50aWFsShEK
C2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3X251bWJlcl9vZl90YXNrcxICGAFKGwoVY3Jld19udW1i
ZXJfb2ZfYWdlbnRzEgIYAUrbAgoLY3Jld19hZ2VudHMSywIKyAJbeyJrZXkiOiAiZTE0OGU1MzIw
MjkzNDk5ZjhjZWJlYTgyNmU3MjU4MmIiLCAiaWQiOiAiNGFkYzNmMmItN2IwNC00MDRlLWEwNDQt
N2JkNjVmYTMyZmE4IiwgInJvbGUiOiAidGVzdCByb2xlIiwgInZlcmJvc2U/IjogZmFsc2UsICJt
YXhfaXRlciI6IDE1LCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIi
LCAibGxtIjogImdwdC00byIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19j
b2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1l
cyI6IFsibGVhcm5fYWJvdXRfYWkiXX1dSo4CCgpjcmV3X3Rhc2tzEv8BCvwBW3sia2V5IjogImYy
NTk3Yzc4NjdmYmUzMjRkYzY1ZGMwOGRmZGJmYzZjIiwgImlkIjogIjg2YzZiODE2LTgyOWMtNDUx
Zi1iMDZkLTUyZjQ4YTdhZWJiMyIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9p
bnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAidGVzdCByb2xlIiwgImFnZW50X2tleSI6ICJl
MTQ4ZTUzMjAyOTM0OTlmOGNlYmVhODI2ZTcyNTgyYiIsICJ0b29sc19uYW1lcyI6IFsibGVhcm5f
YWJvdXRfYWkiXX1degIYAYUBAAEAABKOAgoQZWSU3+i71QSqlD8iiLdyWBII1Pawtza2ZHsqDFRh
c2sgQ3JlYXRlZDABOdj2FKbwTPgXQZhUFabwTPgXSi4KCGNyZXdfa2V5EiIKIDQ5NGYzNjU3MjM3
YWQ4YTMwMzViMmYxYmVlY2RjNjc3SjEKB2NyZXdfaWQSJgokOWMwNzg3NWUtMTMzMi00MmMzLWFh
ZTEtZjNjMjc1YTQyNjYwSi4KCHRhc2tfa2V5EiIKIGYyNTk3Yzc4NjdmYmUzMjRkYzY1ZGMwOGRm
ZGJmYzZjSjEKB3Rhc2tfaWQSJgokODZjNmI4MTYtODI5Yy00NTFmLWIwNmQtNTJmNDhhN2FlYmIz
egIYAYUBAAEAABKRAQoQl3nNMLhrOg+OgsWWX6A9LxIINbCKrQzQ3JkqClRvb2wgVXNhZ2UwATlA
TlCm8Ez4F0FASFGm8Ez4F0oaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjYxLjBKHQoJdG9vbF9uYW1l
EhAKDmxlYXJuX2Fib3V0X0FJSg4KCGF0dGVtcHRzEgIYAXoCGAGFAQABAAASkAIKEL9YI/QwoVBJ
1HBkTLyQxOESCCcKWhev/Dc8Kg5UYXNrIEV4ZWN1dGlvbjABOXiDFabwTPgXQcjEfqbwTPgXSi4K
CGNyZXdfa2V5EiIKIDQ5NGYzNjU3MjM3YWQ4YTMwMzViMmYxYmVlY2RjNjc3SjEKB2NyZXdfaWQS
JgokOWMwNzg3NWUtMTMzMi00MmMzLWFhZTEtZjNjMjc1YTQyNjYwSi4KCHRhc2tfa2V5EiIKIGYy
NTk3Yzc4NjdmYmUzMjRkYzY1ZGMwOGRmZGJmYzZjSjEKB3Rhc2tfaWQSJgokODZjNmI4MTYtODI5
Yy00NTFmLWIwNmQtNTJmNDhhN2FlYmIzegIYAYUBAAEAABLBBwoQ0Le1256mT8wmcvnuLKYeNRII
IYBlVsTs+qEqDENyZXcgQ3JlYXRlZDABOYCBiKrwTPgXQRBeiqrwTPgXShoKDmNyZXdhaV92ZXJz
aW9uEggKBjAuNjEuMEoaCg5weXRob25fdmVyc2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgog
NDk0ZjM2NTcyMzdhZDhhMzAzNWIyZjFiZWVjZGM2NzdKMQoHY3Jld19pZBImCiQyN2VlMGYyYy1h
ZjgwLTQxYWMtYjg3ZC0xNmViYWQyMTVhNTJKHAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxK
EQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdfbnVtYmVyX29mX3Rhc2tzEgIYAUobChVjcmV3X251
bWJlcl9vZl9hZ2VudHMSAhgBSuACCgtjcmV3X2FnZW50cxLQAgrNAlt7ImtleSI6ICJlMTQ4ZTUz
MjAyOTM0OTlmOGNlYmVhODI2ZTcyNTgyYiIsICJpZCI6ICJmMTYyMTFjNS00YWJlLTRhZDAtOWI0
YS0yN2RmMTJhODkyN2UiLCAicm9sZSI6ICJ0ZXN0IHJvbGUiLCAidmVyYm9zZT8iOiBmYWxzZSwg
Im1heF9pdGVyIjogMiwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAi
Z3B0LTRvIiwgImxsbSI6ICJncHQtNG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAi
YWxsb3dfY29kZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9v
bHNfbmFtZXMiOiBbImxlYXJuX2Fib3V0X2FpIl19XUqOAgoKY3Jld190YXNrcxL/AQr8AVt7Imtl
eSI6ICJmMjU5N2M3ODY3ZmJlMzI0ZGM2NWRjMDhkZmRiZmM2YyIsICJpZCI6ICJjN2FiOWRiYi0y
MTc4LTRmOGItOGFiNi1kYTU1YzE0YTBkMGMiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAi
aHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogInRlc3Qgcm9sZSIsICJhZ2VudF9r
ZXkiOiAiZTE0OGU1MzIwMjkzNDk5ZjhjZWJlYTgyNmU3MjU4MmIiLCAidG9vbHNfbmFtZXMiOiBb
ImxlYXJuX2Fib3V0X2FpIl19XXoCGAGFAQABAAASjgIKECr4ueCUCo/tMB7EuBQt6TcSCD/UepYl
WGqAKgxUYXNrIENyZWF0ZWQwATk4kpyq8Ez4F0Hg85yq8Ez4F0ouCghjcmV3X2tleRIiCiA0OTRm
MzY1NzIzN2FkOGEzMDM1YjJmMWJlZWNkYzY3N0oxCgdjcmV3X2lkEiYKJDI3ZWUwZjJjLWFmODAt
NDFhYy1iODdkLTE2ZWJhZDIxNWE1MkouCgh0YXNrX2tleRIiCiBmMjU5N2M3ODY3ZmJlMzI0ZGM2
NWRjMDhkZmRiZmM2Y0oxCgd0YXNrX2lkEiYKJGM3YWI5ZGJiLTIxNzgtNGY4Yi04YWI2LWRhNTVj
MTRhMGQwY3oCGAGFAQABAAASeQoQkj0vmbCBIZPi33W9KrvrYhIIM2g73dOAN9QqEFRvb2wgVXNh
Z2UgRXJyb3IwATnQgsyr8Ez4F0GghM2r8Ez4F0oaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjYxLjBK
DwoDbGxtEggKBmdwdC00b3oCGAGFAQABAAASeQoQavr4/1SWr8x7HD5mAzlM0hIIXPx740Skkd0q
EFRvb2wgVXNhZ2UgRXJyb3IwATkouH9C8Uz4F0FQ1YBC8Uz4F0oaCg5jcmV3YWlfdmVyc2lvbhII
CgYwLjYxLjBKDwoDbGxtEggKBmdwdC00b3oCGAGFAQABAAASkAIKEIgmJ3QURJvSsEifMScSiUsS
CCyiPHcZT8AnKg5UYXNrIEV4ZWN1dGlvbjABOcAinarwTPgXQeBEynvxTPgXSi4KCGNyZXdfa2V5
EiIKIDQ5NGYzNjU3MjM3YWQ4YTMwMzViMmYxYmVlY2RjNjc3SjEKB2NyZXdfaWQSJgokMjdlZTBm
MmMtYWY4MC00MWFjLWI4N2QtMTZlYmFkMjE1YTUySi4KCHRhc2tfa2V5EiIKIGYyNTk3Yzc4Njdm
YmUzMjRkYzY1ZGMwOGRmZGJmYzZjSjEKB3Rhc2tfaWQSJgokYzdhYjlkYmItMjE3OC00ZjhiLThh
YjYtZGE1NWMxNGEwZDBjegIYAYUBAAEAABLEBwoQY+GZuYkP6mwdaVQQc11YuhII7ADKOlFZlzQq
DENyZXcgQ3JlYXRlZDABObCoi3zxTPgXQeCUjXzxTPgXShoKDmNyZXdhaV92ZXJzaW9uEggKBjAu
NjEuMEoaCg5weXRob25fdmVyc2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgogN2U2NjA4OTg5
ODU5YTY3ZWVjODhlZWY3ZmNlODUyMjVKMQoHY3Jld19pZBImCiQxMmE0OTFlNS00NDgwLTQ0MTYt
OTAxYi1iMmI1N2U1ZWU4ZThKHAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19t
ZW1vcnkSAhAAShoKFGNyZXdfbnVtYmVyX29mX3Rhc2tzEgIYAUobChVjcmV3X251bWJlcl9vZl9h
Z2VudHMSAhgBSt8CCgtjcmV3X2FnZW50cxLPAgrMAlt7ImtleSI6ICIyMmFjZDYxMWU0NGVmNWZh
YzA1YjUzM2Q3NWU4ODkzYiIsICJpZCI6ICI5NjljZjhlMy0yZWEwLTQ5ZjgtODNlMS02MzEzYmE4
ODc1ZjUiLCAicm9sZSI6ICJEYXRhIFNjaWVudGlzdCIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4
X2l0ZXIiOiAxNSwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwg
ImxsbSI6ICJncHQtNG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29k
ZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMi
OiBbImdldCBncmVldGluZ3MiXX1dSpICCgpjcmV3X3Rhc2tzEoMCCoACW3sia2V5IjogImEyNzdi
MzRiMmMxNDZmMGM1NmM1ZTEzNTZlOGY4YTU3IiwgImlkIjogImIwMTg0NTI2LTJlOWItNDA0My1h
M2JiLTFiM2QzNWIxNTNhOCIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1
dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAiRGF0YSBTY2llbnRpc3QiLCAiYWdlbnRfa2V5Ijog
IjIyYWNkNjExZTQ0ZWY1ZmFjMDViNTMzZDc1ZTg4OTNiIiwgInRvb2xzX25hbWVzIjogWyJnZXQg
Z3JlZXRpbmdzIl19XXoCGAGFAQABAAASjgIKEI/rrKkPz08VpVWNehfvxJ0SCIpeq76twGj3KgxU
YXNrIENyZWF0ZWQwATlA9aR88Uz4F0HoVqV88Uz4F0ouCghjcmV3X2tleRIiCiA3ZTY2MDg5ODk4
NTlhNjdlZWM4OGVlZjdmY2U4NTIyNUoxCgdjcmV3X2lkEiYKJDEyYTQ5MWU1LTQ0ODAtNDQxNi05
MDFiLWIyYjU3ZTVlZThlOEouCgh0YXNrX2tleRIiCiBhMjc3YjM0YjJjMTQ2ZjBjNTZjNWUxMzU2
ZThmOGE1N0oxCgd0YXNrX2lkEiYKJGIwMTg0NTI2LTJlOWItNDA0My1hM2JiLTFiM2QzNWIxNTNh
OHoCGAGFAQABAAASkAEKEKKr5LR8SkqfqqktFhniLdkSCPMnqI2ma9UoKgpUb29sIFVzYWdlMAE5
sCHgfPFM+BdB+A/hfPFM+BdKGgoOY3Jld2FpX3ZlcnNpb24SCAoGMC42MS4wShwKCXRvb2xfbmFt
ZRIPCg1HZXQgR3JlZXRpbmdzSg4KCGF0dGVtcHRzEgIYAXoCGAGFAQABAAASkAIKEOj2bALdBlz6
1kP1MvHE5T0SCLw4D7D331IOKg5UYXNrIEV4ZWN1dGlvbjABOeCBpXzxTPgXQSjiEH3xTPgXSi4K
CGNyZXdfa2V5EiIKIDdlNjYwODk4OTg1OWE2N2VlYzg4ZWVmN2ZjZTg1MjI1SjEKB2NyZXdfaWQS
JgokMTJhNDkxZTUtNDQ4MC00NDE2LTkwMWItYjJiNTdlNWVlOGU4Si4KCHRhc2tfa2V5EiIKIGEy
NzdiMzRiMmMxNDZmMGM1NmM1ZTEzNTZlOGY4YTU3SjEKB3Rhc2tfaWQSJgokYjAxODQ1MjYtMmU5
Yi00MDQzLWEzYmItMWIzZDM1YjE1M2E4egIYAYUBAAEAABLQBwoQLjz7NWyGPgGU4tVFJ0sh9BII
N6EzU5f/sykqDENyZXcgQ3JlYXRlZDABOajOcX3xTPgXQUCAc33xTPgXShoKDmNyZXdhaV92ZXJz
aW9uEggKBjAuNjEuMEoaCg5weXRob25fdmVyc2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgog
YzMwNzYwMDkzMjY3NjE0NDRkNTdjNzFkMWRhM2YyN2NKMQoHY3Jld19pZBImCiQ1N2Y0NjVhNC03
Zjk1LTQ5Y2MtODNmZC0zZTIwNWRhZDBjZTJKHAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxK
EQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdfbnVtYmVyX29mX3Rhc2tzEgIYAUobChVjcmV3X251
bWJlcl9vZl9hZ2VudHMSAhgBSuUCCgtjcmV3X2FnZW50cxLVAgrSAlt7ImtleSI6ICI5OGYzYjFk
NDdjZTk2OWNmMDU3NzI3Yjc4NDE0MjVjZCIsICJpZCI6ICJjZjcyZDlkNy01MjQwLTRkMzEtYjA2
Mi0xMmNjMDU2OGNjM2MiLCAicm9sZSI6ICJGcmllbmRseSBOZWlnaGJvciIsICJ2ZXJib3NlPyI6
IGZhbHNlLCAibWF4X2l0ZXIiOiAxNSwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGlu
Z19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNl
LCAiYWxsb3dfY29kZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAi
dG9vbHNfbmFtZXMiOiBbImRlY2lkZSBncmVldGluZ3MiXX1dSpgCCgpjcmV3X3Rhc2tzEokCCoYC
W3sia2V5IjogIjgwZDdiY2Q0OTA5OTI5MDA4MzgzMmYwZTk4MzM4MGRmIiwgImlkIjogIjUxNTJk
MmQ2LWYwODYtNGIyMi1hOGMxLTMyODA5NzU1NjZhZCIsICJhc3luY19leGVjdXRpb24/IjogZmFs
c2UsICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAiRnJpZW5kbHkgTmVpZ2hi
b3IiLCAiYWdlbnRfa2V5IjogIjk4ZjNiMWQ0N2NlOTY5Y2YwNTc3MjdiNzg0MTQyNWNkIiwgInRv
b2xzX25hbWVzIjogWyJkZWNpZGUgZ3JlZXRpbmdzIl19XXoCGAGFAQABAAASjgIKEM+95r2LzVVg
kqAMolHjl9oSCN9WyhdF/ucVKgxUYXNrIENyZWF0ZWQwATnoCoJ98Uz4F0HwXIJ98Uz4F0ouCghj
cmV3X2tleRIiCiBjMzA3NjAwOTMyNjc2MTQ0NGQ1N2M3MWQxZGEzZjI3Y0oxCgdjcmV3X2lkEiYK
JDU3ZjQ2NWE0LTdmOTUtNDljYy04M2ZkLTNlMjA1ZGFkMGNlMkouCgh0YXNrX2tleRIiCiA4MGQ3
YmNkNDkwOTkyOTAwODM4MzJmMGU5ODMzODBkZkoxCgd0YXNrX2lkEiYKJDUxNTJkMmQ2LWYwODYt
NGIyMi1hOGMxLTMyODA5NzU1NjZhZHoCGAGFAQABAAASkwEKENJjTKn4eTP/P11ERMIGcdYSCIKF
bGEmcS7bKgpUb29sIFVzYWdlMAE5EFu5ffFM+BdBoD26ffFM+BdKGgoOY3Jld2FpX3ZlcnNpb24S
CAoGMC42MS4wSh8KCXRvb2xfbmFtZRISChBEZWNpZGUgR3JlZXRpbmdzSg4KCGF0dGVtcHRzEgIY
AXoCGAGFAQABAAASkAIKEG29htC06tLF7ihE5Yz6NyMSCAAsKzOcj25nKg5UYXNrIEV4ZWN1dGlv
bjABOQCEgn3xTPgXQfgg7X3xTPgXSi4KCGNyZXdfa2V5EiIKIGMzMDc2MDA5MzI2NzYxNDQ0ZDU3
YzcxZDFkYTNmMjdjSjEKB2NyZXdfaWQSJgokNTdmNDY1YTQtN2Y5NS00OWNjLTgzZmQtM2UyMDVk
YWQwY2UySi4KCHRhc2tfa2V5EiIKIDgwZDdiY2Q0OTA5OTI5MDA4MzgzMmYwZTk4MzM4MGRmSjEK
B3Rhc2tfaWQSJgokNTE1MmQyZDYtZjA4Ni00YjIyLWE4YzEtMzI4MDk3NTU2NmFkegIYAYUBAAEA
AA==
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '18925'
Content-Type:
- application/x-protobuf
User-Agent:
- OTel-OTLP-Exporter-Python/1.27.0
method: POST
uri: https://telemetry.crewai.com:4319/v1/traces
response:
body:
string: "\n\0"
headers:
Content-Length:
- '2'
Content-Type:
- application/x-protobuf
Date:
- Tue, 24 Sep 2024 21:57:39 GMT
status:
code: 200
message: OK
- request:
body: '{"model": "gemma2:latest", "prompt": "### User:\nRespond in 20 words. Who
are you?\n\n", "options": {}, "stream": false}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '120'
Content-Type:
- application/json
User-Agent:
- python-requests/2.31.0
method: POST
uri: http://localhost:8080/api/generate
response:
body:
string: '{"model":"gemma2:latest","created_at":"2024-09-24T21:57:51.284303Z","response":"I
am Gemma, an open-weights AI assistant developed by Google DeepMind. \n","done":true,"done_reason":"stop","context":[106,1645,108,6176,4926,235292,108,54657,575,235248,235284,235276,3907,235265,7702,708,692,235336,109,107,108,106,2516,108,235285,1144,137061,235269,671,2174,235290,30316,16481,20409,6990,731,6238,20555,35777,235265,139,108],"total_duration":14046647083,"load_duration":12942541833,"prompt_eval_count":25,"prompt_eval_duration":177695000,"eval_count":19,"eval_duration":923120000}'
headers:
Content-Length:
- '579'
Content-Type:
- application/json; charset=utf-8
Date:
- Tue, 24 Sep 2024 21:57:51 GMT
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,36 @@
interactions:
- request:
body: '{"model": "llama3.2:3b", "prompt": "### User:\nRespond in 20 words. Who
which model are you?\n\n", "options": {"stop": ["\nObservation:"]}, "stream":
false}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '156'
Content-Type:
- application/json
User-Agent:
- python-requests/2.32.3
method: POST
uri: http://localhost:11434/api/generate
response:
body:
string: '{"model":"llama3.2:3b","created_at":"2025-01-02T20:07:07.623404Z","response":"I''m
an AI designed to assist and communicate with users, utilizing a combination
of natural language processing models.","done":true,"done_reason":"stop","context":[128006,9125,128007,271,38766,1303,33025,2696,25,6790,220,2366,18,271,128009,128006,882,128007,271,14711,2724,512,66454,304,220,508,4339,13,10699,902,1646,527,499,1980,128009,128006,78191,128007,271,40,2846,459,15592,6319,311,7945,323,19570,449,3932,11,35988,264,10824,315,5933,4221,8863,4211,13],"total_duration":1076617833,"load_duration":46505416,"prompt_eval_count":40,"prompt_eval_duration":626000000,"eval_count":22,"eval_duration":399000000}'
headers:
Content-Length:
- '690'
Content-Type:
- application/json; charset=utf-8
Date:
- Thu, 02 Jan 2025 20:07:07 GMT
status:
code: 200
message: OK
version: 1

View File

@@ -1,35 +0,0 @@
interactions:
- request:
body: '{"model": "gemma2:latest", "prompt": "### User:\nRespond in 20 words. Who
are you?\n\n", "options": {"num_predict": 30, "temperature": 0.7}, "stream":
false}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '157'
Content-Type:
- application/json
User-Agent:
- python-requests/2.31.0
method: POST
uri: http://localhost:8080/api/generate
response:
body:
string: '{"model":"gemma2:latest","created_at":"2024-09-24T21:57:52.329049Z","response":"I
am Gemma, an open-weights AI assistant trained by Google DeepMind. \n","done":true,"done_reason":"stop","context":[106,1645,108,6176,4926,235292,108,54657,575,235248,235284,235276,3907,235265,7702,708,692,235336,109,107,108,106,2516,108,235285,1144,137061,235269,671,2174,235290,30316,16481,20409,17363,731,6238,20555,35777,235265,139,108],"total_duration":991843667,"load_duration":31664750,"prompt_eval_count":25,"prompt_eval_duration":51409000,"eval_count":19,"eval_duration":908132000}'
headers:
Content-Length:
- '572'
Content-Type:
- application/json; charset=utf-8
Date:
- Tue, 24 Sep 2024 21:57:52 GMT
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,36 @@
interactions:
- request:
body: '{"model": "llama3.2:3b", "prompt": "### User:\nRespond in 20 words. Which
model are you??\n\n", "options": {"num_predict": 30, "temperature": 0.7}, "stream":
false}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '164'
Content-Type:
- application/json
User-Agent:
- python-requests/2.32.3
method: POST
uri: http://localhost:11434/api/generate
response:
body:
string: '{"model":"llama3.2:3b","created_at":"2025-01-02T20:24:24.812595Z","response":"I''m
an AI, specifically a large language model, designed to understand and respond
to user queries with accuracy.","done":true,"done_reason":"stop","context":[128006,9125,128007,271,38766,1303,33025,2696,25,6790,220,2366,18,271,128009,128006,882,128007,271,14711,2724,512,66454,304,220,508,4339,13,16299,1646,527,499,71291,128009,128006,78191,128007,271,40,2846,459,15592,11,11951,264,3544,4221,1646,11,6319,311,3619,323,6013,311,1217,20126,449,13708,13],"total_duration":827817584,"load_duration":41560542,"prompt_eval_count":39,"prompt_eval_duration":384000000,"eval_count":23,"eval_duration":400000000}'
headers:
Content-Length:
- '683'
Content-Type:
- application/json; charset=utf-8
Date:
- Thu, 02 Jan 2025 20:24:24 GMT
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,146 @@
interactions:
- request:
body: '{"messages": [{"role": "system", "content": "You are Researcher. 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.\nYour personal goal is: Make the best research
and analysis on content about AI and AI agents\nTo give my best complete final
answer to the task use the exact following format:\n\nThought: I now can give
a great answer\nFinal Answer: Your final answer must be the great and the most
complete as possible, it must be outcome described.\n\nI MUST use these formats,
my job depends on it!"}, {"role": "user", "content": "\nCurrent Task: Give me
a list of 5 interesting ideas to explore for na article, what makes them unique
and interesting.\n\nThis is the expect criteria for your final answer: Bullet
point list of 5 interesting ideas.\nyou MUST return the actual complete content
as the final answer, not a summary.\n\nBegin! This is VERY important to you,
use the tools available and give your best Final Answer, your job depends on
it!\n\nThought:"}], "model": "gpt-4o-mini", "stop": ["\nObservation:"], "stream":
false}'
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate
connection:
- keep-alive
content-length:
- '1177'
content-type:
- application/json
host:
- api.openai.com
user-agent:
- OpenAI/Python 1.52.1
x-stainless-arch:
- arm64
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- MacOS
x-stainless-package-version:
- 1.52.1
x-stainless-raw-response:
- 'true'
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.12.7
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
content: "{\n \"id\": \"chatcmpl-AlfwrGToOoVtDhb3ryZMpA07aZy4m\",\n \"object\":
\"chat.completion\",\n \"created\": 1735926029,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
\"assistant\",\n \"content\": \"I now can give a great answer \\nFinal
Answer: \\n- **The Role of Emotional Intelligence in AI Agents**: Explore how
developing emotional intelligence in AI can change user interactions. Investigate
algorithms that enable AI agents to recognize and respond to human emotions,
enhancing user experience in sectors such as therapy, customer service, and
education. This idea is unique as it blends psychology with artificial intelligence,
presenting a new frontier for AI applications.\\n\\n- **AI Agents in Problem-Solving
for Climate Change**: Analyze how AI agents can contribute to developing innovative
solutions for climate change challenges. Focus on their role in predicting climate
patterns, optimizing energy consumption, and managing resources more efficiently.
This topic is unique because it highlights the practical impact of AI on one
of the most pressing global issues.\\n\\n- **The Ethics of Autonomous Decision-Making
AI**: Delve into the ethical implications surrounding AI agents that make autonomous
decisions, especially in critical areas like healthcare, transportation, and
law enforcement. This idea raises questions about accountability and bias, making
it a vital discussion point as AI continues to advance. The unique aspect lies
in the intersection of technology and moral philosophy.\\n\\n- **AI Agents Shaping
the Future of Remote Work**: Investigate how AI agents are transforming remote
work environments through automation, communication facilitation, and performance
monitoring. Discuss unique applications such as virtual assistants, project
management tools, and AI-driven team collaboration platforms. This topic is
particularly relevant as the workforce becomes increasingly remote, making it
an appealing area of exploration.\\n\\n- **Cultural Impacts of AI Agents in
Media and Entertainment**: Examine how AI-driven characters and narratives are
changing the media landscape, from video games to films and animations. Analyze
audience reception and the role of AI in personalizing content. This concept
is unique due to its intersection with digital culture and artistic expression,
offering insights into how technology influences social norms and preferences.\",\n
\ \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\":
\"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 220,\n \"completion_tokens\":
376,\n \"total_tokens\": 596,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n
\ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\":
\"fp_0aa8d3e20b\"\n}\n"
headers:
CF-Cache-Status:
- DYNAMIC
CF-RAY:
- 8fc4c6324d42ad5a-POA
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Fri, 03 Jan 2025 17:40:34 GMT
Server:
- cloudflare
Set-Cookie:
- __cf_bm=zdRUS9YIvR7oCmJGeB7BOAnmxI7FOE5Jae5yRZDCnPE-1735926034-1.0.1.1-gvIEXrMfT69wL2mv4ApivWX67OOpDegjf1LE6g9u3GEDuQdLQok.vlLZD.SdGzK0bMug86JZhBeDZMleJlI2EQ;
path=/; expires=Fri, 03-Jan-25 18:10:34 GMT; domain=.api.openai.com; HttpOnly;
Secure; SameSite=None
- _cfuvid=CW_cKQGYWY3cL.S6Xo5z0cmkmWHy5Q50OA_KjPEijNk-1735926034530-0.0.1.1-604800000;
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
access-control-expose-headers:
- X-Request-ID
alt-svc:
- h3=":443"; ma=86400
openai-organization:
- crewai-iuxna1
openai-processing-ms:
- '5124'
openai-version:
- '2020-10-01'
strict-transport-security:
- max-age=31536000; includeSubDomains; preload
x-ratelimit-limit-requests:
- '30000'
x-ratelimit-limit-tokens:
- '150000000'
x-ratelimit-remaining-requests:
- '29999'
x-ratelimit-remaining-tokens:
- '149999729'
x-ratelimit-reset-requests:
- 2ms
x-ratelimit-reset-tokens:
- 0s
x-request-id:
- req_95ae59da1099e02c0d95bf25ba179fed
http_version: HTTP/1.1
status_code: 200
version: 1

View File

@@ -28,9 +28,10 @@ def test_create_success(mock_subprocess):
with in_temp_dir():
tool_command = ToolCommand()
with patch.object(tool_command, "login") as mock_login, patch(
"sys.stdout", new=StringIO()
) as fake_out:
with (
patch.object(tool_command, "login") as mock_login,
patch("sys.stdout", new=StringIO()) as fake_out,
):
tool_command.create("test-tool")
output = fake_out.getvalue()
@@ -82,7 +83,7 @@ def test_install_success(mock_get, mock_subprocess_run):
capture_output=False,
text=True,
check=True,
env=unittest.mock.ANY
env=unittest.mock.ANY,
)
assert "Successfully installed sample-tool" in output

View File

@@ -333,16 +333,16 @@ def test_manager_agent_delegating_to_assigned_task_agent():
)
mock_task_output = TaskOutput(
description="Mock description",
raw="mocked output",
agent="mocked agent"
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:
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
# Verify execute_sync was called once
@@ -350,12 +350,20 @@ def test_manager_agent_delegating_to_assigned_task_agent():
# Get the tools argument from the call
_, kwargs = mock_execute_sync.call_args
tools = kwargs['tools']
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)
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"])
@@ -404,7 +412,7 @@ def test_manager_agent_delegates_with_varied_role_cases():
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",
@@ -426,13 +434,13 @@ def test_manager_agent_delegates_with_varied_role_cases():
)
mock_task_output = TaskOutput(
description="Mock description",
raw="mocked output",
agent="mocked agent"
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:
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
# Verify execute_sync was called once
@@ -440,20 +448,32 @@ def test_manager_agent_delegates_with_varied_role_cases():
# Get the tools argument from the call
_, kwargs = mock_execute_sync.call_args
tools = kwargs['tools']
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
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"])
@@ -479,6 +499,7 @@ def test_crew_with_delegating_agents():
== "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
@@ -489,6 +510,7 @@ def test_crew_with_delegating_agents_should_not_override_task_tools():
class TestToolInput(BaseModel):
"""Input schema for TestTool."""
query: str = Field(..., description="Query to process")
class TestTool(BaseTool):
@@ -516,24 +538,29 @@ def test_crew_with_delegating_agents_should_not_override_task_tools():
)
mock_task_output = TaskOutput(
description="Mock description",
raw="mocked output",
agent="mocked agent"
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:
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']
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"
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():
@@ -545,6 +572,7 @@ def test_crew_with_delegating_agents_should_not_override_agent_tools():
class TestToolInput(BaseModel):
"""Input schema for TestTool."""
query: str = Field(..., description="Query to process")
class TestTool(BaseTool):
@@ -563,7 +591,7 @@ def test_crew_with_delegating_agents_should_not_override_agent_tools():
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
agent=new_ceo,
)
]
@@ -574,24 +602,29 @@ def test_crew_with_delegating_agents_should_not_override_agent_tools():
)
mock_task_output = TaskOutput(
description="Mock description",
raw="mocked output",
agent="mocked agent"
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:
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']
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"
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():
@@ -603,6 +636,7 @@ def test_task_tools_override_agent_tools():
class TestToolInput(BaseModel):
"""Input schema for TestTool."""
query: str = Field(..., description="Query to process")
class TestTool(BaseTool):
@@ -630,14 +664,10 @@ def test_task_tools_override_agent_tools():
description="Write a test task",
expected_output="Test output",
agent=new_researcher,
tools=[AnotherTestTool()]
tools=[AnotherTestTool()],
)
crew = Crew(
agents=[new_researcher],
tasks=[task],
process=Process.sequential
)
crew = Crew(agents=[new_researcher], tasks=[task], process=Process.sequential)
crew.kickoff()
@@ -650,6 +680,7 @@ def test_task_tools_override_agent_tools():
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():
"""
@@ -702,13 +733,13 @@ def test_task_tools_override_agent_tools_with_allow_delegation():
)
mock_task_output = TaskOutput(
description="Mock description",
raw="mocked output",
agent="mocked agent"
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:
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
@@ -716,16 +747,23 @@ def test_task_tools_override_agent_tools_with_allow_delegation():
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"
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"
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 = [
@@ -1012,8 +1050,8 @@ def test_three_task_with_async_execution():
)
@pytest.mark.vcr(filter_headers=["authorization"])
@pytest.mark.asyncio
@pytest.mark.vcr(filter_headers=["authorization"])
async def test_crew_async_kickoff():
inputs = [
{"topic": "dog"},
@@ -1060,8 +1098,9 @@ async def test_crew_async_kickoff():
assert result[0].token_usage.successful_requests > 0 # type: ignore
@pytest.mark.asyncio
@pytest.mark.vcr(filter_headers=["authorization"])
def test_async_task_execution_call_count():
async def test_async_task_execution_call_count():
from unittest.mock import MagicMock, patch
list_ideas = Task(
@@ -1188,7 +1227,6 @@ def test_kickoff_for_each_empty_input():
assert results == []
@pytest.mark.vcr(filter_headers=["authorization"])
def test_kickoff_for_each_invalid_input():
"""Tests if kickoff_for_each raises TypeError for invalid input types."""
@@ -1211,7 +1249,6 @@ def test_kickoff_for_each_invalid_input():
crew.kickoff_for_each("invalid input")
@pytest.mark.vcr(filter_headers=["authorization"])
def test_kickoff_for_each_error_handling():
"""Tests error handling in kickoff_for_each when kickoff raises an error."""
from unittest.mock import patch
@@ -1248,7 +1285,6 @@ def test_kickoff_for_each_error_handling():
crew.kickoff_for_each(inputs=inputs)
@pytest.mark.vcr(filter_headers=["authorization"])
@pytest.mark.asyncio
async def test_kickoff_async_basic_functionality_and_output():
"""Tests the basic functionality and output of kickoff_async."""
@@ -1283,7 +1319,6 @@ async def test_kickoff_async_basic_functionality_and_output():
mock_kickoff.assert_called_once_with(inputs)
@pytest.mark.vcr(filter_headers=["authorization"])
@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."""
@@ -1330,7 +1365,6 @@ async def test_async_kickoff_for_each_async_basic_functionality_and_output():
mock_kickoff_async.assert_any_call(inputs=input_data)
@pytest.mark.vcr(filter_headers=["authorization"])
@pytest.mark.asyncio
async def test_async_kickoff_for_each_async_empty_input():
"""Tests if akickoff_for_each_async handles an empty input list."""
@@ -1514,12 +1548,12 @@ def test_code_execution_flag_adds_code_tool_upon_kickoff():
crew = Crew(agents=[programmer], tasks=[task])
mock_task_output = TaskOutput(
description="Mock description",
raw="mocked output",
agent="mocked agent"
description="Mock description", raw="mocked output", agent="mocked agent"
)
with patch.object(Task, "execute_sync", return_value=mock_task_output) as mock_execute_sync:
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
@@ -1528,7 +1562,10 @@ def test_code_execution_flag_adds_code_tool_upon_kickoff():
# 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"
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():
@@ -1639,16 +1676,16 @@ def test_hierarchical_crew_creation_tasks_with_agents():
)
mock_task_output = TaskOutput(
description="Mock description",
raw="mocked output",
agent="mocked agent"
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:
with patch.object(
Task, "execute_sync", return_value=mock_task_output
) as mock_execute_sync:
crew.kickoff()
# Verify execute_sync was called once
@@ -1656,12 +1693,20 @@ def test_hierarchical_crew_creation_tasks_with_agents():
# Get the tools argument from the call
_, kwargs = mock_execute_sync.call_args
tools = kwargs['tools']
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)
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"])
@@ -1684,9 +1729,7 @@ def test_hierarchical_crew_creation_tasks_with_async_execution():
)
mock_task_output = TaskOutput(
description="Mock description",
raw="mocked output",
agent="mocked agent"
description="Mock description", raw="mocked output", agent="mocked agent"
)
# Create a mock Future that returns our TaskOutput
@@ -1697,7 +1740,9 @@ def test_hierarchical_crew_creation_tasks_with_async_execution():
# 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:
with patch.object(
Task, "execute_async", return_value=mock_future
) as mock_execute_async:
crew.kickoff()
# Verify execute_async was called once
@@ -1705,12 +1750,20 @@ def test_hierarchical_crew_creation_tasks_with_async_execution():
# Get the tools argument from the call
_, kwargs = mock_execute_async.call_args
tools = kwargs['tools']
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)
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"])
@@ -2039,7 +2092,6 @@ def test_crew_output_file_end_to_end(tmp_path):
assert expected_file.exists(), f"Output file {expected_file} was not created"
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_output_file_validation_failures():
"""Test output file validation failures in a crew context."""
agent = Agent(
@@ -2055,7 +2107,7 @@ def test_crew_output_file_validation_failures():
description="Analyze data",
expected_output="Analysis results",
agent=agent,
output_file="../output.txt"
output_file="../output.txt",
)
Crew(agents=[agent], tasks=[task]).kickoff()
@@ -2065,7 +2117,7 @@ def test_crew_output_file_validation_failures():
description="Analyze data",
expected_output="Analysis results",
agent=agent,
output_file="output.txt | rm -rf /"
output_file="output.txt | rm -rf /",
)
Crew(agents=[agent], tasks=[task]).kickoff()
@@ -2075,7 +2127,7 @@ def test_crew_output_file_validation_failures():
description="Analyze data",
expected_output="Analysis results",
agent=agent,
output_file="~/output.txt"
output_file="~/output.txt",
)
Crew(agents=[agent], tasks=[task]).kickoff()
@@ -2085,12 +2137,11 @@ def test_crew_output_file_validation_failures():
description="Analyze data",
expected_output="Analysis results",
agent=agent,
output_file="{invalid-name}/output.txt"
output_file="{invalid-name}/output.txt",
)
Crew(agents=[agent], tasks=[task]).kickoff()
@pytest.mark.vcr(filter_headers=["authorization"])
def test_manager_agent():
from unittest.mock import patch
@@ -3049,6 +3100,7 @@ def test_task_tools_preserve_code_execution_tools():
class TestToolInput(BaseModel):
"""Input schema for TestTool."""
query: str = Field(..., description="Query to process")
class TestTool(BaseTool):
@@ -3082,7 +3134,7 @@ def test_task_tools_preserve_code_execution_tools():
description="Write a program to calculate fibonacci numbers.",
expected_output="A working fibonacci calculator.",
agent=programmer,
tools=[TestTool()]
tools=[TestTool()],
)
crew = Crew(
@@ -3092,12 +3144,12 @@ def test_task_tools_preserve_code_execution_tools():
)
mock_task_output = TaskOutput(
description="Mock description",
raw="mocked output",
agent="mocked agent"
description="Mock description", raw="mocked output", agent="mocked agent"
)
with patch.object(Task, "execute_sync", return_value=mock_task_output) as mock_execute_sync:
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
@@ -3105,12 +3157,21 @@ def test_task_tools_preserve_code_execution_tools():
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"
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"
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():
@@ -3139,13 +3200,13 @@ def test_multimodal_flag_adds_multimodal_tools():
crew = Crew(agents=[multimodal_agent], tasks=[task], process=Process.sequential)
mock_task_output = TaskOutput(
description="Mock description",
raw="mocked output",
agent="mocked agent"
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:
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
@@ -3153,13 +3214,14 @@ def test_multimodal_flag_adds_multimodal_tools():
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"
)
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():
"""
@@ -3201,10 +3263,10 @@ def test_multimodal_agent_image_tool_handling():
mock_task_output = TaskOutput(
description="Mock description",
raw="A detailed analysis of the image",
agent="Image Analyst"
agent="Image Analyst",
)
with patch.object(Task, 'execute_sync') as mock_execute_sync:
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
@@ -3213,7 +3275,7 @@ def test_multimodal_agent_image_tool_handling():
# Get the tools that were passed to execute_sync
_, kwargs = mock_execute_sync.call_args
tools = kwargs['tools']
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"]
@@ -3223,7 +3285,7 @@ def test_multimodal_agent_image_tool_handling():
image_tool = image_tools[0]
result = image_tool._run(
image_url="https://example.com/test-image.jpg",
action="Please analyze this image"
action="Please analyze this image",
)
# Verify the tool returns the expected format
@@ -3233,6 +3295,7 @@ def test_multimodal_agent_image_tool_handling():
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():
"""
@@ -3246,7 +3309,7 @@ def test_multimodal_agent_live_image_analysis():
allow_delegation=False,
multimodal=True,
verbose=True,
llm="gpt-4o"
llm="gpt-4o",
)
# Create a task for image analysis
@@ -3257,19 +3320,18 @@ def test_multimodal_agent_live_image_analysis():
Image: {image_url}
""",
expected_output="A comprehensive description of the image contents.",
agent=image_analyst
agent=image_analyst,
)
# Create and run the crew
crew = Crew(
agents=[image_analyst],
tasks=[analyze_image]
)
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="
})
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)

View File

@@ -578,14 +578,6 @@ def test_multiple_docling_sources():
assert docling_source.content is not None
def test_docling_source_with_local_file():
current_dir = Path(__file__).parent
pdf_path = current_dir / "crewai_quickstart.pdf"
docling_source = CrewDoclingSource(file_paths=[pdf_path])
assert docling_source.file_paths == [pdf_path]
assert docling_source.content is not None
def test_file_path_validation():
"""Test file path validation for knowledge sources."""
current_dir = Path(__file__).parent
@@ -606,6 +598,6 @@ def test_file_path_validation():
# Test neither file_path nor file_paths provided
with pytest.raises(
ValueError,
match="file_path/file_paths must be a Path, str, or a list of these types"
match="file_path/file_paths must be a Path, str, or a list of these types",
):
PDFKnowledgeSource()

View File

@@ -719,7 +719,7 @@ def test_interpolate_inputs():
task = 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 interesting ideas about {topic}.",
output_file="/tmp/{topic}/output_{date}.txt"
output_file="/tmp/{topic}/output_{date}.txt",
)
task.interpolate_inputs(inputs={"topic": "AI", "date": "2024"})
@@ -742,41 +742,35 @@ def test_interpolate_inputs():
def test_interpolate_only():
"""Test the interpolate_only method for various scenarios including JSON structure preservation."""
task = Task(
description="Unused in this test",
expected_output="Unused in this test"
description="Unused in this test", expected_output="Unused in this test"
)
# Test JSON structure preservation
json_string = '{"info": "Look at {placeholder}", "nested": {"val": "{nestedVal}"}}'
result = task.interpolate_only(
input_string=json_string,
inputs={"placeholder": "the data", "nestedVal": "something else"}
inputs={"placeholder": "the data", "nestedVal": "something else"},
)
assert '"info": "Look at the data"' in result
assert '"val": "something else"' in result
assert "{placeholder}" not in result
assert "{nestedVal}" not in result
# Test normal string interpolation
normal_string = "Hello {name}, welcome to {place}!"
result = task.interpolate_only(
input_string=normal_string,
inputs={"name": "John", "place": "CrewAI"}
input_string=normal_string, inputs={"name": "John", "place": "CrewAI"}
)
assert result == "Hello John, welcome to CrewAI!"
# Test empty string
result = task.interpolate_only(
input_string="",
inputs={"unused": "value"}
)
result = task.interpolate_only(input_string="", inputs={"unused": "value"})
assert result == ""
# Test string with no placeholders
no_placeholders = "Hello, this is a test"
result = task.interpolate_only(
input_string=no_placeholders,
inputs={"unused": "value"}
input_string=no_placeholders, inputs={"unused": "value"}
)
assert result == no_placeholders
@@ -880,56 +874,91 @@ def test_key():
def test_output_file_validation():
"""Test output file path validation."""
# Valid paths
assert Task(
description="Test task",
expected_output="Test output",
output_file="output.txt"
).output_file == "output.txt"
assert Task(
description="Test task",
expected_output="Test output",
output_file="/tmp/output.txt"
).output_file == "tmp/output.txt"
assert Task(
description="Test task",
expected_output="Test output",
output_file="{dir}/output_{date}.txt"
).output_file == "{dir}/output_{date}.txt"
assert (
Task(
description="Test task",
expected_output="Test output",
output_file="output.txt",
).output_file
== "output.txt"
)
assert (
Task(
description="Test task",
expected_output="Test output",
output_file="/tmp/output.txt",
).output_file
== "tmp/output.txt"
)
assert (
Task(
description="Test task",
expected_output="Test output",
output_file="{dir}/output_{date}.txt",
).output_file
== "{dir}/output_{date}.txt"
)
# Invalid paths
with pytest.raises(ValueError, match="Path traversal"):
Task(
description="Test task",
expected_output="Test output",
output_file="../output.txt"
output_file="../output.txt",
)
with pytest.raises(ValueError, match="Path traversal"):
Task(
description="Test task",
expected_output="Test output",
output_file="folder/../output.txt"
output_file="folder/../output.txt",
)
with pytest.raises(ValueError, match="Shell special characters"):
Task(
description="Test task",
expected_output="Test output",
output_file="output.txt | rm -rf /"
output_file="output.txt | rm -rf /",
)
with pytest.raises(ValueError, match="Shell expansion"):
Task(
description="Test task",
expected_output="Test output",
output_file="~/output.txt"
output_file="~/output.txt",
)
with pytest.raises(ValueError, match="Shell expansion"):
Task(
description="Test task",
expected_output="Test output",
output_file="$HOME/output.txt"
output_file="$HOME/output.txt",
)
with pytest.raises(ValueError, match="Invalid template variable"):
Task(
description="Test task",
expected_output="Test output",
output_file="{invalid-name}/output.txt"
output_file="{invalid-name}/output.txt",
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_task_execution_times():
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,
)
task = 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 interesting ideas.",
agent=researcher,
)
assert task.start_time is None
assert task.end_time is None
assert task.execution_duration is None
task.execute_sync(agent=researcher)
assert task.start_time is not None
assert task.end_time is not None
assert task.execution_duration == (task.end_time - task.start_time).total_seconds()

View File

@@ -8,48 +8,49 @@ from crewai.tools.agent_tools.base_agent_tools import BaseAgentTool
class TestAgentTool(BaseAgentTool):
"""Concrete implementation of BaseAgentTool for testing."""
def _run(self, *args, **kwargs):
"""Implement required _run method."""
return "Test response"
@pytest.mark.parametrize("role_name,should_match", [
('Futel Official Infopoint', True), # exact match
(' "Futel Official Infopoint" ', True), # extra quotes and spaces
('Futel Official Infopoint\n', True), # trailing newline
('"Futel Official Infopoint"', True), # embedded quotes
(' FUTEL\nOFFICIAL INFOPOINT ', True), # multiple whitespace and newline
('futel official infopoint', True), # lowercase
('FUTEL OFFICIAL INFOPOINT', True), # uppercase
('Non Existent Agent', False), # non-existent agent
(None, False), # None agent name
])
@pytest.mark.parametrize(
"role_name,should_match",
[
("Futel Official Infopoint", True), # exact match
(' "Futel Official Infopoint" ', True), # extra quotes and spaces
("Futel Official Infopoint\n", True), # trailing newline
('"Futel Official Infopoint"', True), # embedded quotes
(" FUTEL\nOFFICIAL INFOPOINT ", True), # multiple whitespace and newline
("futel official infopoint", True), # lowercase
("FUTEL OFFICIAL INFOPOINT", True), # uppercase
("Non Existent Agent", False), # non-existent agent
(None, False), # None agent name
],
)
def test_agent_tool_role_matching(role_name, should_match):
"""Test that agent tools can match roles regardless of case, whitespace, and special characters."""
# Create test agent
test_agent = Agent(
role='Futel Official Infopoint',
goal='Answer questions about Futel',
backstory='Futel Football Club info',
allow_delegation=False
role="Futel Official Infopoint",
goal="Answer questions about Futel",
backstory="Futel Football Club info",
allow_delegation=False,
)
# Create test agent tool
agent_tool = TestAgentTool(
name="test_tool",
description="Test tool",
agents=[test_agent]
name="test_tool", description="Test tool", agents=[test_agent]
)
# Test role matching
result = agent_tool._execute(
agent_name=role_name,
task='Test task',
context=None
)
result = agent_tool._execute(agent_name=role_name, task="Test task", context=None)
if should_match:
assert "coworker mentioned not found" not in result.lower(), \
f"Should find agent with role name: {role_name}"
assert (
"coworker mentioned not found" not in result.lower()
), f"Should find agent with role name: {role_name}"
else:
assert "coworker mentioned not found" in result.lower(), \
f"Should not find agent with role name: {role_name}"
assert (
"coworker mentioned not found" in result.lower()
), f"Should not find agent with role name: {role_name}"

View File

@@ -15,10 +15,7 @@ def test_task_without_guardrail():
agent.execute_task.return_value = "test result"
agent.crew = None
task = Task(
description="Test task",
expected_output="Output"
)
task = Task(description="Test task", expected_output="Output")
result = task.execute_sync(agent=agent)
assert isinstance(result, TaskOutput)
@@ -27,6 +24,7 @@ def test_task_without_guardrail():
def test_task_with_successful_guardrail():
"""Test that successful guardrail validation passes transformed result."""
def guardrail(result: TaskOutput):
return (True, result.raw.upper())
@@ -35,11 +33,7 @@ def test_task_with_successful_guardrail():
agent.execute_task.return_value = "test result"
agent.crew = None
task = Task(
description="Test task",
expected_output="Output",
guardrail=guardrail
)
task = Task(description="Test task", expected_output="Output", guardrail=guardrail)
result = task.execute_sync(agent=agent)
assert isinstance(result, TaskOutput)
@@ -48,22 +42,20 @@ def test_task_with_successful_guardrail():
def test_task_with_failing_guardrail():
"""Test that failing guardrail triggers retry with error context."""
def guardrail(result: TaskOutput):
return (False, "Invalid format")
agent = Mock()
agent.role = "test_agent"
agent.execute_task.side_effect = [
"bad result",
"good result"
]
agent.execute_task.side_effect = ["bad result", "good result"]
agent.crew = None
task = Task(
description="Test task",
expected_output="Output",
guardrail=guardrail,
max_retries=1
max_retries=1,
)
# First execution fails guardrail, second succeeds
@@ -77,6 +69,7 @@ def test_task_with_failing_guardrail():
def test_task_with_guardrail_retries():
"""Test that guardrail respects max_retries configuration."""
def guardrail(result: TaskOutput):
return (False, "Invalid format")
@@ -89,7 +82,7 @@ def test_task_with_guardrail_retries():
description="Test task",
expected_output="Output",
guardrail=guardrail,
max_retries=2
max_retries=2,
)
with pytest.raises(Exception) as exc_info:
@@ -102,6 +95,7 @@ def test_task_with_guardrail_retries():
def test_guardrail_error_in_context():
"""Test that guardrail error is passed in context for retry."""
def guardrail(result: TaskOutput):
return (False, "Expected JSON, got string")
@@ -113,11 +107,12 @@ def test_guardrail_error_in_context():
description="Test task",
expected_output="Output",
guardrail=guardrail,
max_retries=1
max_retries=1,
)
# Mock execute_task to succeed on second attempt
first_call = True
def execute_task(task, context, tools):
nonlocal first_call
if first_call:

View File

@@ -0,0 +1,84 @@
"""
Tests for verifying the integration of knowledge sources in the planning process.
This module ensures that agent knowledge is properly included during task planning.
"""
from unittest.mock import patch
import pytest
from crewai.agent import Agent
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
from crewai.task import Task
from crewai.utilities.planning_handler import CrewPlanner
@pytest.fixture
def mock_knowledge_source():
"""
Create a mock knowledge source with test content.
Returns:
StringKnowledgeSource:
A knowledge source containing AI-related test content
"""
content = """
Important context about AI:
1. AI systems use machine learning algorithms
2. Neural networks are a key component
3. Training data is essential for good performance
"""
return StringKnowledgeSource(content=content)
@patch('crewai.knowledge.storage.knowledge_storage.chromadb')
def test_knowledge_included_in_planning(mock_chroma):
"""Test that verifies knowledge sources are properly included in planning."""
# Mock ChromaDB collection
mock_collection = mock_chroma.return_value.get_or_create_collection.return_value
mock_collection.add.return_value = None
# Create an agent with knowledge
agent = Agent(
role="AI Researcher",
goal="Research and explain AI concepts",
backstory="Expert in artificial intelligence",
knowledge_sources=[
StringKnowledgeSource(
content="AI systems require careful training and validation."
)
]
)
# Create a task for the agent
task = Task(
description="Explain the basics of AI systems",
expected_output="A clear explanation of AI fundamentals",
agent=agent
)
# Create a crew planner
planner = CrewPlanner([task], None)
# Get the task summary
task_summary = planner._create_tasks_summary()
# Verify that knowledge is included in planning when present
assert "AI systems require careful training" in task_summary, \
"Knowledge content should be present in task summary when knowledge exists"
assert '"agent_knowledge"' in task_summary, \
"agent_knowledge field should be present in task summary when knowledge exists"
# Verify that knowledge is properly formatted
assert isinstance(task.agent.knowledge_sources, list), \
"Knowledge sources should be stored in a list"
assert len(task.agent.knowledge_sources) > 0, \
"At least one knowledge source should be present"
assert task.agent.knowledge_sources[0].content in task_summary, \
"Knowledge source content should be included in task summary"
# Verify that other expected components are still present
assert task.description in task_summary, \
"Task description should be present in task summary"
assert task.expected_output in task_summary, \
"Expected output should be present in task summary"
assert agent.role in task_summary, \
"Agent role should be present in task summary"

View File

@@ -1,10 +1,14 @@
from unittest.mock import patch
from typing import Optional
from unittest.mock import MagicMock, patch
import pytest
from pydantic import BaseModel
from crewai.agent import Agent
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
from crewai.task import Task
from crewai.tasks.task_output import TaskOutput
from crewai.tools.base_tool import BaseTool
from crewai.utilities.planning_handler import (
CrewPlanner,
PlannerTaskPydanticOutput,
@@ -92,7 +96,72 @@ class TestCrewPlanner:
tasks_summary = crew_planner._create_tasks_summary()
assert isinstance(tasks_summary, str)
assert tasks_summary.startswith("\n Task Number 1 - Task 1")
assert tasks_summary.endswith('"agent_tools": []\n ')
assert '"agent_tools": "agent has no tools"' in tasks_summary
# Knowledge field should not be present when empty
assert '"agent_knowledge"' not in tasks_summary
@patch('crewai.knowledge.storage.knowledge_storage.chromadb')
def test_create_tasks_summary_with_knowledge_and_tools(self, mock_chroma):
"""Test task summary generation with both knowledge and tools present."""
# Mock ChromaDB collection
mock_collection = mock_chroma.return_value.get_or_create_collection.return_value
mock_collection.add.return_value = None
# Create mock tools with proper string descriptions and structured tool support
class MockTool(BaseTool):
name: str
description: str
def __init__(self, name: str, description: str):
tool_data = {"name": name, "description": description}
super().__init__(**tool_data)
def __str__(self):
return self.name
def __repr__(self):
return self.name
def to_structured_tool(self):
return self
def _run(self, *args, **kwargs):
pass
def _generate_description(self) -> str:
"""Override _generate_description to avoid args_schema handling."""
return self.description
tool1 = MockTool("tool1", "Tool 1 description")
tool2 = MockTool("tool2", "Tool 2 description")
# Create a task with knowledge and tools
task = Task(
description="Task with knowledge and tools",
expected_output="Expected output",
agent=Agent(
role="Test Agent",
goal="Test Goal",
backstory="Test Backstory",
tools=[tool1, tool2],
knowledge_sources=[
StringKnowledgeSource(content="Test knowledge content")
]
)
)
# Create planner with the new task
planner = CrewPlanner([task], None)
tasks_summary = planner._create_tasks_summary()
# Verify task summary content
assert isinstance(tasks_summary, str)
assert task.description in tasks_summary
assert task.expected_output in tasks_summary
assert '"agent_tools": [tool1, tool2]' in tasks_summary
assert '"agent_knowledge": "[\\"Test knowledge content\\"]"' in tasks_summary
assert task.agent.role in tasks_summary
assert task.agent.goal in tasks_summary
def test_handle_crew_planning_different_llm(self, crew_planner_different_llm):
with patch.object(Task, "execute_sync") as execute:

531
uv.lock generated
View File

@@ -1,10 +1,42 @@
version = 1
requires-python = ">=3.10, <3.13"
resolution-markers = [
"python_full_version < '3.11'",
"python_full_version == '3.11.*'",
"python_full_version >= '3.12' and python_full_version < '3.12.4'",
"python_full_version >= '3.12.4'",
"python_full_version < '3.11' and platform_system == 'Darwin' and sys_platform == 'darwin'",
"python_full_version < '3.11' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'darwin'",
"(python_full_version < '3.11' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'darwin')",
"python_full_version < '3.11' and platform_machine == 'aarch64' and platform_system == 'Darwin' and sys_platform == 'linux'",
"python_full_version < '3.11' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux'",
"python_full_version < '3.11' and platform_machine == 'aarch64' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'linux'",
"(python_full_version < '3.11' and platform_machine != 'aarch64' and platform_system == 'Darwin' and sys_platform != 'darwin') or (python_full_version < '3.11' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version < '3.11' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux'",
"(python_full_version < '3.11' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform != 'darwin') or (python_full_version < '3.11' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version == '3.11.*' and platform_system == 'Darwin' and sys_platform == 'darwin'",
"python_full_version == '3.11.*' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'darwin'",
"(python_full_version == '3.11.*' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform == 'darwin') or (python_full_version == '3.11.*' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'darwin')",
"python_full_version == '3.11.*' and platform_machine == 'aarch64' and platform_system == 'Darwin' and sys_platform == 'linux'",
"python_full_version == '3.11.*' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux'",
"python_full_version == '3.11.*' and platform_machine == 'aarch64' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'linux'",
"(python_full_version == '3.11.*' and platform_machine != 'aarch64' and platform_system == 'Darwin' and sys_platform != 'darwin') or (python_full_version == '3.11.*' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version == '3.11.*' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux'",
"(python_full_version == '3.11.*' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform != 'darwin') or (python_full_version == '3.11.*' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_system == 'Darwin' and sys_platform == 'darwin'",
"python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'darwin'",
"(python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform == 'darwin') or (python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'darwin')",
"python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Darwin' and sys_platform == 'linux'",
"python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux'",
"python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'linux'",
"(python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and platform_system == 'Darwin' and sys_platform != 'darwin') or (python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux'",
"(python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform != 'darwin') or (python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version >= '3.12.4' and platform_system == 'Darwin' and sys_platform == 'darwin'",
"python_full_version >= '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'darwin'",
"(python_full_version >= '3.12.4' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform == 'darwin') or (python_full_version >= '3.12.4' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'darwin')",
"python_full_version >= '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Darwin' and sys_platform == 'linux'",
"python_full_version >= '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux'",
"python_full_version >= '3.12.4' and platform_machine == 'aarch64' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'linux'",
"(python_full_version >= '3.12.4' and platform_machine != 'aarch64' and platform_system == 'Darwin' and sys_platform != 'darwin') or (python_full_version >= '3.12.4' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version >= '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux'",
"(python_full_version >= '3.12.4' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform != 'darwin') or (python_full_version >= '3.12.4' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
[[package]]
@@ -34,7 +66,7 @@ wheels = [
[[package]]
name = "aiohttp"
version = "3.10.10"
version = "3.11.11"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohappyeyeballs" },
@@ -43,55 +75,56 @@ dependencies = [
{ name = "attrs" },
{ name = "frozenlist" },
{ name = "multidict" },
{ name = "propcache" },
{ name = "yarl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/17/7e/16e57e6cf20eb62481a2f9ce8674328407187950ccc602ad07c685279141/aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a", size = 7542993 }
sdist = { url = "https://files.pythonhosted.org/packages/fe/ed/f26db39d29cd3cb2f5a3374304c713fe5ab5a0e4c8ee25a0c45cc6adf844/aiohttp-3.11.11.tar.gz", hash = "sha256:bb49c7f1e6ebf3821a42d81d494f538107610c3a705987f53068546b0e90303e", size = 7669618 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3d/dd/3d40c0e67e79c5c42671e3e268742f1ff96c6573ca43823563d01abd9475/aiohttp-3.10.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be7443669ae9c016b71f402e43208e13ddf00912f47f623ee5994e12fc7d4b3f", size = 586969 },
{ url = "https://files.pythonhosted.org/packages/75/64/8de41b5555e5b43ef6d4ed1261891d33fe45ecc6cb62875bfafb90b9ab93/aiohttp-3.10.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b06b7843929e41a94ea09eb1ce3927865387e3e23ebe108e0d0d09b08d25be9", size = 399367 },
{ url = "https://files.pythonhosted.org/packages/96/36/27bd62ea7ce43906d1443a73691823fc82ffb8fa03276b0e2f7e1037c286/aiohttp-3.10.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:333cf6cf8e65f6a1e06e9eb3e643a0c515bb850d470902274239fea02033e9a8", size = 390720 },
{ url = "https://files.pythonhosted.org/packages/e8/4d/d516b050d811ce0dd26325c383013c104ffa8b58bd361b82e52833f68e78/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:274cfa632350225ce3fdeb318c23b4a10ec25c0e2c880eff951a3842cf358ac1", size = 1228820 },
{ url = "https://files.pythonhosted.org/packages/53/94/964d9327a3e336d89aad52260836e4ec87fdfa1207176550fdf384eaffe7/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9e5e4a85bdb56d224f412d9c98ae4cbd032cc4f3161818f692cd81766eee65a", size = 1264616 },
{ url = "https://files.pythonhosted.org/packages/0c/20/70ce17764b685ca8f5bf4d568881b4e1f1f4ea5e8170f512fdb1a33859d2/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b606353da03edcc71130b52388d25f9a30a126e04caef1fd637e31683033abd", size = 1298402 },
{ url = "https://files.pythonhosted.org/packages/d1/d1/5248225ccc687f498d06c3bca5af2647a361c3687a85eb3aedcc247ee1aa/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026", size = 1222205 },
{ url = "https://files.pythonhosted.org/packages/f2/a3/9296b27cc5d4feadf970a14d0694902a49a985f3fae71b8322a5f77b0baa/aiohttp-3.10.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:578a4b875af3e0daaf1ac6fa983d93e0bbfec3ead753b6d6f33d467100cdc67b", size = 1193804 },
{ url = "https://files.pythonhosted.org/packages/d9/07/f3760160feb12ac51a6168a6da251a4a8f2a70733d49e6ceb9b3e6ee2f03/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8105fd8a890df77b76dd3054cddf01a879fc13e8af576805d667e0fa0224c35d", size = 1193544 },
{ url = "https://files.pythonhosted.org/packages/7e/4c/93a70f9a4ba1c30183a6dd68bfa79cddbf9a674f162f9c62e823a74a5515/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3bcd391d083f636c06a68715e69467963d1f9600f85ef556ea82e9ef25f043f7", size = 1193047 },
{ url = "https://files.pythonhosted.org/packages/ff/a3/36a1e23ff00c7a0cd696c5a28db05db25dc42bfc78c508bd78623ff62a4a/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fbc6264158392bad9df19537e872d476f7c57adf718944cc1e4495cbabf38e2a", size = 1247201 },
{ url = "https://files.pythonhosted.org/packages/55/ae/95399848557b98bb2c402d640b2276ce3a542b94dba202de5a5a1fe29abe/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e48d5021a84d341bcaf95c8460b152cfbad770d28e5fe14a768988c461b821bc", size = 1264102 },
{ url = "https://files.pythonhosted.org/packages/38/f5/02e5c72c1b60d7cceb30b982679a26167e84ac029fd35a93dd4da52c50a3/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2609e9ab08474702cc67b7702dbb8a80e392c54613ebe80db7e8dbdb79837c68", size = 1215760 },
{ url = "https://files.pythonhosted.org/packages/30/17/1463840bad10d02d0439068f37ce5af0b383884b0d5838f46fb027e233bf/aiohttp-3.10.10-cp310-cp310-win32.whl", hash = "sha256:84afcdea18eda514c25bc68b9af2a2b1adea7c08899175a51fe7c4fb6d551257", size = 362678 },
{ url = "https://files.pythonhosted.org/packages/dd/01/a0ef707d93e867a43abbffee3a2cdf30559910750b9176b891628c7ad074/aiohttp-3.10.10-cp310-cp310-win_amd64.whl", hash = "sha256:9c72109213eb9d3874f7ac8c0c5fa90e072d678e117d9061c06e30c85b4cf0e6", size = 381097 },
{ url = "https://files.pythonhosted.org/packages/72/31/3c351d17596194e5a38ef169a4da76458952b2497b4b54645b9d483cbbb0/aiohttp-3.10.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f", size = 586501 },
{ url = "https://files.pythonhosted.org/packages/a4/a8/a559d09eb08478cdead6b7ce05b0c4a133ba27fcdfa91e05d2e62867300d/aiohttp-3.10.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb", size = 398993 },
{ url = "https://files.pythonhosted.org/packages/c5/47/7736d4174613feef61d25332c3bd1a4f8ff5591fbd7331988238a7299485/aiohttp-3.10.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871", size = 390647 },
{ url = "https://files.pythonhosted.org/packages/27/21/e9ba192a04b7160f5a8952c98a1de7cf8072ad150fa3abd454ead1ab1d7f/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c", size = 1306481 },
{ url = "https://files.pythonhosted.org/packages/cf/50/f364c01c8d0def1dc34747b2470969e216f5a37c7ece00fe558810f37013/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38", size = 1344652 },
{ url = "https://files.pythonhosted.org/packages/1d/c2/74f608e984e9b585649e2e83883facad6fa3fc1d021de87b20cc67e8e5ae/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb", size = 1378498 },
{ url = "https://files.pythonhosted.org/packages/9f/a7/05a48c7c0a7a80a5591b1203bf1b64ca2ed6a2050af918d09c05852dc42b/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7", size = 1292718 },
{ url = "https://files.pythonhosted.org/packages/7d/78/a925655018747e9790350180330032e27d6e0d7ed30bde545fae42f8c49c/aiohttp-3.10.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911", size = 1251776 },
{ url = "https://files.pythonhosted.org/packages/47/9d/85c6b69f702351d1236594745a4fdc042fc43f494c247a98dac17e004026/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092", size = 1271716 },
{ url = "https://files.pythonhosted.org/packages/7f/a7/55fc805ff9b14af818903882ece08e2235b12b73b867b521b92994c52b14/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142", size = 1266263 },
{ url = "https://files.pythonhosted.org/packages/1f/ec/d2be2ca7b063e4f91519d550dbc9c1cb43040174a322470deed90b3d3333/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9", size = 1321617 },
{ url = "https://files.pythonhosted.org/packages/c9/a3/b29f7920e1cd0a9a68a45dd3eb16140074d2efb1518d2e1f3e140357dc37/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1", size = 1339227 },
{ url = "https://files.pythonhosted.org/packages/8a/81/34b67235c47e232d807b4bbc42ba9b927c7ce9476872372fddcfd1e41b3d/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a", size = 1299068 },
{ url = "https://files.pythonhosted.org/packages/04/1f/26a7fe11b6ad3184f214733428353c89ae9fe3e4f605a657f5245c5e720c/aiohttp-3.10.10-cp311-cp311-win32.whl", hash = "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94", size = 362223 },
{ url = "https://files.pythonhosted.org/packages/10/91/85dcd93f64011434359ce2666bece981f08d31bc49df33261e625b28595d/aiohttp-3.10.10-cp311-cp311-win_amd64.whl", hash = "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959", size = 381576 },
{ url = "https://files.pythonhosted.org/packages/ae/99/4c5aefe5ad06a1baf206aed6598c7cdcbc7c044c46801cd0d1ecb758cae3/aiohttp-3.10.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c", size = 583536 },
{ url = "https://files.pythonhosted.org/packages/a9/36/8b3bc49b49cb6d2da40ee61ff15dbcc44fd345a3e6ab5bb20844df929821/aiohttp-3.10.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28", size = 395693 },
{ url = "https://files.pythonhosted.org/packages/e1/77/0aa8660dcf11fa65d61712dbb458c4989de220a844bd69778dff25f2d50b/aiohttp-3.10.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f", size = 390898 },
{ url = "https://files.pythonhosted.org/packages/38/d2/b833d95deb48c75db85bf6646de0a697e7fb5d87bd27cbade4f9746b48b1/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138", size = 1312060 },
{ url = "https://files.pythonhosted.org/packages/aa/5f/29fd5113165a0893de8efedf9b4737e0ba92dfcd791415a528f947d10299/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742", size = 1350553 },
{ url = "https://files.pythonhosted.org/packages/ad/cc/f835f74b7d344428469200105236d44606cfa448be1e7c95ca52880d9bac/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7", size = 1392646 },
{ url = "https://files.pythonhosted.org/packages/bf/fe/1332409d845ca601893bbf2d76935e0b93d41686e5f333841c7d7a4a770d/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16", size = 1306310 },
{ url = "https://files.pythonhosted.org/packages/e4/a1/25a7633a5a513278a9892e333501e2e69c83e50be4b57a62285fb7a008c3/aiohttp-3.10.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8", size = 1260255 },
{ url = "https://files.pythonhosted.org/packages/f2/39/30eafe89e0e2a06c25e4762844c8214c0c0cd0fd9ffc3471694a7986f421/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6", size = 1271141 },
{ url = "https://files.pythonhosted.org/packages/5b/fc/33125df728b48391ef1fcb512dfb02072158cc10d041414fb79803463020/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a", size = 1280244 },
{ url = "https://files.pythonhosted.org/packages/3b/61/e42bf2c2934b5caa4e2ec0b5e5fd86989adb022b5ee60c2572a9d77cf6fe/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9", size = 1316805 },
{ url = "https://files.pythonhosted.org/packages/18/32/f52a5e2ae9ad3bba10e026a63a7a23abfa37c7d97aeeb9004eaa98df3ce3/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a", size = 1343930 },
{ url = "https://files.pythonhosted.org/packages/05/be/6a403b464dcab3631fe8e27b0f1d906d9e45c5e92aca97ee007e5a895560/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205", size = 1306186 },
{ url = "https://files.pythonhosted.org/packages/8e/fd/bb50fe781068a736a02bf5c7ad5f3ab53e39f1d1e63110da6d30f7605edc/aiohttp-3.10.10-cp312-cp312-win32.whl", hash = "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628", size = 359289 },
{ url = "https://files.pythonhosted.org/packages/70/9e/5add7e240f77ef67c275c82cc1d08afbca57b77593118c1f6e920ae8ad3f/aiohttp-3.10.10-cp312-cp312-win_amd64.whl", hash = "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf", size = 379313 },
{ url = "https://files.pythonhosted.org/packages/75/7d/ff2e314b8f9e0b1df833e2d4778eaf23eae6b8cc8f922495d110ddcbf9e1/aiohttp-3.11.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a60804bff28662cbcf340a4d61598891f12eea3a66af48ecfdc975ceec21e3c8", size = 708550 },
{ url = "https://files.pythonhosted.org/packages/09/b8/aeb4975d5bba233d6f246941f5957a5ad4e3def8b0855a72742e391925f2/aiohttp-3.11.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b4fa1cb5f270fb3eab079536b764ad740bb749ce69a94d4ec30ceee1b5940d5", size = 468430 },
{ url = "https://files.pythonhosted.org/packages/9c/5b/5b620279b3df46e597008b09fa1e10027a39467387c2332657288e25811a/aiohttp-3.11.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:731468f555656767cda219ab42e033355fe48c85fbe3ba83a349631541715ba2", size = 455593 },
{ url = "https://files.pythonhosted.org/packages/d8/75/0cdf014b816867d86c0bc26f3d3e3f194198dbf33037890beed629cd4f8f/aiohttp-3.11.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb23d8bb86282b342481cad4370ea0853a39e4a32a0042bb52ca6bdde132df43", size = 1584635 },
{ url = "https://files.pythonhosted.org/packages/df/2f/95b8f4e4dfeb57c1d9ad9fa911ede35a0249d75aa339edd2c2270dc539da/aiohttp-3.11.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f047569d655f81cb70ea5be942ee5d4421b6219c3f05d131f64088c73bb0917f", size = 1632363 },
{ url = "https://files.pythonhosted.org/packages/39/cb/70cf69ea7c50f5b0021a84f4c59c3622b2b3b81695f48a2f0e42ef7eba6e/aiohttp-3.11.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd7659baae9ccf94ae5fe8bfaa2c7bc2e94d24611528395ce88d009107e00c6d", size = 1668315 },
{ url = "https://files.pythonhosted.org/packages/2f/cc/3a3fc7a290eabc59839a7e15289cd48f33dd9337d06e301064e1e7fb26c5/aiohttp-3.11.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af01e42ad87ae24932138f154105e88da13ce7d202a6de93fafdafb2883a00ef", size = 1589546 },
{ url = "https://files.pythonhosted.org/packages/15/b4/0f7b0ed41ac6000e283e7332f0f608d734b675a8509763ca78e93714cfb0/aiohttp-3.11.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5854be2f3e5a729800bac57a8d76af464e160f19676ab6aea74bde18ad19d438", size = 1544581 },
{ url = "https://files.pythonhosted.org/packages/58/b9/4d06470fd85c687b6b0e31935ef73dde6e31767c9576d617309a2206556f/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6526e5fb4e14f4bbf30411216780c9967c20c5a55f2f51d3abd6de68320cc2f3", size = 1529256 },
{ url = "https://files.pythonhosted.org/packages/61/a2/6958b1b880fc017fd35f5dfb2c26a9a50c755b75fd9ae001dc2236a4fb79/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:85992ee30a31835fc482468637b3e5bd085fa8fe9392ba0bdcbdc1ef5e9e3c55", size = 1536592 },
{ url = "https://files.pythonhosted.org/packages/0f/dd/b974012a9551fd654f5bb95a6dd3f03d6e6472a17e1a8216dd42e9638d6c/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:88a12ad8ccf325a8a5ed80e6d7c3bdc247d66175afedbe104ee2aaca72960d8e", size = 1607446 },
{ url = "https://files.pythonhosted.org/packages/e0/d3/6c98fd87e638e51f074a3f2061e81fcb92123bcaf1439ac1b4a896446e40/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0a6d3fbf2232e3a08c41eca81ae4f1dff3d8f1a30bae415ebe0af2d2458b8a33", size = 1628809 },
{ url = "https://files.pythonhosted.org/packages/a8/2e/86e6f85cbca02be042c268c3d93e7f35977a0e127de56e319bdd1569eaa8/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84a585799c58b795573c7fa9b84c455adf3e1d72f19a2bf498b54a95ae0d194c", size = 1564291 },
{ url = "https://files.pythonhosted.org/packages/0b/8d/1f4ef3503b767717f65e1f5178b0173ab03cba1a19997ebf7b052161189f/aiohttp-3.11.11-cp310-cp310-win32.whl", hash = "sha256:bfde76a8f430cf5c5584553adf9926534352251d379dcb266ad2b93c54a29745", size = 416601 },
{ url = "https://files.pythonhosted.org/packages/ad/86/81cb83691b5ace3d9aa148dc42bacc3450d749fc88c5ec1973573c1c1779/aiohttp-3.11.11-cp310-cp310-win_amd64.whl", hash = "sha256:0fd82b8e9c383af11d2b26f27a478640b6b83d669440c0a71481f7c865a51da9", size = 442007 },
{ url = "https://files.pythonhosted.org/packages/34/ae/e8806a9f054e15f1d18b04db75c23ec38ec954a10c0a68d3bd275d7e8be3/aiohttp-3.11.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ba74ec819177af1ef7f59063c6d35a214a8fde6f987f7661f4f0eecc468a8f76", size = 708624 },
{ url = "https://files.pythonhosted.org/packages/c7/e0/313ef1a333fb4d58d0c55a6acb3cd772f5d7756604b455181049e222c020/aiohttp-3.11.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4af57160800b7a815f3fe0eba9b46bf28aafc195555f1824555fa2cfab6c1538", size = 468507 },
{ url = "https://files.pythonhosted.org/packages/a9/60/03455476bf1f467e5b4a32a465c450548b2ce724eec39d69f737191f936a/aiohttp-3.11.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffa336210cf9cd8ed117011085817d00abe4c08f99968deef0013ea283547204", size = 455571 },
{ url = "https://files.pythonhosted.org/packages/be/f9/469588603bd75bf02c8ffb8c8a0d4b217eed446b49d4a767684685aa33fd/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b8fe282183e4a3c7a1b72f5ade1094ed1c6345a8f153506d114af5bf8accd9", size = 1685694 },
{ url = "https://files.pythonhosted.org/packages/88/b9/1b7fa43faf6c8616fa94c568dc1309ffee2b6b68b04ac268e5d64b738688/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af41686ccec6a0f2bdc66686dc0f403c41ac2089f80e2214a0f82d001052c03", size = 1743660 },
{ url = "https://files.pythonhosted.org/packages/2a/8b/0248d19dbb16b67222e75f6aecedd014656225733157e5afaf6a6a07e2e8/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70d1f9dde0e5dd9e292a6d4d00058737052b01f3532f69c0c65818dac26dc287", size = 1785421 },
{ url = "https://files.pythonhosted.org/packages/c4/11/f478e071815a46ca0a5ae974651ff0c7a35898c55063305a896e58aa1247/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:249cc6912405917344192b9f9ea5cd5b139d49e0d2f5c7f70bdfaf6b4dbf3a2e", size = 1675145 },
{ url = "https://files.pythonhosted.org/packages/26/5d/284d182fecbb5075ae10153ff7374f57314c93a8681666600e3a9e09c505/aiohttp-3.11.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0eb98d90b6690827dcc84c246811feeb4e1eea683c0eac6caed7549be9c84665", size = 1619804 },
{ url = "https://files.pythonhosted.org/packages/1b/78/980064c2ad685c64ce0e8aeeb7ef1e53f43c5b005edcd7d32e60809c4992/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec82bf1fda6cecce7f7b915f9196601a1bd1a3079796b76d16ae4cce6d0ef89b", size = 1654007 },
{ url = "https://files.pythonhosted.org/packages/21/8d/9e658d63b1438ad42b96f94da227f2e2c1d5c6001c9e8ffcc0bfb22e9105/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9fd46ce0845cfe28f108888b3ab17abff84ff695e01e73657eec3f96d72eef34", size = 1650022 },
{ url = "https://files.pythonhosted.org/packages/85/fd/a032bf7f2755c2df4f87f9effa34ccc1ef5cea465377dbaeef93bb56bbd6/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd176afcf8f5d2aed50c3647d4925d0db0579d96f75a31e77cbaf67d8a87742d", size = 1732899 },
{ url = "https://files.pythonhosted.org/packages/c5/0c/c2b85fde167dd440c7ba50af2aac20b5a5666392b174df54c00f888c5a75/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ec2aa89305006fba9ffb98970db6c8221541be7bee4c1d027421d6f6df7d1ce2", size = 1755142 },
{ url = "https://files.pythonhosted.org/packages/bc/78/91ae1a3b3b3bed8b893c5d69c07023e151b1c95d79544ad04cf68f596c2f/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:92cde43018a2e17d48bb09c79e4d4cb0e236de5063ce897a5e40ac7cb4878773", size = 1692736 },
{ url = "https://files.pythonhosted.org/packages/77/89/a7ef9c4b4cdb546fcc650ca7f7395aaffbd267f0e1f648a436bec33c9b95/aiohttp-3.11.11-cp311-cp311-win32.whl", hash = "sha256:aba807f9569455cba566882c8938f1a549f205ee43c27b126e5450dc9f83cc62", size = 416418 },
{ url = "https://files.pythonhosted.org/packages/fc/db/2192489a8a51b52e06627506f8ac8df69ee221de88ab9bdea77aa793aa6a/aiohttp-3.11.11-cp311-cp311-win_amd64.whl", hash = "sha256:ae545f31489548c87b0cced5755cfe5a5308d00407000e72c4fa30b19c3220ac", size = 442509 },
{ url = "https://files.pythonhosted.org/packages/69/cf/4bda538c502f9738d6b95ada11603c05ec260807246e15e869fc3ec5de97/aiohttp-3.11.11-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e595c591a48bbc295ebf47cb91aebf9bd32f3ff76749ecf282ea7f9f6bb73886", size = 704666 },
{ url = "https://files.pythonhosted.org/packages/46/7b/87fcef2cad2fad420ca77bef981e815df6904047d0a1bd6aeded1b0d1d66/aiohttp-3.11.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ea1b59dc06396b0b424740a10a0a63974c725b1c64736ff788a3689d36c02d2", size = 464057 },
{ url = "https://files.pythonhosted.org/packages/5a/a6/789e1f17a1b6f4a38939fbc39d29e1d960d5f89f73d0629a939410171bc0/aiohttp-3.11.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8811f3f098a78ffa16e0ea36dffd577eb031aea797cbdba81be039a4169e242c", size = 455996 },
{ url = "https://files.pythonhosted.org/packages/b7/dd/485061fbfef33165ce7320db36e530cd7116ee1098e9c3774d15a732b3fd/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7227b87a355ce1f4bf83bfae4399b1f5bb42e0259cb9405824bd03d2f4336a", size = 1682367 },
{ url = "https://files.pythonhosted.org/packages/e9/d7/9ec5b3ea9ae215c311d88b2093e8da17e67b8856673e4166c994e117ee3e/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d40f9da8cabbf295d3a9dae1295c69975b86d941bc20f0a087f0477fa0a66231", size = 1736989 },
{ url = "https://files.pythonhosted.org/packages/d6/fb/ea94927f7bfe1d86178c9d3e0a8c54f651a0a655214cce930b3c679b8f64/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffb3dc385f6bb1568aa974fe65da84723210e5d9707e360e9ecb51f59406cd2e", size = 1793265 },
{ url = "https://files.pythonhosted.org/packages/40/7f/6de218084f9b653026bd7063cd8045123a7ba90c25176465f266976d8c82/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8f5f7515f3552d899c61202d99dcb17d6e3b0de777900405611cd747cecd1b8", size = 1691841 },
{ url = "https://files.pythonhosted.org/packages/77/e2/992f43d87831cbddb6b09c57ab55499332f60ad6fdbf438ff4419c2925fc/aiohttp-3.11.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3499c7ffbfd9c6a3d8d6a2b01c26639da7e43d47c7b4f788016226b1e711caa8", size = 1619317 },
{ url = "https://files.pythonhosted.org/packages/96/74/879b23cdd816db4133325a201287c95bef4ce669acde37f8f1b8669e1755/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8e2bf8029dbf0810c7bfbc3e594b51c4cc9101fbffb583a3923aea184724203c", size = 1641416 },
{ url = "https://files.pythonhosted.org/packages/30/98/b123f6b15d87c54e58fd7ae3558ff594f898d7f30a90899718f3215ad328/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6212a60e5c482ef90f2d788835387070a88d52cf6241d3916733c9176d39eab", size = 1646514 },
{ url = "https://files.pythonhosted.org/packages/d7/38/257fda3dc99d6978ab943141d5165ec74fd4b4164baa15e9c66fa21da86b/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d119fafe7b634dbfa25a8c597718e69a930e4847f0b88e172744be24515140da", size = 1702095 },
{ url = "https://files.pythonhosted.org/packages/0c/f4/ddab089053f9fb96654df5505c0a69bde093214b3c3454f6bfdb1845f558/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:6fba278063559acc730abf49845d0e9a9e1ba74f85f0ee6efd5803f08b285853", size = 1734611 },
{ url = "https://files.pythonhosted.org/packages/c3/d6/f30b2bc520c38c8aa4657ed953186e535ae84abe55c08d0f70acd72ff577/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92fc484e34b733704ad77210c7957679c5c3877bd1e6b6d74b185e9320cc716e", size = 1694576 },
{ url = "https://files.pythonhosted.org/packages/bc/97/b0a88c3f4c6d0020b34045ee6d954058abc870814f6e310c4c9b74254116/aiohttp-3.11.11-cp312-cp312-win32.whl", hash = "sha256:9f5b3c1ed63c8fa937a920b6c1bec78b74ee09593b3f5b979ab2ae5ef60d7600", size = 411363 },
{ url = "https://files.pythonhosted.org/packages/7f/23/cc36d9c398980acaeeb443100f0216f50a7cfe20c67a9fd0a2f1a5a846de/aiohttp-3.11.11-cp312-cp312-win_amd64.whl", hash = "sha256:1e69966ea6ef0c14ee53ef7a3d68b564cc408121ea56c0caa2dc918c1b2f553d", size = 437666 },
]
[[package]]
@@ -211,6 +244,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/0e/38cb7b781371e79e9c697fb78f3ccd18fda8bd547d0a2e76e616561a3792/auth0_python-4.7.2-py3-none-any.whl", hash = "sha256:df2224f9b1e170b3aa12d8bc7ff02eadb7cc229307a09ec6b8a55fd1e0e05dc8", size = 131834 },
]
[[package]]
name = "authlib"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography" },
]
sdist = { url = "https://files.pythonhosted.org/packages/09/47/df70ecd34fbf86d69833fe4e25bb9ecbaab995c8e49df726dd416f6bb822/authlib-1.3.1.tar.gz", hash = "sha256:7ae843f03c06c5c0debd63c9db91f9fda64fa62a42a77419fa15fbb7e7a58917", size = 146074 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/1f/bc95e43ffb57c05b8efcc376dd55a0240bf58f47ddf5a0f92452b6457b75/Authlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:d35800b973099bbadc49b42b256ecb80041ad56b7fe1216a362c7943c088f377", size = 223827 },
]
[[package]]
name = "autoflake"
version = "2.3.1"
@@ -620,6 +665,9 @@ agentops = [
docling = [
{ name = "docling" },
]
embeddings = [
{ name = "tiktoken" },
]
fastembed = [
{ name = "fastembed" },
]
@@ -642,7 +690,6 @@ tools = [
[package.dev-dependencies]
dev = [
{ name = "cairosvg" },
{ name = "crewai-tools" },
{ name = "mkdocs" },
{ name = "mkdocs-material" },
{ name = "mkdocs-material-extensions" },
@@ -667,7 +714,7 @@ requires-dist = [
{ name = "blinker", specifier = ">=1.9.0" },
{ name = "chromadb", specifier = ">=0.5.23" },
{ name = "click", specifier = ">=8.1.7" },
{ name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.17.0" },
{ name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.25.5" },
{ name = "docling", marker = "extra == 'docling'", specifier = ">=2.12.0" },
{ name = "fastembed", marker = "extra == 'fastembed'", specifier = ">=0.4.1" },
{ name = "instructor", specifier = ">=1.3.3" },
@@ -688,6 +735,7 @@ requires-dist = [
{ name = "python-dotenv", specifier = ">=1.0.0" },
{ name = "pyvis", specifier = ">=0.3.2" },
{ name = "regex", specifier = ">=2024.9.11" },
{ name = "tiktoken", marker = "extra == 'embeddings'", specifier = "~=0.7.0" },
{ name = "tomli", specifier = ">=2.0.2" },
{ name = "tomli-w", specifier = ">=1.1.0" },
{ name = "uv", specifier = ">=0.4.25" },
@@ -696,7 +744,6 @@ requires-dist = [
[package.metadata.requires-dev]
dev = [
{ name = "cairosvg", specifier = ">=2.7.1" },
{ name = "crewai-tools", specifier = ">=0.17.0" },
{ name = "mkdocs", specifier = ">=1.4.3" },
{ name = "mkdocs-material", specifier = ">=9.5.7" },
{ name = "mkdocs-material-extensions", specifier = ">=1.3.1" },
@@ -715,26 +762,32 @@ dev = [
[[package]]
name = "crewai-tools"
version = "0.17.0"
version = "0.25.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "beautifulsoup4" },
{ name = "chromadb" },
{ name = "crewai" },
{ name = "docker" },
{ name = "docx2txt" },
{ name = "embedchain" },
{ name = "lancedb" },
{ name = "linkup-sdk" },
{ name = "openai" },
{ name = "pydantic" },
{ name = "pyright" },
{ name = "pytest" },
{ name = "pytube" },
{ name = "requests" },
{ name = "scrapegraph-py" },
{ name = "selenium" },
{ name = "serpapi" },
{ name = "spider-client" },
{ name = "weaviate-client" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cc/15/365f74e0e8313e7a3399bf01d908aa73575c823275f9196ec14c23159878/crewai_tools-0.17.0.tar.gz", hash = "sha256:2a2986000775c76bad45b9f3a2be857d293cf5daffe5f316abc052e630b1e5ce", size = 818983 }
sdist = { url = "https://files.pythonhosted.org/packages/23/2f/fbfd0dc8912d375a2d1272c503f79c83c25f3d2b4b72c230b0672278a1bd/crewai_tools-0.25.6.tar.gz", hash = "sha256:442a7e7e579cb3c671a53c5b7afce645cd31d2db913ecc6d1e22a4c5e1baa840", size = 883175 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/1d/976adc2a4e5237cb03625de412cd051dea7d524084ed442adedfda871526/crewai_tools-0.17.0-py3-none-any.whl", hash = "sha256:85cf15286684ecad579b5a497888c6bf8a079ca443f7dd63a52bf1709655e4a3", size = 467975 },
{ url = "https://files.pythonhosted.org/packages/ce/21/561a81b4f8cfcc2ac6a0c3db3ec86b70a7db6dabb0dd7d13c96be981b2fc/crewai_tools-0.25.6-py3-none-any.whl", hash = "sha256:463e0ee8d780ab7a801992e3960471fb8e64d038866429f70995ddd0a83e0679", size = 514758 },
]
[[package]]
@@ -1572,6 +1625,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1d/1f/acf03ee901313446d52c3916d527d4981de9f6f3edc69267d05509dcfa7b/grpcio-1.67.0-cp312-cp312-win_amd64.whl", hash = "sha256:985b2686f786f3e20326c4367eebdaed3e7aa65848260ff0c6644f817042cb15", size = 4343545 },
]
[[package]]
name = "grpcio-health-checking"
version = "1.62.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "grpcio" },
{ name = "protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/9f/09df9b02fc8eafa3031d878c8a4674a0311293c8c6f1c942cdaeec204126/grpcio-health-checking-1.62.3.tar.gz", hash = "sha256:5074ba0ce8f0dcfe328408ec5c7551b2a835720ffd9b69dade7fa3e0dc1c7a93", size = 15640 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/40/4c/ee3173906196b741ac6ba55a9788ba9ebf2cd05f91715a49b6c3bfbb9d73/grpcio_health_checking-1.62.3-py3-none-any.whl", hash = "sha256:f29da7dd144d73b4465fe48f011a91453e9ff6c8af0d449254cf80021cab3e0d", size = 18547 },
]
[[package]]
name = "grpcio-status"
version = "1.62.3"
@@ -1698,7 +1764,7 @@ wheels = [
[[package]]
name = "httpx"
version = "0.27.2"
version = "0.27.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -1707,9 +1773,9 @@ dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 }
sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/3da5bdf4408b8b2800061c339f240c1802f2e82d55e50bd39c5a881f47f0/httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5", size = 126413 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 },
{ url = "https://files.pythonhosted.org/packages/41/7b/ddacf6dcebb42466abd03f368782142baa82e08fc0c1f8eaa05b4bae87d5/httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5", size = 75590 },
]
[package.optional-dependencies]
@@ -1783,6 +1849,52 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
]
[[package]]
name = "ijson"
version = "3.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6c/83/28e9e93a3a61913e334e3a2e78ea9924bb9f9b1ac45898977f9d9dd6133f/ijson-3.3.0.tar.gz", hash = "sha256:7f172e6ba1bee0d4c8f8ebd639577bfe429dee0f3f96775a067b8bae4492d8a0", size = 60079 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ad/89/96e3608499b4a500b9bc27aa8242704e675849dd65bdfa8682b00a92477e/ijson-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7f7a5250599c366369fbf3bc4e176f5daa28eb6bc7d6130d02462ed335361675", size = 85009 },
{ url = "https://files.pythonhosted.org/packages/e4/7e/1098503500f5316c5f7912a51c91aca5cbc609c09ce4ecd9c4809983c560/ijson-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f87a7e52f79059f9c58f6886c262061065eb6f7554a587be7ed3aa63e6b71b34", size = 57796 },
{ url = "https://files.pythonhosted.org/packages/78/f7/27b8c27a285628719ff55b68507581c86b551eb162ce810fe51e3e1a25f2/ijson-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b73b493af9e947caed75d329676b1b801d673b17481962823a3e55fe529c8b8b", size = 57218 },
{ url = "https://files.pythonhosted.org/packages/0c/c5/1698094cb6a336a223c30e1167cc1b15cdb4bfa75399c1a2eb82fa76cc3c/ijson-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5576415f3d76290b160aa093ff968f8bf6de7d681e16e463a0134106b506f49", size = 117153 },
{ url = "https://files.pythonhosted.org/packages/4b/21/c206dda0945bd832cc9b0894596b0efc2cb1819a0ac61d8be1429ac09494/ijson-3.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e9ffe358d5fdd6b878a8a364e96e15ca7ca57b92a48f588378cef315a8b019e", size = 110781 },
{ url = "https://files.pythonhosted.org/packages/f4/f5/2d733e64577109a9b255d14d031e44a801fa20df9ccc58b54a31e8ecf9e6/ijson-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8643c255a25824ddd0895c59f2319c019e13e949dc37162f876c41a283361527", size = 114527 },
{ url = "https://files.pythonhosted.org/packages/8d/a8/78bfee312aa23417b86189a65f30b0edbceaee96dc6a616cc15f611187d1/ijson-3.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:df3ab5e078cab19f7eaeef1d5f063103e1ebf8c26d059767b26a6a0ad8b250a3", size = 116824 },
{ url = "https://files.pythonhosted.org/packages/5d/a4/aff410f7d6aa1a77ee2ab2d6a2d2758422726270cb149c908a9baf33cf58/ijson-3.3.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3dc1fb02c6ed0bae1b4bf96971258bf88aea72051b6e4cebae97cff7090c0607", size = 112647 },
{ url = "https://files.pythonhosted.org/packages/77/ee/2b5122dc4713f5a954267147da36e7156240ca21b04ed5295bc0cabf0fbe/ijson-3.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e9afd97339fc5a20f0542c971f90f3ca97e73d3050cdc488d540b63fae45329a", size = 114156 },
{ url = "https://files.pythonhosted.org/packages/b3/d7/ad3b266490b60c6939e8a07fd8e4b7e2002aea08eaa9572a016c3e3a9129/ijson-3.3.0-cp310-cp310-win32.whl", hash = "sha256:844c0d1c04c40fd1b60f148dc829d3f69b2de789d0ba239c35136efe9a386529", size = 48931 },
{ url = "https://files.pythonhosted.org/packages/0b/68/b9e1c743274c8a23dddb12d2ed13b5f021f6d21669d51ff7fa2e9e6c19df/ijson-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:d654d045adafdcc6c100e8e911508a2eedbd2a1b5f93f930ba13ea67d7704ee9", size = 50965 },
{ url = "https://files.pythonhosted.org/packages/fd/df/565ba72a6f4b2c833d051af8e2228cfa0b1fef17bb44995c00ad27470c52/ijson-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:501dce8eaa537e728aa35810656aa00460a2547dcb60937c8139f36ec344d7fc", size = 85041 },
{ url = "https://files.pythonhosted.org/packages/f0/42/1361eaa57ece921d0239881bae6a5e102333be5b6e0102a05ec3caadbd5a/ijson-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:658ba9cad0374d37b38c9893f4864f284cdcc7d32041f9808fba8c7bcaadf134", size = 57829 },
{ url = "https://files.pythonhosted.org/packages/f5/b0/143dbfe12e1d1303ea8d8cd6f40e95cea8f03bcad5b79708614a7856c22e/ijson-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2636cb8c0f1023ef16173f4b9a233bcdb1df11c400c603d5f299fac143ca8d70", size = 57217 },
{ url = "https://files.pythonhosted.org/packages/0d/80/b3b60c5e5be2839365b03b915718ca462c544fdc71e7a79b7262837995ef/ijson-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd174b90db68c3bcca273e9391934a25d76929d727dc75224bf244446b28b03b", size = 121878 },
{ url = "https://files.pythonhosted.org/packages/8d/eb/7560fafa4d40412efddf690cb65a9bf2d3429d6035e544103acbf5561dc4/ijson-3.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97a9aea46e2a8371c4cf5386d881de833ed782901ac9f67ebcb63bb3b7d115af", size = 115620 },
{ url = "https://files.pythonhosted.org/packages/51/2b/5a34c7841388dce161966e5286931518de832067cd83e6f003d93271e324/ijson-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c594c0abe69d9d6099f4ece17763d53072f65ba60b372d8ba6de8695ce6ee39e", size = 119200 },
{ url = "https://files.pythonhosted.org/packages/3e/b7/1d64fbec0d0a7b0c02e9ad988a89614532028ead8bb52a2456c92e6ee35a/ijson-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e0ff16c224d9bfe4e9e6bd0395826096cda4a3ef51e6c301e1b61007ee2bd24", size = 121107 },
{ url = "https://files.pythonhosted.org/packages/d4/b9/01044f09850bc545ffc85b35aaec473d4f4ca2b6667299033d252c1b60dd/ijson-3.3.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0015354011303175eae7e2ef5136414e91de2298e5a2e9580ed100b728c07e51", size = 116658 },
{ url = "https://files.pythonhosted.org/packages/fb/0d/53856b61f3d952d299d1695c487e8e28058d01fa2adfba3d6d4b4660c242/ijson-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034642558afa57351a0ffe6de89e63907c4cf6849070cc10a3b2542dccda1afe", size = 118186 },
{ url = "https://files.pythonhosted.org/packages/95/2d/5bd86e2307dd594840ee51c4e32de953fee837f028acf0f6afb08914cd06/ijson-3.3.0-cp311-cp311-win32.whl", hash = "sha256:192e4b65495978b0bce0c78e859d14772e841724d3269fc1667dc6d2f53cc0ea", size = 48938 },
{ url = "https://files.pythonhosted.org/packages/55/e1/4ba2b65b87f67fb19d698984d92635e46d9ce9dd748ce7d009441a586710/ijson-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:72e3488453754bdb45c878e31ce557ea87e1eb0f8b4fc610373da35e8074ce42", size = 50972 },
{ url = "https://files.pythonhosted.org/packages/8a/4d/3992f7383e26a950e02dc704bc6c5786a080d5c25fe0fc5543ef477c1883/ijson-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:988e959f2f3d59ebd9c2962ae71b97c0df58323910d0b368cc190ad07429d1bb", size = 84550 },
{ url = "https://files.pythonhosted.org/packages/1b/cc/3d4372e0d0b02a821b982f1fdf10385512dae9b9443c1597719dd37769a9/ijson-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b2f73f0d0fce5300f23a1383d19b44d103bb113b57a69c36fd95b7c03099b181", size = 57572 },
{ url = "https://files.pythonhosted.org/packages/02/de/970d48b1ff9da5d9513c86fdd2acef5cb3415541c8069e0d92a151b84adb/ijson-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ee57a28c6bf523d7cb0513096e4eb4dac16cd935695049de7608ec110c2b751", size = 56902 },
{ url = "https://files.pythonhosted.org/packages/5e/a0/4537722c8b3b05e82c23dfe09a3a64dd1e44a013a5ca58b1e77dfe48b2f1/ijson-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0155a8f079c688c2ccaea05de1ad69877995c547ba3d3612c1c336edc12a3a5", size = 127400 },
{ url = "https://files.pythonhosted.org/packages/b2/96/54956062a99cf49f7a7064b573dcd756da0563ce57910dc34e27a473d9b9/ijson-3.3.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ab00721304af1ae1afa4313ecfa1bf16b07f55ef91e4a5b93aeaa3e2bd7917c", size = 118786 },
{ url = "https://files.pythonhosted.org/packages/07/74/795319531c5b5504508f595e631d592957f24bed7ff51a15bc4c61e7b24c/ijson-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40ee3821ee90be0f0e95dcf9862d786a7439bd1113e370736bfdf197e9765bfb", size = 126288 },
{ url = "https://files.pythonhosted.org/packages/69/6a/e0cec06fbd98851d5d233b59058c1dc2ea767c9bb6feca41aa9164fff769/ijson-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3b6987a0bc3e6d0f721b42c7a0198ef897ae50579547b0345f7f02486898f5", size = 129569 },
{ url = "https://files.pythonhosted.org/packages/2a/4f/82c0d896d8dcb175f99ced7d87705057bcd13523998b48a629b90139a0dc/ijson-3.3.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:63afea5f2d50d931feb20dcc50954e23cef4127606cc0ecf7a27128ed9f9a9e6", size = 121508 },
{ url = "https://files.pythonhosted.org/packages/2b/b6/8973474eba4a917885e289d9e138267d3d1f052c2d93b8c968755661a42d/ijson-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b5c3e285e0735fd8c5a26d177eca8b52512cdd8687ca86ec77a0c66e9c510182", size = 127896 },
{ url = "https://files.pythonhosted.org/packages/94/25/00e66af887adbbe70002e0479c3c2340bdfa17a168e25d4ab5a27b53582d/ijson-3.3.0-cp312-cp312-win32.whl", hash = "sha256:907f3a8674e489abdcb0206723e5560a5cb1fa42470dcc637942d7b10f28b695", size = 49272 },
{ url = "https://files.pythonhosted.org/packages/25/a2/e187beee237808b2c417109ae0f4f7ee7c81ecbe9706305d6ac2a509cc45/ijson-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8f890d04ad33262d0c77ead53c85f13abfb82f2c8f078dfbf24b78f59534dfdd", size = 51272 },
{ url = "https://files.pythonhosted.org/packages/c3/28/2e1cf00abe5d97aef074e7835b86a94c9a06be4629a0e2c12600792b51ba/ijson-3.3.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2af323a8aec8a50fa9effa6d640691a30a9f8c4925bd5364a1ca97f1ac6b9b5c", size = 54308 },
{ url = "https://files.pythonhosted.org/packages/04/d2/8c541c28da4f931bac8177e251efe2b6902f7c486d2d4bdd669eed4ff5c0/ijson-3.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f64f01795119880023ba3ce43072283a393f0b90f52b66cc0ea1a89aa64a9ccb", size = 66010 },
{ url = "https://files.pythonhosted.org/packages/d0/02/8fec0b9037a368811dba7901035e8e0973ebda308f57f30c42101a16a5f7/ijson-3.3.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a716e05547a39b788deaf22725490855337fc36613288aa8ae1601dc8c525553", size = 66770 },
{ url = "https://files.pythonhosted.org/packages/47/23/90c61f978c83647112460047ea0137bde9c7fe26600ce255bb3e17ea7a21/ijson-3.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473f5d921fadc135d1ad698e2697025045cd8ed7e5e842258295012d8a3bc702", size = 64159 },
{ url = "https://files.pythonhosted.org/packages/20/af/aab1a36072590af62d848f03981f1c587ca40a391fc61e418e388d8b0d46/ijson-3.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd26b396bc3a1e85f4acebeadbf627fa6117b97f4c10b177d5779577c6607744", size = 51095 },
]
[[package]]
name = "imageio"
version = "2.36.1"
@@ -2217,6 +2329,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097 },
]
[[package]]
name = "linkup-sdk"
version = "0.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
{ name = "pydantic" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2e/ba/b06e8f2ca2f0ce255a40ee4505637536acfe83ec997cd8b61bd5cd031513/linkup_sdk-0.2.1.tar.gz", hash = "sha256:b00ba7cb0117358e975d50196501ac49b247509fd236121e40abe40e6a2a3e9a", size = 8918 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4f/90/2903b9e2eba501ceb6c6b4fc57bbeddde7e8964921a05d424f5a6125cbd0/linkup_sdk-0.2.1-py3-none-any.whl", hash = "sha256:bf50c88e659c6d9291cbd5e3e99b6a20a14c9b1eb2dc7acca763a3ae6f84b26e", size = 7961 },
]
[[package]]
name = "litellm"
version = "1.50.2"
@@ -2890,7 +3015,7 @@ name = "nvidia-cudnn-cu12"
version = "9.1.0.70"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" },
{ name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'linux')" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 },
@@ -2917,9 +3042,9 @@ name = "nvidia-cusolver-cu12"
version = "11.4.5.107"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" },
{ name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" },
{ name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" },
{ name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'linux')" },
{ name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'linux')" },
{ name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'linux')" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/1d/8de1e5c67099015c834315e333911273a8c6aaba78923dd1d1e25fc5f217/nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd", size = 124161928 },
@@ -2930,7 +3055,7 @@ name = "nvidia-cusparse-cu12"
version = "12.1.0.106"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" },
{ name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'linux')" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/65/5b/cfaeebf25cd9fdec14338ccb16f6b2c4c7fa9163aefcf057d86b9cc248bb/nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c", size = 195958278 },
@@ -3787,71 +3912,77 @@ wheels = [
[[package]]
name = "pydantic"
version = "2.9.2"
version = "2.10.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 }
sdist = { url = "https://files.pythonhosted.org/packages/70/7e/fb60e6fee04d0ef8f15e4e01ff187a196fa976eb0f0ab524af4599e5754c/pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06", size = 762094 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 },
{ url = "https://files.pythonhosted.org/packages/f3/26/3e1bbe954fde7ee22a6e7d31582c642aad9e84ffe4b5fb61e63b87cd326f/pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d", size = 431765 },
]
[[package]]
name = "pydantic-core"
version = "2.23.4"
version = "2.27.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 }
sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/8b/d3ae387f66277bd8104096d6ec0a145f4baa2966ebb2cad746c0920c9526/pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", size = 1867835 },
{ url = "https://files.pythonhosted.org/packages/46/76/f68272e4c3a7df8777798282c5e47d508274917f29992d84e1898f8908c7/pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", size = 1776689 },
{ url = "https://files.pythonhosted.org/packages/cc/69/5f945b4416f42ea3f3bc9d2aaec66c76084a6ff4ff27555bf9415ab43189/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", size = 1800748 },
{ url = "https://files.pythonhosted.org/packages/50/ab/891a7b0054bcc297fb02d44d05c50e68154e31788f2d9d41d0b72c89fdf7/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", size = 1806469 },
{ url = "https://files.pythonhosted.org/packages/31/7c/6e3fa122075d78f277a8431c4c608f061881b76c2b7faca01d317ee39b5d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", size = 2002246 },
{ url = "https://files.pythonhosted.org/packages/ad/6f/22d5692b7ab63fc4acbc74de6ff61d185804a83160adba5e6cc6068e1128/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", size = 2659404 },
{ url = "https://files.pythonhosted.org/packages/11/ac/1e647dc1121c028b691028fa61a4e7477e6aeb5132628fde41dd34c1671f/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", size = 2053940 },
{ url = "https://files.pythonhosted.org/packages/91/75/984740c17f12c3ce18b5a2fcc4bdceb785cce7df1511a4ce89bca17c7e2d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", size = 1921437 },
{ url = "https://files.pythonhosted.org/packages/a0/74/13c5f606b64d93f0721e7768cd3e8b2102164866c207b8cd6f90bb15d24f/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", size = 1966129 },
{ url = "https://files.pythonhosted.org/packages/18/03/9c4aa5919457c7b57a016c1ab513b1a926ed9b2bb7915bf8e506bf65c34b/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", size = 2110908 },
{ url = "https://files.pythonhosted.org/packages/92/2c/053d33f029c5dc65e5cf44ff03ceeefb7cce908f8f3cca9265e7f9b540c8/pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", size = 1735278 },
{ url = "https://files.pythonhosted.org/packages/de/81/7dfe464eca78d76d31dd661b04b5f2036ec72ea8848dd87ab7375e185c23/pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", size = 1917453 },
{ url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 },
{ url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 },
{ url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 },
{ url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 },
{ url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 },
{ url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 },
{ url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 },
{ url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 },
{ url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 },
{ url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 },
{ url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 },
{ url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 },
{ url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 },
{ url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 },
{ url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 },
{ url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 },
{ url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 },
{ url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 },
{ url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 },
{ url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 },
{ url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 },
{ url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 },
{ url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 },
{ url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 },
{ url = "https://files.pythonhosted.org/packages/13/a9/5d582eb3204464284611f636b55c0a7410d748ff338756323cb1ce721b96/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", size = 1857135 },
{ url = "https://files.pythonhosted.org/packages/2c/57/faf36290933fe16717f97829eabfb1868182ac495f99cf0eda9f59687c9d/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", size = 1740583 },
{ url = "https://files.pythonhosted.org/packages/91/7c/d99e3513dc191c4fec363aef1bf4c8af9125d8fa53af7cb97e8babef4e40/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", size = 1793637 },
{ url = "https://files.pythonhosted.org/packages/29/18/812222b6d18c2d13eebbb0f7cdc170a408d9ced65794fdb86147c77e1982/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", size = 1941963 },
{ url = "https://files.pythonhosted.org/packages/0f/36/c1f3642ac3f05e6bb4aec3ffc399fa3f84895d259cf5f0ce3054b7735c29/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", size = 1915332 },
{ url = "https://files.pythonhosted.org/packages/f7/ca/9c0854829311fb446020ebb540ee22509731abad886d2859c855dd29b904/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", size = 1957926 },
{ url = "https://files.pythonhosted.org/packages/c0/1c/7836b67c42d0cd4441fcd9fafbf6a027ad4b79b6559f80cf11f89fd83648/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", size = 2100342 },
{ url = "https://files.pythonhosted.org/packages/a9/f9/b6bcaf874f410564a78908739c80861a171788ef4d4f76f5009656672dfe/pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", size = 1920344 },
{ url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938 },
{ url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684 },
{ url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169 },
{ url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227 },
{ url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695 },
{ url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662 },
{ url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370 },
{ url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813 },
{ url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287 },
{ url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414 },
{ url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301 },
{ url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685 },
{ url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876 },
{ url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 },
{ url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 },
{ url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 },
{ url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 },
{ url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 },
{ url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 },
{ url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 },
{ url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 },
{ url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 },
{ url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 },
{ url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 },
{ url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 },
{ url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 },
{ url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 },
{ url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 },
{ url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 },
{ url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 },
{ url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 },
{ url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 },
{ url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 },
{ url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 },
{ url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 },
{ url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 },
{ url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 },
{ url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 },
{ url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 },
{ url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 },
{ url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 },
{ url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159 },
{ url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331 },
{ url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467 },
{ url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797 },
{ url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839 },
{ url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861 },
{ url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582 },
{ url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985 },
{ url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 },
]
[[package]]
@@ -4669,6 +4800,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/aa/7d/43ab67228ef98c6b5dd42ab386eae2d7877036970a0d7e3dd3eb47a0d530/scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f", size = 44521212 },
]
[[package]]
name = "scrapegraph-py"
version = "1.8.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
{ name = "beautifulsoup4" },
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/33/90/2388754061394a6c95fd5ad48cf4550208ce081c99cbc883672d52ccc360/scrapegraph_py-1.8.0.tar.gz", hash = "sha256:e075f6e6012a14a038537d0664609229069d9d2c2956bcbf9362f0c5c48de786", size = 108112 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/80/14aeb7ba092cfc6928844a6726855f0c33489107f344e71dd8071f6433ed/scrapegraph_py-1.8.0-py3-none-any.whl", hash = "sha256:279176c972a770bac37a284e0bc25e34793797f30ff24dfba8fbcbfda79c8c88", size = 14460 },
]
[[package]]
name = "selenium"
version = "4.25.0"
@@ -4699,6 +4846,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f8/85/3940bb4c586e10603d169d13ffccd59ed32fcb8d1b8104c3aef0e525b3b2/semchunk-2.2.0-py3-none-any.whl", hash = "sha256:7db19ca90ddb48f99265e789e07a7bb111ae25185f9cc3d44b94e1e61b9067fc", size = 10243 },
]
[[package]]
name = "serpapi"
version = "0.1.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/fa/3fd8809287f3977a3e752bb88610e918d49cb1038b14f4bc51e13e594197/serpapi-0.1.5.tar.gz", hash = "sha256:b9707ed54750fdd2f62dc3a17c6a3fb7fa421dc37902fd65b2263c0ac765a1a5", size = 14191 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/6a/21deade04100d64844e494353a5d65e7971fbdfddf78eb1f248423593ad0/serpapi-0.1.5-py2.py3-none-any.whl", hash = "sha256:6467b6adec1231059f754ccaa952b229efeaa8b9cae6e71f879703ec9e5bb3d1", size = 10966 },
]
[[package]]
name = "setuptools"
version = "75.2.0"
@@ -4782,6 +4941,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 },
]
[[package]]
name = "spider-client"
version = "0.1.25"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
{ name = "ijson" },
{ name = "requests" },
{ name = "tenacity" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b8/f2/06d89322f0054ea72e8d5580199f580e29df23476cb3cfe83a70a2a58a1b/spider-client-0.1.25.tar.gz", hash = "sha256:92ca4ce1d9d715dd8db52684ea417653940d8f3bbc13383d78683bc4fbb899a2", size = 15412 }
[[package]]
name = "sqlalchemy"
version = "2.0.36"
@@ -5155,7 +5326,7 @@ name = "triton"
version = "3.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "filelock", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" },
{ name = "filelock", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'linux')" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/27/14cc3101409b9b4b9241d2ba7deaa93535a217a211c86c4cc7151fb12181/triton-3.0.0-1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e1efef76935b2febc365bfadf74bcb65a6f959a9872e5bddf44cc9e0adce1e1a", size = 209376304 },
@@ -5311,6 +5482,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022 },
]
[[package]]
name = "validators"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/64/07/91582d69320f6f6daaf2d8072608a4ad8884683d4840e7e4f3a9dbdcc639/validators-0.34.0.tar.gz", hash = "sha256:647fe407b45af9a74d245b943b18e6a816acf4926974278f6dd617778e1e781f", size = 70955 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/78/36828a4d857b25896f9774c875714ba4e9b3bc8a92d2debe3f4df3a83d4f/validators-0.34.0-py3-none-any.whl", hash = "sha256:c804b476e3e6d3786fa07a30073a4ef694e617805eb1946ceee3fe5a9b8b1321", size = 43536 },
]
[[package]]
name = "vcrpy"
version = "5.1.0"
@@ -5430,6 +5610,25 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 },
]
[[package]]
name = "weaviate-client"
version = "4.9.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "authlib" },
{ name = "grpcio" },
{ name = "grpcio-health-checking" },
{ name = "grpcio-tools" },
{ name = "httpx" },
{ name = "pydantic" },
{ name = "requests" },
{ name = "validators" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5d/7d/3894d12065d006743271b0b6bcc3bf911910473e91179d5966966816d694/weaviate_client-4.9.6.tar.gz", hash = "sha256:56d67c40fc94b0d53e81e0aa4477baaebbf3646fbec26551df66e396a72adcb6", size = 696813 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2f/40/e3550e743b92ddd8dc69ebfd69cceb6de45b7d9a1cd439995454b499e9a3/weaviate_client-4.9.6-py3-none-any.whl", hash = "sha256:1d3b551939c0f7314f25e417cbcf4cf34e7adf942627993eef36ae6b4a044673", size = 386998 },
]
[[package]]
name = "webencodings"
version = "0.5.1"
@@ -5567,64 +5766,64 @@ wheels = [
[[package]]
name = "yarl"
version = "1.16.0"
version = "1.18.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "multidict" },
{ name = "propcache" },
]
sdist = { url = "https://files.pythonhosted.org/packages/23/52/e9766cc6c2eab7dd1e9749c52c9879317500b46fb97d4105223f86679f93/yarl-1.16.0.tar.gz", hash = "sha256:b6f687ced5510a9a2474bbae96a4352e5ace5fa34dc44a217b0537fec1db00b4", size = 176548 }
sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/30/00b17348655202e4bd24f8d79cd062888e5d3bdbf2ba726615c5d21b54a5/yarl-1.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32468f41242d72b87ab793a86d92f885355bcf35b3355aa650bfa846a5c60058", size = 140016 },
{ url = "https://files.pythonhosted.org/packages/a5/15/9b7b85b72b81f180689257b2bb6e54d5d0764a399679aa06d5dec8ca6e2e/yarl-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:234f3a3032b505b90e65b5bc6652c2329ea7ea8855d8de61e1642b74b4ee65d2", size = 92953 },
{ url = "https://files.pythonhosted.org/packages/31/41/91848bbb76789336d3b786ff144030001b5027b17729b3afa32da668f5b0/yarl-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a0296040e5cddf074c7f5af4a60f3fc42c0237440df7bcf5183be5f6c802ed5", size = 90793 },
{ url = "https://files.pythonhosted.org/packages/6c/99/f1ada764e350ab054e14902f3f68589a7d77469ac47fbc512aa1a78a2f35/yarl-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de6c14dd7c7c0badba48157474ea1f03ebee991530ba742d381b28d4f314d6f3", size = 313155 },
{ url = "https://files.pythonhosted.org/packages/75/fd/998ccdb489ca97d9073d882265203a2fae4c5bff30eb9b8a0bbbed7aef2b/yarl-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b140e532fe0266003c936d017c1ac301e72ee4a3fd51784574c05f53718a55d8", size = 328624 },
{ url = "https://files.pythonhosted.org/packages/2d/5d/395bbae1f509f64e6d26b7ffffff178d70c5480f15af735dfb0afb8f0dc5/yarl-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:019f5d58093402aa8f6661e60fd82a28746ad6d156f6c5336a70a39bd7b162b9", size = 325163 },
{ url = "https://files.pythonhosted.org/packages/1d/25/65601d336189d122483f5ff0276b08278fa4778f833458cfcac5c6eddc87/yarl-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c42998fd1cbeb53cd985bff0e4bc25fbe55fd6eb3a545a724c1012d69d5ec84", size = 318076 },
{ url = "https://files.pythonhosted.org/packages/50/bb/0c9692ec457c1ed023654a9fba6d0c69a20c79b56275d972f6a24ab18547/yarl-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7c30fb38c300fe8140df30a046a01769105e4cf4282567a29b5cdb635b66c4", size = 309551 },
{ url = "https://files.pythonhosted.org/packages/a5/2f/d0ced2050a203241a3f2e05c5bb86038b071f216897defd824dd85333f9e/yarl-1.16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e49e0fd86c295e743fd5be69b8b0712f70a686bc79a16e5268386c2defacaade", size = 317678 },
{ url = "https://files.pythonhosted.org/packages/46/93/b7359aa2bd0567eca72491cd20059744ed6ee00f08cd58c861243f656a90/yarl-1.16.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b9ca7b9147eb1365c8bab03c003baa1300599575effad765e0b07dd3501ea9af", size = 317003 },
{ url = "https://files.pythonhosted.org/packages/87/18/77ef4d45d19ecafad0f7c07d5cf13a757a90122383494bc5a3e8ee68e2f2/yarl-1.16.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27e11db3f1e6a51081a981509f75617b09810529de508a181319193d320bc5c7", size = 322795 },
{ url = "https://files.pythonhosted.org/packages/28/a9/b38880bf79665d1c8a3d4c09d6f7a686a50f8c74caf07603a2b8e5314038/yarl-1.16.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8994c42f4ca25df5380ddf59f315c518c81df6a68fed5bb0c159c6cb6b92f120", size = 337022 },
{ url = "https://files.pythonhosted.org/packages/e9/79/865788b297fc17117e3ff6ea74d5f864185085d61adc3364444732095254/yarl-1.16.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:542fa8e09a581bcdcbb30607c7224beff3fdfb598c798ccd28a8184ffc18b7eb", size = 338357 },
{ url = "https://files.pythonhosted.org/packages/bd/5e/c5cba528448f73c7035c9d3c07261b54312d8caa8372eeeff5e1f07e43ec/yarl-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2bd6a51010c7284d191b79d3b56e51a87d8e1c03b0902362945f15c3d50ed46b", size = 330470 },
{ url = "https://files.pythonhosted.org/packages/1a/e4/90757595d81ec328ad94afa62d0724903a6c72b76e0ee9c9af9d8a399dd2/yarl-1.16.0-cp310-cp310-win32.whl", hash = "sha256:178ccb856e265174a79f59721031060f885aca428983e75c06f78aa24b91d929", size = 82967 },
{ url = "https://files.pythonhosted.org/packages/01/5a/b82ec5e7557b0d938b9475cbb5dcbb1f98c8601101188d79e423dc215cd0/yarl-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe8bba2545427418efc1929c5c42852bdb4143eb8d0a46b09de88d1fe99258e7", size = 89159 },
{ url = "https://files.pythonhosted.org/packages/0a/00/b29affe83de95e403f8a2a669b5a33f1e7dfe686264008100052eb0b05fd/yarl-1.16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d8643975a0080f361639787415a038bfc32d29208a4bf6b783ab3075a20b1ef3", size = 140120 },
{ url = "https://files.pythonhosted.org/packages/3f/22/bcc9799950281a5d4f646536854839ccdbb965e900827ef0750680f81faf/yarl-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:676d96bafc8c2d0039cea0cd3fd44cee7aa88b8185551a2bb93354668e8315c2", size = 92956 },
{ url = "https://files.pythonhosted.org/packages/33/0f/1b76d853d9d921d68bd9991648be17d34e7ac51e2e20e7658f8ee7e2e2ad/yarl-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9525f03269e64310416dbe6c68d3b23e5d34aaa8f47193a1c45ac568cecbc49", size = 90891 },
{ url = "https://files.pythonhosted.org/packages/61/19/3666d990c24aae98c748e2c262adc9b3a71e38834df007ac5317f4bbd789/yarl-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b37d5ec034e668b22cf0ce1074d6c21fd2a08b90d11b1b73139b750a8b0dd97", size = 338857 },
{ url = "https://files.pythonhosted.org/packages/a0/3d/54acbb3cdfcfea03d6a3535cff1e060a2de23e419a4e3955c9661171b8a8/yarl-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f32c4cb7386b41936894685f6e093c8dfaf0960124d91fe0ec29fe439e201d0", size = 354005 },
{ url = "https://files.pythonhosted.org/packages/15/98/cd9fe3938422c88775c94578a6c145aca89ff8368ff64e6032213ac12403/yarl-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b8e265a0545637492a7e12fd7038370d66c9375a61d88c5567d0e044ded9202", size = 351195 },
{ url = "https://files.pythonhosted.org/packages/e2/13/b6eff6ea1667aee948ecd6b1c8fb6473234f8e48f49af97be93251869c51/yarl-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:789a3423f28a5fff46fbd04e339863c169ece97c827b44de16e1a7a42bc915d2", size = 342789 },
{ url = "https://files.pythonhosted.org/packages/fe/05/d98e65ea74a7e44bb033b2cf5bcc16edc1d5212bdc5ca7fbb5e380d89f8e/yarl-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1d1f45e3e8d37c804dca99ab3cf4ab3ed2e7a62cd82542924b14c0a4f46d243", size = 336478 },
{ url = "https://files.pythonhosted.org/packages/7d/47/43de2e94b75f36d84733a35c807d0e33aaf084e98f32e2cbc685102f4ba4/yarl-1.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:621280719c4c5dad4c1391160a9b88925bb8b0ff6a7d5af3224643024871675f", size = 346008 },
{ url = "https://files.pythonhosted.org/packages/e2/de/9c2f900ec5e2f2e20329cfe7dcd9452e326d08cb5ecd098c2d4e9987b65c/yarl-1.16.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed097b26f18a1f5ff05f661dc36528c5f6735ba4ce8c9645e83b064665131349", size = 343745 },
{ url = "https://files.pythonhosted.org/packages/56/cd/b014dce22e37b77caa37f998c6c47434fd78d01e7be07119629f369f5ee1/yarl-1.16.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2f1fe2b2e3ee418862f5ebc0c0083c97f6f6625781382f828f6d4e9b614eba9b", size = 349705 },
{ url = "https://files.pythonhosted.org/packages/07/17/bb191a26f7189423964e008ccb5146ce5258454ef3979f9d4c6860d282c7/yarl-1.16.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:87dd10bc0618991c66cee0cc65fa74a45f4ecb13bceec3c62d78ad2e42b27a16", size = 360767 },
{ url = "https://files.pythonhosted.org/packages/19/09/7d777369e151991b708a5b35280ea7444621d65af5f0545bcdce5d840867/yarl-1.16.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4199db024b58a8abb2cfcedac7b1292c3ad421684571aeb622a02f242280e8d6", size = 364755 },
{ url = "https://files.pythonhosted.org/packages/00/32/7558997d1d2e53dab15f6db5db49fc6b412b63ede3cb8314e5dd7cff14fe/yarl-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:99a9dcd4b71dd5f5f949737ab3f356cfc058c709b4f49833aeffedc2652dac56", size = 357087 },
{ url = "https://files.pythonhosted.org/packages/28/20/c49a95a30c57224e5fb0fc83235295684b041300ce508b71821cb042527d/yarl-1.16.0-cp311-cp311-win32.whl", hash = "sha256:a9394c65ae0ed95679717d391c862dece9afacd8fa311683fc8b4362ce8a410c", size = 83030 },
{ url = "https://files.pythonhosted.org/packages/75/e3/2a746721d6f32886d9bafccdb80174349f180ccae0a287f25ba4312a2618/yarl-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b9101f528ae0f8f65ac9d64dda2bb0627de8a50344b2f582779f32fda747c1d", size = 89616 },
{ url = "https://files.pythonhosted.org/packages/3a/be/82f696c8ce0395c37f62b955202368086e5cc114d5bb9cb1b634cff5e01d/yarl-1.16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4ffb7c129707dd76ced0a4a4128ff452cecf0b0e929f2668ea05a371d9e5c104", size = 141230 },
{ url = "https://files.pythonhosted.org/packages/38/60/45caaa748b53c4b0964f899879fcddc41faa4e0d12c6f0ae3311e8c151ff/yarl-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1a5e9d8ce1185723419c487758d81ac2bde693711947032cce600ca7c9cda7d6", size = 93515 },
{ url = "https://files.pythonhosted.org/packages/54/bd/33aaca2f824dc1d630729e16e313797e8b24c8f7b6803307e5394274e443/yarl-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d743e3118b2640cef7768ea955378c3536482d95550222f908f392167fe62059", size = 91441 },
{ url = "https://files.pythonhosted.org/packages/af/fa/1ce8ca85489925aabdb8d2e7bbeaf74e7d3e6ac069779d6d6b9c7c62a8ed/yarl-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26768342f256e6e3c37533bf9433f5f15f3e59e3c14b2409098291b3efaceacb", size = 330871 },
{ url = "https://files.pythonhosted.org/packages/f1/2a/a8110a225e498b87315827f8b61d24de35f86041834cf8c9c5544380c46b/yarl-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1b0796168b953bca6600c5f97f5ed407479889a36ad7d17183366260f29a6b9", size = 340641 },
{ url = "https://files.pythonhosted.org/packages/d0/64/20cd1cb1f60b3ff49e7d75c1a2083352e7c5939368aafa960712c9e53797/yarl-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:858728086914f3a407aa7979cab743bbda1fe2bdf39ffcd991469a370dd7414d", size = 340245 },
{ url = "https://files.pythonhosted.org/packages/77/a8/7f38bbefb22eb925a68ad1d8193b05f51515614a6c0ebcadf26e9ae5e5ad/yarl-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5570e6d47bcb03215baf4c9ad7bf7c013e56285d9d35013541f9ac2b372593e7", size = 336054 },
{ url = "https://files.pythonhosted.org/packages/b4/a6/ac633ea3ea0c4eb1057e6800db1d077e77493b4b3449a4a97b2fbefadef4/yarl-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66ea8311422a7ba1fc79b4c42c2baa10566469fe5a78500d4e7754d6e6db8724", size = 324405 },
{ url = "https://files.pythonhosted.org/packages/93/cd/4fc87ce9b0df7afb610ffb904f4aef25f59e0ad40a49da19a475facf98b7/yarl-1.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:649bddcedee692ee8a9b7b6e38582cb4062dc4253de9711568e5620d8707c2a3", size = 342235 },
{ url = "https://files.pythonhosted.org/packages/9f/bc/38bae4b716da1206849d88e167d3d2c5695ae9b418a3915220947593e5ca/yarl-1.16.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a91654adb7643cb21b46f04244c5a315a440dcad63213033826549fa2435f71", size = 340835 },
{ url = "https://files.pythonhosted.org/packages/dc/0f/b9efbc0075916a450cbad41299dff3bdd3393cb1d8378bb831c4a6a836e1/yarl-1.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b439cae82034ade094526a8f692b9a2b5ee936452de5e4c5f0f6c48df23f8604", size = 344323 },
{ url = "https://files.pythonhosted.org/packages/87/6d/dc483ea1574005f14ef4c5f5f726cf60327b07ac83bd417d98db23e5285f/yarl-1.16.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:571f781ae8ac463ce30bacebfaef2c6581543776d5970b2372fbe31d7bf31a07", size = 355112 },
{ url = "https://files.pythonhosted.org/packages/10/22/3b7c3728d26b3cc295c51160ae4e2612ab7d3f9df30beece44bf72861730/yarl-1.16.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa7943f04f36d6cafc0cf53ea89824ac2c37acbdb4b316a654176ab8ffd0f968", size = 361506 },
{ url = "https://files.pythonhosted.org/packages/ad/8d/b7b5d43cf22a020b564ddf7502d83df150d797e34f18f6bf5fe0f12cbd91/yarl-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a5cf32539373ff39d97723e39a9283a7277cbf1224f7aef0c56c9598b6486c3", size = 355746 },
{ url = "https://files.pythonhosted.org/packages/d9/a6/a2098bf3f09d38eb540b2b192e180d9d41c2ff64b692783db2188f0a55e3/yarl-1.16.0-cp312-cp312-win32.whl", hash = "sha256:a5b6c09b9b4253d6a208b0f4a2f9206e511ec68dce9198e0fbec4f160137aa67", size = 82675 },
{ url = "https://files.pythonhosted.org/packages/ed/a6/0a54b382cfc336e772b72681d6816a99222dc2d21876e649474973b8d244/yarl-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1208ca14eed2fda324042adf8d6c0adf4a31522fa95e0929027cd487875f0240", size = 88986 },
{ url = "https://files.pythonhosted.org/packages/fb/f7/87a32867ddc1a9817018bfd6109ee57646a543acf0d272843d8393e575f9/yarl-1.16.0-py3-none-any.whl", hash = "sha256:e6980a558d8461230c457218bd6c92dfc1d10205548215c2c21d79dc8d0a96f3", size = 43746 },
{ url = "https://files.pythonhosted.org/packages/d2/98/e005bc608765a8a5569f58e650961314873c8469c333616eb40bff19ae97/yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34", size = 141458 },
{ url = "https://files.pythonhosted.org/packages/df/5d/f8106b263b8ae8a866b46d9be869ac01f9b3fb7f2325f3ecb3df8003f796/yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7", size = 94365 },
{ url = "https://files.pythonhosted.org/packages/56/3e/d8637ddb9ba69bf851f765a3ee288676f7cf64fb3be13760c18cbc9d10bd/yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed", size = 92181 },
{ url = "https://files.pythonhosted.org/packages/76/f9/d616a5c2daae281171de10fba41e1c0e2d8207166fc3547252f7d469b4e1/yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde", size = 315349 },
{ url = "https://files.pythonhosted.org/packages/bb/b4/3ea5e7b6f08f698b3769a06054783e434f6d59857181b5c4e145de83f59b/yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b", size = 330494 },
{ url = "https://files.pythonhosted.org/packages/55/f1/e0fc810554877b1b67420568afff51b967baed5b53bcc983ab164eebf9c9/yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5", size = 326927 },
{ url = "https://files.pythonhosted.org/packages/a9/42/b1753949b327b36f210899f2dd0a0947c0c74e42a32de3f8eb5c7d93edca/yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc", size = 319703 },
{ url = "https://files.pythonhosted.org/packages/f0/6d/e87c62dc9635daefb064b56f5c97df55a2e9cc947a2b3afd4fd2f3b841c7/yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd", size = 310246 },
{ url = "https://files.pythonhosted.org/packages/e3/ef/e2e8d1785cdcbd986f7622d7f0098205f3644546da7919c24b95790ec65a/yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990", size = 319730 },
{ url = "https://files.pythonhosted.org/packages/fc/15/8723e22345bc160dfde68c4b3ae8b236e868f9963c74015f1bc8a614101c/yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db", size = 321681 },
{ url = "https://files.pythonhosted.org/packages/86/09/bf764e974f1516efa0ae2801494a5951e959f1610dd41edbfc07e5e0f978/yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62", size = 324812 },
{ url = "https://files.pythonhosted.org/packages/f6/4c/20a0187e3b903c97d857cf0272d687c1b08b03438968ae8ffc50fe78b0d6/yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760", size = 337011 },
{ url = "https://files.pythonhosted.org/packages/c9/71/6244599a6e1cc4c9f73254a627234e0dad3883ece40cc33dce6265977461/yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b", size = 338132 },
{ url = "https://files.pythonhosted.org/packages/af/f5/e0c3efaf74566c4b4a41cb76d27097df424052a064216beccae8d303c90f/yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690", size = 331849 },
{ url = "https://files.pythonhosted.org/packages/8a/b8/3d16209c2014c2f98a8f658850a57b716efb97930aebf1ca0d9325933731/yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6", size = 84309 },
{ url = "https://files.pythonhosted.org/packages/fd/b7/2e9a5b18eb0fe24c3a0e8bae994e812ed9852ab4fd067c0107fadde0d5f0/yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8", size = 90484 },
{ url = "https://files.pythonhosted.org/packages/40/93/282b5f4898d8e8efaf0790ba6d10e2245d2c9f30e199d1a85cae9356098c/yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069", size = 141555 },
{ url = "https://files.pythonhosted.org/packages/6d/9c/0a49af78df099c283ca3444560f10718fadb8a18dc8b3edf8c7bd9fd7d89/yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193", size = 94351 },
{ url = "https://files.pythonhosted.org/packages/5a/a1/205ab51e148fdcedad189ca8dd587794c6f119882437d04c33c01a75dece/yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889", size = 92286 },
{ url = "https://files.pythonhosted.org/packages/ed/fe/88b690b30f3f59275fb674f5f93ddd4a3ae796c2b62e5bb9ece8a4914b83/yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8", size = 340649 },
{ url = "https://files.pythonhosted.org/packages/07/eb/3b65499b568e01f36e847cebdc8d7ccb51fff716dbda1ae83c3cbb8ca1c9/yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca", size = 356623 },
{ url = "https://files.pythonhosted.org/packages/33/46/f559dc184280b745fc76ec6b1954de2c55595f0ec0a7614238b9ebf69618/yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8", size = 354007 },
{ url = "https://files.pythonhosted.org/packages/af/ba/1865d85212351ad160f19fb99808acf23aab9a0f8ff31c8c9f1b4d671fc9/yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae", size = 344145 },
{ url = "https://files.pythonhosted.org/packages/94/cb/5c3e975d77755d7b3d5193e92056b19d83752ea2da7ab394e22260a7b824/yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3", size = 336133 },
{ url = "https://files.pythonhosted.org/packages/19/89/b77d3fd249ab52a5c40859815765d35c91425b6bb82e7427ab2f78f5ff55/yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb", size = 347967 },
{ url = "https://files.pythonhosted.org/packages/35/bd/f6b7630ba2cc06c319c3235634c582a6ab014d52311e7d7c22f9518189b5/yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e", size = 346397 },
{ url = "https://files.pythonhosted.org/packages/18/1a/0b4e367d5a72d1f095318344848e93ea70da728118221f84f1bf6c1e39e7/yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59", size = 350206 },
{ url = "https://files.pythonhosted.org/packages/b5/cf/320fff4367341fb77809a2d8d7fe75b5d323a8e1b35710aafe41fdbf327b/yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d", size = 362089 },
{ url = "https://files.pythonhosted.org/packages/57/cf/aadba261d8b920253204085268bad5e8cdd86b50162fcb1b10c10834885a/yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e", size = 366267 },
{ url = "https://files.pythonhosted.org/packages/54/58/fb4cadd81acdee6dafe14abeb258f876e4dd410518099ae9a35c88d8097c/yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a", size = 359141 },
{ url = "https://files.pythonhosted.org/packages/9a/7a/4c571597589da4cd5c14ed2a0b17ac56ec9ee7ee615013f74653169e702d/yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1", size = 84402 },
{ url = "https://files.pythonhosted.org/packages/ae/7b/8600250b3d89b625f1121d897062f629883c2f45339623b69b1747ec65fa/yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5", size = 91030 },
{ url = "https://files.pythonhosted.org/packages/33/85/bd2e2729752ff4c77338e0102914897512e92496375e079ce0150a6dc306/yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50", size = 142644 },
{ url = "https://files.pythonhosted.org/packages/ff/74/1178322cc0f10288d7eefa6e4a85d8d2e28187ccab13d5b844e8b5d7c88d/yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576", size = 94962 },
{ url = "https://files.pythonhosted.org/packages/be/75/79c6acc0261e2c2ae8a1c41cf12265e91628c8c58ae91f5ff59e29c0787f/yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640", size = 92795 },
{ url = "https://files.pythonhosted.org/packages/6b/32/927b2d67a412c31199e83fefdce6e645247b4fb164aa1ecb35a0f9eb2058/yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2", size = 332368 },
{ url = "https://files.pythonhosted.org/packages/19/e5/859fca07169d6eceeaa4fde1997c91d8abde4e9a7c018e371640c2da2b71/yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75", size = 342314 },
{ url = "https://files.pythonhosted.org/packages/08/75/76b63ccd91c9e03ab213ef27ae6add2e3400e77e5cdddf8ed2dbc36e3f21/yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512", size = 341987 },
{ url = "https://files.pythonhosted.org/packages/1a/e1/a097d5755d3ea8479a42856f51d97eeff7a3a7160593332d98f2709b3580/yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba", size = 336914 },
{ url = "https://files.pythonhosted.org/packages/0b/42/e1b4d0e396b7987feceebe565286c27bc085bf07d61a59508cdaf2d45e63/yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb", size = 325765 },
{ url = "https://files.pythonhosted.org/packages/7e/18/03a5834ccc9177f97ca1bbb245b93c13e58e8225276f01eedc4cc98ab820/yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272", size = 344444 },
{ url = "https://files.pythonhosted.org/packages/c8/03/a713633bdde0640b0472aa197b5b86e90fbc4c5bc05b727b714cd8a40e6d/yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6", size = 340760 },
{ url = "https://files.pythonhosted.org/packages/eb/99/f6567e3f3bbad8fd101886ea0276c68ecb86a2b58be0f64077396cd4b95e/yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e", size = 346484 },
{ url = "https://files.pythonhosted.org/packages/8e/a9/84717c896b2fc6cb15bd4eecd64e34a2f0a9fd6669e69170c73a8b46795a/yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb", size = 359864 },
{ url = "https://files.pythonhosted.org/packages/1e/2e/d0f5f1bef7ee93ed17e739ec8dbcb47794af891f7d165fa6014517b48169/yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393", size = 364537 },
{ url = "https://files.pythonhosted.org/packages/97/8a/568d07c5d4964da5b02621a517532adb8ec5ba181ad1687191fffeda0ab6/yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285", size = 357861 },
{ url = "https://files.pythonhosted.org/packages/7d/e3/924c3f64b6b3077889df9a1ece1ed8947e7b61b0a933f2ec93041990a677/yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2", size = 84097 },
{ url = "https://files.pythonhosted.org/packages/34/45/0e055320daaabfc169b21ff6174567b2c910c45617b0d79c68d7ab349b02/yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477", size = 90399 },
{ url = "https://files.pythonhosted.org/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109 },
]
[[package]]