mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-07 15:18:29 +00:00
Compare commits
8 Commits
devin/1761
...
devin/1761
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bd929b9f3 | ||
|
|
9728388ea7 | ||
|
|
4371cf5690 | ||
|
|
d28daa26cd | ||
|
|
a850813f2b | ||
|
|
5944a39629 | ||
|
|
c594859ed0 | ||
|
|
2ee27efca7 |
@@ -11,7 +11,7 @@ mode: "wide"
|
||||
<Card
|
||||
title="Bedrock Invoke Agent Tool"
|
||||
icon="cloud"
|
||||
href="/en/tools/tool-integrations/bedrockinvokeagenttool"
|
||||
href="/en/tools/integration/bedrockinvokeagenttool"
|
||||
color="#0891B2"
|
||||
>
|
||||
Invoke Amazon Bedrock Agents from CrewAI to orchestrate actions across AWS services.
|
||||
@@ -20,7 +20,7 @@ mode: "wide"
|
||||
<Card
|
||||
title="CrewAI Automation Tool"
|
||||
icon="bolt"
|
||||
href="/en/tools/tool-integrations/crewaiautomationtool"
|
||||
href="/en/tools/integration/crewaiautomationtool"
|
||||
color="#7C3AED"
|
||||
>
|
||||
Automate deployment and operations by integrating CrewAI with external platforms and workflows.
|
||||
|
||||
@@ -12,7 +12,7 @@ dependencies = [
|
||||
"pytube>=15.0.0",
|
||||
"requests>=2.32.5",
|
||||
"docker>=7.1.0",
|
||||
"crewai==1.0.0",
|
||||
"crewai==1.1.0",
|
||||
"lancedb>=0.5.4",
|
||||
"tiktoken>=0.8.0",
|
||||
"beautifulsoup4>=4.13.4",
|
||||
|
||||
@@ -287,4 +287,4 @@ __all__ = [
|
||||
"ZapierActionTools",
|
||||
]
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__version__ = "1.1.0"
|
||||
|
||||
@@ -1,80 +1,42 @@
|
||||
from collections.abc import Callable
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import json
|
||||
import os
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
|
||||
try:
|
||||
from qdrant_client import QdrantClient
|
||||
from qdrant_client.http.models import FieldCondition, Filter, MatchValue
|
||||
|
||||
QDRANT_AVAILABLE = True
|
||||
except ImportError:
|
||||
QDRANT_AVAILABLE = False
|
||||
QdrantClient = Any # type: ignore[assignment,misc] # type placeholder
|
||||
Filter = Any # type: ignore[assignment,misc]
|
||||
FieldCondition = Any # type: ignore[assignment,misc]
|
||||
MatchValue = Any # type: ignore[assignment,misc]
|
||||
|
||||
from crewai.tools import BaseTool, EnvVar
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from pydantic.types import ImportString
|
||||
|
||||
|
||||
class QdrantToolSchema(BaseModel):
|
||||
"""Input for QdrantTool."""
|
||||
query: str = Field(..., description="Query to search in Qdrant DB.")
|
||||
filter_by: str | None = None
|
||||
filter_value: str | None = None
|
||||
|
||||
query: str = Field(
|
||||
...,
|
||||
description="The query to search retrieve relevant information from the Qdrant database. Pass only the query, not the question.",
|
||||
)
|
||||
filter_by: str | None = Field(
|
||||
default=None,
|
||||
description="Filter by properties. Pass only the properties, not the question.",
|
||||
)
|
||||
filter_value: str | None = Field(
|
||||
default=None,
|
||||
description="Filter by value. Pass only the value, not the question.",
|
||||
)
|
||||
|
||||
class QdrantConfig(BaseModel):
|
||||
"""All Qdrant connection and search settings."""
|
||||
|
||||
qdrant_url: str
|
||||
qdrant_api_key: str | None = None
|
||||
collection_name: str
|
||||
limit: int = 3
|
||||
score_threshold: float = 0.35
|
||||
filter_conditions: list[tuple[str, Any]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class QdrantVectorSearchTool(BaseTool):
|
||||
"""Tool to query and filter results from a Qdrant database.
|
||||
|
||||
This tool enables vector similarity search on internal documents stored in Qdrant,
|
||||
with optional filtering capabilities.
|
||||
|
||||
Attributes:
|
||||
client: Configured QdrantClient instance
|
||||
collection_name: Name of the Qdrant collection to search
|
||||
limit: Maximum number of results to return
|
||||
score_threshold: Minimum similarity score threshold
|
||||
qdrant_url: Qdrant server URL
|
||||
qdrant_api_key: Authentication key for Qdrant
|
||||
"""
|
||||
"""Vector search tool for Qdrant."""
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
client: QdrantClient = None # type: ignore[assignment]
|
||||
|
||||
# --- Metadata ---
|
||||
name: str = "QdrantVectorSearchTool"
|
||||
description: str = "A tool to search the Qdrant database for relevant information on internal documents."
|
||||
description: str = "Search Qdrant vector DB for relevant documents."
|
||||
args_schema: type[BaseModel] = QdrantToolSchema
|
||||
query: str | None = None
|
||||
filter_by: str | None = None
|
||||
filter_value: str | None = None
|
||||
collection_name: str | None = None
|
||||
limit: int | None = Field(default=3)
|
||||
score_threshold: float = Field(default=0.35)
|
||||
qdrant_url: str = Field(
|
||||
...,
|
||||
description="The URL of the Qdrant server",
|
||||
)
|
||||
qdrant_api_key: str | None = Field(
|
||||
default=None,
|
||||
description="The API key for the Qdrant server",
|
||||
)
|
||||
custom_embedding_fn: Callable | None = Field(
|
||||
default=None,
|
||||
description="A custom embedding function to use for vectorization. If not provided, the default model will be used.",
|
||||
)
|
||||
package_dependencies: list[str] = Field(default_factory=lambda: ["qdrant-client"])
|
||||
env_vars: list[EnvVar] = Field(
|
||||
default_factory=lambda: [
|
||||
@@ -83,107 +45,81 @@ class QdrantVectorSearchTool(BaseTool):
|
||||
)
|
||||
]
|
||||
)
|
||||
qdrant_config: QdrantConfig
|
||||
qdrant_package: ImportString[Any] = Field(
|
||||
default="qdrant_client",
|
||||
description="Base package path for Qdrant. Will dynamically import client and models.",
|
||||
)
|
||||
custom_embedding_fn: ImportString[Callable[[str], list[float]]] | None = Field(
|
||||
default=None,
|
||||
description="Optional embedding function or import path.",
|
||||
)
|
||||
client: Any | None = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
if QDRANT_AVAILABLE:
|
||||
self.client = QdrantClient(
|
||||
url=self.qdrant_url,
|
||||
api_key=self.qdrant_api_key if self.qdrant_api_key else None,
|
||||
@model_validator(mode="after")
|
||||
def _setup_qdrant(self) -> QdrantVectorSearchTool:
|
||||
# Import the qdrant_package if it's a string
|
||||
if isinstance(self.qdrant_package, str):
|
||||
self.qdrant_package = importlib.import_module(self.qdrant_package)
|
||||
|
||||
if not self.client:
|
||||
self.client = self.qdrant_package.QdrantClient(
|
||||
url=self.qdrant_config.qdrant_url,
|
||||
api_key=self.qdrant_config.qdrant_api_key or None,
|
||||
)
|
||||
else:
|
||||
import click
|
||||
|
||||
if click.confirm(
|
||||
"The 'qdrant-client' package is required to use the QdrantVectorSearchTool. "
|
||||
"Would you like to install it?"
|
||||
):
|
||||
import subprocess
|
||||
|
||||
subprocess.run(["uv", "add", "qdrant-client"], check=True) # noqa: S607
|
||||
else:
|
||||
raise ImportError(
|
||||
"The 'qdrant-client' package is required to use the QdrantVectorSearchTool. "
|
||||
"Please install it with: uv add qdrant-client"
|
||||
)
|
||||
return self
|
||||
|
||||
def _run(
|
||||
self,
|
||||
query: str,
|
||||
filter_by: str | None = None,
|
||||
filter_value: str | None = None,
|
||||
filter_value: Any | None = None,
|
||||
) -> str:
|
||||
"""Execute vector similarity search on Qdrant.
|
||||
"""Perform vector similarity search."""
|
||||
filter_ = self.qdrant_package.http.models.Filter
|
||||
field_condition = self.qdrant_package.http.models.FieldCondition
|
||||
match_value = self.qdrant_package.http.models.MatchValue
|
||||
conditions = self.qdrant_config.filter_conditions.copy()
|
||||
if filter_by and filter_value is not None:
|
||||
conditions.append((filter_by, filter_value))
|
||||
|
||||
Args:
|
||||
query: Search query to vectorize and match
|
||||
filter_by: Optional metadata field to filter on
|
||||
filter_value: Optional value to filter by
|
||||
|
||||
Returns:
|
||||
JSON string containing search results with metadata and scores
|
||||
|
||||
Raises:
|
||||
ImportError: If qdrant-client is not installed
|
||||
ValueError: If Qdrant credentials are missing
|
||||
"""
|
||||
if not self.qdrant_url:
|
||||
raise ValueError("QDRANT_URL is not set")
|
||||
|
||||
# Create filter if filter parameters are provided
|
||||
search_filter = None
|
||||
if filter_by and filter_value:
|
||||
search_filter = Filter(
|
||||
search_filter = (
|
||||
filter_(
|
||||
must=[
|
||||
FieldCondition(key=filter_by, match=MatchValue(value=filter_value))
|
||||
field_condition(key=k, match=match_value(value=v))
|
||||
for k, v in conditions
|
||||
]
|
||||
)
|
||||
|
||||
# Search in Qdrant using the built-in query method
|
||||
query_vector = (
|
||||
self._vectorize_query(query, embedding_model="text-embedding-3-large")
|
||||
if not self.custom_embedding_fn
|
||||
else self.custom_embedding_fn(query)
|
||||
if conditions
|
||||
else None
|
||||
)
|
||||
search_results = self.client.query_points(
|
||||
collection_name=self.collection_name, # type: ignore[arg-type]
|
||||
query_vector = (
|
||||
self.custom_embedding_fn(query)
|
||||
if self.custom_embedding_fn
|
||||
else (
|
||||
lambda: __import__("openai")
|
||||
.Client(api_key=os.getenv("OPENAI_API_KEY"))
|
||||
.embeddings.create(input=[query], model="text-embedding-3-large")
|
||||
.data[0]
|
||||
.embedding
|
||||
)()
|
||||
)
|
||||
results = self.client.query_points(
|
||||
collection_name=self.qdrant_config.collection_name,
|
||||
query=query_vector,
|
||||
query_filter=search_filter,
|
||||
limit=self.limit, # type: ignore[arg-type]
|
||||
score_threshold=self.score_threshold,
|
||||
limit=self.qdrant_config.limit,
|
||||
score_threshold=self.qdrant_config.score_threshold,
|
||||
)
|
||||
|
||||
# Format results similar to storage implementation
|
||||
results = []
|
||||
# Extract the list of ScoredPoint objects from the tuple
|
||||
for point in search_results:
|
||||
result = {
|
||||
"metadata": point[1][0].payload.get("metadata", {}),
|
||||
"context": point[1][0].payload.get("text", ""),
|
||||
"distance": point[1][0].score,
|
||||
}
|
||||
results.append(result)
|
||||
|
||||
return json.dumps(results, indent=2)
|
||||
|
||||
def _vectorize_query(self, query: str, embedding_model: str) -> list[float]:
|
||||
"""Default vectorization function with openai.
|
||||
|
||||
Args:
|
||||
query (str): The query to vectorize
|
||||
embedding_model (str): The embedding model to use
|
||||
|
||||
Returns:
|
||||
list[float]: The vectorized query
|
||||
"""
|
||||
import openai
|
||||
|
||||
client = openai.Client(api_key=os.getenv("OPENAI_API_KEY"))
|
||||
return (
|
||||
client.embeddings.create(
|
||||
input=[query],
|
||||
model=embedding_model,
|
||||
)
|
||||
.data[0]
|
||||
.embedding
|
||||
return json.dumps(
|
||||
[
|
||||
{
|
||||
"distance": p.score,
|
||||
"metadata": p.payload.get("metadata", {}) if p.payload else {},
|
||||
"context": p.payload.get("text", "") if p.payload else {},
|
||||
}
|
||||
for p in results.points
|
||||
],
|
||||
indent=2,
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ Repository = "https://github.com/crewAIInc/crewAI"
|
||||
|
||||
[project.optional-dependencies]
|
||||
tools = [
|
||||
"crewai-tools==1.0.0",
|
||||
"crewai-tools==1.1.0",
|
||||
]
|
||||
embeddings = [
|
||||
"tiktoken~=0.8.0"
|
||||
@@ -66,11 +66,6 @@ openpyxl = [
|
||||
mem0 = ["mem0ai>=0.1.94"]
|
||||
docling = [
|
||||
"docling>=2.12.0",
|
||||
]
|
||||
aisuite = [
|
||||
"aisuite>=0.1.11",
|
||||
|
||||
|
||||
]
|
||||
qdrant = [
|
||||
"qdrant-client[fastembed]>=1.14.3",
|
||||
@@ -137,13 +132,3 @@ build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "src/crewai/__init__.py"
|
||||
|
||||
# Declare mutually exclusive extras due to conflicting httpx requirements
|
||||
# a2a requires httpx>=0.28.1, while aisuite requires httpx>=0.27.0,<0.28.0
|
||||
# [tool.uv]
|
||||
# conflicts = [
|
||||
# [
|
||||
# { extra = "a2a" },
|
||||
# { extra = "aisuite" },
|
||||
# ],
|
||||
# ]
|
||||
|
||||
@@ -40,7 +40,7 @@ def _suppress_pydantic_deprecation_warnings() -> None:
|
||||
|
||||
_suppress_pydantic_deprecation_warnings()
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__version__ = "1.1.0"
|
||||
_telemetry_submitted = False
|
||||
|
||||
|
||||
|
||||
@@ -148,6 +148,10 @@ class BaseAgent(BaseModel, ABC):
|
||||
default=None, description="Language model that will run the agent."
|
||||
)
|
||||
crew: Any = Field(default=None, description="Crew to which the agent belongs.")
|
||||
language: str | None = Field(
|
||||
default=None,
|
||||
description="Language code for the agent's prompts (e.g., 'en', 'es', 'pt'). If not set, defaults to 'en'.",
|
||||
)
|
||||
i18n: I18N = Field(
|
||||
default_factory=I18N, description="Internationalization settings."
|
||||
)
|
||||
@@ -289,6 +293,10 @@ class BaseAgent(BaseModel, ABC):
|
||||
if self.security_config is None:
|
||||
self.security_config = SecurityConfig()
|
||||
|
||||
# Initialize i18n with language if provided
|
||||
if self.language:
|
||||
self.i18n = I18N(language=self.language)
|
||||
|
||||
return self
|
||||
|
||||
@field_validator("id", mode="before")
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]==1.0.0"
|
||||
"crewai[tools]==1.1.0"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]==1.0.0"
|
||||
"crewai[tools]==1.1.0"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -233,6 +233,10 @@ class Crew(FlowTrackable, BaseModel):
|
||||
default=None,
|
||||
description="Path to the prompt json file to be used for the crew.",
|
||||
)
|
||||
language: str | None = Field(
|
||||
default=None,
|
||||
description="Language code for the crew's prompts (e.g., 'en', 'es', 'pt'). If not set, defaults to 'en'.",
|
||||
)
|
||||
output_log_file: bool | str | None = Field(
|
||||
default=None,
|
||||
description="Path to the log file to be saved",
|
||||
@@ -684,7 +688,10 @@ class Crew(FlowTrackable, BaseModel):
|
||||
self._set_tasks_callbacks()
|
||||
self._set_allow_crewai_trigger_context_for_first_task()
|
||||
|
||||
i18n = I18N(prompt_file=self.prompt_file)
|
||||
i18n = I18N(
|
||||
prompt_file=self.prompt_file,
|
||||
language=self.language if self.language else "en",
|
||||
)
|
||||
|
||||
for agent in self.agents:
|
||||
agent.i18n = i18n
|
||||
@@ -826,7 +833,10 @@ class Crew(FlowTrackable, BaseModel):
|
||||
return self._execute_tasks(self.tasks)
|
||||
|
||||
def _create_manager_agent(self):
|
||||
i18n = I18N(prompt_file=self.prompt_file)
|
||||
i18n = I18N(
|
||||
prompt_file=self.prompt_file,
|
||||
language=self.language if self.language else "en",
|
||||
)
|
||||
if self.manager_agent is not None:
|
||||
self.manager_agent.allow_delegation = True
|
||||
manager = self.manager_agent
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from pyvis.network import Network # type: ignore[import-untyped]
|
||||
|
||||
@@ -29,7 +29,7 @@ _printer = Printer()
|
||||
class FlowPlot:
|
||||
"""Handles the creation and rendering of flow visualization diagrams."""
|
||||
|
||||
def __init__(self, flow: Flow) -> None:
|
||||
def __init__(self, flow: Flow[Any]) -> None:
|
||||
"""
|
||||
Initialize FlowPlot with a flow object.
|
||||
|
||||
@@ -136,7 +136,7 @@ class FlowPlot:
|
||||
f"Unexpected error during flow visualization: {e!s}"
|
||||
) from e
|
||||
finally:
|
||||
self._cleanup_pyvis_lib()
|
||||
self._cleanup_pyvis_lib(filename)
|
||||
|
||||
def _generate_final_html(self, network_html: str) -> str:
|
||||
"""
|
||||
@@ -186,26 +186,33 @@ class FlowPlot:
|
||||
raise IOError(f"Failed to generate visualization HTML: {e!s}") from e
|
||||
|
||||
@staticmethod
|
||||
def _cleanup_pyvis_lib() -> None:
|
||||
def _cleanup_pyvis_lib(filename: str) -> None:
|
||||
"""
|
||||
Clean up the generated lib folder from pyvis.
|
||||
|
||||
This method safely removes the temporary lib directory created by pyvis
|
||||
during network visualization generation.
|
||||
during network visualization generation. The lib folder is created in the
|
||||
same directory as the output HTML file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filename : str
|
||||
The output filename (without .html extension) used for the visualization.
|
||||
"""
|
||||
try:
|
||||
lib_folder = safe_path_join("lib", root=os.getcwd())
|
||||
if os.path.exists(lib_folder) and os.path.isdir(lib_folder):
|
||||
import shutil
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(lib_folder)
|
||||
except ValueError as e:
|
||||
_printer.print(f"Error validating lib folder path: {e}", color="red")
|
||||
output_dir = os.path.dirname(os.path.abspath(filename)) or os.getcwd()
|
||||
lib_folder = os.path.join(output_dir, "lib")
|
||||
if os.path.exists(lib_folder) and os.path.isdir(lib_folder):
|
||||
vis_js = os.path.join(lib_folder, "vis-network.min.js")
|
||||
if os.path.exists(vis_js):
|
||||
shutil.rmtree(lib_folder)
|
||||
except Exception as e:
|
||||
_printer.print(f"Error cleaning up lib folder: {e}", color="red")
|
||||
|
||||
|
||||
def plot_flow(flow: Flow, filename: str = "flow_plot") -> None:
|
||||
def plot_flow(flow: Flow[Any], filename: str = "flow_plot") -> None:
|
||||
"""
|
||||
Convenience function to create and save a flow visualization.
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
"""HTML template processing and generation for flow visualization diagrams."""
|
||||
|
||||
import base64
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from crewai.flow.path_utils import validate_path_exists
|
||||
|
||||
@@ -7,7 +10,7 @@ from crewai.flow.path_utils import validate_path_exists
|
||||
class HTMLTemplateHandler:
|
||||
"""Handles HTML template processing and generation for flow visualization diagrams."""
|
||||
|
||||
def __init__(self, template_path, logo_path):
|
||||
def __init__(self, template_path: str, logo_path: str) -> None:
|
||||
"""
|
||||
Initialize HTMLTemplateHandler with validated template and logo paths.
|
||||
|
||||
@@ -29,23 +32,23 @@ class HTMLTemplateHandler:
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Invalid template or logo path: {e}") from e
|
||||
|
||||
def read_template(self):
|
||||
def read_template(self) -> str:
|
||||
"""Read and return the HTML template file contents."""
|
||||
with open(self.template_path, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
def encode_logo(self):
|
||||
def encode_logo(self) -> str:
|
||||
"""Convert the logo SVG file to base64 encoded string."""
|
||||
with open(self.logo_path, "rb") as logo_file:
|
||||
logo_svg_data = logo_file.read()
|
||||
return base64.b64encode(logo_svg_data).decode("utf-8")
|
||||
|
||||
def extract_body_content(self, html):
|
||||
def extract_body_content(self, html: str) -> str:
|
||||
"""Extract and return content between body tags from HTML string."""
|
||||
match = re.search("<body.*?>(.*?)</body>", html, re.DOTALL)
|
||||
return match.group(1) if match else ""
|
||||
|
||||
def generate_legend_items_html(self, legend_items):
|
||||
def generate_legend_items_html(self, legend_items: list[dict[str, Any]]) -> str:
|
||||
"""Generate HTML markup for the legend items."""
|
||||
legend_items_html = ""
|
||||
for item in legend_items:
|
||||
@@ -73,7 +76,9 @@ class HTMLTemplateHandler:
|
||||
"""
|
||||
return legend_items_html
|
||||
|
||||
def generate_final_html(self, network_body, legend_items_html, title="Flow Plot"):
|
||||
def generate_final_html(
|
||||
self, network_body: str, legend_items_html: str, title: str = "Flow Plot"
|
||||
) -> str:
|
||||
"""Combine all components into final HTML document with network visualization."""
|
||||
html_template = self.read_template()
|
||||
logo_svg_base64 = self.encode_logo()
|
||||
|
||||
@@ -1,4 +1,23 @@
|
||||
def get_legend_items(colors):
|
||||
"""Legend generation for flow visualization diagrams."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from crewai.flow.config import FlowColors
|
||||
|
||||
|
||||
def get_legend_items(colors: FlowColors) -> list[dict[str, Any]]:
|
||||
"""Generate legend items based on flow colors.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
colors : FlowColors
|
||||
Dictionary containing color definitions for flow elements.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[dict[str, Any]]
|
||||
List of legend item dictionaries with labels and styling.
|
||||
"""
|
||||
return [
|
||||
{"label": "Start Method", "color": colors["start"]},
|
||||
{"label": "Method", "color": colors["method"]},
|
||||
@@ -24,7 +43,19 @@ def get_legend_items(colors):
|
||||
]
|
||||
|
||||
|
||||
def generate_legend_items_html(legend_items):
|
||||
def generate_legend_items_html(legend_items: list[dict[str, Any]]) -> str:
|
||||
"""Generate HTML markup for legend items.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
legend_items : list[dict[str, Any]]
|
||||
List of legend item dictionaries containing labels and styling.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
HTML string containing formatted legend items.
|
||||
"""
|
||||
legend_items_html = ""
|
||||
for item in legend_items:
|
||||
if "border" in item:
|
||||
|
||||
@@ -36,28 +36,29 @@ from crewai.flow.utils import (
|
||||
from crewai.utilities.printer import Printer
|
||||
|
||||
|
||||
|
||||
_printer = Printer()
|
||||
|
||||
|
||||
def method_calls_crew(method: Any) -> bool:
|
||||
"""
|
||||
Check if the method contains a call to `.crew()`.
|
||||
Check if the method contains a call to `.crew()`, `.kickoff()`, or `.kickoff_async()`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
method : Any
|
||||
The method to analyze for crew() calls.
|
||||
The method to analyze for crew or agent execution calls.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the method calls .crew(), False otherwise.
|
||||
True if the method calls .crew(), .kickoff(), or .kickoff_async(), False otherwise.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Uses AST analysis to detect method calls, specifically looking for
|
||||
attribute access of 'crew'.
|
||||
attribute access of 'crew', 'kickoff', or 'kickoff_async'.
|
||||
This includes both traditional Crew execution (.crew()) and Agent/LiteAgent
|
||||
execution (.kickoff() or .kickoff_async()).
|
||||
"""
|
||||
try:
|
||||
source = inspect.getsource(method)
|
||||
@@ -68,14 +69,14 @@ def method_calls_crew(method: Any) -> bool:
|
||||
return False
|
||||
|
||||
class CrewCallVisitor(ast.NodeVisitor):
|
||||
"""AST visitor to detect .crew() method calls."""
|
||||
"""AST visitor to detect .crew(), .kickoff(), or .kickoff_async() method calls."""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.found = False
|
||||
|
||||
def visit_Call(self, node):
|
||||
def visit_Call(self, node: ast.Call) -> None:
|
||||
if isinstance(node.func, ast.Attribute):
|
||||
if node.func.attr == "crew":
|
||||
if node.func.attr in ("crew", "kickoff", "kickoff_async"):
|
||||
self.found = True
|
||||
self.generic_visit(node)
|
||||
|
||||
@@ -113,7 +114,7 @@ def add_nodes_to_network(
|
||||
- Regular methods
|
||||
"""
|
||||
|
||||
def human_friendly_label(method_name):
|
||||
def human_friendly_label(method_name: str) -> str:
|
||||
return method_name.replace("_", " ").title()
|
||||
|
||||
node_style: (
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
"""AI Suite LLM integration for CrewAI.
|
||||
|
||||
This module provides integration with AI Suite for LLM capabilities.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import aisuite as ai # type: ignore
|
||||
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
|
||||
|
||||
class AISuiteLLM(BaseLLM):
|
||||
"""AI Suite LLM implementation.
|
||||
|
||||
This class provides integration with AI Suite models through the BaseLLM interface.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model: str,
|
||||
temperature: float | None = None,
|
||||
stop: list[str] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize the AI Suite LLM.
|
||||
|
||||
Args:
|
||||
model: The model identifier for AI Suite.
|
||||
temperature: Optional temperature setting for response generation.
|
||||
stop: Optional list of stop sequences for generation.
|
||||
**kwargs: Additional keyword arguments passed to the AI Suite client.
|
||||
"""
|
||||
super().__init__(model=model, temperature=temperature, stop=stop)
|
||||
self.client = ai.Client()
|
||||
self.kwargs = kwargs
|
||||
|
||||
def call( # type: ignore[override]
|
||||
self,
|
||||
messages: str | list[dict[str, str]],
|
||||
tools: list[dict] | None = None,
|
||||
callbacks: list[Any] | None = None,
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
) -> str | Any:
|
||||
"""Call the AI Suite LLM with the given messages.
|
||||
|
||||
Args:
|
||||
messages: Input messages for the LLM.
|
||||
tools: Optional list of tool schemas for function calling.
|
||||
callbacks: Optional list of callback functions.
|
||||
available_functions: Optional dict mapping function names to callables.
|
||||
from_task: Optional task caller.
|
||||
from_agent: Optional agent caller.
|
||||
|
||||
Returns:
|
||||
The text response from the LLM.
|
||||
"""
|
||||
completion_params = self._prepare_completion_params(messages, tools)
|
||||
response = self.client.chat.completions.create(**completion_params)
|
||||
|
||||
return response.choices[0].message.content
|
||||
|
||||
def _prepare_completion_params(
|
||||
self,
|
||||
messages: str | list[dict[str, str]],
|
||||
tools: list[dict] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Prepare parameters for the AI Suite completion call.
|
||||
|
||||
Args:
|
||||
messages: Input messages for the LLM.
|
||||
tools: Optional list of tool schemas.
|
||||
|
||||
Returns:
|
||||
Dictionary of parameters for the completion API.
|
||||
"""
|
||||
params: dict[str, Any] = {
|
||||
"model": self.model,
|
||||
"messages": messages,
|
||||
"temperature": self.temperature,
|
||||
"tools": tools,
|
||||
**self.kwargs,
|
||||
}
|
||||
|
||||
if self.stop:
|
||||
params["stop"] = self.stop
|
||||
|
||||
return params
|
||||
|
||||
@staticmethod
|
||||
def supports_function_calling() -> bool:
|
||||
"""Check if the LLM supports function calling.
|
||||
|
||||
Returns:
|
||||
False, as AI Suite does not currently support function calling.
|
||||
"""
|
||||
return False
|
||||
60
lib/crewai/src/crewai/mypy.py
Normal file
60
lib/crewai/src/crewai/mypy.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Mypy plugin for CrewAI decorator type checking.
|
||||
|
||||
This plugin informs mypy about attributes injected by the @CrewBase decorator.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from mypy.nodes import MDEF, SymbolTableNode, Var
|
||||
from mypy.plugin import ClassDefContext, Plugin
|
||||
from mypy.types import AnyType, TypeOfAny
|
||||
|
||||
|
||||
class CrewAIPlugin(Plugin):
|
||||
"""Mypy plugin that handles @CrewBase decorator attribute injection."""
|
||||
|
||||
def get_class_decorator_hook(
|
||||
self, fullname: str
|
||||
) -> Callable[[ClassDefContext], None] | None:
|
||||
"""Return hook for class decorators.
|
||||
|
||||
Args:
|
||||
fullname: Fully qualified name of the decorator.
|
||||
|
||||
Returns:
|
||||
Hook function if this is a CrewBase decorator, None otherwise.
|
||||
"""
|
||||
if fullname in ("crewai.project.CrewBase", "crewai.project.crew_base.CrewBase"):
|
||||
return self._crew_base_hook
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _crew_base_hook(ctx: ClassDefContext) -> None:
|
||||
"""Add injected attributes to @CrewBase decorated classes.
|
||||
|
||||
Args:
|
||||
ctx: Context for the class being decorated.
|
||||
"""
|
||||
any_type = AnyType(TypeOfAny.explicit)
|
||||
str_type = ctx.api.named_type("builtins.str")
|
||||
dict_type = ctx.api.named_type("builtins.dict", [str_type, any_type])
|
||||
agents_config_var = Var("agents_config", dict_type)
|
||||
agents_config_var.info = ctx.cls.info
|
||||
agents_config_var._fullname = f"{ctx.cls.info.fullname}.agents_config"
|
||||
ctx.cls.info.names["agents_config"] = SymbolTableNode(MDEF, agents_config_var)
|
||||
tasks_config_var = Var("tasks_config", dict_type)
|
||||
tasks_config_var.info = ctx.cls.info
|
||||
tasks_config_var._fullname = f"{ctx.cls.info.fullname}.tasks_config"
|
||||
ctx.cls.info.names["tasks_config"] = SymbolTableNode(MDEF, tasks_config_var)
|
||||
|
||||
|
||||
def plugin(_: str) -> type[Plugin]:
|
||||
"""Entry point for mypy plugin.
|
||||
|
||||
Args:
|
||||
_: Mypy version string.
|
||||
|
||||
Returns:
|
||||
Plugin class.
|
||||
"""
|
||||
return CrewAIPlugin
|
||||
@@ -20,7 +20,7 @@ from typing_extensions import Self
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai import Agent, Task
|
||||
from crewai import Agent, Crew, Task
|
||||
from crewai.crews.crew_output import CrewOutput
|
||||
from crewai.tools import BaseTool
|
||||
|
||||
@@ -129,6 +129,7 @@ class CrewClass(Protocol):
|
||||
_map_agent_variables: Callable[..., None]
|
||||
map_all_task_variables: Callable[..., None]
|
||||
_map_task_variables: Callable[..., None]
|
||||
crew: Callable[..., Crew]
|
||||
|
||||
|
||||
class DecoratedMethod(Generic[P, R]):
|
||||
|
||||
61
lib/crewai/src/crewai/translations/es.json
Normal file
61
lib/crewai/src/crewai/translations/es.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"hierarchical_manager_agent": {
|
||||
"role": "Gerente del Equipo",
|
||||
"goal": "Gestionar el equipo para completar la tarea de la mejor manera posible.",
|
||||
"backstory": "Eres un gerente experimentado con talento para sacar lo mejor de tu equipo.\nTambién eres conocido por tu habilidad para delegar trabajo a las personas adecuadas y hacer las preguntas correctas para obtener lo mejor de tu equipo.\nAunque no realizas tareas por ti mismo, tienes mucha experiencia en el campo, lo que te permite evaluar adecuadamente el trabajo de los miembros de tu equipo."
|
||||
},
|
||||
"slices": {
|
||||
"observation": "\nObservación:",
|
||||
"task": "\nTarea Actual: {input}\n\n¡Comienza! Esto es MUY importante para ti, usa las herramientas disponibles y da tu mejor Respuesta Final, ¡tu trabajo depende de ello!\n\nPensamiento:",
|
||||
"memory": "\n\n# Contexto útil: \n{memory}",
|
||||
"role_playing": "Eres {role}. {backstory}\nTu objetivo personal es: {goal}",
|
||||
"tools": "\nSOLO tienes acceso a las siguientes herramientas, y NUNCA debes inventar herramientas que no estén listadas aquí:\n\n{tools}\n\nIMPORTANTE: Usa el siguiente formato en tu respuesta:\n\n```\nPensamiento: siempre debes pensar en qué hacer\nAcción: la acción a tomar, solo un nombre de [{tool_names}], solo el nombre, exactamente como está escrito.\nEntrada de Acción: la entrada para la acción, solo un objeto JSON simple, encerrado entre llaves, usando \" para envolver claves y valores.\nObservación: el resultado de la acción\n```\n\nUna vez que se recopile toda la información necesaria, devuelve el siguiente formato:\n\n```\nPensamiento: Ahora conozco la respuesta final\nRespuesta Final: la respuesta final a la pregunta de entrada original\n```",
|
||||
"no_tools": "\nPara dar mi mejor respuesta final completa a la tarea, responde usando exactamente el siguiente formato:\n\nPensamiento: Ahora puedo dar una gran respuesta\nRespuesta Final: Tu respuesta final debe ser lo más completa posible, debe describir el resultado.\n\n¡DEBO usar estos formatos, mi trabajo depende de ello!",
|
||||
"format": "DEBO usar una herramienta (usar una a la vez) O dar mi mejor respuesta final, no ambas al mismo tiempo. Al responder, debo usar el siguiente formato:\n\n```\nPensamiento: siempre debes pensar en qué hacer\nAcción: la acción a tomar, debe ser una de [{tool_names}]\nEntrada de Acción: la entrada para la acción, diccionario encerrado entre llaves\nObservación: el resultado de la acción\n```\nEste proceso de Pensamiento/Acción/Entrada de Acción/Resultado puede repetirse N veces. Una vez que conozca la respuesta final, debo devolver el siguiente formato:\n\n```\nPensamiento: Ahora puedo dar una gran respuesta\nRespuesta Final: Tu respuesta final debe ser lo más completa posible, debe describir el resultado\n\n```",
|
||||
"final_answer_format": "Si no necesitas usar más herramientas, debes dar tu mejor respuesta final completa, asegúrate de que satisfaga los criterios esperados, usa el formato EXACTO a continuación:\n\n```\nPensamiento: Ahora puedo dar una gran respuesta\nRespuesta Final: mi mejor respuesta final completa a la tarea.\n\n```",
|
||||
"format_without_tools": "\nLo siento, no usé el formato correcto. DEBO usar una herramienta (entre las disponibles), O dar mi mejor respuesta final.\nAquí está el formato esperado que debo seguir:\n\n```\nPregunta: la pregunta de entrada que debes responder\nPensamiento: siempre debes pensar en qué hacer\nAcción: la acción a tomar, debe ser una de [{tool_names}]\nEntrada de Acción: la entrada para la acción\nObservación: el resultado de la acción\n```\n Este proceso de Pensamiento/Acción/Entrada de Acción/Resultado puede repetirse N veces. Una vez que conozca la respuesta final, debo devolver el siguiente formato:\n\n```\nPensamiento: Ahora puedo dar una gran respuesta\nRespuesta Final: Tu respuesta final debe ser lo más completa posible, debe describir el resultado\n\n```",
|
||||
"task_with_context": "{task}\n\nEste es el contexto con el que estás trabajando:\n{context}",
|
||||
"expected_output": "\nEste es el criterio esperado para tu respuesta final: {expected_output}\nDEBES devolver el contenido completo real como respuesta final, no un resumen.",
|
||||
"human_feedback": "Recibiste retroalimentación humana sobre tu trabajo, reevalúalo y da una nueva Respuesta Final cuando estés listo.\n {human_feedback}",
|
||||
"getting_input": "Esta es la respuesta final del agente: {final_answer}\n\n",
|
||||
"summarizer_system_message": "Eres un asistente útil que resume texto.",
|
||||
"summarize_instruction": "Resume el siguiente texto, asegúrate de incluir toda la información importante: {group}",
|
||||
"summary": "Este es un resumen de nuestra conversación hasta ahora:\n{merged_summary}",
|
||||
"manager_request": "Tu mejor respuesta a tu compañero de trabajo que te pregunta esto, teniendo en cuenta el contexto compartido.",
|
||||
"formatted_task_instructions": "Asegúrate de que tu respuesta final contenga solo el contenido en el siguiente formato: {output_format}\n\nAsegúrate de que la salida final no incluya marcadores de bloque de código como ```json o ```python.",
|
||||
"conversation_history_instruction": "Eres miembro de un equipo que colabora para lograr un objetivo común. Tu tarea es una acción específica que contribuye a este objetivo más amplio. Para contexto adicional, revisa el historial de conversación entre tú y el usuario que llevó a la iniciación de este equipo. Usa cualquier información o retroalimentación relevante de la conversación para informar la ejecución de tu tarea y asegúrate de que tu respuesta se alinee tanto con la tarea inmediata como con los objetivos generales del equipo.",
|
||||
"feedback_instructions": "Retroalimentación del usuario: {feedback}\nInstrucciones: Usa esta retroalimentación para mejorar la próxima iteración de salida.\nNota: No respondas ni agregues comentarios.",
|
||||
"lite_agent_system_prompt_with_tools": "Eres {role}. {backstory}\nTu objetivo personal es: {goal}\n\nSOLO tienes acceso a las siguientes herramientas, y NUNCA debes inventar herramientas que no estén listadas aquí:\n\n{tools}\n\nIMPORTANTE: Usa el siguiente formato en tu respuesta:\n\n```\nPensamiento: siempre debes pensar en qué hacer\nAcción: la acción a tomar, solo un nombre de [{tool_names}], solo el nombre, exactamente como está escrito.\nEntrada de Acción: la entrada para la acción, solo un objeto JSON simple, encerrado entre llaves, usando \" para envolver claves y valores.\nObservación: el resultado de la acción\n```\n\nUna vez que se recopile toda la información necesaria, devuelve el siguiente formato:\n\n```\nPensamiento: Ahora conozco la respuesta final\nRespuesta Final: la respuesta final a la pregunta de entrada original\n```",
|
||||
"lite_agent_system_prompt_without_tools": "Eres {role}. {backstory}\nTu objetivo personal es: {goal}\n\nPara dar mi mejor respuesta final completa a la tarea, responde usando exactamente el siguiente formato:\n\nPensamiento: Ahora puedo dar una gran respuesta\nRespuesta Final: Tu respuesta final debe ser lo más completa posible, debe describir el resultado.\n\n¡DEBO usar estos formatos, mi trabajo depende de ello!",
|
||||
"lite_agent_response_format": "\nIMPORTANTE: Tu respuesta final DEBE contener toda la información solicitada en el siguiente formato: {response_format}\n\nIMPORTANTE: Asegúrate de que la salida final no incluya marcadores de bloque de código como ```json o ```python.",
|
||||
"knowledge_search_query": "La consulta original es: {task_prompt}.",
|
||||
"knowledge_search_query_system_prompt": "Tu objetivo es reescribir la consulta del usuario para que esté optimizada para la recuperación de una base de datos vectorial. Considera cómo se utilizará la consulta para encontrar documentos relevantes y trata de hacerla más específica y consciente del contexto. \n\n No incluyas ningún otro texto que no sea la consulta reescrita, especialmente ningún preámbulo o postámbulo y solo agrega el formato de salida esperado si es relevante para la consulta reescrita. \n\n Concéntrate en las palabras clave de la tarea prevista y en recuperar la información más relevante. \n\n Habrá algún contexto adicional proporcionado que podría necesitar ser eliminado, como formatos de salida esperados, salidas estructuradas y otras instrucciones."
|
||||
},
|
||||
"errors": {
|
||||
"force_final_answer_error": "No puedes continuar, aquí está la mejor respuesta final que generaste:\n\n {formatted_answer}",
|
||||
"force_final_answer": "Ahora es el momento de que DEBES dar tu mejor respuesta final absoluta. Ignorarás todas las instrucciones anteriores, dejarás de usar cualquier herramienta y simplemente devolverás tu MEJOR respuesta final absoluta.",
|
||||
"agent_tool_unexisting_coworker": "\nError al ejecutar la herramienta. El compañero de trabajo mencionado no se encontró, debe ser una de las siguientes opciones:\n{coworkers}\n",
|
||||
"task_repeated_usage": "Intenté reutilizar la misma entrada, debo dejar de usar esta entrada de acción. Intentaré algo más en su lugar.\n\n",
|
||||
"tool_usage_error": "Encontré un error: {error}",
|
||||
"tool_arguments_error": "Error: la Entrada de Acción no es un diccionario válido de clave, valor.",
|
||||
"wrong_tool_name": "Intentaste usar la herramienta {tool}, pero no existe. Debes usar una de las siguientes herramientas, usa una a la vez: {tools}.",
|
||||
"tool_usage_exception": "Encontré un error al intentar usar la herramienta. Este fue el error: {error}.\n La herramienta {tool} acepta estas entradas: {tool_inputs}",
|
||||
"agent_tool_execution_error": "Error al ejecutar la tarea con el agente '{agent_role}'. Error: {error}",
|
||||
"validation_error": "### El intento anterior falló la validación: {guardrail_result_error}\n\n\n### Resultado anterior:\n{task_output}\n\n\nIntenta de nuevo, asegurándote de abordar el error de validación."
|
||||
},
|
||||
"tools": {
|
||||
"delegate_work": "Delega una tarea específica a uno de los siguientes compañeros de trabajo: {coworkers}\nLa entrada para esta herramienta debe ser el compañero de trabajo, la tarea que quieres que hagan y TODO el contexto necesario para ejecutar la tarea, ellos no saben nada sobre la tarea, así que comparte absolutamente todo lo que sabes, no hagas referencia a cosas sino explícalas.",
|
||||
"ask_question": "Haz una pregunta específica a uno de los siguientes compañeros de trabajo: {coworkers}\nLa entrada para esta herramienta debe ser el compañero de trabajo, la pregunta que tienes para ellos y TODO el contexto necesario para hacer la pregunta correctamente, ellos no saben nada sobre la pregunta, así que comparte absolutamente todo lo que sabes, no hagas referencia a cosas sino explícalas.",
|
||||
"add_image": {
|
||||
"name": "Agregar imagen al contenido",
|
||||
"description": "Ver imagen para entender su contenido, opcionalmente puedes hacer una pregunta sobre la imagen",
|
||||
"default_action": "Por favor proporciona una descripción detallada de esta imagen, incluyendo todos los elementos visuales, contexto y cualquier detalle notable que puedas observar."
|
||||
}
|
||||
},
|
||||
"reasoning": {
|
||||
"initial_plan": "Eres {role}, un profesional con el siguiente trasfondo: {backstory}\n\nTu objetivo principal es: {goal}\n\nComo {role}, estás creando un plan estratégico para una tarea que requiere tu experiencia y perspectiva única.",
|
||||
"refine_plan": "Eres {role}, un profesional con el siguiente trasfondo: {backstory}\n\nTu objetivo principal es: {goal}\n\nComo {role}, estás refinando un plan estratégico para una tarea que requiere tu experiencia y perspectiva única.",
|
||||
"create_plan_prompt": "Eres {role} con este trasfondo: {backstory}\n\nTu objetivo principal es: {goal}\n\nSe te ha asignado la siguiente tarea:\n{description}\n\nSalida esperada:\n{expected_output}\n\nHerramientas disponibles: {tools}\n\nAntes de ejecutar esta tarea, crea un plan detallado que aproveche tu experiencia como {role} y describa:\n1. Tu comprensión de la tarea desde tu perspectiva profesional\n2. Los pasos clave que tomarás para completarla, basándote en tu trasfondo y habilidades\n3. Cómo abordarás cualquier desafío que pueda surgir, considerando tu experiencia\n4. Cómo usarás estratégicamente las herramientas disponibles según tu experiencia, exactamente qué herramientas usar y cómo usarlas\n5. El resultado esperado y cómo se alinea con tu objetivo\n\nDespués de crear tu plan, evalúa si te sientes listo para ejecutar la tarea o si podrías hacerlo mejor.\nConcluye con una de estas declaraciones:\n- \"LISTO: Estoy listo para ejecutar la tarea.\"\n- \"NO LISTO: Necesito refinar mi plan porque [razón específica].\"",
|
||||
"refine_plan_prompt": "Eres {role} con este trasfondo: {backstory}\n\nTu objetivo principal es: {goal}\n\nCreaste el siguiente plan para esta tarea:\n{current_plan}\n\nSin embargo, indicaste que aún no estás listo para ejecutar la tarea.\n\nPor favor, refina tu plan aún más, basándote en tu experiencia como {role} para abordar cualquier brecha o incertidumbre. A medida que refines tu plan, sé específico sobre qué herramientas disponibles usarás, cómo las usarás y por qué son las mejores opciones para cada paso. Describe claramente tu estrategia de uso de herramientas como parte de tu plan mejorado.\n\nDespués de refinar tu plan, evalúa si te sientes listo para ejecutar la tarea.\nConcluye con una de estas declaraciones:\n- \"LISTO: Estoy listo para ejecutar la tarea.\"\n- \"NO LISTO: Necesito refinar mi plan aún más porque [razón específica].\""
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ class I18N(BaseModel):
|
||||
Attributes:
|
||||
_prompts: Internal dictionary storing loaded prompts.
|
||||
prompt_file: Optional path to a custom JSON file containing prompts.
|
||||
language: Language code for the prompts (e.g., 'en', 'es', 'pt'). Defaults to 'en'.
|
||||
"""
|
||||
|
||||
_prompts: dict[str, dict[str, str]] = PrivateAttr()
|
||||
@@ -21,6 +22,10 @@ class I18N(BaseModel):
|
||||
default=None,
|
||||
description="Path to the prompt_file file to load",
|
||||
)
|
||||
language: str = Field(
|
||||
default="en",
|
||||
description="Language code for the prompts (e.g., 'en', 'es', 'pt')",
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def load_prompts(self) -> Self:
|
||||
@@ -38,12 +43,24 @@ class I18N(BaseModel):
|
||||
self._prompts = json.load(f)
|
||||
else:
|
||||
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||
prompts_path = os.path.join(dir_path, "../translations/en.json")
|
||||
prompts_path = os.path.join(
|
||||
dir_path, f"../translations/{self.language}.json"
|
||||
)
|
||||
|
||||
with open(prompts_path, encoding="utf-8") as f:
|
||||
self._prompts = json.load(f)
|
||||
try:
|
||||
with open(prompts_path, encoding="utf-8") as f:
|
||||
self._prompts = json.load(f)
|
||||
except FileNotFoundError:
|
||||
if self.language != "en":
|
||||
fallback_path = os.path.join(dir_path, "../translations/en.json")
|
||||
with open(fallback_path, encoding="utf-8") as f:
|
||||
self._prompts = json.load(f)
|
||||
else:
|
||||
raise
|
||||
except FileNotFoundError as e:
|
||||
raise Exception(f"Prompt file '{self.prompt_file}' not found.") from e
|
||||
raise Exception(
|
||||
f"Prompt file '{self.prompt_file or self.language + '.json'}' not found."
|
||||
) from e
|
||||
except json.JSONDecodeError as e:
|
||||
raise Exception("Error decoding JSON from the prompts file.") from e
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Any, Generic, TypeGuard, TypeVar
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from crewai.utilities.logger_utils import suppress_warnings
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.agent import Agent
|
||||
@@ -11,9 +13,6 @@ if TYPE_CHECKING:
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.utilities.types import LLMMessage
|
||||
|
||||
from crewai.utilities.logger_utils import suppress_warnings
|
||||
|
||||
|
||||
|
||||
T = TypeVar("T", bound=BaseModel)
|
||||
|
||||
@@ -62,9 +61,59 @@ class InternalInstructor(Generic[T]):
|
||||
|
||||
with suppress_warnings():
|
||||
import instructor # type: ignore[import-untyped]
|
||||
from litellm import completion
|
||||
|
||||
self._client = instructor.from_litellm(completion)
|
||||
if (
|
||||
self.llm is not None
|
||||
and hasattr(self.llm, "is_litellm")
|
||||
and self.llm.is_litellm
|
||||
):
|
||||
from litellm import completion
|
||||
|
||||
self._client = instructor.from_litellm(completion)
|
||||
else:
|
||||
self._client = self._create_instructor_client()
|
||||
|
||||
def _create_instructor_client(self) -> Any:
|
||||
"""Create instructor client using the modern from_provider pattern.
|
||||
|
||||
Returns:
|
||||
Instructor client configured for the LLM provider
|
||||
|
||||
Raises:
|
||||
ValueError: If the provider is not supported
|
||||
"""
|
||||
import instructor
|
||||
|
||||
if isinstance(self.llm, str):
|
||||
model_string = self.llm
|
||||
elif self.llm is not None and hasattr(self.llm, "model"):
|
||||
model_string = self.llm.model
|
||||
else:
|
||||
raise ValueError("LLM must be a string or have a model attribute")
|
||||
|
||||
if isinstance(self.llm, str):
|
||||
provider = self._extract_provider()
|
||||
elif self.llm is not None and hasattr(self.llm, "provider"):
|
||||
provider = self.llm.provider
|
||||
else:
|
||||
provider = "openai" # Default fallback
|
||||
|
||||
return instructor.from_provider(f"{provider}/{model_string}")
|
||||
|
||||
def _extract_provider(self) -> str:
|
||||
"""Extract provider from LLM model name.
|
||||
|
||||
Returns:
|
||||
Provider name (e.g., 'openai', 'anthropic', etc.)
|
||||
"""
|
||||
if self.llm is not None and hasattr(self.llm, "provider") and self.llm.provider:
|
||||
return self.llm.provider
|
||||
|
||||
if isinstance(self.llm, str):
|
||||
return self.llm.partition("/")[0] or "openai"
|
||||
if self.llm is not None and hasattr(self.llm, "model"):
|
||||
return self.llm.model.partition("/")[0] or "openai"
|
||||
return "openai"
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Convert the structured output to JSON format.
|
||||
@@ -96,6 +145,6 @@ class InternalInstructor(Generic[T]):
|
||||
else:
|
||||
model_name = self.llm.model
|
||||
|
||||
return self._client.chat.completions.create(
|
||||
return self._client.chat.completions.create( # type: ignore[no-any-return]
|
||||
model=model_name, response_model=self.model, messages=messages
|
||||
)
|
||||
|
||||
@@ -902,7 +902,8 @@ def test_agent_step_callback():
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_agent_function_calling_llm():
|
||||
llm = "gpt-4o"
|
||||
from crewai.llm import LLM
|
||||
llm = LLM(model="gpt-4o", is_litellm=True)
|
||||
|
||||
@tool
|
||||
def learn_about_ai() -> str:
|
||||
|
||||
116
lib/crewai/tests/test_agent_language.py
Normal file
116
lib/crewai/tests/test_agent_language.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import pytest
|
||||
from crewai.agent import Agent
|
||||
from crewai.crew import Crew
|
||||
from crewai.task import Task
|
||||
|
||||
|
||||
def test_agent_default_language():
|
||||
agent = Agent(
|
||||
role="Test Agent",
|
||||
goal="Test goal",
|
||||
backstory="Test backstory"
|
||||
)
|
||||
assert agent.i18n.language == "en"
|
||||
|
||||
|
||||
def test_agent_with_spanish_language():
|
||||
agent = Agent(
|
||||
role="Agente de Prueba",
|
||||
goal="Objetivo de prueba",
|
||||
backstory="Historia de fondo de prueba",
|
||||
language="es"
|
||||
)
|
||||
assert agent.i18n.language == "es"
|
||||
assert "Eres {role}" in agent.i18n.slice("role_playing")
|
||||
|
||||
|
||||
def test_agent_with_english_language_explicit():
|
||||
agent = Agent(
|
||||
role="Test Agent",
|
||||
goal="Test goal",
|
||||
backstory="Test backstory",
|
||||
language="en"
|
||||
)
|
||||
assert agent.i18n.language == "en"
|
||||
assert "You are {role}" in agent.i18n.slice("role_playing")
|
||||
|
||||
|
||||
def test_crew_default_language():
|
||||
agent = Agent(
|
||||
role="Test Agent",
|
||||
goal="Test goal",
|
||||
backstory="Test backstory"
|
||||
)
|
||||
task = Task(
|
||||
description="Test task",
|
||||
expected_output="Test output",
|
||||
agent=agent
|
||||
)
|
||||
crew = Crew(agents=[agent], tasks=[task])
|
||||
assert crew.language is None
|
||||
|
||||
|
||||
def test_crew_with_spanish_language():
|
||||
agent = Agent(
|
||||
role="Agente de Prueba",
|
||||
goal="Objetivo de prueba",
|
||||
backstory="Historia de fondo de prueba"
|
||||
)
|
||||
task = Task(
|
||||
description="Tarea de prueba",
|
||||
expected_output="Salida de prueba",
|
||||
agent=agent
|
||||
)
|
||||
crew = Crew(agents=[agent], tasks=[task], language="es")
|
||||
assert crew.language == "es"
|
||||
|
||||
|
||||
def test_crew_language_propagates_to_agents():
|
||||
agent1 = Agent(
|
||||
role="Agent 1",
|
||||
goal="Goal 1",
|
||||
backstory="Backstory 1"
|
||||
)
|
||||
agent2 = Agent(
|
||||
role="Agent 2",
|
||||
goal="Goal 2",
|
||||
backstory="Backstory 2"
|
||||
)
|
||||
task = Task(
|
||||
description="Test task",
|
||||
expected_output="Test output",
|
||||
agent=agent1
|
||||
)
|
||||
crew = Crew(agents=[agent1, agent2], tasks=[task], language="es")
|
||||
|
||||
assert crew.language == "es"
|
||||
|
||||
|
||||
def test_agent_language_overrides_default():
|
||||
agent_en = Agent(
|
||||
role="English Agent",
|
||||
goal="English goal",
|
||||
backstory="English backstory",
|
||||
language="en"
|
||||
)
|
||||
agent_es = Agent(
|
||||
role="Spanish Agent",
|
||||
goal="Spanish goal",
|
||||
backstory="Spanish backstory",
|
||||
language="es"
|
||||
)
|
||||
|
||||
assert agent_en.i18n.language == "en"
|
||||
assert agent_es.i18n.language == "es"
|
||||
assert "You are {role}" in agent_en.i18n.slice("role_playing")
|
||||
assert "Eres {role}" in agent_es.i18n.slice("role_playing")
|
||||
|
||||
|
||||
def test_agent_without_language_uses_default():
|
||||
agent = Agent(
|
||||
role="Test Agent",
|
||||
goal="Test goal",
|
||||
backstory="Test backstory"
|
||||
)
|
||||
assert agent.language is None
|
||||
assert agent.i18n.language == "en"
|
||||
@@ -850,6 +850,31 @@ def test_flow_plotting():
|
||||
assert isinstance(received_events[0].timestamp, datetime)
|
||||
|
||||
|
||||
def test_method_calls_crew_detection():
|
||||
"""Test that method_calls_crew() detects .crew(), .kickoff(), and .kickoff_async() calls."""
|
||||
from crewai.flow.visualization_utils import method_calls_crew
|
||||
from crewai import Agent
|
||||
|
||||
# Test with a real Flow that uses agent.kickoff()
|
||||
class FlowWithAgentKickoff(Flow):
|
||||
@start()
|
||||
def run_agent(self):
|
||||
agent = Agent(role="test", goal="test", backstory="test")
|
||||
return agent.kickoff("query")
|
||||
|
||||
flow = FlowWithAgentKickoff()
|
||||
assert method_calls_crew(flow.run_agent) is True
|
||||
|
||||
# Test with a Flow that has no crew/agent calls
|
||||
class FlowWithoutCrewCalls(Flow):
|
||||
@start()
|
||||
def simple_method(self):
|
||||
return "Just a regular method"
|
||||
|
||||
flow2 = FlowWithoutCrewCalls()
|
||||
assert method_calls_crew(flow2.simple_method) is False
|
||||
|
||||
|
||||
def test_multiple_routers_from_same_trigger():
|
||||
"""Test that multiple routers triggered by the same method all activate their listeners."""
|
||||
execution_order = []
|
||||
|
||||
@@ -22,7 +22,7 @@ import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vcr_config(request) -> dict:
|
||||
def vcr_config(request: pytest.FixtureRequest) -> dict[str, str]:
|
||||
return {
|
||||
"cassette_library_dir": os.path.join(os.path.dirname(__file__), "cassettes"),
|
||||
}
|
||||
@@ -65,7 +65,7 @@ class CustomConverter(Converter):
|
||||
|
||||
# Fixtures
|
||||
@pytest.fixture
|
||||
def mock_agent():
|
||||
def mock_agent() -> Mock:
|
||||
agent = Mock()
|
||||
agent.function_calling_llm = None
|
||||
agent.llm = Mock()
|
||||
@@ -73,7 +73,7 @@ def mock_agent():
|
||||
|
||||
|
||||
# Tests for convert_to_model
|
||||
def test_convert_to_model_with_valid_json():
|
||||
def test_convert_to_model_with_valid_json() -> None:
|
||||
result = '{"name": "John", "age": 30}'
|
||||
output = convert_to_model(result, SimpleModel, None, None)
|
||||
assert isinstance(output, SimpleModel)
|
||||
@@ -81,7 +81,7 @@ def test_convert_to_model_with_valid_json():
|
||||
assert output.age == 30
|
||||
|
||||
|
||||
def test_convert_to_model_with_invalid_json():
|
||||
def test_convert_to_model_with_invalid_json() -> None:
|
||||
result = '{"name": "John", "age": "thirty"}'
|
||||
with patch("crewai.utilities.converter.handle_partial_json") as mock_handle:
|
||||
mock_handle.return_value = "Fallback result"
|
||||
@@ -89,13 +89,13 @@ def test_convert_to_model_with_invalid_json():
|
||||
assert output == "Fallback result"
|
||||
|
||||
|
||||
def test_convert_to_model_with_no_model():
|
||||
def test_convert_to_model_with_no_model() -> None:
|
||||
result = "Plain text"
|
||||
output = convert_to_model(result, None, None, None)
|
||||
assert output == "Plain text"
|
||||
|
||||
|
||||
def test_convert_to_model_with_special_characters():
|
||||
def test_convert_to_model_with_special_characters() -> None:
|
||||
json_string_test = """
|
||||
{
|
||||
"responses": [
|
||||
@@ -114,7 +114,7 @@ def test_convert_to_model_with_special_characters():
|
||||
)
|
||||
|
||||
|
||||
def test_convert_to_model_with_escaped_special_characters():
|
||||
def test_convert_to_model_with_escaped_special_characters() -> None:
|
||||
json_string_test = json.dumps(
|
||||
{
|
||||
"responses": [
|
||||
@@ -133,7 +133,7 @@ def test_convert_to_model_with_escaped_special_characters():
|
||||
)
|
||||
|
||||
|
||||
def test_convert_to_model_with_multiple_special_characters():
|
||||
def test_convert_to_model_with_multiple_special_characters() -> None:
|
||||
json_string_test = """
|
||||
{
|
||||
"responses": [
|
||||
@@ -153,7 +153,7 @@ def test_convert_to_model_with_multiple_special_characters():
|
||||
|
||||
|
||||
# Tests for validate_model
|
||||
def test_validate_model_pydantic_output():
|
||||
def test_validate_model_pydantic_output() -> None:
|
||||
result = '{"name": "Alice", "age": 25}'
|
||||
output = validate_model(result, SimpleModel, False)
|
||||
assert isinstance(output, SimpleModel)
|
||||
@@ -161,7 +161,7 @@ def test_validate_model_pydantic_output():
|
||||
assert output.age == 25
|
||||
|
||||
|
||||
def test_validate_model_json_output():
|
||||
def test_validate_model_json_output() -> None:
|
||||
result = '{"name": "Bob", "age": 40}'
|
||||
output = validate_model(result, SimpleModel, True)
|
||||
assert isinstance(output, dict)
|
||||
@@ -169,7 +169,7 @@ def test_validate_model_json_output():
|
||||
|
||||
|
||||
# Tests for handle_partial_json
|
||||
def test_handle_partial_json_with_valid_partial():
|
||||
def test_handle_partial_json_with_valid_partial() -> None:
|
||||
result = 'Some text {"name": "Charlie", "age": 35} more text'
|
||||
output = handle_partial_json(result, SimpleModel, False, None)
|
||||
assert isinstance(output, SimpleModel)
|
||||
@@ -177,7 +177,7 @@ def test_handle_partial_json_with_valid_partial():
|
||||
assert output.age == 35
|
||||
|
||||
|
||||
def test_handle_partial_json_with_invalid_partial(mock_agent):
|
||||
def test_handle_partial_json_with_invalid_partial(mock_agent: Mock) -> None:
|
||||
result = "No valid JSON here"
|
||||
with patch("crewai.utilities.converter.convert_with_instructions") as mock_convert:
|
||||
mock_convert.return_value = "Converted result"
|
||||
@@ -189,8 +189,8 @@ def test_handle_partial_json_with_invalid_partial(mock_agent):
|
||||
@patch("crewai.utilities.converter.create_converter")
|
||||
@patch("crewai.utilities.converter.get_conversion_instructions")
|
||||
def test_convert_with_instructions_success(
|
||||
mock_get_instructions, mock_create_converter, mock_agent
|
||||
):
|
||||
mock_get_instructions: Mock, mock_create_converter: Mock, mock_agent: Mock
|
||||
) -> None:
|
||||
mock_get_instructions.return_value = "Instructions"
|
||||
mock_converter = Mock()
|
||||
mock_converter.to_pydantic.return_value = SimpleModel(name="David", age=50)
|
||||
@@ -207,8 +207,8 @@ def test_convert_with_instructions_success(
|
||||
@patch("crewai.utilities.converter.create_converter")
|
||||
@patch("crewai.utilities.converter.get_conversion_instructions")
|
||||
def test_convert_with_instructions_failure(
|
||||
mock_get_instructions, mock_create_converter, mock_agent
|
||||
):
|
||||
mock_get_instructions: Mock, mock_create_converter: Mock, mock_agent: Mock
|
||||
) -> None:
|
||||
mock_get_instructions.return_value = "Instructions"
|
||||
mock_converter = Mock()
|
||||
mock_converter.to_pydantic.return_value = ConverterError("Conversion failed")
|
||||
@@ -222,7 +222,7 @@ def test_convert_with_instructions_failure(
|
||||
|
||||
|
||||
# Tests for get_conversion_instructions
|
||||
def test_get_conversion_instructions_gpt():
|
||||
def test_get_conversion_instructions_gpt() -> None:
|
||||
llm = LLM(model="gpt-4o-mini")
|
||||
with patch.object(LLM, "supports_function_calling") as supports_function_calling:
|
||||
supports_function_calling.return_value = True
|
||||
@@ -237,7 +237,7 @@ def test_get_conversion_instructions_gpt():
|
||||
assert instructions == expected_instructions
|
||||
|
||||
|
||||
def test_get_conversion_instructions_non_gpt():
|
||||
def test_get_conversion_instructions_non_gpt() -> None:
|
||||
llm = LLM(model="ollama/llama3.1", base_url="http://localhost:11434")
|
||||
with patch.object(LLM, "supports_function_calling", return_value=False):
|
||||
instructions = get_conversion_instructions(SimpleModel, llm)
|
||||
@@ -246,17 +246,17 @@ def test_get_conversion_instructions_non_gpt():
|
||||
|
||||
|
||||
# Tests for is_gpt
|
||||
def test_supports_function_calling_true():
|
||||
def test_supports_function_calling_true() -> None:
|
||||
llm = LLM(model="gpt-4o")
|
||||
assert llm.supports_function_calling() is True
|
||||
|
||||
|
||||
def test_supports_function_calling_false():
|
||||
def test_supports_function_calling_false() -> None:
|
||||
llm = LLM(model="non-existent-model", is_litellm=True)
|
||||
assert llm.supports_function_calling() is False
|
||||
|
||||
|
||||
def test_create_converter_with_mock_agent():
|
||||
def test_create_converter_with_mock_agent() -> None:
|
||||
mock_agent = MagicMock()
|
||||
mock_agent.get_output_converter.return_value = MagicMock(spec=Converter)
|
||||
|
||||
@@ -272,7 +272,7 @@ def test_create_converter_with_mock_agent():
|
||||
mock_agent.get_output_converter.assert_called_once()
|
||||
|
||||
|
||||
def test_create_converter_with_custom_converter():
|
||||
def test_create_converter_with_custom_converter() -> None:
|
||||
converter = create_converter(
|
||||
converter_cls=CustomConverter,
|
||||
llm=LLM(model="gpt-4o-mini"),
|
||||
@@ -284,7 +284,7 @@ def test_create_converter_with_custom_converter():
|
||||
assert isinstance(converter, CustomConverter)
|
||||
|
||||
|
||||
def test_create_converter_fails_without_agent_or_converter_cls():
|
||||
def test_create_converter_fails_without_agent_or_converter_cls() -> None:
|
||||
with pytest.raises(
|
||||
ValueError, match="Either agent or converter_cls must be provided"
|
||||
):
|
||||
@@ -293,13 +293,13 @@ def test_create_converter_fails_without_agent_or_converter_cls():
|
||||
)
|
||||
|
||||
|
||||
def test_generate_model_description_simple_model():
|
||||
def test_generate_model_description_simple_model() -> None:
|
||||
description = generate_model_description(SimpleModel)
|
||||
expected_description = '{\n "name": str,\n "age": int\n}'
|
||||
assert description == expected_description
|
||||
|
||||
|
||||
def test_generate_model_description_nested_model():
|
||||
def test_generate_model_description_nested_model() -> None:
|
||||
description = generate_model_description(NestedModel)
|
||||
expected_description = (
|
||||
'{\n "id": int,\n "data": {\n "name": str,\n "age": int\n}\n}'
|
||||
@@ -307,7 +307,7 @@ def test_generate_model_description_nested_model():
|
||||
assert description == expected_description
|
||||
|
||||
|
||||
def test_generate_model_description_optional_field():
|
||||
def test_generate_model_description_optional_field() -> None:
|
||||
class ModelWithOptionalField(BaseModel):
|
||||
name: str
|
||||
age: int | None
|
||||
@@ -317,7 +317,7 @@ def test_generate_model_description_optional_field():
|
||||
assert description == expected_description
|
||||
|
||||
|
||||
def test_generate_model_description_list_field():
|
||||
def test_generate_model_description_list_field() -> None:
|
||||
class ModelWithListField(BaseModel):
|
||||
items: list[int]
|
||||
|
||||
@@ -326,7 +326,7 @@ def test_generate_model_description_list_field():
|
||||
assert description == expected_description
|
||||
|
||||
|
||||
def test_generate_model_description_dict_field():
|
||||
def test_generate_model_description_dict_field() -> None:
|
||||
class ModelWithDictField(BaseModel):
|
||||
attributes: dict[str, int]
|
||||
|
||||
@@ -336,7 +336,7 @@ def test_generate_model_description_dict_field():
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_convert_with_instructions():
|
||||
def test_convert_with_instructions() -> None:
|
||||
llm = LLM(model="gpt-4o-mini")
|
||||
sample_text = "Name: Alice, Age: 30"
|
||||
|
||||
@@ -358,7 +358,7 @@ def test_convert_with_instructions():
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_converter_with_llama3_2_model():
|
||||
def test_converter_with_llama3_2_model() -> None:
|
||||
llm = LLM(model="openrouter/meta-llama/llama-3.2-3b-instruct")
|
||||
sample_text = "Name: Alice Llama, Age: 30"
|
||||
instructions = get_conversion_instructions(SimpleModel, llm)
|
||||
@@ -375,7 +375,7 @@ def test_converter_with_llama3_2_model():
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_converter_with_llama3_1_model():
|
||||
def test_converter_with_llama3_1_model() -> None:
|
||||
llm = LLM(model="ollama/llama3.1", base_url="http://localhost:11434")
|
||||
sample_text = "Name: Alice Llama, Age: 30"
|
||||
instructions = get_conversion_instructions(SimpleModel, llm)
|
||||
@@ -392,7 +392,7 @@ def test_converter_with_llama3_1_model():
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_converter_with_nested_model():
|
||||
def test_converter_with_nested_model() -> None:
|
||||
llm = LLM(model="gpt-4o-mini")
|
||||
sample_text = "Name: John Doe\nAge: 30\nAddress: 123 Main St, Anytown, 12345"
|
||||
|
||||
@@ -416,7 +416,7 @@ def test_converter_with_nested_model():
|
||||
|
||||
|
||||
# Tests for error handling
|
||||
def test_converter_error_handling():
|
||||
def test_converter_error_handling() -> None:
|
||||
llm = Mock(spec=LLM)
|
||||
llm.supports_function_calling.return_value = False
|
||||
llm.call.return_value = "Invalid JSON"
|
||||
@@ -437,7 +437,7 @@ def test_converter_error_handling():
|
||||
|
||||
|
||||
# Tests for retry logic
|
||||
def test_converter_retry_logic():
|
||||
def test_converter_retry_logic() -> None:
|
||||
llm = Mock(spec=LLM)
|
||||
llm.supports_function_calling.return_value = False
|
||||
llm.call.side_effect = [
|
||||
@@ -465,7 +465,7 @@ def test_converter_retry_logic():
|
||||
|
||||
|
||||
# Tests for optional fields
|
||||
def test_converter_with_optional_fields():
|
||||
def test_converter_with_optional_fields() -> None:
|
||||
class OptionalModel(BaseModel):
|
||||
name: str
|
||||
age: int | None
|
||||
@@ -492,7 +492,7 @@ def test_converter_with_optional_fields():
|
||||
|
||||
|
||||
# Tests for list fields
|
||||
def test_converter_with_list_field():
|
||||
def test_converter_with_list_field() -> None:
|
||||
class ListModel(BaseModel):
|
||||
items: list[int]
|
||||
|
||||
@@ -515,7 +515,7 @@ def test_converter_with_list_field():
|
||||
assert output.items == [1, 2, 3]
|
||||
|
||||
|
||||
def test_converter_with_enum():
|
||||
def test_converter_with_enum() -> None:
|
||||
class Color(Enum):
|
||||
RED = "red"
|
||||
GREEN = "green"
|
||||
@@ -546,7 +546,7 @@ def test_converter_with_enum():
|
||||
|
||||
|
||||
# Tests for ambiguous input
|
||||
def test_converter_with_ambiguous_input():
|
||||
def test_converter_with_ambiguous_input() -> None:
|
||||
llm = Mock(spec=LLM)
|
||||
llm.supports_function_calling.return_value = False
|
||||
llm.call.return_value = '{"name": "Charlie", "age": "Not an age"}'
|
||||
@@ -567,7 +567,7 @@ def test_converter_with_ambiguous_input():
|
||||
|
||||
|
||||
# Tests for function calling support
|
||||
def test_converter_with_function_calling():
|
||||
def test_converter_with_function_calling() -> None:
|
||||
llm = Mock(spec=LLM)
|
||||
llm.supports_function_calling.return_value = True
|
||||
|
||||
@@ -580,20 +580,359 @@ def test_converter_with_function_calling():
|
||||
model=SimpleModel,
|
||||
instructions="Convert this text.",
|
||||
)
|
||||
converter._create_instructor = Mock(return_value=instructor)
|
||||
|
||||
with patch.object(converter, '_create_instructor', return_value=instructor):
|
||||
output = converter.to_pydantic()
|
||||
|
||||
output = converter.to_pydantic()
|
||||
|
||||
assert isinstance(output, SimpleModel)
|
||||
assert output.name == "Eve"
|
||||
assert output.age == 35
|
||||
assert isinstance(output, SimpleModel)
|
||||
assert output.name == "Eve"
|
||||
assert output.age == 35
|
||||
instructor.to_pydantic.assert_called_once()
|
||||
|
||||
|
||||
def test_generate_model_description_union_field():
|
||||
def test_generate_model_description_union_field() -> None:
|
||||
class UnionModel(BaseModel):
|
||||
field: int | str | None
|
||||
|
||||
description = generate_model_description(UnionModel)
|
||||
expected_description = '{\n "field": int | str | None\n}'
|
||||
assert description == expected_description
|
||||
|
||||
def test_internal_instructor_with_openai_provider() -> None:
|
||||
"""Test InternalInstructor with OpenAI provider using registry pattern."""
|
||||
from crewai.utilities.internal_instructor import InternalInstructor
|
||||
|
||||
# Mock LLM with OpenAI provider
|
||||
mock_llm = Mock()
|
||||
mock_llm.is_litellm = False
|
||||
mock_llm.model = "gpt-4o"
|
||||
mock_llm.provider = "openai"
|
||||
|
||||
# Mock instructor client
|
||||
mock_client = Mock()
|
||||
mock_client.chat.completions.create.return_value = SimpleModel(name="Test", age=25)
|
||||
|
||||
# Patch the instructor import at the method level
|
||||
with patch.object(InternalInstructor, '_create_instructor_client') as mock_create_client:
|
||||
mock_create_client.return_value = mock_client
|
||||
|
||||
instructor = InternalInstructor(
|
||||
content="Test content",
|
||||
model=SimpleModel,
|
||||
llm=mock_llm
|
||||
)
|
||||
|
||||
result = instructor.to_pydantic()
|
||||
|
||||
assert isinstance(result, SimpleModel)
|
||||
assert result.name == "Test"
|
||||
assert result.age == 25
|
||||
# Verify the method was called with the correct LLM
|
||||
mock_create_client.assert_called_once()
|
||||
|
||||
|
||||
def test_internal_instructor_with_anthropic_provider() -> None:
|
||||
"""Test InternalInstructor with Anthropic provider using registry pattern."""
|
||||
from crewai.utilities.internal_instructor import InternalInstructor
|
||||
|
||||
# Mock LLM with Anthropic provider
|
||||
mock_llm = Mock()
|
||||
mock_llm.is_litellm = False
|
||||
mock_llm.model = "claude-3-5-sonnet-20241022"
|
||||
mock_llm.provider = "anthropic"
|
||||
|
||||
# Mock instructor client
|
||||
mock_client = Mock()
|
||||
mock_client.chat.completions.create.return_value = SimpleModel(name="Bob", age=25)
|
||||
|
||||
# Patch the instructor import at the method level
|
||||
with patch.object(InternalInstructor, '_create_instructor_client') as mock_create_client:
|
||||
mock_create_client.return_value = mock_client
|
||||
|
||||
instructor = InternalInstructor(
|
||||
content="Name: Bob, Age: 25",
|
||||
model=SimpleModel,
|
||||
llm=mock_llm
|
||||
)
|
||||
|
||||
result = instructor.to_pydantic()
|
||||
|
||||
assert isinstance(result, SimpleModel)
|
||||
assert result.name == "Bob"
|
||||
assert result.age == 25
|
||||
# Verify the method was called with the correct LLM
|
||||
mock_create_client.assert_called_once()
|
||||
|
||||
|
||||
def test_factory_pattern_registry_extensibility() -> None:
|
||||
"""Test that the factory pattern registry works with different providers."""
|
||||
from crewai.utilities.internal_instructor import InternalInstructor
|
||||
|
||||
# Test with OpenAI provider
|
||||
mock_llm_openai = Mock()
|
||||
mock_llm_openai.is_litellm = False
|
||||
mock_llm_openai.model = "gpt-4o-mini"
|
||||
mock_llm_openai.provider = "openai"
|
||||
|
||||
mock_client_openai = Mock()
|
||||
mock_client_openai.chat.completions.create.return_value = SimpleModel(name="Alice", age=30)
|
||||
|
||||
with patch.object(InternalInstructor, '_create_instructor_client') as mock_create_client:
|
||||
mock_create_client.return_value = mock_client_openai
|
||||
|
||||
instructor_openai = InternalInstructor(
|
||||
content="Name: Alice, Age: 30",
|
||||
model=SimpleModel,
|
||||
llm=mock_llm_openai
|
||||
)
|
||||
|
||||
result_openai = instructor_openai.to_pydantic()
|
||||
|
||||
assert isinstance(result_openai, SimpleModel)
|
||||
assert result_openai.name == "Alice"
|
||||
assert result_openai.age == 30
|
||||
|
||||
# Test with Anthropic provider
|
||||
mock_llm_anthropic = Mock()
|
||||
mock_llm_anthropic.is_litellm = False
|
||||
mock_llm_anthropic.model = "claude-3-5-sonnet-20241022"
|
||||
mock_llm_anthropic.provider = "anthropic"
|
||||
|
||||
mock_client_anthropic = Mock()
|
||||
mock_client_anthropic.chat.completions.create.return_value = SimpleModel(name="Bob", age=25)
|
||||
|
||||
with patch.object(InternalInstructor, '_create_instructor_client') as mock_create_client:
|
||||
mock_create_client.return_value = mock_client_anthropic
|
||||
|
||||
instructor_anthropic = InternalInstructor(
|
||||
content="Name: Bob, Age: 25",
|
||||
model=SimpleModel,
|
||||
llm=mock_llm_anthropic
|
||||
)
|
||||
|
||||
result_anthropic = instructor_anthropic.to_pydantic()
|
||||
|
||||
assert isinstance(result_anthropic, SimpleModel)
|
||||
assert result_anthropic.name == "Bob"
|
||||
assert result_anthropic.age == 25
|
||||
|
||||
# Test with Bedrock provider
|
||||
mock_llm_bedrock = Mock()
|
||||
mock_llm_bedrock.is_litellm = False
|
||||
mock_llm_bedrock.model = "claude-3-5-sonnet-20241022"
|
||||
mock_llm_bedrock.provider = "bedrock"
|
||||
|
||||
mock_client_bedrock = Mock()
|
||||
mock_client_bedrock.chat.completions.create.return_value = SimpleModel(name="Charlie", age=35)
|
||||
|
||||
with patch.object(InternalInstructor, '_create_instructor_client') as mock_create_client:
|
||||
mock_create_client.return_value = mock_client_bedrock
|
||||
|
||||
instructor_bedrock = InternalInstructor(
|
||||
content="Name: Charlie, Age: 35",
|
||||
model=SimpleModel,
|
||||
llm=mock_llm_bedrock
|
||||
)
|
||||
|
||||
result_bedrock = instructor_bedrock.to_pydantic()
|
||||
|
||||
assert isinstance(result_bedrock, SimpleModel)
|
||||
assert result_bedrock.name == "Charlie"
|
||||
assert result_bedrock.age == 35
|
||||
|
||||
# Test with Google provider
|
||||
mock_llm_google = Mock()
|
||||
mock_llm_google.is_litellm = False
|
||||
mock_llm_google.model = "gemini-1.5-flash"
|
||||
mock_llm_google.provider = "google"
|
||||
|
||||
mock_client_google = Mock()
|
||||
mock_client_google.chat.completions.create.return_value = SimpleModel(name="Diana", age=28)
|
||||
|
||||
with patch.object(InternalInstructor, '_create_instructor_client') as mock_create_client:
|
||||
mock_create_client.return_value = mock_client_google
|
||||
|
||||
instructor_google = InternalInstructor(
|
||||
content="Name: Diana, Age: 28",
|
||||
model=SimpleModel,
|
||||
llm=mock_llm_google
|
||||
)
|
||||
|
||||
result_google = instructor_google.to_pydantic()
|
||||
|
||||
assert isinstance(result_google, SimpleModel)
|
||||
assert result_google.name == "Diana"
|
||||
assert result_google.age == 28
|
||||
|
||||
# Test with Azure provider
|
||||
mock_llm_azure = Mock()
|
||||
mock_llm_azure.is_litellm = False
|
||||
mock_llm_azure.model = "gpt-4o"
|
||||
mock_llm_azure.provider = "azure"
|
||||
|
||||
mock_client_azure = Mock()
|
||||
mock_client_azure.chat.completions.create.return_value = SimpleModel(name="Eve", age=32)
|
||||
|
||||
with patch.object(InternalInstructor, '_create_instructor_client') as mock_create_client:
|
||||
mock_create_client.return_value = mock_client_azure
|
||||
|
||||
instructor_azure = InternalInstructor(
|
||||
content="Name: Eve, Age: 32",
|
||||
model=SimpleModel,
|
||||
llm=mock_llm_azure
|
||||
)
|
||||
|
||||
result_azure = instructor_azure.to_pydantic()
|
||||
|
||||
assert isinstance(result_azure, SimpleModel)
|
||||
assert result_azure.name == "Eve"
|
||||
assert result_azure.age == 32
|
||||
|
||||
|
||||
def test_internal_instructor_with_bedrock_provider() -> None:
|
||||
"""Test InternalInstructor with AWS Bedrock provider using registry pattern."""
|
||||
from crewai.utilities.internal_instructor import InternalInstructor
|
||||
|
||||
# Mock LLM with Bedrock provider
|
||||
mock_llm = Mock()
|
||||
mock_llm.is_litellm = False
|
||||
mock_llm.model = "claude-3-5-sonnet-20241022"
|
||||
mock_llm.provider = "bedrock"
|
||||
|
||||
# Mock instructor client
|
||||
mock_client = Mock()
|
||||
mock_client.chat.completions.create.return_value = SimpleModel(name="Charlie", age=35)
|
||||
|
||||
# Patch the instructor import at the method level
|
||||
with patch.object(InternalInstructor, '_create_instructor_client') as mock_create_client:
|
||||
mock_create_client.return_value = mock_client
|
||||
|
||||
instructor = InternalInstructor(
|
||||
content="Name: Charlie, Age: 35",
|
||||
model=SimpleModel,
|
||||
llm=mock_llm
|
||||
)
|
||||
|
||||
result = instructor.to_pydantic()
|
||||
|
||||
assert isinstance(result, SimpleModel)
|
||||
assert result.name == "Charlie"
|
||||
assert result.age == 35
|
||||
# Verify the method was called with the correct LLM
|
||||
mock_create_client.assert_called_once()
|
||||
|
||||
|
||||
def test_internal_instructor_with_gemini_provider() -> None:
|
||||
"""Test InternalInstructor with Google Gemini provider using registry pattern."""
|
||||
from crewai.utilities.internal_instructor import InternalInstructor
|
||||
|
||||
# Mock LLM with Gemini provider
|
||||
mock_llm = Mock()
|
||||
mock_llm.is_litellm = False
|
||||
mock_llm.model = "gemini-1.5-flash"
|
||||
mock_llm.provider = "google"
|
||||
|
||||
# Mock instructor client
|
||||
mock_client = Mock()
|
||||
mock_client.chat.completions.create.return_value = SimpleModel(name="Diana", age=28)
|
||||
|
||||
# Patch the instructor import at the method level
|
||||
with patch.object(InternalInstructor, '_create_instructor_client') as mock_create_client:
|
||||
mock_create_client.return_value = mock_client
|
||||
|
||||
instructor = InternalInstructor(
|
||||
content="Name: Diana, Age: 28",
|
||||
model=SimpleModel,
|
||||
llm=mock_llm
|
||||
)
|
||||
|
||||
result = instructor.to_pydantic()
|
||||
|
||||
assert isinstance(result, SimpleModel)
|
||||
assert result.name == "Diana"
|
||||
assert result.age == 28
|
||||
# Verify the method was called with the correct LLM
|
||||
mock_create_client.assert_called_once()
|
||||
|
||||
|
||||
def test_internal_instructor_with_azure_provider() -> None:
|
||||
"""Test InternalInstructor with Azure OpenAI provider using registry pattern."""
|
||||
from crewai.utilities.internal_instructor import InternalInstructor
|
||||
|
||||
# Mock LLM with Azure provider
|
||||
mock_llm = Mock()
|
||||
mock_llm.is_litellm = False
|
||||
mock_llm.model = "gpt-4o"
|
||||
mock_llm.provider = "azure"
|
||||
|
||||
# Mock instructor client
|
||||
mock_client = Mock()
|
||||
mock_client.chat.completions.create.return_value = SimpleModel(name="Eve", age=32)
|
||||
|
||||
# Patch the instructor import at the method level
|
||||
with patch.object(InternalInstructor, '_create_instructor_client') as mock_create_client:
|
||||
mock_create_client.return_value = mock_client
|
||||
|
||||
instructor = InternalInstructor(
|
||||
content="Name: Eve, Age: 32",
|
||||
model=SimpleModel,
|
||||
llm=mock_llm
|
||||
)
|
||||
|
||||
result = instructor.to_pydantic()
|
||||
|
||||
assert isinstance(result, SimpleModel)
|
||||
assert result.name == "Eve"
|
||||
assert result.age == 32
|
||||
# Verify the method was called with the correct LLM
|
||||
mock_create_client.assert_called_once()
|
||||
|
||||
|
||||
def test_internal_instructor_unsupported_provider() -> None:
|
||||
"""Test InternalInstructor with unsupported provider raises appropriate error."""
|
||||
from crewai.utilities.internal_instructor import InternalInstructor
|
||||
|
||||
# Mock LLM with unsupported provider
|
||||
mock_llm = Mock()
|
||||
mock_llm.is_litellm = False
|
||||
mock_llm.model = "unsupported-model"
|
||||
mock_llm.provider = "unsupported"
|
||||
|
||||
# Mock the _create_instructor_client method to raise an error for unsupported providers
|
||||
with patch.object(InternalInstructor, '_create_instructor_client') as mock_create_client:
|
||||
mock_create_client.side_effect = Exception("Unsupported provider: unsupported")
|
||||
|
||||
# This should raise an error when trying to create the instructor client
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
instructor = InternalInstructor(
|
||||
content="Test content",
|
||||
model=SimpleModel,
|
||||
llm=mock_llm
|
||||
)
|
||||
instructor.to_pydantic()
|
||||
|
||||
# Verify it's the expected error
|
||||
assert "Unsupported provider" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_internal_instructor_real_unsupported_provider() -> None:
|
||||
"""Test InternalInstructor with real unsupported provider using actual instructor library."""
|
||||
from crewai.utilities.internal_instructor import InternalInstructor
|
||||
|
||||
# Mock LLM with unsupported provider that would actually fail with instructor
|
||||
mock_llm = Mock()
|
||||
mock_llm.is_litellm = False
|
||||
mock_llm.model = "unsupported-model"
|
||||
mock_llm.provider = "unsupported"
|
||||
|
||||
# This should raise a ConfigurationError from the real instructor library
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
instructor = InternalInstructor(
|
||||
content="Test content",
|
||||
model=SimpleModel,
|
||||
llm=mock_llm
|
||||
)
|
||||
instructor.to_pydantic()
|
||||
|
||||
# Verify it's a configuration error about unsupported provider
|
||||
assert "Unsupported provider" in str(exc_info.value) or "unsupported" in str(exc_info.value).lower()
|
||||
|
||||
98
lib/crewai/tests/utilities/test_i18n_multilanguage.py
Normal file
98
lib/crewai/tests/utilities/test_i18n_multilanguage.py
Normal file
@@ -0,0 +1,98 @@
|
||||
import pytest
|
||||
from crewai.utilities.i18n import I18N
|
||||
|
||||
|
||||
def test_default_language_is_english():
|
||||
i18n = I18N()
|
||||
assert i18n.language == "en"
|
||||
assert isinstance(i18n.slice("role_playing"), str)
|
||||
assert "You are {role}" in i18n.slice("role_playing")
|
||||
|
||||
|
||||
def test_explicit_english_language():
|
||||
i18n = I18N(language="en")
|
||||
assert i18n.language == "en"
|
||||
assert isinstance(i18n.slice("role_playing"), str)
|
||||
assert "You are {role}" in i18n.slice("role_playing")
|
||||
|
||||
|
||||
def test_spanish_language():
|
||||
i18n = I18N(language="es")
|
||||
assert i18n.language == "es"
|
||||
assert isinstance(i18n.slice("role_playing"), str)
|
||||
assert "Eres {role}" in i18n.slice("role_playing")
|
||||
|
||||
|
||||
def test_spanish_hierarchical_manager():
|
||||
i18n = I18N(language="es")
|
||||
role = i18n.retrieve("hierarchical_manager_agent", "role")
|
||||
goal = i18n.retrieve("hierarchical_manager_agent", "goal")
|
||||
backstory = i18n.retrieve("hierarchical_manager_agent", "backstory")
|
||||
|
||||
assert role == "Gerente del Equipo"
|
||||
assert "Gestionar el equipo" in goal
|
||||
assert "gerente experimentado" in backstory
|
||||
|
||||
|
||||
def test_spanish_errors():
|
||||
i18n = I18N(language="es")
|
||||
error = i18n.errors("tool_usage_error")
|
||||
assert "Encontré un error" in error
|
||||
|
||||
|
||||
def test_spanish_tools():
|
||||
i18n = I18N(language="es")
|
||||
delegate_work = i18n.tools("delegate_work")
|
||||
assert "Delega una tarea específica" in delegate_work
|
||||
|
||||
|
||||
def test_fallback_to_english_for_unsupported_language():
|
||||
i18n = I18N(language="fr")
|
||||
assert isinstance(i18n.slice("role_playing"), str)
|
||||
assert "You are {role}" in i18n.slice("role_playing")
|
||||
|
||||
|
||||
def test_custom_prompt_file_overrides_language():
|
||||
import os
|
||||
import tempfile
|
||||
import json
|
||||
|
||||
custom_prompts = {
|
||||
"slices": {
|
||||
"role_playing": "Custom role playing prompt"
|
||||
}
|
||||
}
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
||||
json.dump(custom_prompts, f)
|
||||
temp_file = f.name
|
||||
|
||||
try:
|
||||
i18n = I18N(prompt_file=temp_file, language="es")
|
||||
assert i18n.slice("role_playing") == "Custom role playing prompt"
|
||||
finally:
|
||||
os.unlink(temp_file)
|
||||
|
||||
|
||||
def test_retrieve_with_spanish():
|
||||
i18n = I18N(language="es")
|
||||
observation = i18n.retrieve("slices", "observation")
|
||||
assert "Observación" in observation
|
||||
|
||||
|
||||
def test_spanish_reasoning_prompts():
|
||||
i18n = I18N(language="es")
|
||||
initial_plan = i18n.retrieve("reasoning", "initial_plan")
|
||||
assert "Eres {role}" in initial_plan
|
||||
assert "profesional" in initial_plan
|
||||
|
||||
|
||||
def test_language_parameter_validation():
|
||||
i18n = I18N(language="en")
|
||||
assert i18n.language == "en"
|
||||
|
||||
i18n_es = I18N(language="es")
|
||||
assert i18n_es.language == "es"
|
||||
|
||||
i18n_pt = I18N(language="pt")
|
||||
assert i18n_pt.language == "pt"
|
||||
@@ -1,3 +1,3 @@
|
||||
"""CrewAI development tools."""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__version__ = "1.1.0"
|
||||
|
||||
@@ -124,7 +124,7 @@ exclude = [
|
||||
"lib/crewai-tools/tests/",
|
||||
"lib/crewai/src/crewai/experimental/a2a"
|
||||
]
|
||||
plugins = ["pydantic.mypy"]
|
||||
plugins = ["pydantic.mypy", "crewai.mypy"]
|
||||
|
||||
|
||||
[tool.bandit]
|
||||
|
||||
56
uv.lock
generated
56
uv.lock
generated
@@ -204,18 +204,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aisuite"
|
||||
version = "0.1.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/17/07/129a68a6f74a80fc1d189064a2f576a84a1a05f14f211fde9352668d1c25/aisuite-0.1.11.tar.gz", hash = "sha256:27260075f8502b9cb40ef476cae29544e39316bbf4b4318464eb4c728e72146a", size = 27533, upload-time = "2025-03-26T12:04:44.068Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/f7/a2799bf017d0303bb2f6c10f55f9c85619a0c8b9cf77fb8a9579961bfe88/aisuite-0.1.11-py3-none-any.whl", hash = "sha256:14293e9b7d81268dabe9b1cbb41cab64ca6c0272b52166213a7fa80196140d7c", size = 41222, upload-time = "2025-03-26T12:04:42.472Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
@@ -1098,9 +1086,6 @@ dependencies = [
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
aisuite = [
|
||||
{ name = "aisuite" },
|
||||
]
|
||||
anthropic = [
|
||||
{ name = "anthropic" },
|
||||
]
|
||||
@@ -1153,7 +1138,6 @@ watson = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "aisuite", marker = "extra == 'aisuite'", specifier = ">=0.1.11" },
|
||||
{ name = "anthropic", marker = "extra == 'anthropic'", specifier = ">=0.69.0" },
|
||||
{ name = "appdirs", specifier = ">=1.4.4" },
|
||||
{ name = "azure-ai-inference", marker = "extra == 'azure-ai-inference'", specifier = ">=1.0.0b9" },
|
||||
@@ -1196,7 +1180,7 @@ requires-dist = [
|
||||
{ name = "uv", specifier = ">=0.4.25" },
|
||||
{ name = "voyageai", marker = "extra == 'voyageai'", specifier = ">=0.3.5" },
|
||||
]
|
||||
provides-extras = ["aisuite", "anthropic", "aws", "azure-ai-inference", "bedrock", "docling", "embeddings", "google-genai", "litellm", "mem0", "openpyxl", "pandas", "pdfplumber", "qdrant", "tools", "voyageai", "watson"]
|
||||
provides-extras = ["anthropic", "aws", "azure-ai-inference", "bedrock", "docling", "embeddings", "google-genai", "litellm", "mem0", "openpyxl", "pandas", "pdfplumber", "qdrant", "tools", "voyageai", "watson"]
|
||||
|
||||
[[package]]
|
||||
name = "crewai-devtools"
|
||||
@@ -1810,7 +1794,7 @@ name = "exceptiongroup"
|
||||
version = "1.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
|
||||
wheels = [
|
||||
@@ -4445,7 +4429,7 @@ name = "nvidia-cudnn-cu12"
|
||||
version = "9.10.2.21"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-cublas-cu12" },
|
||||
{ name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" },
|
||||
@@ -4456,7 +4440,7 @@ name = "nvidia-cufft-cu12"
|
||||
version = "11.3.3.83"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-nvjitlink-cu12" },
|
||||
{ name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" },
|
||||
@@ -4483,9 +4467,9 @@ name = "nvidia-cusolver-cu12"
|
||||
version = "11.7.3.90"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-cublas-cu12" },
|
||||
{ name = "nvidia-cusparse-cu12" },
|
||||
{ name = "nvidia-nvjitlink-cu12" },
|
||||
{ name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
{ name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
{ name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" },
|
||||
@@ -4496,7 +4480,7 @@ name = "nvidia-cusparse-cu12"
|
||||
version = "12.5.8.93"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-nvjitlink-cu12" },
|
||||
{ name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" },
|
||||
@@ -4556,9 +4540,9 @@ name = "ocrmac"
|
||||
version = "1.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "pillow" },
|
||||
{ name = "pyobjc-framework-vision" },
|
||||
{ name = "click", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pillow", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-vision", marker = "sys_platform == 'darwin'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dd/dc/de3e9635774b97d9766f6815bbb3f5ec9bce347115f10d9abbf2733a9316/ocrmac-1.0.0.tar.gz", hash = "sha256:5b299e9030c973d1f60f82db000d6c2e5ff271601878c7db0885e850597d1d2e", size = 1463997, upload-time = "2024-11-07T12:00:00.197Z" }
|
||||
wheels = [
|
||||
@@ -6183,7 +6167,7 @@ name = "pyobjc-framework-cocoa"
|
||||
version = "11.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core" },
|
||||
{ name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4b/c5/7a866d24bc026f79239b74d05e2cf3088b03263da66d53d1b4cf5207f5ae/pyobjc_framework_cocoa-11.1.tar.gz", hash = "sha256:87df76b9b73e7ca699a828ff112564b59251bb9bbe72e610e670a4dc9940d038", size = 5565335, upload-time = "2025-06-14T20:56:59.683Z" }
|
||||
wheels = [
|
||||
@@ -6199,8 +6183,8 @@ name = "pyobjc-framework-coreml"
|
||||
version = "11.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core" },
|
||||
{ name = "pyobjc-framework-cocoa" },
|
||||
{ name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0d/5d/4309f220981d769b1a2f0dcb2c5c104490d31389a8ebea67e5595ce1cb74/pyobjc_framework_coreml-11.1.tar.gz", hash = "sha256:775923eefb9eac2e389c0821b10564372de8057cea89f1ea1cdaf04996c970a7", size = 82005, upload-time = "2025-06-14T20:57:12.004Z" }
|
||||
wheels = [
|
||||
@@ -6216,8 +6200,8 @@ name = "pyobjc-framework-quartz"
|
||||
version = "11.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core" },
|
||||
{ name = "pyobjc-framework-cocoa" },
|
||||
{ name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/ac/6308fec6c9ffeda9942fef72724f4094c6df4933560f512e63eac37ebd30/pyobjc_framework_quartz-11.1.tar.gz", hash = "sha256:a57f35ccfc22ad48c87c5932818e583777ff7276605fef6afad0ac0741169f75", size = 3953275, upload-time = "2025-06-14T20:58:17.924Z" }
|
||||
wheels = [
|
||||
@@ -6233,10 +6217,10 @@ name = "pyobjc-framework-vision"
|
||||
version = "11.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core" },
|
||||
{ name = "pyobjc-framework-cocoa" },
|
||||
{ name = "pyobjc-framework-coreml" },
|
||||
{ name = "pyobjc-framework-quartz" },
|
||||
{ name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-coreml", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/40/a8/7128da4d0a0103cabe58910a7233e2f98d18c590b1d36d4b3efaaedba6b9/pyobjc_framework_vision-11.1.tar.gz", hash = "sha256:26590512ee7758da3056499062a344b8a351b178be66d4b719327884dde4216b", size = 133721, upload-time = "2025-06-14T20:58:46.095Z" }
|
||||
wheels = [
|
||||
|
||||
Reference in New Issue
Block a user