From 1b09b085a741bdc87b1786410edef04c34b28f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Sun, 10 Nov 2024 11:00:16 -0300 Subject: [PATCH 01/16] preparing new version --- pyproject.toml | 2 +- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b9e6bc986..7102c9efa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.76.9" +version = "0.79.0" description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks." readme = "README.md" requires-python = ">=3.10,<=3.13" diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 0a2f02a59..353c11b88 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.76.9" +__version__ = "0.79.0" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 447cfcb86..136fa2026 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.9,<1.0.0" + "crewai[tools]>=0.79.0,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 1ef8f7b36..032f7ee9b 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.9,<1.0.0", + "crewai[tools]>=0.79.0,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index 53f304283..53a81a620 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.76.9,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.79.0,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index 33d5c58af..89275861d 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.9,<1.0.0" + "crewai[tools]>=0.79.0,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index 849298b6b..b60edf21b 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.9" + "crewai[tools]>=0.79.0" ] diff --git a/uv.lock b/uv.lock index f7f6a1839..0034b42e0 100644 --- a/uv.lock +++ b/uv.lock @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.76.9" +version = "0.79.0" source = { editable = "." } dependencies = [ { name = "appdirs" }, From 40d378abfbcdd2a71fdb4999f679a0a4b9ff6552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Sun, 10 Nov 2024 11:36:03 -0300 Subject: [PATCH 02/16] updating LLM docs --- docs/concepts/llms.mdx | 132 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 8 deletions(-) diff --git a/docs/concepts/llms.mdx b/docs/concepts/llms.mdx index d432070d4..5757feca3 100644 --- a/docs/concepts/llms.mdx +++ b/docs/concepts/llms.mdx @@ -25,7 +25,100 @@ By default, CrewAI uses the `gpt-4o-mini` model. It uses environment variables i - `OPENAI_API_BASE` - `OPENAI_API_KEY` -### 2. Custom LLM Objects +### 2. Updating YAML files + +You can update the `agents.yml` file to refer to the LLM you want to use: + +```yaml Code +researcher: + role: Research Specialist + goal: Conduct comprehensive research and analysis to gather relevant information, + synthesize findings, and produce well-documented insights. + backstory: A dedicated research professional with years of experience in academic + investigation, literature review, and data analysis, known for thorough and + methodical approaches to complex research questions. + verbose: true + llm: openai/gpt-4o + # llm: azure/gpt-4o-mini + # llm: gemini/gemini-pro + # llm: anthropic/claude-3-5-sonnet-20240620 + # llm: bedrock/anthropic.claude-3-sonnet-20240229-v1:0 + # llm: mistral/mistral-large-latest + # llm: ollama/llama3:70b + # llm: groq/llama-3.2-90b-vision-preview + # llm: watsonx/meta-llama/llama-3-1-70b-instruct + # ... +``` + +Keep in mind that you will need to set certain ENV vars depending on the model you are +using to account for the credentials or set a custom LLM object like described below. +Here are some of the required ENV vars for some of the LLM integrations: + + + + ```python Code + OPENAI_API_KEY= + OPENAI_API_BASE= + OPENAI_MODEL_NAME= + OPENAI_ORGANIZATION= # OPTIONAL + OPENAI_API_BASE= # OPTIONAL + ``` + + + + ```python Code + ANTHROPIC_API_KEY= + ``` + + + + ```python Code + GEMINI_API_KEY= + ``` + + + + ```python Code + AZURE_API_KEY= # "my-azure-api-key" + AZURE_API_BASE= # "https://example-endpoint.openai.azure.com" + AZURE_API_VERSION= # "2023-05-15" + AZURE_AD_TOKEN= # Optional + AZURE_API_TYPE= # Optional + ``` + + + + ```python Code + AWS_ACCESS_KEY_ID= + AWS_SECRET_ACCESS_KEY= + AWS_DEFAULT_REGION= + ``` + + + + ```python Code + MISTRAL_API_KEY= + ``` + + + + ```python Code + GROQ_API_KEY= + ``` + + + + ```python Code + WATSONX_URL= # (required) Base URL of your WatsonX instance + WATSONX_APIKEY= # (required) IBM cloud API key + WATSONX_TOKEN= # (required) IAM auth token (alternative to APIKEY) + WATSONX_PROJECT_ID= # (optional) Project ID of your WatsonX instance + WATSONX_DEPLOYMENT_SPACE_ID= # (optional) ID of deployment space for deployed models + ``` + + + +### 3. Custom LLM Objects Pass a custom LLM implementation or object from another library. @@ -102,7 +195,7 @@ When configuring an LLM for your agent, you have access to a wide range of param These are examples of how to configure LLMs for your agent. - + ```python Code @@ -133,10 +226,10 @@ These are examples of how to configure LLMs for your agent. model="cerebras/llama-3.1-70b", api_key="your-api-key-here" ) - agent = Agent(llm=llm, ...) + agent = Agent(llm=llm, ...) ``` - + CrewAI supports using Ollama for running open-source models locally: @@ -150,7 +243,7 @@ These are examples of how to configure LLMs for your agent. agent = Agent( llm=LLM( - model="ollama/llama3.1", + model="ollama/llama3.1", base_url="http://localhost:11434" ), ... @@ -164,7 +257,7 @@ These are examples of how to configure LLMs for your agent. from crewai import LLM llm = LLM( - model="groq/llama3-8b-8192", + model="groq/llama3-8b-8192", api_key="your-api-key-here" ) agent = Agent(llm=llm, ...) @@ -189,7 +282,7 @@ These are examples of how to configure LLMs for your agent. from crewai import LLM llm = LLM( - model="fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct", + model="fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct", api_key="your-api-key-here" ) agent = Agent(llm=llm, ...) @@ -224,6 +317,29 @@ These are examples of how to configure LLMs for your agent. + You can use IBM Watson by seeting the following ENV vars: + + ```python Code + WATSONX_URL= + WATSONX_APIKEY= + WATSONX_PROJECT_ID= + ``` + + You can then define your agents llms by updating the `agents.yml` + + ```yaml Code + researcher: + role: Research Specialist + goal: Conduct comprehensive research and analysis to gather relevant information, + synthesize findings, and produce well-documented insights. + backstory: A dedicated research professional with years of experience in academic + investigation, literature review, and data analysis, known for thorough and + methodical approaches to complex research questions. + verbose: true + llm: watsonx/meta-llama/llama-3-1-70b-instruct + ``` + + You can also set up agents more dynamically as a base level LLM instance, like bellow: ```python Code from crewai import LLM @@ -247,7 +363,7 @@ These are examples of how to configure LLMs for your agent. api_key="your-api-key-here", base_url="your_api_endpoint" ) - agent = Agent(llm=llm, ...) + agent = Agent(llm=llm, ...) ``` From 50bf146d1e3319c84d342d42f9f914e8744fdcba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Sun, 10 Nov 2024 20:47:56 -0300 Subject: [PATCH 03/16] preparing new version --- pyproject.toml | 8 ++++---- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- .../cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 14 +++++++------- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7102c9efa..d57811f32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.79.0" +version = "0.79.1" description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks." readme = "README.md" requires-python = ">=3.10,<=3.13" @@ -16,7 +16,7 @@ dependencies = [ "opentelemetry-exporter-otlp-proto-http>=1.22.0", "instructor>=1.3.3", "regex>=2024.9.11", - "crewai-tools>=0.13.4", + "crewai-tools>=0.14.0", "click>=8.1.7", "python-dotenv>=1.0.0", "appdirs>=1.4.4", @@ -37,7 +37,7 @@ Documentation = "https://docs.crewai.com" Repository = "https://github.com/crewAIInc/crewAI" [project.optional-dependencies] -tools = ["crewai-tools>=0.13.4"] +tools = ["crewai-tools>=0.14.0"] agentops = ["agentops>=0.3.0"] [tool.uv] @@ -52,7 +52,7 @@ dev-dependencies = [ "mkdocs-material-extensions>=1.3.1", "pillow>=10.2.0", "cairosvg>=2.7.1", - "crewai-tools>=0.13.4", + "crewai-tools>=0.14.0", "pytest>=8.0.0", "pytest-vcr>=1.0.2", "python-dotenv>=1.0.0", diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 353c11b88..1da8e2455 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.79.0" +__version__ = "0.79.1" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 136fa2026..4c5a7cb39 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.0,<1.0.0" + "crewai[tools]>=0.79.1,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 032f7ee9b..a90efe904 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.0,<1.0.0", + "crewai[tools]>=0.79.1,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index 53a81a620..b049a98f5 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.79.0,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.79.1,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index 89275861d..ee36d771b 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.0,<1.0.0" + "crewai[tools]>=0.79.1,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index b60edf21b..a60e0b668 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.0" + "crewai[tools]>=0.79.1" ] diff --git a/uv.lock b/uv.lock index 0034b42e0..a71556e2d 100644 --- a/uv.lock +++ b/uv.lock @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.79.0" +version = "0.79.1" source = { editable = "." } dependencies = [ { name = "appdirs" }, @@ -665,8 +665,8 @@ requires-dist = [ { name = "auth0-python", specifier = ">=4.7.1" }, { name = "chromadb", specifier = ">=0.4.24" }, { name = "click", specifier = ">=8.1.7" }, - { name = "crewai-tools", specifier = ">=0.13.4" }, - { name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.13.4" }, + { name = "crewai-tools", specifier = ">=0.14.0" }, + { name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.14.0" }, { name = "instructor", specifier = ">=1.3.3" }, { name = "json-repair", specifier = ">=0.25.2" }, { name = "jsonref", specifier = ">=1.1.0" }, @@ -688,7 +688,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "cairosvg", specifier = ">=2.7.1" }, - { name = "crewai-tools", specifier = ">=0.13.4" }, + { name = "crewai-tools", specifier = ">=0.14.0" }, { name = "mkdocs", specifier = ">=1.4.3" }, { name = "mkdocs-material", specifier = ">=9.5.7" }, { name = "mkdocs-material-extensions", specifier = ">=1.3.1" }, @@ -707,7 +707,7 @@ dev = [ [[package]] name = "crewai-tools" -version = "0.13.4" +version = "0.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -725,9 +725,9 @@ dependencies = [ { name = "requests" }, { name = "selenium" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/bd/eff7b633a0b28ff4ed115adde1499e3dcc683e4f0b5c378a4c6f5c0c1bf6/crewai_tools-0.13.4.tar.gz", hash = "sha256:b6ac527633b7018471d892c21ac96bc961a86b6626d996b1ed7d53cd481d4505", size = 816588 } +sdist = { url = "https://files.pythonhosted.org/packages/64/bd/eff7b633a0b28ff4ed115adde1499e3dcc683e4f0b5c378a4c6f5c0c1bf6/crewai_tools-0.14.0.tar.gz", hash = "sha256:b6ac527633b7018471d892c21ac96bc961a86b6626d996b1ed7d53cd481d4505", size = 816588 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/40/93cd347d854059cf5e54a81b70f896deea7ad1f03e9c024549eb323c4da5/crewai_tools-0.13.4-py3-none-any.whl", hash = "sha256:eda78fe3c4df57676259d8dd6b2610fa31f89b90909512f15893adb57fb9e825", size = 463703 }, + { url = "https://files.pythonhosted.org/packages/6c/40/93cd347d854059cf5e54a81b70f896deea7ad1f03e9c024549eb323c4da5/crewai_tools-0.14.0-py3-none-any.whl", hash = "sha256:eda78fe3c4df57676259d8dd6b2610fa31f89b90909512f15893adb57fb9e825", size = 463703 }, ] [[package]] From 40a676b7ac8a2d429f99a27917dc25133f44c583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Sun, 10 Nov 2024 21:16:36 -0300 Subject: [PATCH 04/16] curring new version --- pyproject.toml | 2 +- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- src/crewai/memory/contextual/contextual_memory.py | 4 ---- uv.lock | 2 +- 9 files changed, 8 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d57811f32..31853a82d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.79.1" +version = "0.79.2" description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks." readme = "README.md" requires-python = ">=3.10,<=3.13" diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 1da8e2455..77cbc19f5 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.79.1" +__version__ = "0.79.2" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 4c5a7cb39..fea666134 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.1,<1.0.0" + "crewai[tools]>=0.79.2,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index a90efe904..3a708f780 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.1,<1.0.0", + "crewai[tools]>=0.79.2,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index b049a98f5..616fa0836 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.79.1,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.79.2,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index ee36d771b..ca8d99629 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.1,<1.0.0" + "crewai[tools]>=0.79.2,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index a60e0b668..8a412ab25 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.1" + "crewai[tools]>=0.79.2" ] diff --git a/src/crewai/memory/contextual/contextual_memory.py b/src/crewai/memory/contextual/contextual_memory.py index 3d3a9c6c1..5d91cf47d 100644 --- a/src/crewai/memory/contextual/contextual_memory.py +++ b/src/crewai/memory/contextual/contextual_memory.py @@ -34,7 +34,6 @@ class ContextualMemory: formatted_results = "\n".join( [f"- {result['context']}" for result in stm_results] ) - print("formatted_results stm", formatted_results) return f"Recent Insights:\n{formatted_results}" if stm_results else "" def _fetch_ltm_context(self, task) -> Optional[str]: @@ -54,8 +53,6 @@ class ContextualMemory: formatted_results = list(dict.fromkeys(formatted_results)) formatted_results = "\n".join([f"- {result}" for result in formatted_results]) # type: ignore # Incompatible types in assignment (expression has type "str", variable has type "list[str]") - print("formatted_results ltm", formatted_results) - return f"Historical Data:\n{formatted_results}" if ltm_results else "" def _fetch_entity_context(self, query) -> str: @@ -67,5 +64,4 @@ class ContextualMemory: formatted_results = "\n".join( [f"- {result['context']}" for result in em_results] # type: ignore # Invalid index type "str" for "str"; expected type "SupportsIndex | slice" ) - print("formatted_results em", formatted_results) return f"Entities:\n{formatted_results}" if em_results else "" diff --git a/uv.lock b/uv.lock index a71556e2d..b59d377ee 100644 --- a/uv.lock +++ b/uv.lock @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.79.1" +version = "0.79.2" source = { editable = "." } dependencies = [ { name = "appdirs" }, From 49220ec163f87aa4a289231a119043409d8d89ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Sun, 10 Nov 2024 23:46:38 -0300 Subject: [PATCH 05/16] preparing new version --- pyproject.toml | 2 +- src/crewai/agent.py | 75 ++++++++++++++---------- src/crewai/agents/crew_agent_executor.py | 7 ++- uv.lock | 6 +- 4 files changed, 53 insertions(+), 37 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 31853a82d..b9e403140 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.79.2" +version = "0.79.3" description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks." readme = "README.md" requires-python = ">=3.10,<=3.13" diff --git a/src/crewai/agent.py b/src/crewai/agent.py index d30746808..c8998fd19 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -123,6 +123,11 @@ class Agent(BaseAgent): @model_validator(mode="after") def post_init_setup(self): self.agent_ops_agent_name = self.role + unnacepted_attributes = [ + "AWS_ACCESS_KEY_ID", + "AWS_SECRET_ACCESS_KEY", + "AWS_REGION_NAME", + ] # Handle different cases for self.llm if isinstance(self.llm, str): @@ -146,39 +151,49 @@ class Agent(BaseAgent): if api_base: llm_params["base_url"] = api_base + set_provider = model_name.split("/")[0] if "/" in model_name else "openai" + # Iterate over all environment variables to find matching API keys or use defaults for provider, env_vars in ENV_VARS.items(): - for env_var in env_vars: - # Check if the environment variable is set - if "key_name" in env_var: - env_value = os.environ.get(env_var["key_name"]) - if env_value: - # Map key names containing "API_KEY" to "api_key" - key_name = ( - "api_key" - if "API_KEY" in env_var["key_name"] - else env_var["key_name"] + if provider == set_provider: + for env_var in env_vars: + if env_var["key_name"] in unnacepted_attributes: + continue + # Check if the environment variable is set + if "key_name" in env_var: + env_value = os.environ.get(env_var["key_name"]) + print( + f"Checking env var {env_var['key_name']}: {env_value}" ) - # Map key names containing "API_BASE" to "api_base" - key_name = ( - "api_base" - if "API_BASE" in env_var["key_name"] - else key_name - ) - # Map key names containing "API_VERSION" to "api_version" - key_name = ( - "api_version" - if "API_VERSION" in env_var["key_name"] - else key_name - ) - llm_params[key_name] = env_value - # Check for default values if the environment variable is not set - elif env_var.get("default", False): - for key, value in env_var.items(): - if key not in ["prompt", "key_name", "default"]: - # Only add default if the key is already set in os.environ - if key in os.environ: - llm_params[key] = value + if env_value: + # Map key names containing "API_KEY" to "api_key" + key_name = ( + "api_key" + if "API_KEY" in env_var["key_name"] + else env_var["key_name"] + ) + # Map key names containing "API_BASE" to "api_base" + key_name = ( + "api_base" + if "API_BASE" in env_var["key_name"] + else key_name + ) + # Map key names containing "API_VERSION" to "api_version" + key_name = ( + "api_version" + if "API_VERSION" in env_var["key_name"] + else key_name + ) + print(f"Mapped key name: {key_name}") + llm_params[key_name] = env_value + # Check for default values if the environment variable is not set + elif env_var.get("default", False): + for key, value in env_var.items(): + if key not in ["prompt", "key_name", "default"]: + # Only add default if the key is already set in os.environ + if key in os.environ: + print(f"Using default value for {key}: {value}") + llm_params[key] = value self.llm = LLM(**llm_params) else: diff --git a/src/crewai/agents/crew_agent_executor.py b/src/crewai/agents/crew_agent_executor.py index 9e9ad9c7e..aa641e061 100644 --- a/src/crewai/agents/crew_agent_executor.py +++ b/src/crewai/agents/crew_agent_executor.py @@ -332,9 +332,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): if self.crew is not None and hasattr(self.crew, "_train_iteration"): train_iteration = self.crew._train_iteration if agent_id in training_data and isinstance(train_iteration, int): - training_data[agent_id][train_iteration][ - "improved_output" - ] = result.output + training_data[agent_id][train_iteration]["improved_output"] = ( + result.output + ) training_handler.save(training_data) else: self._logger.log( @@ -385,4 +385,5 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): return CrewAgentParser(agent=self.agent).parse(answer) def _format_msg(self, prompt: str, role: str = "user") -> Dict[str, str]: + prompt = prompt.rstrip() return {"role": role, "content": prompt} diff --git a/uv.lock b/uv.lock index b59d377ee..0b541a3e7 100644 --- a/uv.lock +++ b/uv.lock @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.79.2" +version = "0.79.3" source = { editable = "." } dependencies = [ { name = "appdirs" }, @@ -725,9 +725,9 @@ dependencies = [ { name = "requests" }, { name = "selenium" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/bd/eff7b633a0b28ff4ed115adde1499e3dcc683e4f0b5c378a4c6f5c0c1bf6/crewai_tools-0.14.0.tar.gz", hash = "sha256:b6ac527633b7018471d892c21ac96bc961a86b6626d996b1ed7d53cd481d4505", size = 816588 } +sdist = { url = "https://files.pythonhosted.org/packages/9b/6d/4fa91b481b120f83bb58f365203d8aa8564e8ced1035d79f8aedb7d71e2f/crewai_tools-0.14.0.tar.gz", hash = "sha256:510f3a194bcda4fdae4314bd775521964b5f229ddbe451e5d9e0216cae57f4e3", size = 815892 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/40/93cd347d854059cf5e54a81b70f896deea7ad1f03e9c024549eb323c4da5/crewai_tools-0.14.0-py3-none-any.whl", hash = "sha256:eda78fe3c4df57676259d8dd6b2610fa31f89b90909512f15893adb57fb9e825", size = 463703 }, + { url = "https://files.pythonhosted.org/packages/c8/ed/9f4e64e1507062957b0118085332d38b621c1000874baef2d1c4069bfd97/crewai_tools-0.14.0-py3-none-any.whl", hash = "sha256:0a804a828c29869c3af3253f4fc4c3967a3f80f06dab22e9bbe9526608a31564", size = 462980 }, ] [[package]] From 6d677541c738edd10e80dbe8d5d07bb4f2b60d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Mon, 11 Nov 2024 00:03:52 -0300 Subject: [PATCH 06/16] preparing new version --- pyproject.toml | 2 +- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b9e403140..8a5d2f1b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.79.3" +version = "0.79.4" description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks." readme = "README.md" requires-python = ">=3.10,<=3.13" diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 77cbc19f5..8d01b7cb5 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.79.2" +__version__ = "0.79.4" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index fea666134..8606a2134 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.2,<1.0.0" + "crewai[tools]>=0.79.4,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 3a708f780..69e26f220 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.2,<1.0.0", + "crewai[tools]>=0.79.4,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index 616fa0836..b09ce842f 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.79.2,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.79.4,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index ca8d99629..16d6360db 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.2,<1.0.0" + "crewai[tools]>=0.79.4,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index 8a412ab25..dc928ad1e 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.2" + "crewai[tools]>=0.79.4" ] diff --git a/uv.lock b/uv.lock index 0b541a3e7..844e0b862 100644 --- a/uv.lock +++ b/uv.lock @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.79.3" +version = "0.79.4" source = { editable = "." } dependencies = [ { name = "appdirs" }, From 8610faef22024fb24486851402ae6ee7df95625c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Mon, 11 Nov 2024 02:29:40 -0300 Subject: [PATCH 07/16] add missing init --- src/crewai/tools/cache_tools/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/crewai/tools/cache_tools/__init__.py diff --git a/src/crewai/tools/cache_tools/__init__.py b/src/crewai/tools/cache_tools/__init__.py new file mode 100644 index 000000000..e69de29bb From 4afb022572e2f48ee5379aaa591ead833ec0959b Mon Sep 17 00:00:00 2001 From: Thiago Moretto Date: Tue, 12 Nov 2024 15:04:57 -0300 Subject: [PATCH 08/16] fix LiteLLM callback replacement --- src/crewai/llm.py | 16 +- .../test_llm_callback_replacement.yaml | 205 ++++++++++++++++++ tests/llm_test.py | 30 +++ 3 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 tests/cassettes/test_llm_callback_replacement.yaml create mode 100644 tests/llm_test.py diff --git a/src/crewai/llm.py b/src/crewai/llm.py index 577cb6a43..7ea8155a6 100644 --- a/src/crewai/llm.py +++ b/src/crewai/llm.py @@ -118,12 +118,12 @@ class LLM: litellm.drop_params = True litellm.set_verbose = False - litellm.callbacks = callbacks + self.set_callbacks(callbacks) def call(self, messages: List[Dict[str, str]], callbacks: List[Any] = []) -> str: with suppress_warnings(): if callbacks and len(callbacks) > 0: - litellm.callbacks = callbacks + self.set_callbacks(callbacks) try: params = { @@ -181,3 +181,15 @@ class LLM: def get_context_window_size(self) -> int: # Only using 75% of the context window size to avoid cutting the message in the middle return int(LLM_CONTEXT_WINDOW_SIZES.get(self.model, 8192) * 0.75) + + def set_callbacks(self, callbacks: List[Any]): + callback_types = [type(callback) for callback in callbacks] + for callback in litellm.success_callback[:]: + if type(callback) in callback_types: + litellm.success_callback.remove(callback) + + for callback in litellm._async_success_callback[:]: + if type(callback) in callback_types: + litellm._async_success_callback.remove(callback) + + litellm.callbacks = callbacks diff --git a/tests/cassettes/test_llm_callback_replacement.yaml b/tests/cassettes/test_llm_callback_replacement.yaml new file mode 100644 index 000000000..7b8f7e707 --- /dev/null +++ b/tests/cassettes/test_llm_callback_replacement.yaml @@ -0,0 +1,205 @@ +interactions: +- request: + body: '{"messages": [{"role": "user", "content": "Hello, world!"}], "model": "gpt-4o-mini", + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '101' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - Linux + 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.11.9 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA4xSwWrcMBS8+ytedY6LvWvYZi8lpZSkBJLSQiChGK307FUi66nSc9Ml7L8H2e56 + l7bQiw8zb8Yzg14yAGG0WINQW8mq8za/+Oqv5MUmXv+8+/Hl3uO3j59u1efreHO+/PAszpKCNo+o + +LfqraLOW2RDbqRVQMmYXMvVsqyWy1VVDERHGm2StZ7zivLOOJMvikWVF6u8fDept2QURrGGhwwA + 4GX4ppxO4y+xhsFrQDqMUbYo1ocjABHIJkTIGE1k6ViczaQix+iG6JdoLb2BS3oGJR1cwSiAHfXA + pOXu/bEwYNNHmcK73toJ3x+SWGp9oE2c+APeGGfitg4oI7n018jkxcDuM4DvQ+P+pITwgTrPNdMT + umRYlqOdmHeeyfOJY2JpZ3gxjXRqVmtkaWw8GkwoqbaoZ+W8ruy1oSMiO6r8Z5a/eY+1jWv/x34m + lELPqGsfUBt12nc+C5ge4b/ODhMPgUXcRcauboxrMfhgxifQ+LrYyEKXi6opRbbPXgEAAP//AwAM + DMWoEAMAAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e185b2c1b790303-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 12 Nov 2024 17:49:00 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=l.QrRLcNZkML_KSfxjir6YCV35B8GNTitBTNh7cPGc4-1731433740-1.0.1.1-j1ejlmykyoI8yk6i6pQjtPoovGzfxI2f5vG6u0EqodQMjCvhbHfNyN_wmYkeT._BMvFi.zDQ8m_PqEHr8tSdEQ; + path=/; expires=Tue, 12-Nov-24 18:19:00 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=jcCDyMK__Fd0V5DMeqt9yXdlKc7Hsw87a1K01pZu9l0-1731433740848-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: + - user-tqfegqsiobpvvjmn0giaipdq + openai-processing-ms: + - '322' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '200000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '199978' + x-ratelimit-reset-requests: + - 8.64s + x-ratelimit-reset-tokens: + - 6ms + x-request-id: + - req_037288753767e763a51a04eae757ca84 + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "user", "content": "Hello, world from another agent!"}], + "model": "gpt-4o-mini", "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '120' + content-type: + - application/json + cookie: + - __cf_bm=l.QrRLcNZkML_KSfxjir6YCV35B8GNTitBTNh7cPGc4-1731433740-1.0.1.1-j1ejlmykyoI8yk6i6pQjtPoovGzfxI2f5vG6u0EqodQMjCvhbHfNyN_wmYkeT._BMvFi.zDQ8m_PqEHr8tSdEQ; + _cfuvid=jcCDyMK__Fd0V5DMeqt9yXdlKc7Hsw87a1K01pZu9l0-1731433740848-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - Linux + 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.11.9 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA4xSy27bMBC86yu2PFuBZAt14UvRU5MA7aVAEKAIBJpcSUwoLkuu6jiB/z3QI5aM + tkAvPMzsDGZ2+ZoACKPFDoRqJKvW2/TLD3+z//oiD8dfL7d339zvW125x9zX90/3mVj1Cto/ouJ3 + 1ZWi1ltkQ26kVUDJ2Lvm201ebDbbIh+IljTaXlZ7TgtKW+NMus7WRZpt0/zTpG7IKIxiBz8TAIDX + 4e1zOo3PYgfZ6h1pMUZZo9idhwBEINsjQsZoIkvHYjWTihyjG6Jfo7X0Ab4bhcAEipxDxXAw3IB0 + xA0GkDU6voJrOoCSDm5gNIUjdcCk5fHz0jxg1UXZF3SdtRN+Oqe1VPtA+zjxZ7wyzsSmDCgjuT5Z + ZPJiYE8JwMOwle6iqPCBWs8l0xO63jAvRjsx32JBfpxIJpZ2xjfTJi/dSo0sjY2LrQolVYN6Vs4n + kJ02tCCSRec/w/zNe+xtXP0/9jOhFHpGXfqA2qjLwvNYwP6n/mvsvOMhsIjHyNiWlXE1Bh/M+E8q + X2Z7mel8XVS5SE7JGwAAAP//AwA/cK4yNQMAAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e185b31398a0303-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 12 Nov 2024 17:49:02 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - user-tqfegqsiobpvvjmn0giaipdq + openai-processing-ms: + - '889' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '200000' + x-ratelimit-remaining-requests: + - '9998' + x-ratelimit-remaining-tokens: + - '199975' + x-ratelimit-reset-requests: + - 16.489s + x-ratelimit-reset-tokens: + - 7ms + x-request-id: + - req_bde3810b36a4859688e53d1df64bdd20 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/llm_test.py b/tests/llm_test.py new file mode 100644 index 000000000..e824d54c9 --- /dev/null +++ b/tests/llm_test.py @@ -0,0 +1,30 @@ +import pytest + +from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess +from crewai.llm import LLM +from crewai.utilities.token_counter_callback import TokenCalcHandler + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_llm_callback_replacement(): + llm = LLM(model="gpt-4o-mini") + + calc_handler_1 = TokenCalcHandler(token_cost_process=TokenProcess()) + calc_handler_2 = TokenCalcHandler(token_cost_process=TokenProcess()) + + llm.call( + messages=[{"role": "user", "content": "Hello, world!"}], + callbacks=[calc_handler_1], + ) + usage_metrics_1 = calc_handler_1.token_cost_process.get_summary() + + llm.call( + messages=[{"role": "user", "content": "Hello, world from another agent!"}], + callbacks=[calc_handler_2], + ) + usage_metrics_2 = calc_handler_2.token_cost_process.get_summary() + + # The first handler should not have been updated + assert usage_metrics_1.successful_requests == 1 + assert usage_metrics_2.successful_requests == 1 + assert usage_metrics_1 == calc_handler_1.token_cost_process.get_summary() From c7b9ae02fd48ff0d43852398a11f945a69951fc9 Mon Sep 17 00:00:00 2001 From: Thiago Moretto Date: Tue, 12 Nov 2024 16:43:43 -0300 Subject: [PATCH 09/16] fix test_agent_usage_metrics_are_captured_for_hierarchical_process --- tests/crew_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/crew_test.py b/tests/crew_test.py index 24b892271..5f39557e0 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -1280,10 +1280,10 @@ def test_agent_usage_metrics_are_captured_for_hierarchical_process(): assert result.raw == "Howdy!" assert result.token_usage == UsageMetrics( - total_tokens=2626, - prompt_tokens=2482, - completion_tokens=144, - successful_requests=5, + total_tokens=1673, + prompt_tokens=1562, + completion_tokens=111, + successful_requests=3, ) From bcfcf88e789e14ca20113b0a3268a2cecb380836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Tue, 12 Nov 2024 18:37:50 -0300 Subject: [PATCH 10/16] removing prints --- src/crewai/agent.py | 5 ----- src/crewai/cli/run_crew.py | 1 - 2 files changed, 6 deletions(-) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index c8998fd19..fc43137a2 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -162,9 +162,6 @@ class Agent(BaseAgent): # Check if the environment variable is set if "key_name" in env_var: env_value = os.environ.get(env_var["key_name"]) - print( - f"Checking env var {env_var['key_name']}: {env_value}" - ) if env_value: # Map key names containing "API_KEY" to "api_key" key_name = ( @@ -184,7 +181,6 @@ class Agent(BaseAgent): if "API_VERSION" in env_var["key_name"] else key_name ) - print(f"Mapped key name: {key_name}") llm_params[key_name] = env_value # Check for default values if the environment variable is not set elif env_var.get("default", False): @@ -192,7 +188,6 @@ class Agent(BaseAgent): if key not in ["prompt", "key_name", "default"]: # Only add default if the key is already set in os.environ if key in os.environ: - print(f"Using default value for {key}: {value}") llm_params[key] = value self.llm = LLM(**llm_params) diff --git a/src/crewai/cli/run_crew.py b/src/crewai/cli/run_crew.py index 20d6aed01..5450cf32b 100644 --- a/src/crewai/cli/run_crew.py +++ b/src/crewai/cli/run_crew.py @@ -24,7 +24,6 @@ def run_crew() -> None: f"Please run `crewai update` to update your pyproject.toml to use uv.", fg="red", ) - print() try: subprocess.run(command, capture_output=False, text=True, check=True) From b98f8f9fe1f79d9d380f3bd8ddb6bc800cf59d0e Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Wed, 13 Nov 2024 10:07:28 -0300 Subject: [PATCH 11/16] fix: Step callback issue (#1595) * fix: Step callback issue * fix: Add empty thought since its required --- src/crewai/agents/crew_agent_executor.py | 37 ++++++++++++------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/crewai/agents/crew_agent_executor.py b/src/crewai/agents/crew_agent_executor.py index aa641e061..bf14e6915 100644 --- a/src/crewai/agents/crew_agent_executor.py +++ b/src/crewai/agents/crew_agent_executor.py @@ -145,25 +145,26 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): formatted_answer.result = action_result self._show_logs(formatted_answer) - if self.step_callback: - self.step_callback(formatted_answer) + if self.step_callback: + self.step_callback(formatted_answer) - if self._should_force_answer(): - if self.have_forced_answer: - return AgentFinish( - output=self._i18n.errors( - "force_final_answer_error" - ).format(formatted_answer.text), - text=formatted_answer.text, - ) - else: - formatted_answer.text += ( - f'\n{self._i18n.errors("force_final_answer")}' - ) - self.have_forced_answer = True - self.messages.append( - self._format_msg(formatted_answer.text, role="assistant") - ) + if self._should_force_answer(): + if self.have_forced_answer: + return AgentFinish( + thought="", + output=self._i18n.errors( + "force_final_answer_error" + ).format(formatted_answer.text), + text=formatted_answer.text, + ) + else: + formatted_answer.text += ( + f'\n{self._i18n.errors("force_final_answer")}' + ) + self.have_forced_answer = True + self.messages.append( + self._format_msg(formatted_answer.text, role="assistant") + ) except OutputParserException as e: self.messages.append({"role": "user", "content": e.error}) From 36aa4bcb4615bc5da33d65bd24dbceb80291ce51 Mon Sep 17 00:00:00 2001 From: Thiago Moretto Date: Wed, 13 Nov 2024 10:16:30 -0300 Subject: [PATCH 12/16] Cached prompt tokens on usage metrics --- .../utilities/base_token_process.py | 6 + src/crewai/types/usage_metrics.py | 5 + .../utilities/token_counter_callback.py | 13 +- .../test_crew_kickoff_usage_metrics.yaml | 206 ++++++++++-------- tests/crew_test.py | 2 + 5 files changed, 137 insertions(+), 95 deletions(-) diff --git a/src/crewai/agents/agent_builder/utilities/base_token_process.py b/src/crewai/agents/agent_builder/utilities/base_token_process.py index e971d018e..91df8140e 100644 --- a/src/crewai/agents/agent_builder/utilities/base_token_process.py +++ b/src/crewai/agents/agent_builder/utilities/base_token_process.py @@ -4,6 +4,7 @@ from crewai.types.usage_metrics import UsageMetrics class TokenProcess: total_tokens: int = 0 prompt_tokens: int = 0 + cached_prompt_tokens: int = 0 completion_tokens: int = 0 successful_requests: int = 0 @@ -15,6 +16,10 @@ class TokenProcess: self.completion_tokens = self.completion_tokens + tokens self.total_tokens = self.total_tokens + tokens + def sum_cached_prompt_tokens(self, tokens: int): + self.cached_prompt_tokens = self.cached_prompt_tokens + tokens + self.total_tokens = self.total_tokens + tokens + def sum_successful_requests(self, requests: int): self.successful_requests = self.successful_requests + requests @@ -22,6 +27,7 @@ class TokenProcess: return UsageMetrics( total_tokens=self.total_tokens, prompt_tokens=self.prompt_tokens, + cached_prompt_tokens=self.cached_prompt_tokens, completion_tokens=self.completion_tokens, successful_requests=self.successful_requests, ) diff --git a/src/crewai/types/usage_metrics.py b/src/crewai/types/usage_metrics.py index a5cee6a0f..e87a79e33 100644 --- a/src/crewai/types/usage_metrics.py +++ b/src/crewai/types/usage_metrics.py @@ -8,6 +8,7 @@ class UsageMetrics(BaseModel): Attributes: total_tokens: Total number of tokens used. prompt_tokens: Number of tokens used in prompts. + cached_prompt_tokens: Number of cached prompt tokens used. completion_tokens: Number of tokens used in completions. successful_requests: Number of successful requests made. """ @@ -16,6 +17,9 @@ class UsageMetrics(BaseModel): prompt_tokens: int = Field( default=0, description="Number of tokens used in prompts." ) + cached_prompt_tokens: int = Field( + default=0, description="Number of cached prompt tokens used." + ) completion_tokens: int = Field( default=0, description="Number of tokens used in completions." ) @@ -32,5 +36,6 @@ class UsageMetrics(BaseModel): """ self.total_tokens += usage_metrics.total_tokens self.prompt_tokens += usage_metrics.prompt_tokens + self.cached_prompt_tokens += usage_metrics.cached_prompt_tokens self.completion_tokens += usage_metrics.completion_tokens self.successful_requests += usage_metrics.successful_requests diff --git a/src/crewai/utilities/token_counter_callback.py b/src/crewai/utilities/token_counter_callback.py index 1b6215232..feaaa4c22 100644 --- a/src/crewai/utilities/token_counter_callback.py +++ b/src/crewai/utilities/token_counter_callback.py @@ -1,5 +1,5 @@ from litellm.integrations.custom_logger import CustomLogger - +from litellm.types.utils import Usage from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess @@ -11,8 +11,11 @@ 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(response_obj["usage"].prompt_tokens) - self.token_cost_process.sum_completion_tokens( - response_obj["usage"].completion_tokens - ) + 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 + ) diff --git a/tests/cassettes/test_crew_kickoff_usage_metrics.yaml b/tests/cassettes/test_crew_kickoff_usage_metrics.yaml index cc0863ee4..1851d0901 100644 --- a/tests/cassettes/test_crew_kickoff_usage_metrics.yaml +++ b/tests/cassettes/test_crew_kickoff_usage_metrics.yaml @@ -10,7 +10,8 @@ interactions: criteria for your final answer: 1 bullet point about dog that''s under 15 words.\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"}' + Answer, your job depends on it!\n\nThought:"}], "model": "gpt-4o-mini", "stop": + ["\nObservation:"], "stream": false}' headers: accept: - application/json @@ -19,49 +20,50 @@ interactions: connection: - keep-alive content-length: - - '869' + - '919' content-type: - application/json - cookie: - - __cf_bm=9.8sBYBkvBR8R1K_bVF7xgU..80XKlEIg3N2OBbTSCU-1727214102-1.0.1.1-.qiTLXbPamYUMSuyNsOEB9jhGu.jOifujOrx9E2JZvStbIZ9RTIiE44xKKNfLPxQkOi6qAT3h6htK8lPDGV_5g; - _cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000 host: - api.openai.com user-agent: - - OpenAI/Python 1.47.0 + - OpenAI/Python 1.52.1 x-stainless-arch: - - arm64 + - x64 x-stainless-async: - 'false' x-stainless-lang: - python x-stainless-os: - - MacOS + - Linux x-stainless-package-version: - - 1.47.0 + - 1.52.1 x-stainless-raw-response: - 'true' + x-stainless-retry-count: + - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.11.7 + - 3.11.9 method: POST uri: https://api.openai.com/v1/chat/completions response: - content: "{\n \"id\": \"chatcmpl-AB7auGDrAVE0iXSBBhySZp3xE8gvP\",\n \"object\": - \"chat.completion\",\n \"created\": 1727214164,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"I now can give a great answer\\nFinal - Answer: Dogs are unparalleled in loyalty and companionship to humans.\",\n \"refusal\": - null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n - \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 175,\n \"completion_tokens\": - 21,\n \"total_tokens\": 196,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": - 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" + body: + string: !!binary | + H4sIAAAAAAAAA4xSy27bMBC86ysWPEuB7ciV7VuAIkUObQ+59QFhTa0kttQuS9Jx08D/XkhyLAVJ + gV4EaGdnMLPDpwRAmUrtQOkWo+6czW7u41q2t3+cvCvuPvxafSG+58XHTzXlxWeV9gzZ/yAdn1lX + WjpnKRrhEdaeMFKvuiyul3m+uV7lA9BJRbanNS5muWSdYZOtFqs8WxTZcnNmt2I0BbWDrwkAwNPw + 7X1yRb/VDhbp86SjELAhtbssASgvtp8oDMGEiBxVOoFaOBIP1u+A5QgaGRrzQIDQ9LYBORzJA3zj + W8No4Wb438F7aQKgJ7DyiBb6zMhGOKRA3CJrww10xBEttIQ2toBcgTyQR2vhSNZmezLcXM39eKoP + Afub8MHa8/x0CWilcV724Yxf5rVhE9rSEwbhPkyI4tSAnhKA78MhDy9uo5yXzsUyyk/iMHSzHvXU + 1N+Ejo0BqCgR7Yy13aZv6JUVRTQ2zKpQGnVL1USdesNDZWQGJLPUr928pT0mN9z8j/wEaE0uUlU6 + T5XRLxNPa5765/2vtcuVB8MqPIZIXVkbbsg7b8bHVbuyXm9xs8xXRa2SU/IXAAD//wMAq2ZCBWoD + AAA= headers: CF-Cache-Status: - DYNAMIC CF-RAY: - - 8c85f22ddda01cf3-GRU + - 8e19bf36db158761-GRU Connection: - keep-alive Content-Encoding: @@ -69,19 +71,27 @@ interactions: Content-Type: - application/json Date: - - Tue, 24 Sep 2024 21:42:44 GMT + - Tue, 12 Nov 2024 21:52:04 GMT Server: - cloudflare + Set-Cookie: + - __cf_bm=MkvcnvacGpTyn.y0OkFRoFXuAwg4oxjMhViZJTt9mw0-1731448324-1.0.1.1-oekkH_B0xOoPnIFw15LpqFCkZ2cu7VBTJVLDGylan4I67NjX.tlPvOiX9kvtP5Acewi28IE2IwlwtrZWzCH3vw; + path=/; expires=Tue, 12-Nov-24 22:22:04 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=4.17346mfw5npZfYNbCx3Vj1VAVPy.tH0Jm2gkTteJ8-1731448324998-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 + - user-tqfegqsiobpvvjmn0giaipdq openai-processing-ms: - - '349' + - '601' openai-version: - '2020-10-01' strict-transport-security: @@ -89,19 +99,20 @@ interactions: x-ratelimit-limit-requests: - '10000' x-ratelimit-limit-tokens: - - '30000000' + - '200000' x-ratelimit-remaining-requests: - '9999' x-ratelimit-remaining-tokens: - - '29999792' + - '199793' x-ratelimit-reset-requests: - - 6ms + - 8.64s x-ratelimit-reset-tokens: - - 0s + - 62ms x-request-id: - - req_4c8cd76fdfba7b65e5ce85397b33c22b - http_version: HTTP/1.1 - status_code: 200 + - req_77fb166b4e272bfd45c37c08d2b93b0c + status: + code: 200 + message: OK - request: body: '{"messages": [{"role": "system", "content": "You are cat Researcher. You have a lot of experience with cat.\nYour personal goal is: Express hot takes @@ -113,7 +124,8 @@ interactions: criteria for your final answer: 1 bullet point about cat that''s under 15 words.\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"}' + Answer, your job depends on it!\n\nThought:"}], "model": "gpt-4o-mini", "stop": + ["\nObservation:"], "stream": false}' headers: accept: - application/json @@ -122,49 +134,53 @@ interactions: connection: - keep-alive content-length: - - '869' + - '919' content-type: - application/json cookie: - - __cf_bm=9.8sBYBkvBR8R1K_bVF7xgU..80XKlEIg3N2OBbTSCU-1727214102-1.0.1.1-.qiTLXbPamYUMSuyNsOEB9jhGu.jOifujOrx9E2JZvStbIZ9RTIiE44xKKNfLPxQkOi6qAT3h6htK8lPDGV_5g; - _cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000 + - __cf_bm=MkvcnvacGpTyn.y0OkFRoFXuAwg4oxjMhViZJTt9mw0-1731448324-1.0.1.1-oekkH_B0xOoPnIFw15LpqFCkZ2cu7VBTJVLDGylan4I67NjX.tlPvOiX9kvtP5Acewi28IE2IwlwtrZWzCH3vw; + _cfuvid=4.17346mfw5npZfYNbCx3Vj1VAVPy.tH0Jm2gkTteJ8-1731448324998-0.0.1.1-604800000 host: - api.openai.com user-agent: - - OpenAI/Python 1.47.0 + - OpenAI/Python 1.52.1 x-stainless-arch: - - arm64 + - x64 x-stainless-async: - 'false' x-stainless-lang: - python x-stainless-os: - - MacOS + - Linux x-stainless-package-version: - - 1.47.0 + - 1.52.1 x-stainless-raw-response: - 'true' + x-stainless-retry-count: + - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.11.7 + - 3.11.9 method: POST uri: https://api.openai.com/v1/chat/completions response: - content: "{\n \"id\": \"chatcmpl-AB7auNbAqjT3rgBX92rhxBLuhaLBj\",\n \"object\": - \"chat.completion\",\n \"created\": 1727214164,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal - Answer: Cats are highly independent, agile, and intuitive creatures beloved - by millions worldwide.\",\n \"refusal\": null\n },\n \"logprobs\": - null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": - 175,\n \"completion_tokens\": 28,\n \"total_tokens\": 203,\n \"completion_tokens_details\": - {\n \"reasoning_tokens\": 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" + body: + string: !!binary | + H4sIAAAAAAAAA4xSy27bMBC86ysWPFuB7MhN6ltQIGmBnlL00BcEmlxJ21JLhlzFLQL/eyH5IRlt + gV4EaGZnMLPLlwxAkVUbUKbVYrrg8rsPsg4P+Orxs9XvPz0U8eP966dS6sdo3wa1GBR++x2NnFRX + xnfBoZDnA20iasHBdXlzvSzL2+vVeiQ6b9ENsiZIXvq8I6Z8VazKvLjJl7dHdevJYFIb+JIBALyM + 3yEnW/ypNlAsTkiHKekG1eY8BKCidwOidEqURLOoxUQaz4I8Rn8H7HdgNENDzwgamiE2aE47jABf + +Z5YO7gb/zfwRksCHRGGGAHZIg/D1GmXFiBtpGfiBjyDtEgR/I5BMHYJNFvomZ56hIAxedaOhDBd + zYNFrPukh+Vw79wR35+bOt+E6LfpyJ/xmphSW0XUyfPQKokPamT3GcC3caP9xZJUiL4LUon/gZzG + I60Pfmo65MSuTqR40W6GF8c7XPpVFkWTS7ObKKNNi3aSTgfUvSU/I7JZ6z/T/M370Jy4+R/7iTAG + g6CtQkRL5rLxNBZxeOf/GjtveQys0q8k2FU1cYMxRDq8sjpUxVYXdrkq66XK9tlvAAAA//8DAIjK + KzJzAwAA headers: CF-Cache-Status: - DYNAMIC CF-RAY: - - 8c85f2321c1c1cf3-GRU + - 8e19bf3fae118761-GRU Connection: - keep-alive Content-Encoding: @@ -172,7 +188,7 @@ interactions: Content-Type: - application/json Date: - - Tue, 24 Sep 2024 21:42:45 GMT + - Tue, 12 Nov 2024 21:52:05 GMT Server: - cloudflare Transfer-Encoding: @@ -181,10 +197,12 @@ interactions: - nosniff access-control-expose-headers: - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 openai-organization: - - crewai-iuxna1 + - user-tqfegqsiobpvvjmn0giaipdq openai-processing-ms: - - '430' + - '464' openai-version: - '2020-10-01' strict-transport-security: @@ -192,19 +210,20 @@ interactions: x-ratelimit-limit-requests: - '10000' x-ratelimit-limit-tokens: - - '30000000' + - '200000' x-ratelimit-remaining-requests: - - '9999' + - '9998' x-ratelimit-remaining-tokens: - - '29999792' + - '199792' x-ratelimit-reset-requests: - - 6ms + - 16.369s x-ratelimit-reset-tokens: - - 0s + - 62ms x-request-id: - - req_ace859b7d9e83d9fa7753ce23bb03716 - http_version: HTTP/1.1 - status_code: 200 + - req_91706b23d0ef23458ba63ec18304cd28 + status: + code: 200 + message: OK - request: body: '{"messages": [{"role": "system", "content": "You are apple Researcher. You have a lot of experience with apple.\nYour personal goal is: Express hot @@ -217,7 +236,7 @@ interactions: under 15 words.\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"}' + "gpt-4o-mini", "stop": ["\nObservation:"], "stream": false}' headers: accept: - application/json @@ -226,49 +245,53 @@ interactions: connection: - keep-alive content-length: - - '879' + - '929' content-type: - application/json cookie: - - __cf_bm=9.8sBYBkvBR8R1K_bVF7xgU..80XKlEIg3N2OBbTSCU-1727214102-1.0.1.1-.qiTLXbPamYUMSuyNsOEB9jhGu.jOifujOrx9E2JZvStbIZ9RTIiE44xKKNfLPxQkOi6qAT3h6htK8lPDGV_5g; - _cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000 + - __cf_bm=MkvcnvacGpTyn.y0OkFRoFXuAwg4oxjMhViZJTt9mw0-1731448324-1.0.1.1-oekkH_B0xOoPnIFw15LpqFCkZ2cu7VBTJVLDGylan4I67NjX.tlPvOiX9kvtP5Acewi28IE2IwlwtrZWzCH3vw; + _cfuvid=4.17346mfw5npZfYNbCx3Vj1VAVPy.tH0Jm2gkTteJ8-1731448324998-0.0.1.1-604800000 host: - api.openai.com user-agent: - - OpenAI/Python 1.47.0 + - OpenAI/Python 1.52.1 x-stainless-arch: - - arm64 + - x64 x-stainless-async: - 'false' x-stainless-lang: - python x-stainless-os: - - MacOS + - Linux x-stainless-package-version: - - 1.47.0 + - 1.52.1 x-stainless-raw-response: - 'true' + x-stainless-retry-count: + - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.11.7 + - 3.11.9 method: POST uri: https://api.openai.com/v1/chat/completions response: - content: "{\n \"id\": \"chatcmpl-AB7avZ0yqY18ukQS7SnLkZydsx72b\",\n \"object\": - \"chat.completion\",\n \"created\": 1727214165,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"I now can give a great answer.\\n\\nFinal - Answer: Apples are incredibly versatile, nutritious, and a staple in diets globally.\",\n - \ \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\": - \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 175,\n \"completion_tokens\": - 25,\n \"total_tokens\": 200,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": - 0\n }\n },\n \"system_fingerprint\": \"fp_a5d11b2ef2\"\n}\n" + body: + string: !!binary | + H4sIAAAAAAAAA4xSPW/bMBDd9SsOXLpIgeTITarNS4t26JJubSHQ5IliSh1ZHv0RBP7vhSTHctAU + 6CJQ7909vHd3zxmAsFo0IFQvkxqCKzYPaf1b2/hhW+8PR9N9Kh9W5Zdhjebr4zeRjx1++4gqvXTd + KD8Eh8l6mmkVUSYcVau726qu729X7ydi8Brd2GZCKmpfDJZssSpXdVHeFdX9ubv3ViGLBr5nAADP + 03f0SRqPooEyf0EGZJYGRXMpAhDRuxERktlykpREvpDKU0KarH8G8gdQksDYPYIEM9oGSXzACPCD + PlqSDjbTfwObEBy+Y0Dl+YkTDmApoYkyIUMvoz7IiDmw79L8kqSBMe7HMMAoB4fM7ikHpF6SsmRg + xxgBjwGjRVJ4c+00YrdjOU6Lds6d8dMluvMmRL/lM3/BO0uW+zaiZE9jTE4+iIk9ZQA/pxHvXk1N + hOiHkNrkfyHxtLX1rCeWzS7svEsAkXyS7govq/wNvVZjktbx1ZKEkqpHvbQuG5U7bf0VkV2l/tvN + W9pzckvmf+QXQikMCXUbImqrXideyiKOh/+vssuUJ8NiPpO2s2Qwhmjns+tCW25lqatV3VUiO2V/ + AAAA//8DAPtpFJCEAwAA headers: CF-Cache-Status: - DYNAMIC CF-RAY: - - 8c85f2369a761cf3-GRU + - 8e19bf447ba48761-GRU Connection: - keep-alive Content-Encoding: @@ -276,7 +299,7 @@ interactions: Content-Type: - application/json Date: - - Tue, 24 Sep 2024 21:42:46 GMT + - Tue, 12 Nov 2024 21:52:06 GMT Server: - cloudflare Transfer-Encoding: @@ -285,10 +308,12 @@ interactions: - nosniff access-control-expose-headers: - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 openai-organization: - - crewai-iuxna1 + - user-tqfegqsiobpvvjmn0giaipdq openai-processing-ms: - - '389' + - '655' openai-version: - '2020-10-01' strict-transport-security: @@ -296,17 +321,18 @@ interactions: x-ratelimit-limit-requests: - '10000' x-ratelimit-limit-tokens: - - '30000000' + - '200000' x-ratelimit-remaining-requests: - - '9999' + - '9997' x-ratelimit-remaining-tokens: - - '29999791' + - '199791' x-ratelimit-reset-requests: - - 6ms + - 24.239s x-ratelimit-reset-tokens: - - 0s + - 62ms x-request-id: - - req_0167388f0a7a7f1a1026409834ceb914 - http_version: HTTP/1.1 - status_code: 200 + - req_a228208b0e965ecee334a6947d6c9e7c + status: + code: 200 + message: OK version: 1 diff --git a/tests/crew_test.py b/tests/crew_test.py index 5f39557e0..8a7fc193a 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -564,6 +564,7 @@ def test_crew_kickoff_usage_metrics(): assert result.token_usage.prompt_tokens > 0 assert result.token_usage.completion_tokens > 0 assert result.token_usage.successful_requests > 0 + assert result.token_usage.cached_prompt_tokens == 0 def test_agents_rpm_is_never_set_if_crew_max_RPM_is_not_set(): @@ -1284,6 +1285,7 @@ def test_agent_usage_metrics_are_captured_for_hierarchical_process(): prompt_tokens=1562, completion_tokens=111, successful_requests=3, + cached_prompt_tokens=0 ) From c725105b1f49da983e029cdbb38093ed01507ba7 Mon Sep 17 00:00:00 2001 From: Thiago Moretto Date: Wed, 13 Nov 2024 10:18:30 -0300 Subject: [PATCH 13/16] do not include cached on total --- src/crewai/agents/agent_builder/utilities/base_token_process.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/crewai/agents/agent_builder/utilities/base_token_process.py b/src/crewai/agents/agent_builder/utilities/base_token_process.py index 91df8140e..320d34caa 100644 --- a/src/crewai/agents/agent_builder/utilities/base_token_process.py +++ b/src/crewai/agents/agent_builder/utilities/base_token_process.py @@ -18,7 +18,6 @@ class TokenProcess: def sum_cached_prompt_tokens(self, tokens: int): self.cached_prompt_tokens = self.cached_prompt_tokens + tokens - self.total_tokens = self.total_tokens + tokens def sum_successful_requests(self, requests: int): self.successful_requests = self.successful_requests + requests From c57cbd8591dc352485d4bb213649ca5c085fd5b5 Mon Sep 17 00:00:00 2001 From: Thiago Moretto Date: Wed, 13 Nov 2024 10:47:49 -0300 Subject: [PATCH 14/16] Fix crew_train_success test --- tests/crew_test.py | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/tests/crew_test.py b/tests/crew_test.py index 8a7fc193a..5f31061ec 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -1779,26 +1779,22 @@ def test_crew_train_success( ] ) - crew_training_handler.assert_has_calls( - [ - mock.call("training_data.pkl"), - mock.call().load(), - mock.call("trained_agents_data.pkl"), - mock.call().save_trained_data( - agent_id="Researcher", - trained_data=task_evaluator().evaluate_training_data().model_dump(), - ), - mock.call("trained_agents_data.pkl"), - mock.call().save_trained_data( - agent_id="Senior Writer", - trained_data=task_evaluator().evaluate_training_data().model_dump(), - ), - mock.call(), - mock.call().load(), - mock.call(), - mock.call().load(), - ] - ) + crew_training_handler.assert_any_call("training_data.pkl") + crew_training_handler().load.assert_called() + + crew_training_handler.assert_any_call("trained_agents_data.pkl") + crew_training_handler().load.assert_called() + + crew_training_handler().save_trained_data.assert_has_calls([ + mock.call( + agent_id="Researcher", + trained_data=task_evaluator().evaluate_training_data().model_dump(), + ), + mock.call( + agent_id="Senior Writer", + trained_data=task_evaluator().evaluate_training_data().model_dump(), + ) + ]) def test_crew_train_error(): From 9285ebf8a210d076d392ef02901a620ef568327f Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Thu, 14 Nov 2024 13:12:35 -0300 Subject: [PATCH 15/16] feat: Reduce level for Bandit and fix code to adapt (#1604) --- .github/workflows/security-checker.yml | 2 +- src/crewai/cli/authentication/main.py | 6 ++++-- src/crewai/memory/storage/kickoff_task_outputs_storage.py | 2 +- src/crewai/memory/storage/ltm_sqlite_storage.py | 2 +- src/crewai/utilities/file_handler.py | 8 ++++++-- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/security-checker.yml b/.github/workflows/security-checker.yml index d0d309b4c..665f49292 100644 --- a/.github/workflows/security-checker.yml +++ b/.github/workflows/security-checker.yml @@ -19,5 +19,5 @@ jobs: run: pip install bandit - name: Run Bandit - run: bandit -c pyproject.toml -r src/ -lll + run: bandit -c pyproject.toml -r src/ -ll diff --git a/src/crewai/cli/authentication/main.py b/src/crewai/cli/authentication/main.py index 331b583e8..543f06844 100644 --- a/src/crewai/cli/authentication/main.py +++ b/src/crewai/cli/authentication/main.py @@ -34,7 +34,9 @@ class AuthenticationCommand: "scope": "openid", "audience": AUTH0_AUDIENCE, } - response = requests.post(url=self.DEVICE_CODE_URL, data=device_code_payload) + response = requests.post( + url=self.DEVICE_CODE_URL, data=device_code_payload, timeout=20 + ) response.raise_for_status() return response.json() @@ -54,7 +56,7 @@ class AuthenticationCommand: attempts = 0 while True and attempts < 5: - response = requests.post(self.TOKEN_URL, data=token_payload) + response = requests.post(self.TOKEN_URL, data=token_payload, timeout=30) token_data = response.json() if response.status_code == 200: diff --git a/src/crewai/memory/storage/kickoff_task_outputs_storage.py b/src/crewai/memory/storage/kickoff_task_outputs_storage.py index dbb5f124b..26905191c 100644 --- a/src/crewai/memory/storage/kickoff_task_outputs_storage.py +++ b/src/crewai/memory/storage/kickoff_task_outputs_storage.py @@ -103,7 +103,7 @@ class KickoffTaskOutputsSQLiteStorage: else value ) - query = f"UPDATE latest_kickoff_task_outputs SET {', '.join(fields)} WHERE task_index = ?" + query = f"UPDATE latest_kickoff_task_outputs SET {', '.join(fields)} WHERE task_index = ?" # nosec values.append(task_index) cursor.execute(query, tuple(values)) diff --git a/src/crewai/memory/storage/ltm_sqlite_storage.py b/src/crewai/memory/storage/ltm_sqlite_storage.py index 7fb388a62..93d993ee6 100644 --- a/src/crewai/memory/storage/ltm_sqlite_storage.py +++ b/src/crewai/memory/storage/ltm_sqlite_storage.py @@ -83,7 +83,7 @@ class LTMSQLiteStorage: WHERE task_description = ? ORDER BY datetime DESC, score ASC LIMIT {latest_n} - """, + """, # nosec (task_description,), ) rows = cursor.fetchall() diff --git a/src/crewai/utilities/file_handler.py b/src/crewai/utilities/file_handler.py index 091bd930a..bb97b940f 100644 --- a/src/crewai/utilities/file_handler.py +++ b/src/crewai/utilities/file_handler.py @@ -16,7 +16,11 @@ class FileHandler: def log(self, **kwargs): now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - message = f"{now}: " + ", ".join([f"{key}=\"{value}\"" for key, value in kwargs.items()]) + "\n" + message = ( + f"{now}: " + + ", ".join([f'{key}="{value}"' for key, value in kwargs.items()]) + + "\n" + ) with open(self._path, "a", encoding="utf-8") as file: file.write(message + "\n") @@ -63,7 +67,7 @@ class PickleHandler: with open(self.file_path, "rb") as file: try: - return pickle.load(file) + return pickle.load(file) # nosec except EOFError: return {} # Return an empty dictionary if the file is empty or corrupted except Exception: From e70bc94ab6c14fbfe0491a562d5252908160e033 Mon Sep 17 00:00:00 2001 From: Dev Khant Date: Fri, 15 Nov 2024 00:29:24 +0530 Subject: [PATCH 16/16] Add support for retrieving user preferences and memories using Mem0 (#1209) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Integrate Mem0 * Update src/crewai/memory/contextual/contextual_memory.py Co-authored-by: Deshraj Yadav * 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 * 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 Co-authored-by: João Moura Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> --- docs/concepts/crews.mdx | 3 +- docs/concepts/memory.mdx | 42 +++ poetry.lock | 6 +- pyproject.toml | 1 + src/crewai/agent.py | 2 + src/crewai/crew.py | 24 +- src/crewai/memory/__init__.py | 3 +- .../memory/contextual/contextual_memory.py | 47 ++- src/crewai/memory/entity/entity_memory.py | 42 ++- src/crewai/memory/memory.py | 11 +- .../memory/short_term/short_term_memory.py | 39 ++- src/crewai/memory/storage/interface.py | 6 +- src/crewai/memory/storage/mem0_storage.py | 104 +++++++ src/crewai/memory/user/__init__.py | 0 src/crewai/memory/user/user_memory.py | 45 +++ src/crewai/memory/user/user_memory_item.py | 8 + .../test_save_and_search_with_provider.yaml | 270 ++++++++++++++++++ 17 files changed, 619 insertions(+), 34 deletions(-) create mode 100644 src/crewai/memory/storage/mem0_storage.py create mode 100644 src/crewai/memory/user/__init__.py create mode 100644 src/crewai/memory/user/user_memory.py create mode 100644 src/crewai/memory/user/user_memory_item.py create mode 100644 tests/memory/cassettes/test_save_and_search_with_provider.yaml diff --git a/docs/concepts/crews.mdx b/docs/concepts/crews.mdx index 43451ca4b..ec0f190de 100644 --- a/docs/concepts/crews.mdx +++ b/docs/concepts/crews.mdx @@ -22,7 +22,8 @@ A crew in crewAI represents a collaborative group of agents working together to | **Max RPM** _(optional)_ | `max_rpm` | Maximum requests per minute the crew adheres to during execution. Defaults to `None`. | | **Language** _(optional)_ | `language` | Language used for the crew, defaults to English. | | **Language File** _(optional)_ | `language_file` | Path to the language file to be used for the crew. | -| **Memory** _(optional)_ | `memory` | Utilized for storing execution memories (short-term, long-term, entity memory). Defaults to `False`. | +| **Memory** _(optional)_ | `memory` | Utilized for storing execution memories (short-term, long-term, entity memory). | +| **Memory Config** _(optional)_ | `memory_config` | Configuration for the memory provider to be used by the crew. | | **Cache** _(optional)_ | `cache` | Specifies whether to use a cache for storing the results of tools' execution. Defaults to `True`. | | **Embedder** _(optional)_ | `embedder` | Configuration for the embedder to be used by the crew. Mostly used by memory for now. Default is `{"provider": "openai"}`. | | **Full Output** _(optional)_ | `full_output` | Whether the crew should return the full output with all tasks outputs or just the final output. Defaults to `False`. | diff --git a/docs/concepts/memory.mdx b/docs/concepts/memory.mdx index bda9f3401..a7677cec1 100644 --- a/docs/concepts/memory.mdx +++ b/docs/concepts/memory.mdx @@ -18,6 +18,7 @@ reason, and learn from past interactions. | **Long-Term Memory** | Preserves valuable insights and learnings from past executions, allowing agents to build and refine their knowledge over time. | | **Entity Memory** | Captures and organizes information about entities (people, places, concepts) encountered during tasks, facilitating deeper understanding and relationship mapping. Uses `RAG` for storing entity information. | | **Contextual Memory**| Maintains the context of interactions by combining `ShortTermMemory`, `LongTermMemory`, and `EntityMemory`, aiding in the coherence and relevance of agent responses over a sequence of tasks or a conversation. | +| **User Memory** | Stores user-specific information and preferences, enhancing personalization and user experience. | ## How Memory Systems Empower Agents @@ -92,6 +93,47 @@ my_crew = Crew( ) ``` +## Integrating Mem0 for Enhanced User Memory + +[Mem0](https://mem0.ai/) is a self-improving memory layer for LLM applications, enabling personalized AI experiences. + +To include user-specific memory you can get your API key [here](https://app.mem0.ai/dashboard/api-keys) and refer the [docs](https://docs.mem0.ai/platform/quickstart#4-1-create-memories) for adding user preferences. + + +```python Code +import os +from crewai import Crew, Process +from mem0 import MemoryClient + +# Set environment variables for Mem0 +os.environ["MEM0_API_KEY"] = "m0-xx" + +# Step 1: Record preferences based on past conversation or user input +client = MemoryClient() +messages = [ + {"role": "user", "content": "Hi there! I'm planning a vacation and could use some advice."}, + {"role": "assistant", "content": "Hello! I'd be happy to help with your vacation planning. What kind of destination do you prefer?"}, + {"role": "user", "content": "I am more of a beach person than a mountain person."}, + {"role": "assistant", "content": "That's interesting. Do you like hotels or Airbnb?"}, + {"role": "user", "content": "I like Airbnb more."}, +] +client.add(messages, user_id="john") + +# Step 2: Create a Crew with User Memory + +crew = Crew( + agents=[...], + tasks=[...], + verbose=True, + process=Process.sequential, + memory=True, + memory_config={ + "provider": "mem0", + "config": {"user_id": "john"}, + }, +) +``` + ## Additional Embedding Providers diff --git a/poetry.lock b/poetry.lock index 8ba39f6c3..094b84664 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1597,12 +1597,12 @@ files = [ google-auth = ">=2.14.1,<3.0.dev0" googleapis-common-protos = ">=1.56.2,<2.0.dev0" grpcio = [ - {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ - {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, ] proto-plus = ">=1.22.3,<2.0.0dev" protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" @@ -4286,8 +4286,8 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" diff --git a/pyproject.toml b/pyproject.toml index 8a5d2f1b9..66adfee3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ Repository = "https://github.com/crewAIInc/crewAI" [project.optional-dependencies] tools = ["crewai-tools>=0.14.0"] agentops = ["agentops>=0.3.0"] +mem0 = ["mem0ai>=0.1.29"] [tool.uv] dev-dependencies = [ diff --git a/src/crewai/agent.py b/src/crewai/agent.py index fc43137a2..4e9a0685f 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -262,9 +262,11 @@ class Agent(BaseAgent): if self.crew and self.crew.memory: contextual_memory = ContextualMemory( + self.crew.memory_config, self.crew._short_term_memory, self.crew._long_term_memory, self.crew._entity_memory, + self.crew._user_memory, ) memory = contextual_memory.build_context_for_task(task, context) if memory.strip() != "": diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 7bcaa82ad..04820adf8 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -27,6 +27,7 @@ from crewai.llm import LLM from crewai.memory.entity.entity_memory import EntityMemory from crewai.memory.long_term.long_term_memory import LongTermMemory from crewai.memory.short_term.short_term_memory import ShortTermMemory +from crewai.memory.user.user_memory import UserMemory from crewai.process import Process from crewai.task import Task from crewai.tasks.conditional_task import ConditionalTask @@ -71,6 +72,7 @@ class Crew(BaseModel): manager_llm: The language model that will run manager agent. manager_agent: Custom agent that will be used as manager. memory: Whether the crew should use memory to store memories of it's execution. + memory_config: Configuration for the memory to be used for the crew. cache: Whether the crew should use a cache to store the results of the tools execution. function_calling_llm: The language model that will run the tool calling for all the agents. process: The process flow that the crew will follow (e.g., sequential, hierarchical). @@ -94,6 +96,7 @@ class Crew(BaseModel): _short_term_memory: Optional[InstanceOf[ShortTermMemory]] = PrivateAttr() _long_term_memory: Optional[InstanceOf[LongTermMemory]] = PrivateAttr() _entity_memory: Optional[InstanceOf[EntityMemory]] = PrivateAttr() + _user_memory: Optional[InstanceOf[UserMemory]] = PrivateAttr() _train: Optional[bool] = PrivateAttr(default=False) _train_iteration: Optional[int] = PrivateAttr() _inputs: Optional[Dict[str, Any]] = PrivateAttr(default=None) @@ -114,6 +117,10 @@ class Crew(BaseModel): default=False, description="Whether the crew should use memory to store memories of it's execution", ) + memory_config: Optional[Dict[str, Any]] = Field( + default=None, + description="Configuration for the memory to be used for the crew.", + ) short_term_memory: Optional[InstanceOf[ShortTermMemory]] = Field( default=None, description="An Instance of the ShortTermMemory to be used by the Crew", @@ -126,7 +133,11 @@ class Crew(BaseModel): default=None, description="An Instance of the EntityMemory to be used by the Crew", ) - embedder: Optional[Any] = Field( + user_memory: Optional[InstanceOf[UserMemory]] = Field( + default=None, + description="An instance of the UserMemory to be used by the Crew to store/fetch memories of a specific user.", + ) + embedder: Optional[dict] = Field( default=None, description="Configuration for the embedder to be used for the crew.", ) @@ -238,13 +249,22 @@ class Crew(BaseModel): self._short_term_memory = ( self.short_term_memory if self.short_term_memory - else ShortTermMemory(crew=self, embedder_config=self.embedder) + else ShortTermMemory( + crew=self, + embedder_config=self.embedder, + ) ) self._entity_memory = ( self.entity_memory if self.entity_memory else EntityMemory(crew=self, embedder_config=self.embedder) ) + if hasattr(self, "memory_config") and self.memory_config is not None: + self._user_memory = ( + self.user_memory if self.user_memory else UserMemory(crew=self) + ) + else: + self._user_memory = None return self @model_validator(mode="after") diff --git a/src/crewai/memory/__init__.py b/src/crewai/memory/__init__.py index 8182bede7..3f7ca2ad6 100644 --- a/src/crewai/memory/__init__.py +++ b/src/crewai/memory/__init__.py @@ -1,5 +1,6 @@ from .entity.entity_memory import EntityMemory from .long_term.long_term_memory import LongTermMemory from .short_term.short_term_memory import ShortTermMemory +from .user.user_memory import UserMemory -__all__ = ["EntityMemory", "LongTermMemory", "ShortTermMemory"] +__all__ = ["UserMemory", "EntityMemory", "LongTermMemory", "ShortTermMemory"] diff --git a/src/crewai/memory/contextual/contextual_memory.py b/src/crewai/memory/contextual/contextual_memory.py index 5d91cf47d..9598fe6ee 100644 --- a/src/crewai/memory/contextual/contextual_memory.py +++ b/src/crewai/memory/contextual/contextual_memory.py @@ -1,13 +1,25 @@ -from typing import Optional +from typing import Optional, Dict, Any -from crewai.memory import EntityMemory, LongTermMemory, ShortTermMemory +from crewai.memory import EntityMemory, LongTermMemory, ShortTermMemory, UserMemory class ContextualMemory: - def __init__(self, stm: ShortTermMemory, ltm: LongTermMemory, em: EntityMemory): + def __init__( + self, + memory_config: Optional[Dict[str, Any]], + stm: ShortTermMemory, + ltm: LongTermMemory, + em: EntityMemory, + um: UserMemory, + ): + if memory_config is not None: + self.memory_provider = memory_config.get("provider") + else: + self.memory_provider = None self.stm = stm self.ltm = ltm self.em = em + self.um = um def build_context_for_task(self, task, context) -> str: """ @@ -23,6 +35,8 @@ class ContextualMemory: context.append(self._fetch_ltm_context(task.description)) context.append(self._fetch_stm_context(query)) context.append(self._fetch_entity_context(query)) + if self.memory_provider == "mem0": + context.append(self._fetch_user_context(query)) return "\n".join(filter(None, context)) def _fetch_stm_context(self, query) -> str: @@ -32,7 +46,10 @@ class ContextualMemory: """ stm_results = self.stm.search(query) formatted_results = "\n".join( - [f"- {result['context']}" for result in stm_results] + [ + f"- {result['memory'] if self.memory_provider == 'mem0' else result['context']}" + for result in stm_results + ] ) return f"Recent Insights:\n{formatted_results}" if stm_results else "" @@ -62,6 +79,26 @@ class ContextualMemory: """ em_results = self.em.search(query) formatted_results = "\n".join( - [f"- {result['context']}" for result in em_results] # type: ignore # Invalid index type "str" for "str"; expected type "SupportsIndex | slice" + [ + f"- {result['memory'] if self.memory_provider == 'mem0' else result['context']}" + for result in em_results + ] # type: ignore # Invalid index type "str" for "str"; expected type "SupportsIndex | slice" ) return f"Entities:\n{formatted_results}" if em_results else "" + + def _fetch_user_context(self, query: str) -> str: + """ + Fetches and formats relevant user information from User Memory. + Args: + query (str): The search query to find relevant user memories. + Returns: + str: Formatted user memories as bullet points, or an empty string if none found. + """ + user_memories = self.um.search(query) + if not user_memories: + return "" + + formatted_memories = "\n".join( + f"- {result['memory']}" for result in user_memories + ) + return f"User memories/preferences:\n{formatted_memories}" diff --git a/src/crewai/memory/entity/entity_memory.py b/src/crewai/memory/entity/entity_memory.py index 4de0594c7..134e19bfa 100644 --- a/src/crewai/memory/entity/entity_memory.py +++ b/src/crewai/memory/entity/entity_memory.py @@ -11,21 +11,43 @@ class EntityMemory(Memory): """ def __init__(self, crew=None, embedder_config=None, storage=None): - storage = ( - storage - if storage - else RAGStorage( - type="entities", - allow_reset=True, - embedder_config=embedder_config, - crew=crew, + if hasattr(crew, "memory_config") and crew.memory_config is not None: + self.memory_provider = crew.memory_config.get("provider") + else: + self.memory_provider = None + + if self.memory_provider == "mem0": + try: + from crewai.memory.storage.mem0_storage import Mem0Storage + except ImportError: + raise ImportError( + "Mem0 is not installed. Please install it with `pip install mem0ai`." + ) + storage = Mem0Storage(type="entities", crew=crew) + else: + storage = ( + storage + if storage + else RAGStorage( + type="entities", + allow_reset=False, + embedder_config=embedder_config, + crew=crew, + ) ) - ) super().__init__(storage) def save(self, item: EntityMemoryItem) -> None: # type: ignore # BUG?: Signature of "save" incompatible with supertype "Memory" """Saves an entity item into the SQLite storage.""" - data = f"{item.name}({item.type}): {item.description}" + if self.memory_provider == "mem0": + data = f""" + Remember details about the following entity: + Name: {item.name} + Type: {item.type} + Entity Description: {item.description} + """ + else: + data = f"{item.name}({item.type}): {item.description}" super().save(data, item.metadata) def reset(self) -> None: diff --git a/src/crewai/memory/memory.py b/src/crewai/memory/memory.py index d0bcd614f..4869f8e6b 100644 --- a/src/crewai/memory/memory.py +++ b/src/crewai/memory/memory.py @@ -23,5 +23,12 @@ class Memory: self.storage.save(value, metadata) - def search(self, query: str) -> List[Dict[str, Any]]: - return self.storage.search(query) + def search( + self, + query: str, + limit: int = 3, + score_threshold: float = 0.35, + ) -> List[Any]: + return self.storage.search( + query=query, limit=limit, score_threshold=score_threshold + ) diff --git a/src/crewai/memory/short_term/short_term_memory.py b/src/crewai/memory/short_term/short_term_memory.py index 919fb6115..67a568d63 100644 --- a/src/crewai/memory/short_term/short_term_memory.py +++ b/src/crewai/memory/short_term/short_term_memory.py @@ -14,13 +14,27 @@ class ShortTermMemory(Memory): """ def __init__(self, crew=None, embedder_config=None, storage=None): - storage = ( - storage - if storage - else RAGStorage( - type="short_term", embedder_config=embedder_config, crew=crew + if hasattr(crew, "memory_config") and crew.memory_config is not None: + self.memory_provider = crew.memory_config.get("provider") + else: + self.memory_provider = None + + if self.memory_provider == "mem0": + try: + from crewai.memory.storage.mem0_storage import Mem0Storage + except ImportError: + raise ImportError( + "Mem0 is not installed. Please install it with `pip install mem0ai`." + ) + storage = Mem0Storage(type="short_term", crew=crew) + else: + storage = ( + storage + if storage + else RAGStorage( + type="short_term", embedder_config=embedder_config, crew=crew + ) ) - ) super().__init__(storage) def save( @@ -30,11 +44,20 @@ class ShortTermMemory(Memory): agent: Optional[str] = None, ) -> None: item = ShortTermMemoryItem(data=value, metadata=metadata, agent=agent) + if self.memory_provider == "mem0": + item.data = f"Remember the following insights from Agent run: {item.data}" super().save(value=item.data, metadata=item.metadata, agent=item.agent) - def search(self, query: str, score_threshold: float = 0.35): - return self.storage.search(query=query, score_threshold=score_threshold) # type: ignore # BUG? The reference is to the parent class, but the parent class does not have this parameters + def search( + self, + query: str, + limit: int = 3, + score_threshold: float = 0.35, + ): + return self.storage.search( + query=query, limit=limit, score_threshold=score_threshold + ) # type: ignore # BUG? The reference is to the parent class, but the parent class does not have this parameters def reset(self) -> None: try: diff --git a/src/crewai/memory/storage/interface.py b/src/crewai/memory/storage/interface.py index 8fbe10b03..8bec9a14f 100644 --- a/src/crewai/memory/storage/interface.py +++ b/src/crewai/memory/storage/interface.py @@ -7,8 +7,10 @@ class Storage: def save(self, value: Any, metadata: Dict[str, Any]) -> None: pass - def search(self, key: str) -> List[Dict[str, Any]]: # type: ignore - pass + def search( + self, query: str, limit: int, score_threshold: float + ) -> Dict[str, Any] | List[Any]: + return {} def reset(self) -> None: pass diff --git a/src/crewai/memory/storage/mem0_storage.py b/src/crewai/memory/storage/mem0_storage.py new file mode 100644 index 000000000..34aab9716 --- /dev/null +++ b/src/crewai/memory/storage/mem0_storage.py @@ -0,0 +1,104 @@ +import os +from typing import Any, Dict, List + +from mem0 import MemoryClient +from crewai.memory.storage.interface import Storage + + +class Mem0Storage(Storage): + """ + Extends Storage to handle embedding and searching across entities using Mem0. + """ + + def __init__(self, type, crew=None): + super().__init__() + + if type not in ["user", "short_term", "long_term", "entities"]: + raise ValueError("Invalid type for Mem0Storage. Must be 'user' or 'agent'.") + + self.memory_type = type + self.crew = crew + self.memory_config = crew.memory_config + + # User ID is required for user memory type "user" since it's used as a unique identifier for the user. + user_id = self._get_user_id() + if type == "user" and not user_id: + raise ValueError("User ID is required for user memory type") + + # API key in memory config overrides the environment variable + mem0_api_key = self.memory_config.get("config", {}).get("api_key") or os.getenv( + "MEM0_API_KEY" + ) + self.memory = MemoryClient(api_key=mem0_api_key) + + def _sanitize_role(self, role: str) -> str: + """ + Sanitizes agent roles to ensure valid directory names. + """ + return role.replace("\n", "").replace(" ", "_").replace("/", "_") + + def save(self, value: Any, metadata: Dict[str, Any]) -> None: + user_id = self._get_user_id() + agent_name = self._get_agent_name() + if self.memory_type == "user": + self.memory.add(value, user_id=user_id, metadata={**metadata}) + elif self.memory_type == "short_term": + agent_name = self._get_agent_name() + self.memory.add( + value, agent_id=agent_name, metadata={"type": "short_term", **metadata} + ) + elif self.memory_type == "long_term": + agent_name = self._get_agent_name() + self.memory.add( + value, + agent_id=agent_name, + infer=False, + metadata={"type": "long_term", **metadata}, + ) + elif self.memory_type == "entities": + entity_name = None + self.memory.add( + value, user_id=entity_name, metadata={"type": "entity", **metadata} + ) + + def search( + self, + query: str, + limit: int = 3, + score_threshold: float = 0.35, + ) -> List[Any]: + params = {"query": query, "limit": limit} + if self.memory_type == "user": + user_id = self._get_user_id() + params["user_id"] = user_id + elif self.memory_type == "short_term": + agent_name = self._get_agent_name() + params["agent_id"] = agent_name + params["metadata"] = {"type": "short_term"} + elif self.memory_type == "long_term": + agent_name = self._get_agent_name() + params["agent_id"] = agent_name + params["metadata"] = {"type": "long_term"} + elif self.memory_type == "entities": + agent_name = self._get_agent_name() + params["agent_id"] = agent_name + params["metadata"] = {"type": "entity"} + + # Discard the filters for now since we create the filters + # automatically when the crew is created. + results = self.memory.search(**params) + return [r for r in results if r["score"] >= score_threshold] + + def _get_user_id(self): + if self.memory_type == "user": + if hasattr(self, "memory_config") and self.memory_config is not None: + return self.memory_config.get("config", {}).get("user_id") + else: + return None + return None + + def _get_agent_name(self): + agents = self.crew.agents if self.crew else [] + agents = [self._sanitize_role(agent.role) for agent in agents] + agents = "_".join(agents) + return agents diff --git a/src/crewai/memory/user/__init__.py b/src/crewai/memory/user/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/crewai/memory/user/user_memory.py b/src/crewai/memory/user/user_memory.py new file mode 100644 index 000000000..25e36617c --- /dev/null +++ b/src/crewai/memory/user/user_memory.py @@ -0,0 +1,45 @@ +from typing import Any, Dict, Optional + +from crewai.memory.memory import Memory + + +class UserMemory(Memory): + """ + UserMemory class for handling user memory storage and retrieval. + Inherits from the Memory class and utilizes an instance of a class that + adheres to the Storage for data storage, specifically working with + MemoryItem instances. + """ + + def __init__(self, crew=None): + try: + from crewai.memory.storage.mem0_storage import Mem0Storage + except ImportError: + raise ImportError( + "Mem0 is not installed. Please install it with `pip install mem0ai`." + ) + storage = Mem0Storage(type="user", crew=crew) + super().__init__(storage) + + def save( + self, + value, + metadata: Optional[Dict[str, Any]] = None, + agent: Optional[str] = None, + ) -> None: + # TODO: Change this function since we want to take care of the case where we save memories for the usr + data = f"Remember the details about the user: {value}" + super().save(data, metadata) + + def search( + self, + query: str, + limit: int = 3, + score_threshold: float = 0.35, + ): + results = super().search( + query=query, + limit=limit, + score_threshold=score_threshold, + ) + return results diff --git a/src/crewai/memory/user/user_memory_item.py b/src/crewai/memory/user/user_memory_item.py new file mode 100644 index 000000000..288c1544a --- /dev/null +++ b/src/crewai/memory/user/user_memory_item.py @@ -0,0 +1,8 @@ +from typing import Any, Dict, Optional + + +class UserMemoryItem: + def __init__(self, data: Any, user: str, metadata: Optional[Dict[str, Any]] = None): + self.data = data + self.user = user + self.metadata = metadata if metadata is not None else {} diff --git a/tests/memory/cassettes/test_save_and_search_with_provider.yaml b/tests/memory/cassettes/test_save_and_search_with_provider.yaml new file mode 100644 index 000000000..c30f3f065 --- /dev/null +++ b/tests/memory/cassettes/test_save_and_search_with_provider.yaml @@ -0,0 +1,270 @@ +interactions: +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - api.mem0.ai + user-agent: + - python-httpx/0.27.0 + method: GET + uri: https://api.mem0.ai/v1/memories/?user_id=test + response: + body: + string: '[]' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8b477138bad847b9-BOM + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + Date: + - Sat, 17 Aug 2024 06:00:11 GMT + NEL: + - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=uuyH2foMJVDpV%2FH52g1q%2FnvXKe3dBKVzvsK0mqmSNezkiszNR9OgrEJfVqmkX%2FlPFRP2sH4zrOuzGo6k%2FjzsjYJczqSWJUZHN2pPujiwnr1E9W%2BdLGKmG6%2FqPrGYAy2SBRWkkJVWsTO3OQ%3D%3D"}],"group":"cf-nel","max_age":604800}' + Server: + - cloudflare + allow: + - GET, POST, DELETE, OPTIONS + alt-svc: + - h3=":443"; ma=86400 + cross-origin-opener-policy: + - same-origin + referrer-policy: + - same-origin + vary: + - Accept, origin, Cookie + x-content-type-options: + - nosniff + x-frame-options: + - DENY + status: + code: 200 + message: OK +- request: + body: '{"batch": [{"properties": {"python_version": "3.12.4 (v3.12.4:8e8a4baf65, + Jun 6 2024, 17:33:18) [Clang 13.0.0 (clang-1300.0.29.30)]", "os": "darwin", + "os_version": "Darwin Kernel Version 23.4.0: Wed Feb 21 21:44:54 PST 2024; root:xnu-10063.101.15~2/RELEASE_ARM64_T6030", + "os_release": "23.4.0", "processor": "arm", "machine": "arm64", "function": + "mem0.client.main.MemoryClient", "$lib": "posthog-python", "$lib_version": "3.5.0", + "$geoip_disable": true}, "timestamp": "2024-08-17T06:00:11.526640+00:00", "context": + {}, "distinct_id": "fd411bd3-99a2-42d6-acd7-9fca8ad09580", "event": "client.init"}], + "historical_migration": false, "sentAt": "2024-08-17T06:00:11.701621+00:00", + "api_key": "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '740' + Content-Type: + - application/json + User-Agent: + - posthog-python/3.5.0 + method: POST + uri: https://us.i.posthog.com/batch/ + response: + body: + string: '{"status":"Ok"}' + headers: + Connection: + - keep-alive + Content-Length: + - '15' + Content-Type: + - application/json + Date: + - Sat, 17 Aug 2024 06:00:12 GMT + access-control-allow-credentials: + - 'true' + server: + - envoy + vary: + - origin, access-control-request-method, access-control-request-headers + x-envoy-upstream-service-time: + - '69' + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "user", "content": "Remember the following insights + from Agent run: test value with provider"}], "metadata": {"task": "test_task_provider", + "agent": "test_agent_provider"}, "app_id": "Researcher"}' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '219' + content-type: + - application/json + host: + - api.mem0.ai + user-agent: + - python-httpx/0.27.0 + method: POST + uri: https://api.mem0.ai/v1/memories/ + response: + body: + string: '{"message":"ok"}' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8b477140282547b9-BOM + Connection: + - keep-alive + Content-Length: + - '16' + Content-Type: + - application/json + Date: + - Sat, 17 Aug 2024 06:00:13 GMT + NEL: + - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=FRjJKSk3YxVj03wA7S05H8ts35KnWfqS3wb6Rfy4kVZ4BgXfw7nJbm92wI6vEv5fWcAcHVnOlkJDggs11B01BMuB2k3a9RqlBi0dJNiMuk%2Bgm5xE%2BODMPWJctYNRwQMjNVbteUpS%2Fad8YA%3D%3D"}],"group":"cf-nel","max_age":604800}' + Server: + - cloudflare + allow: + - GET, POST, DELETE, OPTIONS + alt-svc: + - h3=":443"; ma=86400 + cross-origin-opener-policy: + - same-origin + referrer-policy: + - same-origin + vary: + - Accept, origin, Cookie + x-content-type-options: + - nosniff + x-frame-options: + - DENY + status: + code: 200 + message: OK +- request: + body: '{"query": "test value with provider", "limit": 3, "app_id": "Researcher"}' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '73' + content-type: + - application/json + host: + - api.mem0.ai + user-agent: + - python-httpx/0.27.0 + method: POST + uri: https://api.mem0.ai/v1/memories/search/ + response: + body: + string: '[]' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8b47714d083b47b9-BOM + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + Date: + - Sat, 17 Aug 2024 06:00:14 GMT + NEL: + - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=2DRWL1cdKdMvnE8vx1fPUGeTITOgSGl3N5g84PS6w30GRqpfz79BtSx6REhpnOiFV8kM6KGqln0iCZ5yoHc2jBVVJXhPJhQ5t0uerD9JFnkphjISrJOU1MJjZWneT9PlNABddxvVNCmluA%3D%3D"}],"group":"cf-nel","max_age":604800}' + Server: + - cloudflare + allow: + - POST, OPTIONS + alt-svc: + - h3=":443"; ma=86400 + cross-origin-opener-policy: + - same-origin + referrer-policy: + - same-origin + vary: + - Accept, origin, Cookie + x-content-type-options: + - nosniff + x-frame-options: + - DENY + status: + code: 200 + message: OK +- request: + body: '{"batch": [{"properties": {"python_version": "3.12.4 (v3.12.4:8e8a4baf65, + Jun 6 2024, 17:33:18) [Clang 13.0.0 (clang-1300.0.29.30)]", "os": "darwin", + "os_version": "Darwin Kernel Version 23.4.0: Wed Feb 21 21:44:54 PST 2024; root:xnu-10063.101.15~2/RELEASE_ARM64_T6030", + "os_release": "23.4.0", "processor": "arm", "machine": "arm64", "function": + "mem0.client.main.MemoryClient", "$lib": "posthog-python", "$lib_version": "3.5.0", + "$geoip_disable": true}, "timestamp": "2024-08-17T06:00:13.593952+00:00", "context": + {}, "distinct_id": "fd411bd3-99a2-42d6-acd7-9fca8ad09580", "event": "client.add"}], + "historical_migration": false, "sentAt": "2024-08-17T06:00:13.858277+00:00", + "api_key": "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '739' + Content-Type: + - application/json + User-Agent: + - posthog-python/3.5.0 + method: POST + uri: https://us.i.posthog.com/batch/ + response: + body: + string: '{"status":"Ok"}' + headers: + Connection: + - keep-alive + Content-Length: + - '15' + Content-Type: + - application/json + Date: + - Sat, 17 Aug 2024 06:00:13 GMT + access-control-allow-credentials: + - 'true' + server: + - envoy + vary: + - origin, access-control-request-method, access-control-request-headers + x-envoy-upstream-service-time: + - '33' + status: + code: 200 + message: OK +version: 1