mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-05 14:18:30 +00:00
Compare commits
11 Commits
1.6.0
...
gl/chore/u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cd5a30873 | ||
|
|
02580f58d1 | ||
|
|
8b83bf3e54 | ||
|
|
93f1fbd75e | ||
|
|
0803318002 | ||
|
|
6fb13ee3e0 | ||
|
|
67e39073c7 | ||
|
|
722d316824 | ||
|
|
a824d52e5e | ||
|
|
d8fe83f76c | ||
|
|
46785adf58 |
@@ -1212,7 +1212,7 @@ Learn how to get the most out of your LLM configuration:
|
||||
```python
|
||||
import httpx
|
||||
from crewai import LLM
|
||||
from crewai.llms.hooks import BaseInterceptor
|
||||
from crewai.llm.hooks import BaseInterceptor
|
||||
|
||||
class CustomInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
"""Custom interceptor to modify requests and responses."""
|
||||
|
||||
@@ -8,8 +8,8 @@ from crewai.crew import Crew
|
||||
from crewai.crews.crew_output import CrewOutput
|
||||
from crewai.flow.flow import Flow
|
||||
from crewai.knowledge.knowledge import Knowledge
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.llm.core import LLM
|
||||
from crewai.process import Process
|
||||
from crewai.task import Task
|
||||
from crewai.tasks.llm_guardrail import LLMGuardrail
|
||||
|
||||
@@ -39,7 +39,7 @@ from crewai.knowledge.knowledge import Knowledge
|
||||
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
|
||||
from crewai.knowledge.utils.knowledge_utils import extract_knowledge_context
|
||||
from crewai.lite_agent import LiteAgent
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.mcp import (
|
||||
MCPClient,
|
||||
MCPServerConfig,
|
||||
@@ -633,7 +633,7 @@ class Agent(BaseAgent):
|
||||
)
|
||||
|
||||
self.agent_executor = CrewAgentExecutor(
|
||||
llm=self.llm,
|
||||
llm=self.llm, # type: ignore[arg-type]
|
||||
task=task, # type: ignore[arg-type]
|
||||
agent=self,
|
||||
crew=self.crew,
|
||||
@@ -810,6 +810,7 @@ class Agent(BaseAgent):
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.tools.mcp_native_tool import MCPNativeTool
|
||||
|
||||
transport: StdioTransport | HTTPTransport | SSETransport
|
||||
if isinstance(mcp_config, MCPServerStdio):
|
||||
transport = StdioTransport(
|
||||
command=mcp_config.command,
|
||||
@@ -903,10 +904,12 @@ class Agent(BaseAgent):
|
||||
server_name=server_name,
|
||||
run_context=None,
|
||||
)
|
||||
if mcp_config.tool_filter(context, tool):
|
||||
# Try new signature first
|
||||
if mcp_config.tool_filter(context, tool): # type: ignore[arg-type,call-arg]
|
||||
filtered_tools.append(tool)
|
||||
except (TypeError, AttributeError):
|
||||
if mcp_config.tool_filter(tool):
|
||||
# Fallback to old signature
|
||||
if mcp_config.tool_filter(tool): # type: ignore[arg-type,call-arg]
|
||||
filtered_tools.append(tool)
|
||||
else:
|
||||
# Not callable - include tool
|
||||
@@ -981,7 +984,9 @@ class Agent(BaseAgent):
|
||||
path = parsed.path.replace("/", "_").strip("_")
|
||||
return f"{domain}_{path}" if path else domain
|
||||
|
||||
def _get_mcp_tool_schemas(self, server_params: dict) -> dict[str, dict]:
|
||||
def _get_mcp_tool_schemas(
|
||||
self, server_params: dict[str, Any]
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Get tool schemas from MCP server for wrapper creation with caching."""
|
||||
server_url = server_params["url"]
|
||||
|
||||
@@ -995,7 +1000,7 @@ class Agent(BaseAgent):
|
||||
self._logger.log(
|
||||
"debug", f"Using cached MCP tool schemas for {server_url}"
|
||||
)
|
||||
return cached_data
|
||||
return cast(dict[str, dict[str, Any]], cached_data)
|
||||
|
||||
try:
|
||||
schemas = asyncio.run(self._get_mcp_tool_schemas_async(server_params))
|
||||
@@ -1013,7 +1018,7 @@ class Agent(BaseAgent):
|
||||
|
||||
async def _get_mcp_tool_schemas_async(
|
||||
self, server_params: dict[str, Any]
|
||||
) -> dict[str, dict]:
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Async implementation of MCP tool schema retrieval with timeouts and retries."""
|
||||
server_url = server_params["url"]
|
||||
return await self._retry_mcp_discovery(
|
||||
@@ -1021,7 +1026,7 @@ class Agent(BaseAgent):
|
||||
)
|
||||
|
||||
async def _retry_mcp_discovery(
|
||||
self, operation_func, server_url: str
|
||||
self, operation_func: Any, server_url: str
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Retry MCP discovery operation with exponential backoff, avoiding try-except in loop."""
|
||||
last_error = None
|
||||
@@ -1052,7 +1057,7 @@ class Agent(BaseAgent):
|
||||
|
||||
@staticmethod
|
||||
async def _attempt_mcp_discovery(
|
||||
operation_func, server_url: str
|
||||
operation_func: Any, server_url: str
|
||||
) -> tuple[dict[str, dict[str, Any]] | None, str, bool]:
|
||||
"""Attempt single MCP discovery operation and return (result, error_message, should_retry)."""
|
||||
try:
|
||||
@@ -1156,13 +1161,13 @@ class Agent(BaseAgent):
|
||||
Field(..., description=field_description),
|
||||
)
|
||||
else:
|
||||
field_definitions[field_name] = (
|
||||
field_definitions[field_name] = ( # type: ignore[assignment]
|
||||
field_type | None,
|
||||
Field(default=None, description=field_description),
|
||||
)
|
||||
|
||||
model_name = f"{tool_name.replace('-', '_').replace(' ', '_')}Schema"
|
||||
return create_model(model_name, **field_definitions)
|
||||
return create_model(model_name, **field_definitions) # type: ignore[no-any-return,call-overload]
|
||||
|
||||
def _json_type_to_python(self, field_schema: dict[str, Any]) -> type:
|
||||
"""Convert JSON Schema type to Python type.
|
||||
@@ -1182,16 +1187,16 @@ class Agent(BaseAgent):
|
||||
if "const" in option:
|
||||
types.append(str)
|
||||
else:
|
||||
types.append(self._json_type_to_python(option))
|
||||
types.append(self._json_type_to_python(option)) # type: ignore[arg-type]
|
||||
unique_types = list(set(types))
|
||||
if len(unique_types) > 1:
|
||||
result = unique_types[0]
|
||||
for t in unique_types[1:]:
|
||||
result = result | t
|
||||
result = result | t # type: ignore[assignment]
|
||||
return result
|
||||
return unique_types[0]
|
||||
|
||||
type_mapping = {
|
||||
type_mapping: dict[str, type] = {
|
||||
"string": str,
|
||||
"number": float,
|
||||
"integer": int,
|
||||
@@ -1200,10 +1205,10 @@ class Agent(BaseAgent):
|
||||
"object": dict,
|
||||
}
|
||||
|
||||
return type_mapping.get(json_type, Any)
|
||||
return type_mapping.get(json_type or "", Any)
|
||||
|
||||
@staticmethod
|
||||
def _fetch_amp_mcp_servers(mcp_name: str) -> list[dict]:
|
||||
def _fetch_amp_mcp_servers(mcp_name: str) -> list[dict[str, Any]]:
|
||||
"""Fetch MCP server configurations from CrewAI AMP API."""
|
||||
# TODO: Implement AMP API call to "integrations/mcps" endpoint
|
||||
# Should return list of server configs with URLs
|
||||
|
||||
@@ -137,7 +137,7 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta):
|
||||
default=False,
|
||||
description="Enable agent to delegate and ask questions among each other.",
|
||||
)
|
||||
tools: list[BaseTool] | None = Field(
|
||||
tools: list[BaseTool] = Field(
|
||||
default_factory=list, description="Tools at agents' disposal"
|
||||
)
|
||||
max_iter: int = Field(
|
||||
@@ -161,7 +161,7 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta):
|
||||
description="An instance of the ToolsHandler class.",
|
||||
)
|
||||
tools_results: list[dict[str, Any]] = Field(
|
||||
default=[], description="Results of the tools used by the agent."
|
||||
default_factory=list, description="Results of the tools used by the agent."
|
||||
)
|
||||
max_tokens: int | None = Field(
|
||||
default=None, description="Maximum number of tokens for the agent's execution."
|
||||
@@ -265,7 +265,7 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta):
|
||||
if not mcps:
|
||||
return mcps
|
||||
|
||||
validated_mcps = []
|
||||
validated_mcps: list[str | MCPServerConfig] = []
|
||||
for mcp in mcps:
|
||||
if isinstance(mcp, str):
|
||||
if mcp.startswith(("https://", "crewai-amp:")):
|
||||
|
||||
@@ -51,7 +51,7 @@ if TYPE_CHECKING:
|
||||
from crewai.agent import Agent
|
||||
from crewai.agents.tools_handler import ToolsHandler
|
||||
from crewai.crew import Crew
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.task import Task
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.tools.structured_tool import CrewStructuredTool
|
||||
|
||||
@@ -14,7 +14,8 @@ import tomli
|
||||
from crewai.cli.utils import read_toml
|
||||
from crewai.cli.version import get_crewai_version
|
||||
from crewai.crew import Crew
|
||||
from crewai.llm import LLM, BaseLLM
|
||||
from crewai.llm import LLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.types.crew_chat import ChatInputField, ChatInputs
|
||||
from crewai.utilities.llm_utils import create_llm
|
||||
from crewai.utilities.printer import Printer
|
||||
|
||||
@@ -56,8 +56,8 @@ from crewai.events.types.crew_events import (
|
||||
from crewai.flow.flow_trackable import FlowTrackable
|
||||
from crewai.knowledge.knowledge import Knowledge
|
||||
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.llm.core import LLM
|
||||
from crewai.memory.entity.entity_memory import EntityMemory
|
||||
from crewai.memory.external.external_memory import ExternalMemory
|
||||
from crewai.memory.long_term.long_term_memory import LongTermMemory
|
||||
|
||||
@@ -89,7 +89,7 @@ from crewai.events.types.tool_usage_events import (
|
||||
ToolUsageStartedEvent,
|
||||
)
|
||||
from crewai.events.utils.console_formatter import ConsoleFormatter
|
||||
from crewai.llm import LLM
|
||||
from crewai.llm.core import LLM
|
||||
from crewai.task import Task
|
||||
from crewai.telemetry.telemetry import Telemetry
|
||||
from crewai.utilities import Logger
|
||||
|
||||
@@ -7,7 +7,7 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from crewai.agent import Agent
|
||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||
from crewai.llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.task import Task
|
||||
from crewai.utilities.llm_utils import create_llm
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ from crewai.events.types.agent_events import (
|
||||
from crewai.events.types.logging_events import AgentLogsExecutionEvent
|
||||
from crewai.flow.flow_trackable import FlowTrackable
|
||||
from crewai.lite_agent_output import LiteAgentOutput
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.llm.core import LLM
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.tools.structured_tool import CrewStructuredTool
|
||||
from crewai.utilities.agent_utils import (
|
||||
@@ -504,7 +504,7 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
AgentFinish: The final result of the agent execution.
|
||||
"""
|
||||
# Execute the agent loop
|
||||
formatted_answer = None
|
||||
formatted_answer: AgentAction | AgentFinish | None = None
|
||||
while not isinstance(formatted_answer, AgentFinish):
|
||||
try:
|
||||
if has_reached_max_iterations(self._iterations, self.max_iterations):
|
||||
@@ -553,7 +553,8 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
show_logs=self._show_logs,
|
||||
)
|
||||
|
||||
self._append_message(formatted_answer.text, role="assistant")
|
||||
if formatted_answer is not None:
|
||||
self._append_message(formatted_answer.text, role="assistant")
|
||||
except OutputParserError as e: # noqa: PERF203
|
||||
self._printer.print(
|
||||
content="Failed to parse LLM output. Retrying...",
|
||||
|
||||
4
lib/crewai/src/crewai/llm/__init__.py
Normal file
4
lib/crewai/src/crewai/llm/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from crewai.llm.core import LLM
|
||||
|
||||
|
||||
__all__ = ["LLM"]
|
||||
588
lib/crewai/src/crewai/llm/base_llm.py
Normal file
588
lib/crewai/src/crewai/llm/base_llm.py
Normal file
@@ -0,0 +1,588 @@
|
||||
"""Base LLM abstract class for CrewAI.
|
||||
|
||||
This module provides the abstract base class for all LLM implementations
|
||||
in CrewAI, including common functionality for native SDK implementations.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any, Final
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import httpx
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from crewai.events.event_bus import crewai_event_bus
|
||||
from crewai.events.types.llm_events import (
|
||||
LLMCallCompletedEvent,
|
||||
LLMCallFailedEvent,
|
||||
LLMCallStartedEvent,
|
||||
LLMCallType,
|
||||
LLMStreamChunkEvent,
|
||||
)
|
||||
from crewai.events.types.tool_usage_events import (
|
||||
ToolUsageErrorEvent,
|
||||
ToolUsageFinishedEvent,
|
||||
ToolUsageStartedEvent,
|
||||
)
|
||||
from crewai.llm.hooks.base import BaseInterceptor
|
||||
from crewai.llm.internal.meta import LLMMeta
|
||||
from crewai.types.usage_metrics import UsageMetrics
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.task import Task
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.utilities.types import LLMMessage
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
DEFAULT_CONTEXT_WINDOW_SIZE: Final[int] = 4096
|
||||
DEFAULT_SUPPORTS_STOP_WORDS: Final[bool] = True
|
||||
_JSON_EXTRACTION_PATTERN: Final[re.Pattern[str]] = re.compile(r"\{.*}", re.DOTALL)
|
||||
|
||||
|
||||
class BaseLLM(BaseModel, ABC, metaclass=LLMMeta):
|
||||
"""Abstract base class for LLM implementations.
|
||||
|
||||
This class defines the interface that all LLM implementations must follow.
|
||||
Users can extend this class to create custom LLM implementations that don't
|
||||
rely on litellm's authentication mechanism.
|
||||
|
||||
Custom LLM implementations should handle error cases gracefully, including
|
||||
timeouts, authentication failures, and malformed responses. They should also
|
||||
implement proper validation for input parameters and provide clear error
|
||||
messages when things go wrong.
|
||||
|
||||
Attributes:
|
||||
model: The model identifier/name.
|
||||
temperature: Optional temperature setting for response generation.
|
||||
stop: A list of stop sequences that the LLM should use to stop generation.
|
||||
"""
|
||||
|
||||
# Core fields
|
||||
model: str = Field(..., description="The model identifier/name")
|
||||
temperature: float | None = Field(
|
||||
None, description="Temperature setting for response generation"
|
||||
)
|
||||
api_key: str | None = Field(None, description="API key for authentication")
|
||||
base_url: str | None = Field(None, description="Base URL for API requests")
|
||||
provider: str = Field(
|
||||
default="openai", description="Provider name (openai, anthropic, etc.)"
|
||||
)
|
||||
stop: list[str] = Field(
|
||||
default_factory=list,
|
||||
description="Stop sequences for generation",
|
||||
alias="stop_sequences",
|
||||
)
|
||||
|
||||
# Internal fields
|
||||
is_litellm: bool = Field(
|
||||
default=False, description="Whether this instance uses LiteLLM"
|
||||
)
|
||||
interceptor: BaseInterceptor[httpx.Request, httpx.Response] | None = Field(
|
||||
default=None, description="HTTP request/response interceptor"
|
||||
)
|
||||
_token_usage: dict[str, int] = {
|
||||
"total_tokens": 0,
|
||||
"prompt_tokens": 0,
|
||||
"completion_tokens": 0,
|
||||
"successful_requests": 0,
|
||||
"cached_prompt_tokens": 0,
|
||||
}
|
||||
|
||||
@field_validator("api_key", mode="after")
|
||||
@classmethod
|
||||
def _validate_api_key(cls, value: str | None) -> str | None:
|
||||
"""Validate API key for authentication.
|
||||
|
||||
Args:
|
||||
value: API key value or None
|
||||
|
||||
Returns:
|
||||
API key from environment if not provided, or the original value
|
||||
"""
|
||||
if value is None:
|
||||
cls_name = cls.__name__
|
||||
provider_prefix = cls_name.replace("Completion", "").upper()
|
||||
env_var = f"{provider_prefix}_API_KEY"
|
||||
value = os.getenv(env_var)
|
||||
return value
|
||||
|
||||
@field_validator("stop", mode="before")
|
||||
@classmethod
|
||||
def _normalize_stop(cls, value: Any) -> list[str]:
|
||||
"""Normalize stop sequences to a list.
|
||||
|
||||
Args:
|
||||
value: Stop sequences as string, list, or None
|
||||
|
||||
Returns:
|
||||
Normalized list of stop sequences
|
||||
"""
|
||||
if value is None:
|
||||
return []
|
||||
if isinstance(value, str):
|
||||
return [value]
|
||||
if isinstance(value, list):
|
||||
return value
|
||||
return []
|
||||
|
||||
@property
|
||||
def additional_params(self) -> dict[str, Any]:
|
||||
"""Get additional parameters stored as extra fields.
|
||||
|
||||
Returns:
|
||||
Dictionary of additional parameters
|
||||
"""
|
||||
return self.__pydantic_extra__ or {}
|
||||
|
||||
@additional_params.setter
|
||||
def additional_params(self, value: dict[str, Any]) -> None:
|
||||
"""Set additional parameters as extra fields.
|
||||
|
||||
Args:
|
||||
value: Dictionary of additional parameters to set
|
||||
"""
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError("additional_params must be a dictionary")
|
||||
if self.__pydantic_extra__ is None:
|
||||
self.__pydantic_extra__ = {}
|
||||
self.__pydantic_extra__.update(value)
|
||||
|
||||
@abstractmethod
|
||||
def call(
|
||||
self,
|
||||
messages: str | list[LLMMessage],
|
||||
tools: list[dict[str, BaseTool]] | None = None,
|
||||
callbacks: list[Any] | None = None,
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str | Any:
|
||||
"""Call the LLM with the given messages.
|
||||
|
||||
Args:
|
||||
messages: Input messages for the LLM.
|
||||
Can be a string or list of message dictionaries.
|
||||
If string, it will be converted to a single user message.
|
||||
If list, each dict must have 'role' and 'content' keys.
|
||||
tools: Optional list of tool schemas for function calling.
|
||||
Each tool should define its name, description, and parameters.
|
||||
callbacks: Optional list of callback functions to be executed
|
||||
during and after the LLM call.
|
||||
available_functions: Optional dict mapping function names to callables
|
||||
that can be invoked by the LLM.
|
||||
from_task: Optional task caller to be used for the LLM call.
|
||||
from_agent: Optional agent caller to be used for the LLM call.
|
||||
response_model: Optional response model to be used for the LLM call.
|
||||
|
||||
Returns:
|
||||
Either a text response from the LLM (str) or
|
||||
the result of a tool function call (Any).
|
||||
|
||||
Raises:
|
||||
ValueError: If the messages format is invalid.
|
||||
TimeoutError: If the LLM request times out.
|
||||
RuntimeError: If the LLM request fails for other reasons.
|
||||
"""
|
||||
|
||||
def _convert_tools_for_interference(
|
||||
self, tools: list[dict[str, BaseTool]]
|
||||
) -> list[dict[str, BaseTool]]:
|
||||
"""Convert tools to a format that can be used for interference.
|
||||
|
||||
Args:
|
||||
tools: List of tools to convert.
|
||||
|
||||
Returns:
|
||||
List of converted tools (default implementation returns as-is)
|
||||
"""
|
||||
return tools
|
||||
|
||||
def supports_stop_words(self) -> bool:
|
||||
"""Check if the LLM supports stop words.
|
||||
|
||||
Returns:
|
||||
True if the LLM supports stop words, False otherwise.
|
||||
"""
|
||||
return DEFAULT_SUPPORTS_STOP_WORDS
|
||||
|
||||
def _supports_stop_words_implementation(self) -> bool:
|
||||
"""Check if stop words are configured for this LLM instance.
|
||||
|
||||
Native providers can override supports_stop_words() to return this value
|
||||
to ensure consistent behavior based on whether stop words are actually configured.
|
||||
|
||||
Returns:
|
||||
True if stop words are configured and can be applied
|
||||
"""
|
||||
return bool(self.stop)
|
||||
|
||||
def _apply_stop_words(self, content: str) -> str:
|
||||
"""Apply stop words to truncate response content.
|
||||
|
||||
This method provides consistent stop word behavior across all native SDK providers.
|
||||
Native providers should call this method to post-process their responses.
|
||||
|
||||
Args:
|
||||
content: The raw response content from the LLM
|
||||
|
||||
Returns:
|
||||
Content truncated at the first occurrence of any stop word
|
||||
|
||||
Example:
|
||||
>>> llm = MyNativeLLM(stop=["Observation:", "Final Answer:"])
|
||||
>>> response = (
|
||||
... "I need to search.\\n\\nAction: search\\nObservation: Found results"
|
||||
... )
|
||||
>>> llm._apply_stop_words(response)
|
||||
"I need to search.\\n\\nAction: search"
|
||||
"""
|
||||
if not self.stop or not content:
|
||||
return content
|
||||
|
||||
# Find the earliest occurrence of any stop word
|
||||
earliest_stop_pos = len(content)
|
||||
found_stop_word = None
|
||||
|
||||
for stop_word in self.stop:
|
||||
stop_pos = content.find(stop_word)
|
||||
if stop_pos != -1 and stop_pos < earliest_stop_pos:
|
||||
earliest_stop_pos = stop_pos
|
||||
found_stop_word = stop_word
|
||||
|
||||
# Truncate at the stop word if found
|
||||
if found_stop_word is not None:
|
||||
truncated = content[:earliest_stop_pos].strip()
|
||||
logging.debug(
|
||||
f"Applied stop word '{found_stop_word}' at position {earliest_stop_pos}"
|
||||
)
|
||||
return truncated
|
||||
|
||||
return content
|
||||
|
||||
def get_context_window_size(self) -> int:
|
||||
"""Get the context window size for the LLM.
|
||||
|
||||
Returns:
|
||||
The number of tokens/characters the model can handle.
|
||||
"""
|
||||
# Default implementation - subclasses should override with model-specific values
|
||||
return DEFAULT_CONTEXT_WINDOW_SIZE
|
||||
|
||||
# Common helper methods for native SDK implementations
|
||||
|
||||
def _emit_call_started_event(
|
||||
self,
|
||||
messages: str | list[LLMMessage],
|
||||
tools: list[dict[str, BaseTool]] | None = None,
|
||||
callbacks: list[Any] | None = None,
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
) -> None:
|
||||
"""Emit LLM call started event."""
|
||||
if not hasattr(crewai_event_bus, "emit"):
|
||||
raise ValueError("crewai_event_bus does not have an emit method") from None
|
||||
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=LLMCallStartedEvent(
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
callbacks=callbacks,
|
||||
available_functions=available_functions,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
model=self.model,
|
||||
),
|
||||
)
|
||||
|
||||
def _emit_call_completed_event(
|
||||
self,
|
||||
response: Any,
|
||||
call_type: LLMCallType,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
messages: str | list[dict[str, Any]] | None = None,
|
||||
) -> None:
|
||||
"""Emit LLM call completed event."""
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=LLMCallCompletedEvent(
|
||||
messages=messages,
|
||||
response=response,
|
||||
call_type=call_type,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
model=self.model,
|
||||
),
|
||||
)
|
||||
|
||||
def _emit_call_failed_event(
|
||||
self,
|
||||
error: str,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
) -> None:
|
||||
"""Emit LLM call failed event."""
|
||||
if not hasattr(crewai_event_bus, "emit"):
|
||||
raise ValueError("crewai_event_bus does not have an emit method") from None
|
||||
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=LLMCallFailedEvent(
|
||||
error=error,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
),
|
||||
)
|
||||
|
||||
def _emit_stream_chunk_event(
|
||||
self,
|
||||
chunk: str,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
tool_call: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Emit stream chunk event."""
|
||||
if not hasattr(crewai_event_bus, "emit"):
|
||||
raise ValueError("crewai_event_bus does not have an emit method") from None
|
||||
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=LLMStreamChunkEvent(
|
||||
chunk=chunk,
|
||||
tool_call=tool_call,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
),
|
||||
)
|
||||
|
||||
def _handle_tool_execution(
|
||||
self,
|
||||
function_name: str,
|
||||
function_args: dict[str, Any],
|
||||
available_functions: dict[str, Any],
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
) -> str | None:
|
||||
"""Handle tool execution with proper event emission.
|
||||
|
||||
Args:
|
||||
function_name: Name of the function to execute
|
||||
function_args: Arguments to pass to the function
|
||||
available_functions: Dict of available functions
|
||||
from_task: Optional task object
|
||||
from_agent: Optional agent object
|
||||
|
||||
Returns:
|
||||
Result of function execution or None if function not found
|
||||
"""
|
||||
if function_name not in available_functions:
|
||||
logging.warning(
|
||||
f"Function '{function_name}' not found in available functions"
|
||||
)
|
||||
return None
|
||||
|
||||
try:
|
||||
# Emit tool usage started event
|
||||
started_at = datetime.now()
|
||||
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=ToolUsageStartedEvent(
|
||||
tool_name=function_name,
|
||||
tool_args=function_args,
|
||||
from_agent=from_agent,
|
||||
from_task=from_task,
|
||||
),
|
||||
)
|
||||
|
||||
# Execute the function
|
||||
fn = available_functions[function_name]
|
||||
result = fn(**function_args)
|
||||
|
||||
# Emit tool usage finished event
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=ToolUsageFinishedEvent(
|
||||
output=result,
|
||||
tool_name=function_name,
|
||||
tool_args=function_args,
|
||||
started_at=started_at,
|
||||
finished_at=datetime.now(),
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
),
|
||||
)
|
||||
|
||||
# Emit LLM call completed event for tool call
|
||||
self._emit_call_completed_event(
|
||||
response=result,
|
||||
call_type=LLMCallType.TOOL_CALL,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
)
|
||||
|
||||
return str(result)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error executing function '{function_name}': {e!s}"
|
||||
logging.error(error_msg)
|
||||
|
||||
# Emit tool usage error event
|
||||
if not hasattr(crewai_event_bus, "emit"):
|
||||
raise ValueError(
|
||||
"crewai_event_bus does not have an emit method"
|
||||
) from None
|
||||
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=ToolUsageErrorEvent(
|
||||
tool_name=function_name,
|
||||
tool_args=function_args,
|
||||
error=error_msg,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
),
|
||||
)
|
||||
|
||||
# Emit LLM call failed event
|
||||
self._emit_call_failed_event(
|
||||
error=error_msg,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def _format_messages(self, messages: str | list[LLMMessage]) -> list[LLMMessage]:
|
||||
"""Convert messages to standard format.
|
||||
|
||||
Args:
|
||||
messages: Input messages (string or list of message dicts)
|
||||
|
||||
Returns:
|
||||
List of message dictionaries with 'role' and 'content' keys
|
||||
|
||||
Raises:
|
||||
ValueError: If message format is invalid
|
||||
"""
|
||||
if isinstance(messages, str):
|
||||
return [{"role": "user", "content": messages}]
|
||||
|
||||
# Validate message format
|
||||
for i, msg in enumerate(messages):
|
||||
if not isinstance(msg, dict):
|
||||
raise ValueError(f"Message at index {i} must be a dictionary")
|
||||
if "role" not in msg or "content" not in msg:
|
||||
raise ValueError(
|
||||
f"Message at index {i} must have 'role' and 'content' keys"
|
||||
)
|
||||
|
||||
return messages
|
||||
|
||||
@staticmethod
|
||||
def _validate_structured_output(
|
||||
response: str,
|
||||
response_format: type[BaseModel] | None,
|
||||
) -> str | BaseModel:
|
||||
"""Validate and parse structured output.
|
||||
|
||||
Args:
|
||||
response: Raw response string
|
||||
response_format: Optional Pydantic model for structured output
|
||||
|
||||
Returns:
|
||||
Parsed response (BaseModel instance if response_format provided, otherwise string)
|
||||
|
||||
Raises:
|
||||
ValueError: If structured output validation fails
|
||||
"""
|
||||
if response_format is None:
|
||||
return response
|
||||
|
||||
try:
|
||||
# Try to parse as JSON first
|
||||
if response.strip().startswith("{") or response.strip().startswith("["):
|
||||
data = json.loads(response)
|
||||
return response_format.model_validate(data)
|
||||
|
||||
json_match = _JSON_EXTRACTION_PATTERN.search(response)
|
||||
if json_match:
|
||||
data = json.loads(json_match.group())
|
||||
return response_format.model_validate(data)
|
||||
|
||||
raise ValueError("No JSON found in response")
|
||||
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
logging.warning(f"Failed to parse structured output: {e}")
|
||||
raise ValueError(
|
||||
f"Failed to parse response into {response_format.__name__}: {e}"
|
||||
) from e
|
||||
|
||||
@staticmethod
|
||||
def _extract_provider(model: str) -> str:
|
||||
"""Extract provider from model string.
|
||||
|
||||
Args:
|
||||
model: Model string (e.g., 'openai/gpt-4' or 'gpt-4')
|
||||
|
||||
Returns:
|
||||
Provider name (e.g., 'openai')
|
||||
"""
|
||||
if "/" in model:
|
||||
return model.partition("/")[0]
|
||||
return "openai" # Default provider
|
||||
|
||||
def _track_token_usage_internal(self, usage_data: dict[str, Any]) -> None:
|
||||
"""Track token usage internally in the LLM instance.
|
||||
|
||||
Args:
|
||||
usage_data: Token usage data from the API response
|
||||
"""
|
||||
# Extract tokens in a provider-agnostic way
|
||||
prompt_tokens = (
|
||||
usage_data.get("prompt_tokens")
|
||||
or usage_data.get("prompt_token_count")
|
||||
or usage_data.get("input_tokens")
|
||||
or 0
|
||||
)
|
||||
|
||||
completion_tokens = (
|
||||
usage_data.get("completion_tokens")
|
||||
or usage_data.get("candidates_token_count")
|
||||
or usage_data.get("output_tokens")
|
||||
or 0
|
||||
)
|
||||
|
||||
cached_tokens = (
|
||||
usage_data.get("cached_tokens")
|
||||
or usage_data.get("cached_prompt_tokens")
|
||||
or 0
|
||||
)
|
||||
|
||||
self._token_usage["prompt_tokens"] += prompt_tokens
|
||||
self._token_usage["completion_tokens"] += completion_tokens
|
||||
self._token_usage["total_tokens"] += prompt_tokens + completion_tokens
|
||||
self._token_usage["successful_requests"] += 1
|
||||
self._token_usage["cached_prompt_tokens"] += cached_tokens
|
||||
|
||||
def get_token_usage_summary(self) -> UsageMetrics:
|
||||
"""Get summary of token usage for this LLM instance.
|
||||
|
||||
Returns:
|
||||
Dictionary with token usage totals
|
||||
"""
|
||||
return UsageMetrics(**self._token_usage)
|
||||
587
lib/crewai/src/crewai/llm/constants.py
Normal file
587
lib/crewai/src/crewai/llm/constants.py
Normal file
@@ -0,0 +1,587 @@
|
||||
from typing import Literal, TypeAlias
|
||||
|
||||
|
||||
SupportedNativeProviders: TypeAlias = Literal[
|
||||
"openai",
|
||||
"anthropic",
|
||||
"claude",
|
||||
"azure",
|
||||
"azure_openai",
|
||||
"google",
|
||||
"gemini",
|
||||
"bedrock",
|
||||
"aws",
|
||||
]
|
||||
|
||||
SUPPORTED_NATIVE_PROVIDERS: list[SupportedNativeProviders] = [
|
||||
"openai",
|
||||
"anthropic",
|
||||
"claude",
|
||||
"azure",
|
||||
"azure_openai",
|
||||
"google",
|
||||
"gemini",
|
||||
"bedrock",
|
||||
"aws",
|
||||
]
|
||||
|
||||
|
||||
OpenAIModels: TypeAlias = Literal[
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0125",
|
||||
"gpt-3.5-turbo-0301",
|
||||
"gpt-3.5-turbo-0613",
|
||||
"gpt-3.5-turbo-1106",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-3.5-turbo-instruct",
|
||||
"gpt-3.5-turbo-instruct-0914",
|
||||
"gpt-4",
|
||||
"gpt-4-0125-preview",
|
||||
"gpt-4-0314",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-32k",
|
||||
"gpt-4-32k-0314",
|
||||
"gpt-4-32k-0613",
|
||||
"gpt-4-turbo",
|
||||
"gpt-4-turbo-2024-04-09",
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-4-vision-preview",
|
||||
"gpt-4.1",
|
||||
"gpt-4.1-2025-04-14",
|
||||
"gpt-4.1-mini",
|
||||
"gpt-4.1-mini-2025-04-14",
|
||||
"gpt-4.1-nano",
|
||||
"gpt-4.1-nano-2025-04-14",
|
||||
"gpt-4o",
|
||||
"gpt-4o-2024-05-13",
|
||||
"gpt-4o-2024-08-06",
|
||||
"gpt-4o-2024-11-20",
|
||||
"gpt-4o-audio-preview",
|
||||
"gpt-4o-audio-preview-2024-10-01",
|
||||
"gpt-4o-audio-preview-2024-12-17",
|
||||
"gpt-4o-audio-preview-2025-06-03",
|
||||
"gpt-4o-mini",
|
||||
"gpt-4o-mini-2024-07-18",
|
||||
"gpt-4o-mini-audio-preview",
|
||||
"gpt-4o-mini-audio-preview-2024-12-17",
|
||||
"gpt-4o-mini-realtime-preview",
|
||||
"gpt-4o-mini-realtime-preview-2024-12-17",
|
||||
"gpt-4o-mini-search-preview",
|
||||
"gpt-4o-mini-search-preview-2025-03-11",
|
||||
"gpt-4o-mini-transcribe",
|
||||
"gpt-4o-mini-tts",
|
||||
"gpt-4o-realtime-preview",
|
||||
"gpt-4o-realtime-preview-2024-10-01",
|
||||
"gpt-4o-realtime-preview-2024-12-17",
|
||||
"gpt-4o-realtime-preview-2025-06-03",
|
||||
"gpt-4o-search-preview",
|
||||
"gpt-4o-search-preview-2025-03-11",
|
||||
"gpt-4o-transcribe",
|
||||
"gpt-4o-transcribe-diarize",
|
||||
"gpt-5",
|
||||
"gpt-5-2025-08-07",
|
||||
"gpt-5-chat",
|
||||
"gpt-5-chat-latest",
|
||||
"gpt-5-codex",
|
||||
"gpt-5-mini",
|
||||
"gpt-5-mini-2025-08-07",
|
||||
"gpt-5-nano",
|
||||
"gpt-5-nano-2025-08-07",
|
||||
"gpt-5-pro",
|
||||
"gpt-5-pro-2025-10-06",
|
||||
"gpt-5-search-api",
|
||||
"gpt-5-search-api-2025-10-14",
|
||||
"gpt-audio",
|
||||
"gpt-audio-2025-08-28",
|
||||
"gpt-audio-mini",
|
||||
"gpt-audio-mini-2025-10-06",
|
||||
"gpt-image-1",
|
||||
"gpt-image-1-mini",
|
||||
"gpt-realtime",
|
||||
"gpt-realtime-2025-08-28",
|
||||
"gpt-realtime-mini",
|
||||
"gpt-realtime-mini-2025-10-06",
|
||||
"o1",
|
||||
"o1-preview",
|
||||
"o1-2024-12-17",
|
||||
"o1-mini",
|
||||
"o1-mini-2024-09-12",
|
||||
"o1-pro",
|
||||
"o1-pro-2025-03-19",
|
||||
"o3-mini",
|
||||
"o3",
|
||||
"o4-mini",
|
||||
"whisper-1",
|
||||
]
|
||||
OPENAI_MODELS: list[OpenAIModels] = [
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0125",
|
||||
"gpt-3.5-turbo-0301",
|
||||
"gpt-3.5-turbo-0613",
|
||||
"gpt-3.5-turbo-1106",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-3.5-turbo-instruct",
|
||||
"gpt-3.5-turbo-instruct-0914",
|
||||
"gpt-4",
|
||||
"gpt-4-0125-preview",
|
||||
"gpt-4-0314",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-32k",
|
||||
"gpt-4-32k-0314",
|
||||
"gpt-4-32k-0613",
|
||||
"gpt-4-turbo",
|
||||
"gpt-4-turbo-2024-04-09",
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-4-vision-preview",
|
||||
"gpt-4.1",
|
||||
"gpt-4.1-2025-04-14",
|
||||
"gpt-4.1-mini",
|
||||
"gpt-4.1-mini-2025-04-14",
|
||||
"gpt-4.1-nano",
|
||||
"gpt-4.1-nano-2025-04-14",
|
||||
"gpt-4o",
|
||||
"gpt-4o-2024-05-13",
|
||||
"gpt-4o-2024-08-06",
|
||||
"gpt-4o-2024-11-20",
|
||||
"gpt-4o-audio-preview",
|
||||
"gpt-4o-audio-preview-2024-10-01",
|
||||
"gpt-4o-audio-preview-2024-12-17",
|
||||
"gpt-4o-audio-preview-2025-06-03",
|
||||
"gpt-4o-mini",
|
||||
"gpt-4o-mini-2024-07-18",
|
||||
"gpt-4o-mini-audio-preview",
|
||||
"gpt-4o-mini-audio-preview-2024-12-17",
|
||||
"gpt-4o-mini-realtime-preview",
|
||||
"gpt-4o-mini-realtime-preview-2024-12-17",
|
||||
"gpt-4o-mini-search-preview",
|
||||
"gpt-4o-mini-search-preview-2025-03-11",
|
||||
"gpt-4o-mini-transcribe",
|
||||
"gpt-4o-mini-tts",
|
||||
"gpt-4o-realtime-preview",
|
||||
"gpt-4o-realtime-preview-2024-10-01",
|
||||
"gpt-4o-realtime-preview-2024-12-17",
|
||||
"gpt-4o-realtime-preview-2025-06-03",
|
||||
"gpt-4o-search-preview",
|
||||
"gpt-4o-search-preview-2025-03-11",
|
||||
"gpt-4o-transcribe",
|
||||
"gpt-4o-transcribe-diarize",
|
||||
"gpt-5",
|
||||
"gpt-5-2025-08-07",
|
||||
"gpt-5-chat",
|
||||
"gpt-5-chat-latest",
|
||||
"gpt-5-codex",
|
||||
"gpt-5-mini",
|
||||
"gpt-5-mini-2025-08-07",
|
||||
"gpt-5-nano",
|
||||
"gpt-5-nano-2025-08-07",
|
||||
"gpt-5-pro",
|
||||
"gpt-5-pro-2025-10-06",
|
||||
"gpt-5-search-api",
|
||||
"gpt-5-search-api-2025-10-14",
|
||||
"gpt-audio",
|
||||
"gpt-audio-2025-08-28",
|
||||
"gpt-audio-mini",
|
||||
"gpt-audio-mini-2025-10-06",
|
||||
"gpt-image-1",
|
||||
"gpt-image-1-mini",
|
||||
"gpt-realtime",
|
||||
"gpt-realtime-2025-08-28",
|
||||
"gpt-realtime-mini",
|
||||
"gpt-realtime-mini-2025-10-06",
|
||||
"o1",
|
||||
"o1-preview",
|
||||
"o1-2024-12-17",
|
||||
"o1-mini",
|
||||
"o1-mini-2024-09-12",
|
||||
"o1-pro",
|
||||
"o1-pro-2025-03-19",
|
||||
"o3-mini",
|
||||
"o3",
|
||||
"o4-mini",
|
||||
"whisper-1",
|
||||
]
|
||||
|
||||
|
||||
AnthropicModels: TypeAlias = Literal[
|
||||
"claude-3-7-sonnet-latest",
|
||||
"claude-3-7-sonnet-20250219",
|
||||
"claude-3-5-haiku-latest",
|
||||
"claude-3-5-haiku-20241022",
|
||||
"claude-haiku-4-5",
|
||||
"claude-haiku-4-5-20251001",
|
||||
"claude-sonnet-4-20250514",
|
||||
"claude-sonnet-4-0",
|
||||
"claude-4-sonnet-20250514",
|
||||
"claude-sonnet-4-5",
|
||||
"claude-sonnet-4-5-20250929",
|
||||
"claude-3-5-sonnet-latest",
|
||||
"claude-3-5-sonnet-20241022",
|
||||
"claude-3-5-sonnet-20240620",
|
||||
"claude-opus-4-0",
|
||||
"claude-opus-4-20250514",
|
||||
"claude-4-opus-20250514",
|
||||
"claude-opus-4-1",
|
||||
"claude-opus-4-1-20250805",
|
||||
"claude-3-opus-latest",
|
||||
"claude-3-opus-20240229",
|
||||
"claude-3-sonnet-20240229",
|
||||
"claude-3-haiku-latest",
|
||||
"claude-3-haiku-20240307",
|
||||
]
|
||||
ANTHROPIC_MODELS: list[AnthropicModels] = [
|
||||
"claude-3-7-sonnet-latest",
|
||||
"claude-3-7-sonnet-20250219",
|
||||
"claude-3-5-haiku-latest",
|
||||
"claude-3-5-haiku-20241022",
|
||||
"claude-haiku-4-5",
|
||||
"claude-haiku-4-5-20251001",
|
||||
"claude-sonnet-4-20250514",
|
||||
"claude-sonnet-4-0",
|
||||
"claude-4-sonnet-20250514",
|
||||
"claude-sonnet-4-5",
|
||||
"claude-sonnet-4-5-20250929",
|
||||
"claude-3-5-sonnet-latest",
|
||||
"claude-3-5-sonnet-20241022",
|
||||
"claude-3-5-sonnet-20240620",
|
||||
"claude-opus-4-0",
|
||||
"claude-opus-4-20250514",
|
||||
"claude-4-opus-20250514",
|
||||
"claude-opus-4-1",
|
||||
"claude-opus-4-1-20250805",
|
||||
"claude-3-opus-latest",
|
||||
"claude-3-opus-20240229",
|
||||
"claude-3-sonnet-20240229",
|
||||
"claude-3-haiku-latest",
|
||||
"claude-3-haiku-20240307",
|
||||
]
|
||||
|
||||
GeminiModels: TypeAlias = Literal[
|
||||
"gemini-2.5-pro",
|
||||
"gemini-2.5-pro-preview-03-25",
|
||||
"gemini-2.5-pro-preview-05-06",
|
||||
"gemini-2.5-pro-preview-06-05",
|
||||
"gemini-2.5-flash",
|
||||
"gemini-2.5-flash-preview-05-20",
|
||||
"gemini-2.5-flash-preview-04-17",
|
||||
"gemini-2.5-flash-image",
|
||||
"gemini-2.5-flash-image-preview",
|
||||
"gemini-2.5-flash-lite",
|
||||
"gemini-2.5-flash-lite-preview-06-17",
|
||||
"gemini-2.5-flash-preview-09-2025",
|
||||
"gemini-2.5-flash-lite-preview-09-2025",
|
||||
"gemini-2.5-flash-preview-tts",
|
||||
"gemini-2.5-pro-preview-tts",
|
||||
"gemini-2.5-computer-use-preview-10-2025",
|
||||
"gemini-2.0-flash",
|
||||
"gemini-2.0-flash-001",
|
||||
"gemini-2.0-flash-exp",
|
||||
"gemini-2.0-flash-exp-image-generation",
|
||||
"gemini-2.0-flash-lite",
|
||||
"gemini-2.0-flash-lite-001",
|
||||
"gemini-2.0-flash-lite-preview",
|
||||
"gemini-2.0-flash-lite-preview-02-05",
|
||||
"gemini-2.0-flash-preview-image-generation",
|
||||
"gemini-2.0-flash-thinking-exp",
|
||||
"gemini-2.0-flash-thinking-exp-01-21",
|
||||
"gemini-2.0-flash-thinking-exp-1219",
|
||||
"gemini-2.0-pro-exp",
|
||||
"gemini-2.0-pro-exp-02-05",
|
||||
"gemini-exp-1206",
|
||||
"gemini-1.5-pro",
|
||||
"gemini-1.5-flash",
|
||||
"gemini-1.5-flash-8b",
|
||||
"gemini-flash-latest",
|
||||
"gemini-flash-lite-latest",
|
||||
"gemini-pro-latest",
|
||||
"gemini-2.0-flash-live-001",
|
||||
"gemini-live-2.5-flash-preview",
|
||||
"gemini-2.5-flash-live-preview",
|
||||
"gemini-robotics-er-1.5-preview",
|
||||
"gemini-gemma-2-27b-it",
|
||||
"gemini-gemma-2-9b-it",
|
||||
"gemma-3-1b-it",
|
||||
"gemma-3-4b-it",
|
||||
"gemma-3-12b-it",
|
||||
"gemma-3-27b-it",
|
||||
"gemma-3n-e2b-it",
|
||||
"gemma-3n-e4b-it",
|
||||
"learnlm-2.0-flash-experimental",
|
||||
]
|
||||
GEMINI_MODELS: list[GeminiModels] = [
|
||||
"gemini-2.5-pro",
|
||||
"gemini-2.5-pro-preview-03-25",
|
||||
"gemini-2.5-pro-preview-05-06",
|
||||
"gemini-2.5-pro-preview-06-05",
|
||||
"gemini-2.5-flash",
|
||||
"gemini-2.5-flash-preview-05-20",
|
||||
"gemini-2.5-flash-preview-04-17",
|
||||
"gemini-2.5-flash-image",
|
||||
"gemini-2.5-flash-image-preview",
|
||||
"gemini-2.5-flash-lite",
|
||||
"gemini-2.5-flash-lite-preview-06-17",
|
||||
"gemini-2.5-flash-preview-09-2025",
|
||||
"gemini-2.5-flash-lite-preview-09-2025",
|
||||
"gemini-2.5-flash-preview-tts",
|
||||
"gemini-2.5-pro-preview-tts",
|
||||
"gemini-2.5-computer-use-preview-10-2025",
|
||||
"gemini-2.0-flash",
|
||||
"gemini-2.0-flash-001",
|
||||
"gemini-2.0-flash-exp",
|
||||
"gemini-2.0-flash-exp-image-generation",
|
||||
"gemini-2.0-flash-lite",
|
||||
"gemini-2.0-flash-lite-001",
|
||||
"gemini-2.0-flash-lite-preview",
|
||||
"gemini-2.0-flash-lite-preview-02-05",
|
||||
"gemini-2.0-flash-preview-image-generation",
|
||||
"gemini-2.0-flash-thinking-exp",
|
||||
"gemini-2.0-flash-thinking-exp-01-21",
|
||||
"gemini-2.0-flash-thinking-exp-1219",
|
||||
"gemini-2.0-pro-exp",
|
||||
"gemini-2.0-pro-exp-02-05",
|
||||
"gemini-exp-1206",
|
||||
"gemini-1.5-pro",
|
||||
"gemini-1.5-flash",
|
||||
"gemini-1.5-flash-8b",
|
||||
"gemini-flash-latest",
|
||||
"gemini-flash-lite-latest",
|
||||
"gemini-pro-latest",
|
||||
"gemini-2.0-flash-live-001",
|
||||
"gemini-live-2.5-flash-preview",
|
||||
"gemini-2.5-flash-live-preview",
|
||||
"gemini-robotics-er-1.5-preview",
|
||||
"gemini-gemma-2-27b-it",
|
||||
"gemini-gemma-2-9b-it",
|
||||
"gemma-3-1b-it",
|
||||
"gemma-3-4b-it",
|
||||
"gemma-3-12b-it",
|
||||
"gemma-3-27b-it",
|
||||
"gemma-3n-e2b-it",
|
||||
"gemma-3n-e4b-it",
|
||||
"learnlm-2.0-flash-experimental",
|
||||
]
|
||||
|
||||
|
||||
AzureModels: TypeAlias = Literal[
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0301",
|
||||
"gpt-3.5-turbo-0613",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-35-turbo",
|
||||
"gpt-35-turbo-0125",
|
||||
"gpt-35-turbo-1106",
|
||||
"gpt-35-turbo-16k-0613",
|
||||
"gpt-35-turbo-instruct-0914",
|
||||
"gpt-4",
|
||||
"gpt-4-0314",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-0125-preview",
|
||||
"gpt-4-32k",
|
||||
"gpt-4-32k-0314",
|
||||
"gpt-4-32k-0613",
|
||||
"gpt-4-turbo",
|
||||
"gpt-4-turbo-2024-04-09",
|
||||
"gpt-4-vision",
|
||||
"gpt-4o",
|
||||
"gpt-4o-2024-05-13",
|
||||
"gpt-4o-2024-08-06",
|
||||
"gpt-4o-2024-11-20",
|
||||
"gpt-4o-mini",
|
||||
"gpt-5",
|
||||
"o1",
|
||||
"o1-mini",
|
||||
"o1-preview",
|
||||
"o3-mini",
|
||||
"o3",
|
||||
"o4-mini",
|
||||
]
|
||||
AZURE_MODELS: list[AzureModels] = [
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0301",
|
||||
"gpt-3.5-turbo-0613",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-35-turbo",
|
||||
"gpt-35-turbo-0125",
|
||||
"gpt-35-turbo-1106",
|
||||
"gpt-35-turbo-16k-0613",
|
||||
"gpt-35-turbo-instruct-0914",
|
||||
"gpt-4",
|
||||
"gpt-4-0314",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-0125-preview",
|
||||
"gpt-4-32k",
|
||||
"gpt-4-32k-0314",
|
||||
"gpt-4-32k-0613",
|
||||
"gpt-4-turbo",
|
||||
"gpt-4-turbo-2024-04-09",
|
||||
"gpt-4-vision",
|
||||
"gpt-4o",
|
||||
"gpt-4o-2024-05-13",
|
||||
"gpt-4o-2024-08-06",
|
||||
"gpt-4o-2024-11-20",
|
||||
"gpt-4o-mini",
|
||||
"gpt-5",
|
||||
"o1",
|
||||
"o1-mini",
|
||||
"o1-preview",
|
||||
"o3-mini",
|
||||
"o3",
|
||||
"o4-mini",
|
||||
]
|
||||
|
||||
|
||||
BedrockModels: TypeAlias = Literal[
|
||||
"ai21.jamba-1-5-large-v1:0",
|
||||
"ai21.jamba-1-5-mini-v1:0",
|
||||
"amazon.nova-lite-v1:0",
|
||||
"amazon.nova-lite-v1:0:24k",
|
||||
"amazon.nova-lite-v1:0:300k",
|
||||
"amazon.nova-micro-v1:0",
|
||||
"amazon.nova-micro-v1:0:128k",
|
||||
"amazon.nova-micro-v1:0:24k",
|
||||
"amazon.nova-premier-v1:0",
|
||||
"amazon.nova-premier-v1:0:1000k",
|
||||
"amazon.nova-premier-v1:0:20k",
|
||||
"amazon.nova-premier-v1:0:8k",
|
||||
"amazon.nova-premier-v1:0:mm",
|
||||
"amazon.nova-pro-v1:0",
|
||||
"amazon.nova-pro-v1:0:24k",
|
||||
"amazon.nova-pro-v1:0:300k",
|
||||
"amazon.titan-text-express-v1",
|
||||
"amazon.titan-text-express-v1:0:8k",
|
||||
"amazon.titan-text-lite-v1",
|
||||
"amazon.titan-text-lite-v1:0:4k",
|
||||
"amazon.titan-tg1-large",
|
||||
"anthropic.claude-3-5-haiku-20241022-v1:0",
|
||||
"anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
"anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
"anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0:200k",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0:48k",
|
||||
"anthropic.claude-3-opus-20240229-v1:0",
|
||||
"anthropic.claude-3-opus-20240229-v1:0:12k",
|
||||
"anthropic.claude-3-opus-20240229-v1:0:200k",
|
||||
"anthropic.claude-3-opus-20240229-v1:0:28k",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0:200k",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0:28k",
|
||||
"anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
"anthropic.claude-instant-v1:2:100k",
|
||||
"anthropic.claude-opus-4-1-20250805-v1:0",
|
||||
"anthropic.claude-opus-4-20250514-v1:0",
|
||||
"anthropic.claude-sonnet-4-20250514-v1:0",
|
||||
"anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
"anthropic.claude-v2:0:100k",
|
||||
"anthropic.claude-v2:0:18k",
|
||||
"anthropic.claude-v2:1:18k",
|
||||
"anthropic.claude-v2:1:200k",
|
||||
"cohere.command-r-plus-v1:0",
|
||||
"cohere.command-r-v1:0",
|
||||
"cohere.rerank-v3-5:0",
|
||||
"deepseek.r1-v1:0",
|
||||
"meta.llama3-1-70b-instruct-v1:0",
|
||||
"meta.llama3-1-8b-instruct-v1:0",
|
||||
"meta.llama3-2-11b-instruct-v1:0",
|
||||
"meta.llama3-2-1b-instruct-v1:0",
|
||||
"meta.llama3-2-3b-instruct-v1:0",
|
||||
"meta.llama3-2-90b-instruct-v1:0",
|
||||
"meta.llama3-3-70b-instruct-v1:0",
|
||||
"meta.llama3-70b-instruct-v1:0",
|
||||
"meta.llama3-8b-instruct-v1:0",
|
||||
"meta.llama4-maverick-17b-instruct-v1:0",
|
||||
"meta.llama4-scout-17b-instruct-v1:0",
|
||||
"mistral.mistral-7b-instruct-v0:2",
|
||||
"mistral.mistral-large-2402-v1:0",
|
||||
"mistral.mistral-small-2402-v1:0",
|
||||
"mistral.mixtral-8x7b-instruct-v0:1",
|
||||
"mistral.pixtral-large-2502-v1:0",
|
||||
"openai.gpt-oss-120b-1:0",
|
||||
"openai.gpt-oss-20b-1:0",
|
||||
"qwen.qwen3-32b-v1:0",
|
||||
"qwen.qwen3-coder-30b-a3b-v1:0",
|
||||
"twelvelabs.pegasus-1-2-v1:0",
|
||||
]
|
||||
BEDROCK_MODELS: list[BedrockModels] = [
|
||||
"ai21.jamba-1-5-large-v1:0",
|
||||
"ai21.jamba-1-5-mini-v1:0",
|
||||
"amazon.nova-lite-v1:0",
|
||||
"amazon.nova-lite-v1:0:24k",
|
||||
"amazon.nova-lite-v1:0:300k",
|
||||
"amazon.nova-micro-v1:0",
|
||||
"amazon.nova-micro-v1:0:128k",
|
||||
"amazon.nova-micro-v1:0:24k",
|
||||
"amazon.nova-premier-v1:0",
|
||||
"amazon.nova-premier-v1:0:1000k",
|
||||
"amazon.nova-premier-v1:0:20k",
|
||||
"amazon.nova-premier-v1:0:8k",
|
||||
"amazon.nova-premier-v1:0:mm",
|
||||
"amazon.nova-pro-v1:0",
|
||||
"amazon.nova-pro-v1:0:24k",
|
||||
"amazon.nova-pro-v1:0:300k",
|
||||
"amazon.titan-text-express-v1",
|
||||
"amazon.titan-text-express-v1:0:8k",
|
||||
"amazon.titan-text-lite-v1",
|
||||
"amazon.titan-text-lite-v1:0:4k",
|
||||
"amazon.titan-tg1-large",
|
||||
"anthropic.claude-3-5-haiku-20241022-v1:0",
|
||||
"anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
"anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
"anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0:200k",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0:48k",
|
||||
"anthropic.claude-3-opus-20240229-v1:0",
|
||||
"anthropic.claude-3-opus-20240229-v1:0:12k",
|
||||
"anthropic.claude-3-opus-20240229-v1:0:200k",
|
||||
"anthropic.claude-3-opus-20240229-v1:0:28k",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0:200k",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0:28k",
|
||||
"anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
"anthropic.claude-instant-v1:2:100k",
|
||||
"anthropic.claude-opus-4-1-20250805-v1:0",
|
||||
"anthropic.claude-opus-4-20250514-v1:0",
|
||||
"anthropic.claude-sonnet-4-20250514-v1:0",
|
||||
"anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
"anthropic.claude-v2:0:100k",
|
||||
"anthropic.claude-v2:0:18k",
|
||||
"anthropic.claude-v2:1:18k",
|
||||
"anthropic.claude-v2:1:200k",
|
||||
"cohere.command-r-plus-v1:0",
|
||||
"cohere.command-r-v1:0",
|
||||
"cohere.rerank-v3-5:0",
|
||||
"deepseek.r1-v1:0",
|
||||
"meta.llama3-1-70b-instruct-v1:0",
|
||||
"meta.llama3-1-8b-instruct-v1:0",
|
||||
"meta.llama3-2-11b-instruct-v1:0",
|
||||
"meta.llama3-2-1b-instruct-v1:0",
|
||||
"meta.llama3-2-3b-instruct-v1:0",
|
||||
"meta.llama3-2-90b-instruct-v1:0",
|
||||
"meta.llama3-3-70b-instruct-v1:0",
|
||||
"meta.llama3-70b-instruct-v1:0",
|
||||
"meta.llama3-8b-instruct-v1:0",
|
||||
"meta.llama4-maverick-17b-instruct-v1:0",
|
||||
"meta.llama4-scout-17b-instruct-v1:0",
|
||||
"mistral.mistral-7b-instruct-v0:2",
|
||||
"mistral.mistral-large-2402-v1:0",
|
||||
"mistral.mistral-small-2402-v1:0",
|
||||
"mistral.mixtral-8x7b-instruct-v0:1",
|
||||
"mistral.pixtral-large-2502-v1:0",
|
||||
"openai.gpt-oss-120b-1:0",
|
||||
"openai.gpt-oss-20b-1:0",
|
||||
"qwen.qwen3-32b-v1:0",
|
||||
"qwen.qwen3-coder-30b-a3b-v1:0",
|
||||
"twelvelabs.pegasus-1-2-v1:0",
|
||||
]
|
||||
|
||||
SupportedModels: TypeAlias = (
|
||||
OpenAIModels | AnthropicModels | GeminiModels | AzureModels | BedrockModels
|
||||
)
|
||||
@@ -20,9 +20,7 @@ from typing import (
|
||||
)
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import httpx
|
||||
from pydantic import BaseModel, Field
|
||||
from typing_extensions import Self
|
||||
|
||||
from crewai.events.event_bus import crewai_event_bus
|
||||
from crewai.events.types.llm_events import (
|
||||
@@ -37,14 +35,7 @@ from crewai.events.types.tool_usage_events import (
|
||||
ToolUsageFinishedEvent,
|
||||
ToolUsageStartedEvent,
|
||||
)
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llms.constants import (
|
||||
ANTHROPIC_MODELS,
|
||||
AZURE_MODELS,
|
||||
BEDROCK_MODELS,
|
||||
GEMINI_MODELS,
|
||||
OPENAI_MODELS,
|
||||
)
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.utilities import InternalInstructor
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededError,
|
||||
@@ -61,7 +52,6 @@ if TYPE_CHECKING:
|
||||
from litellm.utils import supports_response_schema
|
||||
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.task import Task
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.utilities.types import LLMMessage
|
||||
@@ -327,249 +317,57 @@ class AccumulatedToolArgs(BaseModel):
|
||||
|
||||
|
||||
class LLM(BaseLLM):
|
||||
completion_cost: float | None = None
|
||||
"""LiteLLM-based LLM implementation for CrewAI.
|
||||
|
||||
def __new__(cls, model: str, is_litellm: bool = False, **kwargs: Any) -> LLM:
|
||||
"""Factory method that routes to native SDK or falls back to LiteLLM.
|
||||
This class provides LiteLLM integration for models not covered by native providers.
|
||||
The metaclass (LLMMeta) automatically routes to native providers when appropriate.
|
||||
"""
|
||||
|
||||
Routing priority:
|
||||
1. If 'provider' kwarg is present, use that provider with constants
|
||||
2. If only 'model' kwarg, use constants to infer provider
|
||||
3. If "/" in model name:
|
||||
- Check if prefix is a native provider (openai/anthropic/azure/bedrock/gemini)
|
||||
- If yes, validate model against constants
|
||||
- If valid, route to native SDK; otherwise route to LiteLLM
|
||||
"""
|
||||
if not model or not isinstance(model, str):
|
||||
raise ValueError("Model must be a non-empty string")
|
||||
# LiteLLM-specific fields
|
||||
completion_cost: float | None = Field(None, description="Cost of completion")
|
||||
timeout: float | int | None = Field(None, description="Request timeout")
|
||||
top_p: float | None = Field(None, description="Top-p sampling parameter")
|
||||
n: int | None = Field(None, description="Number of completions to generate")
|
||||
max_completion_tokens: int | None = Field(
|
||||
None, description="Maximum completion tokens"
|
||||
)
|
||||
max_tokens: int | float | None = Field(None, description="Maximum total tokens")
|
||||
presence_penalty: float | None = Field(None, description="Presence penalty")
|
||||
frequency_penalty: float | None = Field(None, description="Frequency penalty")
|
||||
logit_bias: dict[int, float] | None = Field(None, description="Logit bias")
|
||||
response_format: type[BaseModel] | None = Field(
|
||||
None, description="Response format model"
|
||||
)
|
||||
seed: int | None = Field(None, description="Random seed for reproducibility")
|
||||
logprobs: int | None = Field(None, description="Log probabilities to return")
|
||||
top_logprobs: int | None = Field(None, description="Top log probabilities")
|
||||
api_base: str | None = Field(None, description="API base URL (alias for base_url)")
|
||||
api_version: str | None = Field(None, description="API version")
|
||||
callbacks: list[Any] | None = Field(None, description="Callback functions")
|
||||
context_window_size: int = Field(0, description="Context window size in tokens")
|
||||
reasoning_effort: Literal["none", "low", "medium", "high"] | None = Field(
|
||||
None, description="Reasoning effort level"
|
||||
)
|
||||
is_anthropic: bool = Field(False, description="Whether model is from Anthropic")
|
||||
stream: bool = Field(False, description="Whether to stream responses")
|
||||
|
||||
explicit_provider = kwargs.get("provider")
|
||||
|
||||
if explicit_provider:
|
||||
provider = explicit_provider
|
||||
use_native = True
|
||||
model_string = model
|
||||
elif "/" in model:
|
||||
prefix, _, model_part = model.partition("/")
|
||||
|
||||
provider_mapping = {
|
||||
"openai": "openai",
|
||||
"anthropic": "anthropic",
|
||||
"claude": "anthropic",
|
||||
"azure": "azure",
|
||||
"azure_openai": "azure",
|
||||
"google": "gemini",
|
||||
"gemini": "gemini",
|
||||
"bedrock": "bedrock",
|
||||
"aws": "bedrock",
|
||||
}
|
||||
|
||||
canonical_provider = provider_mapping.get(prefix.lower())
|
||||
|
||||
if canonical_provider and cls._validate_model_in_constants(
|
||||
model_part, canonical_provider
|
||||
):
|
||||
provider = canonical_provider
|
||||
use_native = True
|
||||
model_string = model_part
|
||||
else:
|
||||
provider = prefix
|
||||
use_native = False
|
||||
model_string = model_part
|
||||
else:
|
||||
provider = cls._infer_provider_from_model(model)
|
||||
use_native = True
|
||||
model_string = model
|
||||
|
||||
native_class = cls._get_native_provider(provider) if use_native else None
|
||||
if native_class and not is_litellm and provider in SUPPORTED_NATIVE_PROVIDERS:
|
||||
try:
|
||||
# Remove 'provider' from kwargs if it exists to avoid duplicate keyword argument
|
||||
kwargs_copy = {k: v for k, v in kwargs.items() if k != 'provider'}
|
||||
return cast(
|
||||
Self, native_class(model=model_string, provider=provider, **kwargs_copy)
|
||||
)
|
||||
except NotImplementedError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise ImportError(f"Error importing native provider: {e}") from e
|
||||
|
||||
# FALLBACK to LiteLLM
|
||||
if not LITELLM_AVAILABLE:
|
||||
logger.error("LiteLLM is not available, falling back to LiteLLM")
|
||||
raise ImportError("Fallback to LiteLLM is not available") from None
|
||||
|
||||
instance = object.__new__(cls)
|
||||
super(LLM, instance).__init__(model=model, is_litellm=True, **kwargs)
|
||||
instance.is_litellm = True
|
||||
return instance
|
||||
|
||||
@classmethod
|
||||
def _validate_model_in_constants(cls, model: str, provider: str) -> bool:
|
||||
"""Validate if a model name exists in the provider's constants.
|
||||
def model_post_init(self, __context: Any) -> None:
|
||||
"""Initialize LiteLLM-specific settings after model initialization.
|
||||
|
||||
Args:
|
||||
model: The model name to validate
|
||||
provider: The provider to check against (canonical name)
|
||||
|
||||
Returns:
|
||||
True if the model exists in the provider's constants, False otherwise
|
||||
__context: Pydantic context
|
||||
"""
|
||||
if provider == "openai":
|
||||
return model in OPENAI_MODELS
|
||||
super().model_post_init(__context)
|
||||
|
||||
if provider == "anthropic" or provider == "claude":
|
||||
return model in ANTHROPIC_MODELS
|
||||
# Configure LiteLLM
|
||||
if LITELLM_AVAILABLE:
|
||||
litellm.drop_params = True
|
||||
|
||||
if provider == "gemini":
|
||||
return model in GEMINI_MODELS
|
||||
# Determine if this is an Anthropic model
|
||||
self.is_anthropic = self._is_anthropic_model(self.model)
|
||||
|
||||
if provider == "bedrock":
|
||||
return model in BEDROCK_MODELS
|
||||
|
||||
if provider == "azure":
|
||||
# azure does not provide a list of available models, determine a better way to handle this
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _infer_provider_from_model(cls, model: str) -> str:
|
||||
"""Infer the provider from the model name.
|
||||
|
||||
Args:
|
||||
model: The model name without provider prefix
|
||||
|
||||
Returns:
|
||||
The inferred provider name, defaults to "openai"
|
||||
"""
|
||||
|
||||
if model in OPENAI_MODELS:
|
||||
return "openai"
|
||||
|
||||
if model in ANTHROPIC_MODELS:
|
||||
return "anthropic"
|
||||
|
||||
if model in GEMINI_MODELS:
|
||||
return "gemini"
|
||||
|
||||
if model in BEDROCK_MODELS:
|
||||
return "bedrock"
|
||||
|
||||
if model in AZURE_MODELS:
|
||||
return "azure"
|
||||
|
||||
return "openai"
|
||||
|
||||
@classmethod
|
||||
def _get_native_provider(cls, provider: str) -> type | None:
|
||||
"""Get native provider class if available."""
|
||||
if provider == "openai":
|
||||
from crewai.llms.providers.openai.completion import OpenAICompletion
|
||||
|
||||
return OpenAICompletion
|
||||
|
||||
if provider == "anthropic" or provider == "claude":
|
||||
from crewai.llms.providers.anthropic.completion import (
|
||||
AnthropicCompletion,
|
||||
)
|
||||
|
||||
return AnthropicCompletion
|
||||
|
||||
if provider == "azure" or provider == "azure_openai":
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
return AzureCompletion
|
||||
|
||||
if provider == "google" or provider == "gemini":
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
|
||||
return GeminiCompletion
|
||||
|
||||
if provider == "bedrock":
|
||||
from crewai.llms.providers.bedrock.completion import BedrockCompletion
|
||||
|
||||
return BedrockCompletion
|
||||
|
||||
return None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model: str,
|
||||
timeout: float | int | None = None,
|
||||
temperature: float | None = None,
|
||||
top_p: float | None = None,
|
||||
n: int | None = None,
|
||||
stop: str | list[str] | None = None,
|
||||
max_completion_tokens: int | None = None,
|
||||
max_tokens: int | float | None = None,
|
||||
presence_penalty: float | None = None,
|
||||
frequency_penalty: float | None = None,
|
||||
logit_bias: dict[int, float] | None = None,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
seed: int | None = None,
|
||||
logprobs: int | None = None,
|
||||
top_logprobs: int | None = None,
|
||||
base_url: str | None = None,
|
||||
api_base: str | None = None,
|
||||
api_version: str | None = None,
|
||||
api_key: str | None = None,
|
||||
callbacks: list[Any] | None = None,
|
||||
reasoning_effort: Literal["none", "low", "medium", "high"] | None = None,
|
||||
stream: bool = False,
|
||||
interceptor: BaseInterceptor[httpx.Request, httpx.Response] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize LLM instance.
|
||||
|
||||
Note: This __init__ method is only called for fallback instances.
|
||||
Native provider instances handle their own initialization in their respective classes.
|
||||
"""
|
||||
super().__init__(
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
api_key=api_key,
|
||||
base_url=base_url,
|
||||
timeout=timeout,
|
||||
**kwargs,
|
||||
)
|
||||
self.model = model
|
||||
self.timeout = timeout
|
||||
self.temperature = temperature
|
||||
self.top_p = top_p
|
||||
self.n = n
|
||||
self.max_completion_tokens = max_completion_tokens
|
||||
self.max_tokens = max_tokens
|
||||
self.presence_penalty = presence_penalty
|
||||
self.frequency_penalty = frequency_penalty
|
||||
self.logit_bias = logit_bias
|
||||
self.response_format = response_format
|
||||
self.seed = seed
|
||||
self.logprobs = logprobs
|
||||
self.top_logprobs = top_logprobs
|
||||
self.base_url = base_url
|
||||
self.api_base = api_base
|
||||
self.api_version = api_version
|
||||
self.api_key = api_key
|
||||
self.callbacks = callbacks
|
||||
self.context_window_size = 0
|
||||
self.reasoning_effort = reasoning_effort
|
||||
self.additional_params = kwargs
|
||||
self.is_anthropic = self._is_anthropic_model(model)
|
||||
self.stream = stream
|
||||
self.interceptor = interceptor
|
||||
|
||||
litellm.drop_params = True
|
||||
|
||||
# Normalize self.stop to always be a list[str]
|
||||
if stop is None:
|
||||
self.stop: list[str] = []
|
||||
elif isinstance(stop, str):
|
||||
self.stop = [stop]
|
||||
else:
|
||||
self.stop = stop
|
||||
|
||||
self.set_callbacks(callbacks or [])
|
||||
# Set up callbacks
|
||||
self.set_callbacks(self.callbacks or [])
|
||||
self.set_env_callbacks()
|
||||
|
||||
@staticmethod
|
||||
@@ -1649,7 +1447,7 @@ class LLM(BaseLLM):
|
||||
**filtered_params,
|
||||
)
|
||||
|
||||
def __deepcopy__(self, memo: dict[int, Any] | None) -> LLM:
|
||||
def __deepcopy__(self, memo: dict[int, Any] | None) -> LLM: # type: ignore[override]
|
||||
"""Create a deep copy of the LLM instance."""
|
||||
import copy
|
||||
|
||||
6
lib/crewai/src/crewai/llm/hooks/__init__.py
Normal file
6
lib/crewai/src/crewai/llm/hooks/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""Interceptor contracts for crewai"""
|
||||
|
||||
from crewai.llm.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
__all__ = ["BaseInterceptor"]
|
||||
133
lib/crewai/src/crewai/llm/hooks/base.py
Normal file
133
lib/crewai/src/crewai/llm/hooks/base.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""Base classes for LLM transport interceptors.
|
||||
|
||||
This module provides abstract base classes for intercepting and modifying
|
||||
outbound and inbound messages at the transport level.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
||||
|
||||
from pydantic_core import core_schema
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic import GetCoreSchemaHandler
|
||||
from pydantic_core import CoreSchema
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
|
||||
class BaseInterceptor(ABC, Generic[T, U]):
|
||||
"""Abstract base class for intercepting transport-level messages.
|
||||
|
||||
Provides hooks to intercept and modify outbound and inbound messages
|
||||
at the transport layer.
|
||||
|
||||
Type parameters:
|
||||
T: Outbound message type (e.g., httpx.Request)
|
||||
U: Inbound message type (e.g., httpx.Response)
|
||||
|
||||
Example:
|
||||
>>> import httpx
|
||||
>>> class CustomInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
... def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
... message.headers["X-Custom-Header"] = "value"
|
||||
... return message
|
||||
...
|
||||
... def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
... print(f"Status: {message.status_code}")
|
||||
... return message
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def on_outbound(self, message: T) -> T:
|
||||
"""Intercept outbound message before sending.
|
||||
|
||||
Args:
|
||||
message: Outbound message object.
|
||||
|
||||
Returns:
|
||||
Modified message object.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def on_inbound(self, message: U) -> U:
|
||||
"""Intercept inbound message after receiving.
|
||||
|
||||
Args:
|
||||
message: Inbound message object.
|
||||
|
||||
Returns:
|
||||
Modified message object.
|
||||
"""
|
||||
...
|
||||
|
||||
async def aon_outbound(self, message: T) -> T:
|
||||
"""Async version of on_outbound.
|
||||
|
||||
Args:
|
||||
message: Outbound message object.
|
||||
|
||||
Returns:
|
||||
Modified message object.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def aon_inbound(self, message: U) -> U:
|
||||
"""Async version of on_inbound.
|
||||
|
||||
Args:
|
||||
message: Inbound message object.
|
||||
|
||||
Returns:
|
||||
Modified message object.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def __get_pydantic_core_schema__(
|
||||
cls, _source_type: Any, _handler: GetCoreSchemaHandler
|
||||
) -> CoreSchema:
|
||||
"""Generate Pydantic core schema for BaseInterceptor.
|
||||
|
||||
This allows the generic BaseInterceptor to be used in Pydantic models
|
||||
without requiring arbitrary_types_allowed=True. The schema validates
|
||||
that the value is an instance of BaseInterceptor.
|
||||
|
||||
Args:
|
||||
_source_type: The source type being validated (unused).
|
||||
_handler: Handler for generating schemas (unused).
|
||||
|
||||
Returns:
|
||||
A Pydantic core schema that validates BaseInterceptor instances.
|
||||
"""
|
||||
return core_schema.no_info_plain_validator_function(
|
||||
_validate_interceptor,
|
||||
serialization=core_schema.plain_serializer_function_ser_schema(
|
||||
lambda x: x, return_schema=core_schema.any_schema()
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _validate_interceptor(value: Any) -> BaseInterceptor[T, U]:
|
||||
"""Validate that the value is a BaseInterceptor instance.
|
||||
|
||||
Args:
|
||||
value: The value to validate.
|
||||
|
||||
Returns:
|
||||
The validated BaseInterceptor instance.
|
||||
|
||||
Raises:
|
||||
ValueError: If the value is not a BaseInterceptor instance.
|
||||
"""
|
||||
if not isinstance(value, BaseInterceptor):
|
||||
raise ValueError(
|
||||
f"Expected BaseInterceptor instance, got {type(value).__name__}"
|
||||
)
|
||||
return value
|
||||
123
lib/crewai/src/crewai/llm/hooks/transport.py
Normal file
123
lib/crewai/src/crewai/llm/hooks/transport.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""HTTP transport implementations for LLM request/response interception.
|
||||
|
||||
This module provides internal transport classes that integrate with BaseInterceptor
|
||||
to enable request/response modification at the transport level.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING, TypedDict
|
||||
|
||||
from httpx import (
|
||||
AsyncHTTPTransport as _AsyncHTTPTransport,
|
||||
HTTPTransport as _HTTPTransport,
|
||||
)
|
||||
from typing_extensions import NotRequired, Unpack
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ssl import SSLContext
|
||||
|
||||
from httpx import Limits, Request, Response
|
||||
from httpx._types import CertTypes, ProxyTypes
|
||||
|
||||
from crewai.llm.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
class HTTPTransportKwargs(TypedDict, total=False):
|
||||
"""Typed dictionary for httpx.HTTPTransport initialization parameters.
|
||||
|
||||
These parameters configure the underlying HTTP transport behavior including
|
||||
SSL verification, proxies, connection limits, and low-level socket options.
|
||||
"""
|
||||
|
||||
verify: bool | str | SSLContext
|
||||
cert: NotRequired[CertTypes]
|
||||
trust_env: bool
|
||||
http1: bool
|
||||
http2: bool
|
||||
limits: Limits
|
||||
proxy: NotRequired[ProxyTypes]
|
||||
uds: NotRequired[str]
|
||||
local_address: NotRequired[str]
|
||||
retries: int
|
||||
socket_options: NotRequired[
|
||||
Iterable[
|
||||
tuple[int, int, int]
|
||||
| tuple[int, int, bytes | bytearray]
|
||||
| tuple[int, int, None, int]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
class HTTPTransport(_HTTPTransport):
|
||||
"""HTTP transport that uses an interceptor for request/response modification.
|
||||
|
||||
This transport is used internally when a user provides a BaseInterceptor.
|
||||
Users should not instantiate this class directly - instead, pass an interceptor
|
||||
to the LLM client and this transport will be created automatically.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
interceptor: BaseInterceptor[Request, Response],
|
||||
**kwargs: Unpack[HTTPTransportKwargs],
|
||||
) -> None:
|
||||
"""Initialize transport with interceptor.
|
||||
|
||||
Args:
|
||||
interceptor: HTTP interceptor for modifying raw request/response objects.
|
||||
**kwargs: HTTPTransport configuration parameters (verify, cert, proxy, etc.).
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.interceptor = interceptor
|
||||
|
||||
def handle_request(self, request: Request) -> Response:
|
||||
"""Handle request with interception.
|
||||
|
||||
Args:
|
||||
request: The HTTP request to handle.
|
||||
|
||||
Returns:
|
||||
The HTTP response.
|
||||
"""
|
||||
request = self.interceptor.on_outbound(request)
|
||||
response = super().handle_request(request)
|
||||
return self.interceptor.on_inbound(response)
|
||||
|
||||
|
||||
class AsyncHTTPTransport(_AsyncHTTPTransport):
|
||||
"""Async HTTP transport that uses an interceptor for request/response modification.
|
||||
|
||||
This transport is used internally when a user provides a BaseInterceptor.
|
||||
Users should not instantiate this class directly - instead, pass an interceptor
|
||||
to the LLM client and this transport will be created automatically.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
interceptor: BaseInterceptor[Request, Response],
|
||||
**kwargs: Unpack[HTTPTransportKwargs],
|
||||
) -> None:
|
||||
"""Initialize async transport with interceptor.
|
||||
|
||||
Args:
|
||||
interceptor: HTTP interceptor for modifying raw request/response objects.
|
||||
**kwargs: HTTPTransport configuration parameters (verify, cert, proxy, etc.).
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.interceptor = interceptor
|
||||
|
||||
async def handle_async_request(self, request: Request) -> Response:
|
||||
"""Handle async request with interception.
|
||||
|
||||
Args:
|
||||
request: The HTTP request to handle.
|
||||
|
||||
Returns:
|
||||
The HTTP response.
|
||||
"""
|
||||
request = await self.interceptor.aon_outbound(request)
|
||||
response = await super().handle_async_request(request)
|
||||
return await self.interceptor.aon_inbound(response)
|
||||
14
lib/crewai/src/crewai/llm/internal/constants.py
Normal file
14
lib/crewai/src/crewai/llm/internal/constants.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from crewai.llm.constants import SupportedNativeProviders
|
||||
|
||||
|
||||
PROVIDER_MAPPING: dict[str, SupportedNativeProviders] = {
|
||||
"openai": "openai",
|
||||
"anthropic": "anthropic",
|
||||
"claude": "anthropic",
|
||||
"azure": "azure",
|
||||
"azure_openai": "azure",
|
||||
"google": "gemini",
|
||||
"gemini": "gemini",
|
||||
"bedrock": "bedrock",
|
||||
"aws": "bedrock",
|
||||
}
|
||||
251
lib/crewai/src/crewai/llm/internal/meta.py
Normal file
251
lib/crewai/src/crewai/llm/internal/meta.py
Normal file
@@ -0,0 +1,251 @@
|
||||
"""Metaclass for LLM provider routing.
|
||||
|
||||
This metaclass enables automatic routing to native provider implementations
|
||||
based on the model parameter at instantiation time.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
|
||||
from pydantic import ConfigDict
|
||||
from pydantic._internal._model_construction import ModelMetaclass
|
||||
|
||||
from crewai.llm.constants import (
|
||||
ANTHROPIC_MODELS,
|
||||
AZURE_MODELS,
|
||||
BEDROCK_MODELS,
|
||||
GEMINI_MODELS,
|
||||
OPENAI_MODELS,
|
||||
SUPPORTED_NATIVE_PROVIDERS,
|
||||
SupportedModels,
|
||||
SupportedNativeProviders,
|
||||
)
|
||||
from crewai.llm.internal.constants import PROVIDER_MAPPING
|
||||
|
||||
|
||||
class LLMMeta(ModelMetaclass):
|
||||
"""Metaclass for LLM that handles provider routing.
|
||||
|
||||
This metaclass intercepts LLM instantiation and routes to the appropriate
|
||||
native provider implementation based on the model parameter.
|
||||
"""
|
||||
|
||||
def __new__(
|
||||
mcs,
|
||||
name: str,
|
||||
bases: tuple[type, ...],
|
||||
namespace: dict[str, Any],
|
||||
**kwargs: Any,
|
||||
) -> type:
|
||||
"""Create new LLM class with proper model_config for custom LLMs.
|
||||
|
||||
Args:
|
||||
name: Class name
|
||||
bases: Base classes
|
||||
namespace: Class namespace
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
New class
|
||||
"""
|
||||
if name != "BaseLLM" and any(
|
||||
base.__name__ in ("BaseLLM", "LLM") for base in bases
|
||||
):
|
||||
if "model_config" not in namespace:
|
||||
namespace["model_config"] = ConfigDict(
|
||||
extra="allow", populate_by_name=True
|
||||
)
|
||||
elif isinstance(namespace["model_config"], dict):
|
||||
config_dict = cast(
|
||||
ConfigDict, cast(object, dict(namespace["model_config"]))
|
||||
)
|
||||
config_dict.setdefault("extra", "allow")
|
||||
config_dict.setdefault("populate_by_name", True)
|
||||
namespace["model_config"] = ConfigDict(**config_dict)
|
||||
|
||||
return super().__new__(mcs, name, bases, namespace)
|
||||
|
||||
def __call__(cls, *args: Any, **kwargs: Any) -> Any: # noqa: N805
|
||||
"""Route to appropriate provider implementation at instantiation time.
|
||||
|
||||
Args:
|
||||
*args: Positional arguments (model should be first for LLM class)
|
||||
**kwargs: Keyword arguments including model, is_litellm, etc.
|
||||
|
||||
Returns:
|
||||
Instance of the appropriate provider class or LLM class
|
||||
|
||||
Raises:
|
||||
ValueError: If model is not a valid string
|
||||
"""
|
||||
if cls.__name__ != "LLM":
|
||||
return super().__call__(*args, **kwargs)
|
||||
|
||||
model = cast(
|
||||
str | SupportedModels | None,
|
||||
(kwargs.get("model") or (args[0] if args else None)),
|
||||
)
|
||||
is_litellm = kwargs.get("is_litellm", False)
|
||||
|
||||
if not model or not isinstance(model, str):
|
||||
raise ValueError("Model must be a non-empty string")
|
||||
|
||||
if args and not kwargs.get("model"):
|
||||
kwargs["model"] = cast(SupportedModels, args[0])
|
||||
_ = args[1:]
|
||||
explicit_provider = cast(SupportedNativeProviders, kwargs.get("provider"))
|
||||
|
||||
if explicit_provider:
|
||||
provider = explicit_provider
|
||||
use_native = True
|
||||
model_string = model
|
||||
elif "/" in model:
|
||||
prefix, _, model_part = cast(
|
||||
tuple[SupportedNativeProviders, Any, SupportedModels],
|
||||
model.partition("/"),
|
||||
)
|
||||
|
||||
canonical_provider = PROVIDER_MAPPING.get(prefix.lower())
|
||||
|
||||
if canonical_provider and cls._validate_model_in_constants(
|
||||
model_part, canonical_provider
|
||||
):
|
||||
provider = canonical_provider
|
||||
use_native = True
|
||||
model_string = model_part
|
||||
else:
|
||||
provider = prefix
|
||||
use_native = False
|
||||
model_string = model_part
|
||||
else:
|
||||
provider = cls._infer_provider_from_model(model)
|
||||
use_native = True
|
||||
model_string = model
|
||||
|
||||
native_class = cls._get_native_provider(provider) if use_native else None
|
||||
if native_class and not is_litellm and provider in SUPPORTED_NATIVE_PROVIDERS:
|
||||
try:
|
||||
kwargs_copy = {
|
||||
k: v for k, v in kwargs.items() if k not in ("provider", "model")
|
||||
}
|
||||
return native_class(
|
||||
model=model_string, provider=provider, **kwargs_copy
|
||||
)
|
||||
except NotImplementedError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise ImportError(f"Error importing native provider: {e}") from e
|
||||
|
||||
try:
|
||||
import litellm # noqa: F401
|
||||
except ImportError:
|
||||
logging.error("LiteLLM is not available, falling back to LiteLLM")
|
||||
raise ImportError("Fallback to LiteLLM is not available") from None
|
||||
|
||||
kwargs_copy = {
|
||||
k: v for k, v in kwargs.items() if k not in ("model", "is_litellm")
|
||||
}
|
||||
return super().__call__(model=model, is_litellm=True, **kwargs_copy)
|
||||
|
||||
@staticmethod
|
||||
def _validate_model_in_constants(
|
||||
model: SupportedModels, provider: SupportedNativeProviders | None
|
||||
) -> bool:
|
||||
"""Validate if a model name exists in the provider's constants.
|
||||
|
||||
Args:
|
||||
model: The model name to validate
|
||||
provider: The provider to check against (canonical name)
|
||||
|
||||
Returns:
|
||||
True if the model exists in the provider's constants, False otherwise
|
||||
"""
|
||||
|
||||
if provider == "openai":
|
||||
return model in OPENAI_MODELS
|
||||
|
||||
if provider == "anthropic" or provider == "claude":
|
||||
return model in ANTHROPIC_MODELS
|
||||
|
||||
if provider == "gemini":
|
||||
return model in GEMINI_MODELS
|
||||
|
||||
if provider == "bedrock":
|
||||
return model in BEDROCK_MODELS
|
||||
|
||||
if provider == "azure":
|
||||
# azure does not provide a list of available models
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _infer_provider_from_model(
|
||||
model: SupportedModels | str,
|
||||
) -> SupportedNativeProviders:
|
||||
"""Infer the provider from the model name.
|
||||
|
||||
Args:
|
||||
model: The model name without provider prefix
|
||||
|
||||
Returns:
|
||||
The inferred provider name, defaults to "openai"
|
||||
"""
|
||||
|
||||
if model in OPENAI_MODELS:
|
||||
return "openai"
|
||||
|
||||
if model in ANTHROPIC_MODELS:
|
||||
return "anthropic"
|
||||
|
||||
if model in GEMINI_MODELS:
|
||||
return "gemini"
|
||||
|
||||
if model in BEDROCK_MODELS:
|
||||
return "bedrock"
|
||||
|
||||
if model in AZURE_MODELS:
|
||||
return "azure"
|
||||
|
||||
return "openai"
|
||||
|
||||
@staticmethod
|
||||
def _get_native_provider(provider: SupportedNativeProviders | None) -> type | None:
|
||||
"""Get native provider class if available.
|
||||
|
||||
Args:
|
||||
provider: The provider name
|
||||
|
||||
Returns:
|
||||
The provider class or None if not available
|
||||
"""
|
||||
if provider == "openai":
|
||||
from crewai.llm.providers.openai.completion import OpenAICompletion
|
||||
|
||||
return OpenAICompletion
|
||||
|
||||
if provider == "anthropic" or provider == "claude":
|
||||
from crewai.llm.providers.anthropic.completion import (
|
||||
AnthropicCompletion,
|
||||
)
|
||||
|
||||
return AnthropicCompletion
|
||||
|
||||
if provider == "azure" or provider == "azure_openai":
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
|
||||
return AzureCompletion
|
||||
|
||||
if provider == "google" or provider == "gemini":
|
||||
from crewai.llm.providers.gemini.completion import GeminiCompletion
|
||||
|
||||
return GeminiCompletion
|
||||
|
||||
if provider == "bedrock":
|
||||
from crewai.llm.providers.bedrock.completion import BedrockCompletion
|
||||
|
||||
return BedrockCompletion
|
||||
|
||||
return None
|
||||
@@ -2,14 +2,18 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from pydantic import BaseModel
|
||||
from dotenv import load_dotenv
|
||||
import httpx
|
||||
from pydantic import BaseModel, Field, PrivateAttr, model_validator
|
||||
from typing_extensions import Self
|
||||
|
||||
from crewai.events.types.llm_events import LLMCallType
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llms.hooks.transport import HTTPTransport
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.llm.core import CONTEXT_WINDOW_USAGE_RATIO
|
||||
from crewai.llm.hooks.transport import HTTPTransport
|
||||
from crewai.llm.providers.utils.common import safe_tool_conversion
|
||||
from crewai.utilities.agent_utils import is_context_length_exceeded
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededError,
|
||||
@@ -18,114 +22,85 @@ from crewai.utilities.types import LLMMessage
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from anthropic.types import Message
|
||||
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.task import Task
|
||||
|
||||
|
||||
try:
|
||||
from anthropic import Anthropic
|
||||
from anthropic.types import Message
|
||||
from anthropic.types.tool_use_block import ToolUseBlock
|
||||
import httpx
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
'Anthropic native provider not available, to install: uv add "crewai[anthropic]"'
|
||||
) from None
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class AnthropicCompletion(BaseLLM):
|
||||
"""Anthropic native completion implementation.
|
||||
|
||||
This class provides direct integration with the Anthropic Python SDK,
|
||||
offering native tool use, streaming support, and proper message formatting.
|
||||
|
||||
Attributes:
|
||||
model: Anthropic model name (e.g., 'claude-3-5-sonnet-20241022')
|
||||
base_url: Custom base URL for Anthropic API
|
||||
timeout: Request timeout in seconds
|
||||
max_retries: Maximum number of retries
|
||||
max_tokens: Maximum tokens in response (required for Anthropic)
|
||||
top_p: Nucleus sampling parameter
|
||||
stream: Enable streaming responses
|
||||
client_params: Additional parameters for the Anthropic client
|
||||
interceptor: HTTP interceptor for modifying requests/responses at transport level
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "claude-3-5-sonnet-20241022",
|
||||
api_key: str | None = None,
|
||||
base_url: str | None = None,
|
||||
timeout: float | None = None,
|
||||
max_retries: int = 2,
|
||||
temperature: float | None = None,
|
||||
max_tokens: int = 4096, # Required for Anthropic
|
||||
top_p: float | None = None,
|
||||
stop_sequences: list[str] | None = None,
|
||||
stream: bool = False,
|
||||
client_params: dict[str, Any] | None = None,
|
||||
interceptor: BaseInterceptor[httpx.Request, httpx.Response] | None = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""Initialize Anthropic chat completion client.
|
||||
base_url: str | None = Field(
|
||||
default=None, description="Custom base URL for Anthropic API"
|
||||
)
|
||||
timeout: float | None = Field(
|
||||
default=None, description="Request timeout in seconds"
|
||||
)
|
||||
max_retries: int = Field(default=2, description="Maximum number of retries")
|
||||
max_tokens: int = Field(
|
||||
default=4096, description="Maximum tokens in response (required for Anthropic)"
|
||||
)
|
||||
top_p: float | None = Field(default=None, description="Nucleus sampling parameter")
|
||||
stream: bool = Field(default=False, description="Enable streaming responses")
|
||||
client_params: dict[str, Any] | None = Field(
|
||||
default_factory=dict, description="Additional Anthropic client parameters"
|
||||
)
|
||||
_client: Anthropic = PrivateAttr(default=None) # type: ignore[assignment]
|
||||
|
||||
Args:
|
||||
model: Anthropic model name (e.g., 'claude-3-5-sonnet-20241022')
|
||||
api_key: Anthropic API key (defaults to ANTHROPIC_API_KEY env var)
|
||||
base_url: Custom base URL for Anthropic API
|
||||
timeout: Request timeout in seconds
|
||||
max_retries: Maximum number of retries
|
||||
temperature: Sampling temperature (0-1)
|
||||
max_tokens: Maximum tokens in response (required for Anthropic)
|
||||
top_p: Nucleus sampling parameter
|
||||
stop_sequences: Stop sequences (Anthropic uses stop_sequences, not stop)
|
||||
stream: Enable streaming responses
|
||||
client_params: Additional parameters for the Anthropic client
|
||||
interceptor: HTTP interceptor for modifying requests/responses at transport level.
|
||||
**kwargs: Additional parameters
|
||||
"""
|
||||
super().__init__(
|
||||
model=model, temperature=temperature, stop=stop_sequences or [], **kwargs
|
||||
)
|
||||
_is_claude_3: bool = PrivateAttr(default=False)
|
||||
_supports_tools: bool = PrivateAttr(default=False)
|
||||
|
||||
# Client params
|
||||
self.interceptor = interceptor
|
||||
self.client_params = client_params
|
||||
self.base_url = base_url
|
||||
self.timeout = timeout
|
||||
self.max_retries = max_retries
|
||||
@model_validator(mode="after")
|
||||
def setup_client(self) -> Self:
|
||||
"""Initialize the Anthropic client and model-specific settings."""
|
||||
self._client = Anthropic(**self._get_client_params())
|
||||
|
||||
self.client = Anthropic(**self._get_client_params())
|
||||
self._is_claude_3 = "claude-3" in self.model.lower()
|
||||
self._supports_tools = self._is_claude_3
|
||||
|
||||
# Store completion parameters
|
||||
self.max_tokens = max_tokens
|
||||
self.top_p = top_p
|
||||
self.stream = stream
|
||||
self.stop_sequences = stop_sequences or []
|
||||
|
||||
# Model-specific settings
|
||||
self.is_claude_3 = "claude-3" in model.lower()
|
||||
self.supports_tools = self.is_claude_3 # Claude 3+ supports tool use
|
||||
return self
|
||||
|
||||
@property
|
||||
def stop(self) -> list[str]:
|
||||
"""Get stop sequences sent to the API."""
|
||||
return self.stop_sequences
|
||||
def is_claude_3(self) -> bool:
|
||||
"""Check if model is Claude 3."""
|
||||
return self._is_claude_3
|
||||
|
||||
@stop.setter
|
||||
def stop(self, value: list[str] | str | None) -> None:
|
||||
"""Set stop sequences.
|
||||
|
||||
Synchronizes stop_sequences to ensure values set by CrewAgentExecutor
|
||||
are properly sent to the Anthropic API.
|
||||
|
||||
Args:
|
||||
value: Stop sequences as a list, single string, or None
|
||||
"""
|
||||
if value is None:
|
||||
self.stop_sequences = []
|
||||
elif isinstance(value, str):
|
||||
self.stop_sequences = [value]
|
||||
elif isinstance(value, list):
|
||||
self.stop_sequences = value
|
||||
else:
|
||||
self.stop_sequences = []
|
||||
@property
|
||||
def supports_tools(self) -> bool:
|
||||
"""Check if model supports tools."""
|
||||
return self._supports_tools
|
||||
|
||||
def _get_client_params(self) -> dict[str, Any]:
|
||||
"""Get client parameters."""
|
||||
|
||||
if self.api_key is None:
|
||||
self.api_key = os.getenv("ANTHROPIC_API_KEY")
|
||||
if self.api_key is None:
|
||||
raise ValueError("ANTHROPIC_API_KEY is required")
|
||||
|
||||
client_params = {
|
||||
"api_key": self.api_key,
|
||||
"base_url": self.base_url,
|
||||
@@ -149,8 +124,8 @@ class AnthropicCompletion(BaseLLM):
|
||||
tools: list[dict[str, Any]] | None = None,
|
||||
callbacks: list[Any] | None = None,
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str | Any:
|
||||
"""Call Anthropic messages API.
|
||||
@@ -245,8 +220,8 @@ class AnthropicCompletion(BaseLLM):
|
||||
params["temperature"] = self.temperature
|
||||
if self.top_p is not None:
|
||||
params["top_p"] = self.top_p
|
||||
if self.stop_sequences:
|
||||
params["stop_sequences"] = self.stop_sequences
|
||||
if self.stop:
|
||||
params["stop_sequences"] = self.stop
|
||||
|
||||
# Handle tools for Claude 3+
|
||||
if tools and self.supports_tools:
|
||||
@@ -266,8 +241,6 @@ class AnthropicCompletion(BaseLLM):
|
||||
continue
|
||||
|
||||
try:
|
||||
from crewai.llms.providers.utils.common import safe_tool_conversion
|
||||
|
||||
name, description, parameters = safe_tool_conversion(tool, "Anthropic")
|
||||
except (ImportError, KeyError, ValueError) as e:
|
||||
logging.error(f"Error converting tool to Anthropic format: {e}")
|
||||
@@ -341,8 +314,8 @@ class AnthropicCompletion(BaseLLM):
|
||||
self,
|
||||
params: dict[str, Any],
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str | Any:
|
||||
"""Handle non-streaming message completion."""
|
||||
@@ -357,7 +330,7 @@ class AnthropicCompletion(BaseLLM):
|
||||
params["tool_choice"] = {"type": "tool", "name": "structured_output"}
|
||||
|
||||
try:
|
||||
response: Message = self.client.messages.create(**params)
|
||||
response: Message = self._client.messages.create(**params)
|
||||
|
||||
except Exception as e:
|
||||
if is_context_length_exceeded(e):
|
||||
@@ -429,8 +402,8 @@ class AnthropicCompletion(BaseLLM):
|
||||
self,
|
||||
params: dict[str, Any],
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str:
|
||||
"""Handle streaming message completion."""
|
||||
@@ -451,7 +424,7 @@ class AnthropicCompletion(BaseLLM):
|
||||
stream_params = {k: v for k, v in params.items() if k != "stream"}
|
||||
|
||||
# Make streaming API call
|
||||
with self.client.messages.stream(**stream_params) as stream:
|
||||
with self._client.messages.stream(**stream_params) as stream:
|
||||
for event in stream:
|
||||
if hasattr(event, "delta") and hasattr(event.delta, "text"):
|
||||
text_delta = event.delta.text
|
||||
@@ -525,8 +498,8 @@ class AnthropicCompletion(BaseLLM):
|
||||
tool_uses: list[ToolUseBlock],
|
||||
params: dict[str, Any],
|
||||
available_functions: dict[str, Any],
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
) -> str:
|
||||
"""Handle the complete tool use conversation flow.
|
||||
|
||||
@@ -579,7 +552,7 @@ class AnthropicCompletion(BaseLLM):
|
||||
|
||||
try:
|
||||
# Send tool results back to Claude for final response
|
||||
final_response: Message = self.client.messages.create(**follow_up_params)
|
||||
final_response: Message = self._client.messages.create(**follow_up_params)
|
||||
|
||||
# Track token usage for follow-up call
|
||||
follow_up_usage = self._extract_anthropic_token_usage(final_response)
|
||||
@@ -636,7 +609,6 @@ class AnthropicCompletion(BaseLLM):
|
||||
|
||||
def get_context_window_size(self) -> int:
|
||||
"""Get the context window size for the model."""
|
||||
from crewai.llm import CONTEXT_WINDOW_USAGE_RATIO
|
||||
|
||||
# Context window sizes for Anthropic models
|
||||
context_windows = {
|
||||
@@ -5,8 +5,12 @@ import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
from dotenv import load_dotenv
|
||||
from pydantic import BaseModel, Field, PrivateAttr, model_validator
|
||||
from typing_extensions import Self
|
||||
|
||||
from crewai.llm.core import CONTEXT_WINDOW_USAGE_RATIO, LLM_CONTEXT_WINDOW_SIZES
|
||||
from crewai.llm.providers.utils.common import safe_tool_conversion
|
||||
from crewai.utilities.agent_utils import is_context_length_exceeded
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededError,
|
||||
@@ -15,7 +19,8 @@ from crewai.utilities.types import LLMMessage
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.task import Task
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
|
||||
|
||||
@@ -36,7 +41,7 @@ try:
|
||||
)
|
||||
|
||||
from crewai.events.types.llm_events import LLMCallType
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
@@ -44,111 +49,109 @@ except ImportError:
|
||||
) from None
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class AzureCompletion(BaseLLM):
|
||||
"""Azure AI Inference native completion implementation.
|
||||
|
||||
This class provides direct integration with the Azure AI Inference Python SDK,
|
||||
offering native function calling, streaming support, and proper Azure authentication.
|
||||
|
||||
Attributes:
|
||||
model: Azure deployment name or model name
|
||||
endpoint: Azure endpoint URL
|
||||
api_version: Azure API version
|
||||
timeout: Request timeout in seconds
|
||||
max_retries: Maximum number of retries
|
||||
top_p: Nucleus sampling parameter
|
||||
frequency_penalty: Frequency penalty (-2 to 2)
|
||||
presence_penalty: Presence penalty (-2 to 2)
|
||||
max_tokens: Maximum tokens in response
|
||||
stream: Enable streaming responses
|
||||
interceptor: HTTP interceptor (not yet supported for Azure)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model: str,
|
||||
api_key: str | None = None,
|
||||
endpoint: str | None = None,
|
||||
api_version: str | None = None,
|
||||
timeout: float | None = None,
|
||||
max_retries: int = 2,
|
||||
temperature: float | None = None,
|
||||
top_p: float | None = None,
|
||||
frequency_penalty: float | None = None,
|
||||
presence_penalty: float | None = None,
|
||||
max_tokens: int | None = None,
|
||||
stop: list[str] | None = None,
|
||||
stream: bool = False,
|
||||
interceptor: BaseInterceptor[Any, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""Initialize Azure AI Inference chat completion client.
|
||||
endpoint: str = Field( # type: ignore[assignment]
|
||||
default_factory=lambda: os.getenv("AZURE_ENDPOINT")
|
||||
or os.getenv("AZURE_OPENAI_ENDPOINT")
|
||||
or os.getenv("AZURE_API_BASE"),
|
||||
description="Azure endpoint URL (defaults to AZURE_ENDPOINT env var)",
|
||||
)
|
||||
api_version: str = Field(
|
||||
default_factory=lambda: os.getenv("AZURE_API_VERSION", "2024-06-01"),
|
||||
description="Azure API version (defaults to AZURE_API_VERSION env var or 2024-06-01)",
|
||||
)
|
||||
timeout: float | None = Field(
|
||||
default=None, description="Request timeout in seconds"
|
||||
)
|
||||
max_retries: int = Field(default=2, description="Maximum number of retries")
|
||||
top_p: float | None = Field(default=None, description="Nucleus sampling parameter")
|
||||
frequency_penalty: float | None = Field(
|
||||
default=None, le=2.0, ge=-2.0, description="Frequency penalty (-2 to 2)"
|
||||
)
|
||||
presence_penalty: float | None = Field(
|
||||
default=None, le=2.0, ge=-2.0, description="Presence penalty (-2 to 2)"
|
||||
)
|
||||
max_tokens: int | None = Field(
|
||||
default=None, description="Maximum tokens in response"
|
||||
)
|
||||
stream: bool = Field(default=False, description="Enable streaming responses")
|
||||
_client: ChatCompletionsClient = PrivateAttr(default=None) # type: ignore[assignment]
|
||||
|
||||
Args:
|
||||
model: Azure deployment name or model name
|
||||
api_key: Azure API key (defaults to AZURE_API_KEY env var)
|
||||
endpoint: Azure endpoint URL (defaults to AZURE_ENDPOINT env var)
|
||||
api_version: Azure API version (defaults to AZURE_API_VERSION env var)
|
||||
timeout: Request timeout in seconds
|
||||
max_retries: Maximum number of retries
|
||||
temperature: Sampling temperature (0-2)
|
||||
top_p: Nucleus sampling parameter
|
||||
frequency_penalty: Frequency penalty (-2 to 2)
|
||||
presence_penalty: Presence penalty (-2 to 2)
|
||||
max_tokens: Maximum tokens in response
|
||||
stop: Stop sequences
|
||||
stream: Enable streaming responses
|
||||
interceptor: HTTP interceptor (not yet supported for Azure).
|
||||
**kwargs: Additional parameters
|
||||
"""
|
||||
if interceptor is not None:
|
||||
_is_openai_model: bool = PrivateAttr(default=False)
|
||||
_is_azure_openai_endpoint: bool = PrivateAttr(default=False)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def setup_client(self) -> Self:
|
||||
"""Initialize the Azure client and validate configuration."""
|
||||
if self.interceptor is not None:
|
||||
raise NotImplementedError(
|
||||
"HTTP interceptors are not yet supported for Azure AI Inference provider. "
|
||||
"Interceptors are currently supported for OpenAI and Anthropic providers only."
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
model=model, temperature=temperature, stop=stop or [], **kwargs
|
||||
)
|
||||
|
||||
self.api_key = api_key or os.getenv("AZURE_API_KEY")
|
||||
self.endpoint = (
|
||||
endpoint
|
||||
or os.getenv("AZURE_ENDPOINT")
|
||||
or os.getenv("AZURE_OPENAI_ENDPOINT")
|
||||
or os.getenv("AZURE_API_BASE")
|
||||
)
|
||||
self.api_version = api_version or os.getenv("AZURE_API_VERSION") or "2024-06-01"
|
||||
self.timeout = timeout
|
||||
self.max_retries = max_retries
|
||||
if not self.api_key:
|
||||
self.api_key = os.getenv("AZURE_API_KEY")
|
||||
|
||||
if not self.api_key:
|
||||
raise ValueError(
|
||||
"Azure API key is required. Set AZURE_API_KEY environment variable or pass api_key parameter."
|
||||
)
|
||||
if not self.endpoint:
|
||||
raise ValueError(
|
||||
"Azure endpoint is required. Set AZURE_ENDPOINT environment variable or pass endpoint parameter."
|
||||
)
|
||||
|
||||
# Validate and potentially fix Azure OpenAI endpoint URL
|
||||
self.endpoint = self._validate_and_fix_endpoint(self.endpoint, model)
|
||||
self.endpoint = self._validate_and_fix_endpoint(self.endpoint, self.model)
|
||||
|
||||
# Build client kwargs
|
||||
client_kwargs = {
|
||||
client_kwargs: dict[str, Any] = {
|
||||
"endpoint": self.endpoint,
|
||||
"credential": AzureKeyCredential(self.api_key),
|
||||
}
|
||||
|
||||
# Add api_version if specified (primarily for Azure OpenAI endpoints)
|
||||
if self.api_version:
|
||||
client_kwargs["api_version"] = self.api_version
|
||||
|
||||
self.client = ChatCompletionsClient(**client_kwargs) # type: ignore[arg-type]
|
||||
self._client = ChatCompletionsClient(**client_kwargs)
|
||||
|
||||
self.top_p = top_p
|
||||
self.frequency_penalty = frequency_penalty
|
||||
self.presence_penalty = presence_penalty
|
||||
self.max_tokens = max_tokens
|
||||
self.stream = stream
|
||||
|
||||
self.is_openai_model = any(
|
||||
prefix in model.lower() for prefix in ["gpt-", "o1-", "text-"]
|
||||
self._is_openai_model = any(
|
||||
prefix in self.model.lower() for prefix in ["gpt-", "o1-", "text-"]
|
||||
)
|
||||
|
||||
self.is_azure_openai_endpoint = (
|
||||
self._is_azure_openai_endpoint = (
|
||||
"openai.azure.com" in self.endpoint
|
||||
and "/openai/deployments/" in self.endpoint
|
||||
)
|
||||
|
||||
def _validate_and_fix_endpoint(self, endpoint: str, model: str) -> str:
|
||||
return self
|
||||
|
||||
@property
|
||||
def is_openai_model(self) -> bool:
|
||||
"""Check if model is an OpenAI model."""
|
||||
return self._is_openai_model
|
||||
|
||||
@property
|
||||
def is_azure_openai_endpoint(self) -> bool:
|
||||
"""Check if endpoint is an Azure OpenAI endpoint."""
|
||||
return self._is_azure_openai_endpoint
|
||||
|
||||
def _validate_and_fix_endpoint(self, endpoint: str | None, model: str) -> str:
|
||||
"""Validate and fix Azure endpoint URL format.
|
||||
|
||||
Azure OpenAI endpoints should be in the format:
|
||||
@@ -160,7 +163,15 @@ class AzureCompletion(BaseLLM):
|
||||
|
||||
Returns:
|
||||
Validated and potentially corrected endpoint URL
|
||||
|
||||
Raises:
|
||||
ValueError: If endpoint is None or empty
|
||||
"""
|
||||
if not endpoint:
|
||||
raise ValueError(
|
||||
"Azure endpoint is required. Set AZURE_ENDPOINT environment variable or pass endpoint parameter."
|
||||
)
|
||||
|
||||
if "openai.azure.com" in endpoint and "/openai/deployments/" not in endpoint:
|
||||
endpoint = endpoint.rstrip("/")
|
||||
|
||||
@@ -177,8 +188,8 @@ class AzureCompletion(BaseLLM):
|
||||
tools: list[dict[str, BaseTool]] | None = None,
|
||||
callbacks: list[Any] | None = None,
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str | Any:
|
||||
"""Call Azure AI Inference chat completions API.
|
||||
@@ -317,8 +328,6 @@ class AzureCompletion(BaseLLM):
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Convert CrewAI tool format to Azure OpenAI function calling format."""
|
||||
|
||||
from crewai.llms.providers.utils.common import safe_tool_conversion
|
||||
|
||||
azure_tools = []
|
||||
|
||||
for tool in tools:
|
||||
@@ -371,14 +380,14 @@ class AzureCompletion(BaseLLM):
|
||||
self,
|
||||
params: dict[str, Any],
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str | Any:
|
||||
"""Handle non-streaming chat completion."""
|
||||
# Make API call
|
||||
try:
|
||||
response: ChatCompletions = self.client.complete(**params)
|
||||
response: ChatCompletions = self._client.complete(**params)
|
||||
|
||||
if not response.choices:
|
||||
raise ValueError("No choices returned from Azure API")
|
||||
@@ -467,8 +476,8 @@ class AzureCompletion(BaseLLM):
|
||||
self,
|
||||
params: dict[str, Any],
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str:
|
||||
"""Handle streaming chat completion."""
|
||||
@@ -476,7 +485,7 @@ class AzureCompletion(BaseLLM):
|
||||
tool_calls = {}
|
||||
|
||||
# Make streaming API call
|
||||
for update in self.client.complete(**params):
|
||||
for update in self._client.complete(**params):
|
||||
if isinstance(update, StreamingChatCompletionsUpdate):
|
||||
if update.choices:
|
||||
choice = update.choices[0]
|
||||
@@ -554,7 +563,6 @@ class AzureCompletion(BaseLLM):
|
||||
|
||||
def get_context_window_size(self) -> int:
|
||||
"""Get the context window size for the model."""
|
||||
from crewai.llm import CONTEXT_WINDOW_USAGE_RATIO, LLM_CONTEXT_WINDOW_SIZES
|
||||
|
||||
min_context = 1024
|
||||
max_context = 2097152
|
||||
@@ -5,11 +5,15 @@ import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any, TypedDict, cast
|
||||
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import Required
|
||||
from dotenv import load_dotenv
|
||||
from mypy_boto3_bedrock_runtime.client import BedrockRuntimeClient
|
||||
from pydantic import BaseModel, Field, PrivateAttr, model_validator
|
||||
from typing_extensions import Required, Self
|
||||
|
||||
from crewai.events.types.llm_events import LLMCallType
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.llm.core import CONTEXT_WINDOW_USAGE_RATIO
|
||||
from crewai.llm.providers.utils.common import safe_tool_conversion
|
||||
from crewai.utilities.agent_utils import is_context_length_exceeded
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededError,
|
||||
@@ -30,7 +34,8 @@ if TYPE_CHECKING:
|
||||
ToolTypeDef,
|
||||
)
|
||||
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.task import Task
|
||||
|
||||
|
||||
try:
|
||||
@@ -72,6 +77,9 @@ else:
|
||||
topK: int
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class ToolInputSchema(TypedDict):
|
||||
"""Type definition for tool input schema in Converse API."""
|
||||
|
||||
@@ -141,74 +149,84 @@ class BedrockCompletion(BaseLLM):
|
||||
- Complete streaming event handling (messageStart, contentBlockStart, etc.)
|
||||
- Response metadata and trace information capture
|
||||
- Model-specific conversation format handling (e.g., Cohere requirements)
|
||||
|
||||
Attributes:
|
||||
model: The Bedrock model ID to use
|
||||
aws_access_key_id: AWS access key (defaults to environment variable)
|
||||
aws_secret_access_key: AWS secret key (defaults to environment variable)
|
||||
aws_session_token: AWS session token for temporary credentials
|
||||
region_name: AWS region name
|
||||
max_tokens: Maximum tokens to generate
|
||||
top_p: Nucleus sampling parameter
|
||||
top_k: Top-k sampling parameter (Claude models only)
|
||||
stream: Whether to use streaming responses
|
||||
guardrail_config: Guardrail configuration for content filtering
|
||||
additional_model_request_fields: Model-specific request parameters
|
||||
additional_model_response_field_paths: Custom response field paths
|
||||
interceptor: HTTP interceptor (not yet supported for Bedrock)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
aws_access_key_id: str | None = None,
|
||||
aws_secret_access_key: str | None = None,
|
||||
aws_session_token: str | None = None,
|
||||
region_name: str = "us-east-1",
|
||||
temperature: float | None = None,
|
||||
max_tokens: int | None = None,
|
||||
top_p: float | None = None,
|
||||
top_k: int | None = None,
|
||||
stop_sequences: Sequence[str] | None = None,
|
||||
stream: bool = False,
|
||||
guardrail_config: dict[str, Any] | None = None,
|
||||
additional_model_request_fields: dict[str, Any] | None = None,
|
||||
additional_model_response_field_paths: list[str] | None = None,
|
||||
interceptor: BaseInterceptor[Any, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize AWS Bedrock completion client.
|
||||
aws_access_key_id: str = Field( # type: ignore[assignment]
|
||||
default_factory=lambda: os.getenv("AWS_ACCESS_KEY_ID"),
|
||||
description="AWS access key (defaults to environment variable)",
|
||||
)
|
||||
aws_secret_access_key: str = Field( # type: ignore[assignment]
|
||||
default_factory=lambda: os.getenv("AWS_SECRET_ACCESS_KEY"),
|
||||
description="AWS secret key (defaults to environment variable)",
|
||||
)
|
||||
aws_session_token: str = Field( # type: ignore[assignment]
|
||||
default_factory=lambda: os.getenv("AWS_SESSION_TOKEN"),
|
||||
description="AWS session token for temporary credentials",
|
||||
)
|
||||
region_name: str = Field(
|
||||
default_factory=lambda: os.getenv("AWS_REGION", "us-east-1"),
|
||||
description="AWS region name",
|
||||
)
|
||||
max_tokens: int | None = Field(
|
||||
default=None, description="Maximum tokens to generate"
|
||||
)
|
||||
top_p: float | None = Field(default=None, description="Nucleus sampling parameter")
|
||||
top_k: int | None = Field(
|
||||
default=None, description="Top-k sampling parameter (Claude models only)"
|
||||
)
|
||||
stream: bool = Field(
|
||||
default=False, description="Whether to use streaming responses"
|
||||
)
|
||||
guardrail_config: dict[str, Any] = Field(
|
||||
default_factory=dict,
|
||||
description="Guardrail configuration for content filtering",
|
||||
)
|
||||
additional_model_request_fields: dict[str, Any] = Field(
|
||||
default_factory=dict, description="Model-specific request parameters"
|
||||
)
|
||||
additional_model_response_field_paths: list[str] = Field(
|
||||
default_factory=list, description="Custom response field paths"
|
||||
)
|
||||
_client: BedrockRuntimeClient = PrivateAttr( # type: ignore[assignment]
|
||||
default_factory=lambda: Session().client,
|
||||
)
|
||||
|
||||
Args:
|
||||
model: The Bedrock model ID to use
|
||||
aws_access_key_id: AWS access key (defaults to environment variable)
|
||||
aws_secret_access_key: AWS secret key (defaults to environment variable)
|
||||
aws_session_token: AWS session token for temporary credentials
|
||||
region_name: AWS region name
|
||||
temperature: Sampling temperature for response generation
|
||||
max_tokens: Maximum tokens to generate
|
||||
top_p: Nucleus sampling parameter
|
||||
top_k: Top-k sampling parameter (Claude models only)
|
||||
stop_sequences: List of sequences that stop generation
|
||||
stream: Whether to use streaming responses
|
||||
guardrail_config: Guardrail configuration for content filtering
|
||||
additional_model_request_fields: Model-specific request parameters
|
||||
additional_model_response_field_paths: Custom response field paths
|
||||
interceptor: HTTP interceptor (not yet supported for Bedrock).
|
||||
**kwargs: Additional parameters
|
||||
"""
|
||||
if interceptor is not None:
|
||||
_is_claude_model: bool = PrivateAttr(default=False)
|
||||
_supports_tools: bool = PrivateAttr(default=True)
|
||||
_supports_streaming: bool = PrivateAttr(default=True)
|
||||
_model_id: str = PrivateAttr()
|
||||
|
||||
@model_validator(mode="after")
|
||||
def setup_client(self) -> Self:
|
||||
"""Initialize the Bedrock client and validate configuration."""
|
||||
if self.interceptor is not None:
|
||||
raise NotImplementedError(
|
||||
"HTTP interceptors are not yet supported for AWS Bedrock provider. "
|
||||
"Interceptors are currently supported for OpenAI and Anthropic providers only."
|
||||
)
|
||||
|
||||
# Extract provider from kwargs to avoid duplicate argument
|
||||
kwargs.pop("provider", None)
|
||||
|
||||
super().__init__(
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
stop=stop_sequences or [],
|
||||
provider="bedrock",
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# Initialize Bedrock client with proper configuration
|
||||
session = Session(
|
||||
aws_access_key_id=aws_access_key_id or os.getenv("AWS_ACCESS_KEY_ID"),
|
||||
aws_secret_access_key=aws_secret_access_key
|
||||
or os.getenv("AWS_SECRET_ACCESS_KEY"),
|
||||
aws_session_token=aws_session_token or os.getenv("AWS_SESSION_TOKEN"),
|
||||
region_name=region_name,
|
||||
aws_access_key_id=self.aws_access_key_id,
|
||||
aws_secret_access_key=self.aws_secret_access_key,
|
||||
aws_session_token=self.aws_session_token,
|
||||
region_name=self.region_name,
|
||||
)
|
||||
|
||||
# Configure client with timeouts and retries following AWS best practices
|
||||
config = Config(
|
||||
read_timeout=300,
|
||||
retries={
|
||||
@@ -218,54 +236,34 @@ class BedrockCompletion(BaseLLM):
|
||||
tcp_keepalive=True,
|
||||
)
|
||||
|
||||
self.client = session.client("bedrock-runtime", config=config)
|
||||
self.region_name = region_name
|
||||
self._client = session.client("bedrock-runtime", config=config)
|
||||
|
||||
# Store completion parameters
|
||||
self.max_tokens = max_tokens
|
||||
self.top_p = top_p
|
||||
self.top_k = top_k
|
||||
self.stream = stream
|
||||
self.stop_sequences = stop_sequences or []
|
||||
self._is_claude_model = "claude" in self.model.lower()
|
||||
self._supports_tools = True
|
||||
self._supports_streaming = True
|
||||
self._model_id = self.model
|
||||
|
||||
# Store advanced features (optional)
|
||||
self.guardrail_config = guardrail_config
|
||||
self.additional_model_request_fields = additional_model_request_fields
|
||||
self.additional_model_response_field_paths = (
|
||||
additional_model_response_field_paths
|
||||
)
|
||||
|
||||
# Model-specific settings
|
||||
self.is_claude_model = "claude" in model.lower()
|
||||
self.supports_tools = True # Converse API supports tools for most models
|
||||
self.supports_streaming = True
|
||||
|
||||
# Handle inference profiles for newer models
|
||||
self.model_id = model
|
||||
return self
|
||||
|
||||
@property
|
||||
def stop(self) -> list[str]:
|
||||
"""Get stop sequences sent to the API."""
|
||||
return list(self.stop_sequences)
|
||||
def is_claude_model(self) -> bool:
|
||||
"""Check if model is a Claude model."""
|
||||
return self._is_claude_model
|
||||
|
||||
@stop.setter
|
||||
def stop(self, value: Sequence[str] | str | None) -> None:
|
||||
"""Set stop sequences.
|
||||
@property
|
||||
def supports_tools(self) -> bool:
|
||||
"""Check if model supports tools."""
|
||||
return self._supports_tools
|
||||
|
||||
Synchronizes stop_sequences to ensure values set by CrewAgentExecutor
|
||||
are properly sent to the Bedrock API.
|
||||
@property
|
||||
def supports_streaming(self) -> bool:
|
||||
"""Check if model supports streaming."""
|
||||
return self._supports_streaming
|
||||
|
||||
Args:
|
||||
value: Stop sequences as a Sequence, single string, or None
|
||||
"""
|
||||
if value is None:
|
||||
self.stop_sequences = []
|
||||
elif isinstance(value, str):
|
||||
self.stop_sequences = [value]
|
||||
elif isinstance(value, Sequence):
|
||||
self.stop_sequences = list(value)
|
||||
else:
|
||||
self.stop_sequences = []
|
||||
@property
|
||||
def model_id(self) -> str:
|
||||
"""Get the model ID."""
|
||||
return self._model_id
|
||||
|
||||
def call(
|
||||
self,
|
||||
@@ -273,8 +271,8 @@ class BedrockCompletion(BaseLLM):
|
||||
tools: list[dict[Any, Any]] | None = None,
|
||||
callbacks: list[Any] | None = None,
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str | Any:
|
||||
"""Call AWS Bedrock Converse API."""
|
||||
@@ -359,8 +357,8 @@ class BedrockCompletion(BaseLLM):
|
||||
messages: list[dict[str, Any]],
|
||||
body: BedrockConverseRequestBody,
|
||||
available_functions: Mapping[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
) -> str:
|
||||
"""Handle non-streaming converse API call following AWS best practices."""
|
||||
try:
|
||||
@@ -378,7 +376,7 @@ class BedrockCompletion(BaseLLM):
|
||||
raise ValueError(f"Invalid message format at index {i}")
|
||||
|
||||
# Call Bedrock Converse API with proper error handling
|
||||
response = self.client.converse(
|
||||
response = self._client.converse(
|
||||
modelId=self.model_id,
|
||||
messages=cast(
|
||||
"Sequence[MessageTypeDef | MessageOutputTypeDef]",
|
||||
@@ -540,8 +538,8 @@ class BedrockCompletion(BaseLLM):
|
||||
messages: list[dict[str, Any]],
|
||||
body: BedrockConverseRequestBody,
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
) -> str:
|
||||
"""Handle streaming converse API call with comprehensive event handling."""
|
||||
full_response = ""
|
||||
@@ -549,7 +547,7 @@ class BedrockCompletion(BaseLLM):
|
||||
tool_use_id = None
|
||||
|
||||
try:
|
||||
response = self.client.converse_stream(
|
||||
response = self._client.converse_stream(
|
||||
modelId=self.model_id,
|
||||
messages=cast(
|
||||
"Sequence[MessageTypeDef | MessageOutputTypeDef]",
|
||||
@@ -778,7 +776,6 @@ class BedrockCompletion(BaseLLM):
|
||||
tools: list[dict[str, Any]],
|
||||
) -> list[ConverseToolTypeDef]:
|
||||
"""Convert CrewAI tools to Converse API format following AWS specification."""
|
||||
from crewai.llms.providers.utils.common import safe_tool_conversion
|
||||
|
||||
converse_tools: list[ConverseToolTypeDef] = []
|
||||
|
||||
@@ -818,8 +815,8 @@ class BedrockCompletion(BaseLLM):
|
||||
config["temperature"] = float(self.temperature)
|
||||
if self.top_p is not None:
|
||||
config["topP"] = float(self.top_p)
|
||||
if self.stop_sequences:
|
||||
config["stopSequences"] = self.stop_sequences
|
||||
if self.stop:
|
||||
config["stopSequences"] = self.stop
|
||||
|
||||
if self.is_claude_model and self.top_k is not None:
|
||||
# top_k is supported by Claude models
|
||||
@@ -871,7 +868,6 @@ class BedrockCompletion(BaseLLM):
|
||||
|
||||
def get_context_window_size(self) -> int:
|
||||
"""Get the context window size for the model."""
|
||||
from crewai.llm import CONTEXT_WINDOW_USAGE_RATIO
|
||||
|
||||
# Context window sizes for common Bedrock models
|
||||
context_windows = {
|
||||
@@ -1,12 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, cast
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from pydantic import BaseModel
|
||||
from dotenv import load_dotenv
|
||||
from pydantic import BaseModel, Field, PrivateAttr, model_validator
|
||||
from typing_extensions import Self
|
||||
|
||||
from crewai.events.types.llm_events import LLMCallType
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.llm.core import CONTEXT_WINDOW_USAGE_RATIO, LLM_CONTEXT_WINDOW_SIZES
|
||||
from crewai.llm.providers.utils.common import safe_tool_conversion
|
||||
from crewai.utilities.agent_utils import is_context_length_exceeded
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededError,
|
||||
@@ -14,6 +19,11 @@ from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
from crewai.utilities.types import LLMMessage
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.task import Task
|
||||
|
||||
|
||||
try:
|
||||
from google import genai # type: ignore[import-untyped]
|
||||
from google.genai import types # type: ignore[import-untyped]
|
||||
@@ -24,111 +34,93 @@ except ImportError:
|
||||
) from None
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class GeminiCompletion(BaseLLM):
|
||||
"""Google Gemini native completion implementation.
|
||||
|
||||
This class provides direct integration with the Google Gen AI Python SDK,
|
||||
offering native function calling, streaming support, and proper Gemini formatting.
|
||||
|
||||
Attributes:
|
||||
model: Gemini model name (e.g., 'gemini-2.0-flash-001', 'gemini-1.5-pro')
|
||||
project: Google Cloud project ID (for Vertex AI)
|
||||
location: Google Cloud location (for Vertex AI, defaults to 'us-central1')
|
||||
top_p: Nucleus sampling parameter
|
||||
top_k: Top-k sampling parameter
|
||||
max_output_tokens: Maximum tokens in response
|
||||
stream: Enable streaming responses
|
||||
safety_settings: Safety filter settings
|
||||
client_params: Additional parameters for Google Gen AI Client constructor
|
||||
interceptor: HTTP interceptor (not yet supported for Gemini)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "gemini-2.0-flash-001",
|
||||
api_key: str | None = None,
|
||||
project: str | None = None,
|
||||
location: str | None = None,
|
||||
temperature: float | None = None,
|
||||
top_p: float | None = None,
|
||||
top_k: int | None = None,
|
||||
max_output_tokens: int | None = None,
|
||||
stop_sequences: list[str] | None = None,
|
||||
stream: bool = False,
|
||||
safety_settings: dict[str, Any] | None = None,
|
||||
client_params: dict[str, Any] | None = None,
|
||||
interceptor: BaseInterceptor[Any, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""Initialize Google Gemini chat completion client.
|
||||
project: str | None = Field(
|
||||
default_factory=lambda: os.getenv("GOOGLE_CLOUD_PROJECT"),
|
||||
description="Google Cloud project ID (for Vertex AI)",
|
||||
)
|
||||
location: str = Field(
|
||||
default_factory=lambda: os.getenv("GOOGLE_CLOUD_LOCATION", "us-central1"),
|
||||
description="Google Cloud location (for Vertex AI, defaults to 'us-central1')",
|
||||
)
|
||||
top_p: float | None = Field(default=None, description="Nucleus sampling parameter")
|
||||
top_k: int | None = Field(default=None, description="Top-k sampling parameter")
|
||||
max_output_tokens: int | None = Field(
|
||||
default=None, description="Maximum tokens in response"
|
||||
)
|
||||
stream: bool = Field(default=False, description="Enable streaming responses")
|
||||
safety_settings: dict[str, Any] = Field(
|
||||
default_factory=dict, description="Safety filter settings"
|
||||
)
|
||||
client_params: dict[str, Any] = Field(
|
||||
default_factory=dict,
|
||||
description="Additional parameters for Google Gen AI Client constructor",
|
||||
)
|
||||
_client: Any = PrivateAttr(default=None)
|
||||
|
||||
Args:
|
||||
model: Gemini model name (e.g., 'gemini-2.0-flash-001', 'gemini-1.5-pro')
|
||||
api_key: Google API key (defaults to GOOGLE_API_KEY or GEMINI_API_KEY env var)
|
||||
project: Google Cloud project ID (for Vertex AI)
|
||||
location: Google Cloud location (for Vertex AI, defaults to 'us-central1')
|
||||
temperature: Sampling temperature (0-2)
|
||||
top_p: Nucleus sampling parameter
|
||||
top_k: Top-k sampling parameter
|
||||
max_output_tokens: Maximum tokens in response
|
||||
stop_sequences: Stop sequences
|
||||
stream: Enable streaming responses
|
||||
safety_settings: Safety filter settings
|
||||
client_params: Additional parameters to pass to the Google Gen AI Client constructor.
|
||||
Supports parameters like http_options, credentials, debug_config, etc.
|
||||
interceptor: HTTP interceptor (not yet supported for Gemini).
|
||||
**kwargs: Additional parameters
|
||||
"""
|
||||
if interceptor is not None:
|
||||
_is_gemini_2: bool = PrivateAttr(default=False)
|
||||
_is_gemini_1_5: bool = PrivateAttr(default=False)
|
||||
_supports_tools: bool = PrivateAttr(default=False)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def setup_client(self) -> Self:
|
||||
"""Initialize the Gemini client and validate configuration."""
|
||||
if self.interceptor is not None:
|
||||
raise NotImplementedError(
|
||||
"HTTP interceptors are not yet supported for Google Gemini provider. "
|
||||
"Interceptors are currently supported for OpenAI and Anthropic providers only."
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
model=model, temperature=temperature, stop=stop_sequences or [], **kwargs
|
||||
)
|
||||
|
||||
# Store client params for later use
|
||||
self.client_params = client_params or {}
|
||||
|
||||
# Get API configuration with environment variable fallbacks
|
||||
self.api_key = (
|
||||
api_key or os.getenv("GOOGLE_API_KEY") or os.getenv("GEMINI_API_KEY")
|
||||
)
|
||||
self.project = project or os.getenv("GOOGLE_CLOUD_PROJECT")
|
||||
self.location = location or os.getenv("GOOGLE_CLOUD_LOCATION") or "us-central1"
|
||||
if self.api_key is None:
|
||||
self.api_key = os.getenv("GOOGLE_API_KEY") or os.getenv("GEMINI_API_KEY")
|
||||
|
||||
use_vertexai = os.getenv("GOOGLE_GENAI_USE_VERTEXAI", "").lower() == "true"
|
||||
|
||||
self.client = self._initialize_client(use_vertexai)
|
||||
self._client = self._initialize_client(use_vertexai)
|
||||
|
||||
# Store completion parameters
|
||||
self.top_p = top_p
|
||||
self.top_k = top_k
|
||||
self.max_output_tokens = max_output_tokens
|
||||
self.stream = stream
|
||||
self.safety_settings = safety_settings or {}
|
||||
self.stop_sequences = stop_sequences or []
|
||||
self._is_gemini_2 = "gemini-2" in self.model.lower()
|
||||
self._is_gemini_1_5 = "gemini-1.5" in self.model.lower()
|
||||
self._supports_tools = self._is_gemini_1_5 or self._is_gemini_2
|
||||
|
||||
# Model-specific settings
|
||||
self.is_gemini_2 = "gemini-2" in model.lower()
|
||||
self.is_gemini_1_5 = "gemini-1.5" in model.lower()
|
||||
self.supports_tools = self.is_gemini_1_5 or self.is_gemini_2
|
||||
return self
|
||||
|
||||
@property
|
||||
def stop(self) -> list[str]:
|
||||
"""Get stop sequences sent to the API."""
|
||||
return self.stop_sequences
|
||||
def is_gemini_2(self) -> bool:
|
||||
"""Check if model is Gemini 2."""
|
||||
return self._is_gemini_2
|
||||
|
||||
@stop.setter
|
||||
def stop(self, value: list[str] | str | None) -> None:
|
||||
"""Set stop sequences.
|
||||
@property
|
||||
def is_gemini_1_5(self) -> bool:
|
||||
"""Check if model is Gemini 1.5."""
|
||||
return self._is_gemini_1_5
|
||||
|
||||
Synchronizes stop_sequences to ensure values set by CrewAgentExecutor
|
||||
are properly sent to the Gemini API.
|
||||
@property
|
||||
def supports_tools(self) -> bool:
|
||||
"""Check if model supports tools."""
|
||||
return self._supports_tools
|
||||
|
||||
Args:
|
||||
value: Stop sequences as a list, single string, or None
|
||||
"""
|
||||
if value is None:
|
||||
self.stop_sequences = []
|
||||
elif isinstance(value, str):
|
||||
self.stop_sequences = [value]
|
||||
elif isinstance(value, list):
|
||||
self.stop_sequences = value
|
||||
else:
|
||||
self.stop_sequences = []
|
||||
|
||||
def _initialize_client(self, use_vertexai: bool = False) -> genai.Client: # type: ignore[no-any-unimported]
|
||||
def _initialize_client(self, use_vertexai: bool = False) -> Any:
|
||||
"""Initialize the Google Gen AI client with proper parameter handling.
|
||||
|
||||
Args:
|
||||
@@ -150,12 +142,9 @@ class GeminiCompletion(BaseLLM):
|
||||
"location": self.location,
|
||||
}
|
||||
)
|
||||
|
||||
client_params.pop("api_key", None)
|
||||
|
||||
elif self.api_key:
|
||||
client_params["api_key"] = self.api_key
|
||||
|
||||
client_params.pop("vertexai", None)
|
||||
client_params.pop("project", None)
|
||||
client_params.pop("location", None)
|
||||
@@ -180,11 +169,10 @@ class GeminiCompletion(BaseLLM):
|
||||
params = {}
|
||||
|
||||
if (
|
||||
hasattr(self, "client")
|
||||
and hasattr(self.client, "vertexai")
|
||||
and self.client.vertexai
|
||||
hasattr(self, "_client")
|
||||
and hasattr(self._client, "vertexai")
|
||||
and self._client.vertexai
|
||||
):
|
||||
# Vertex AI configuration
|
||||
params.update(
|
||||
{
|
||||
"vertexai": True,
|
||||
@@ -206,8 +194,8 @@ class GeminiCompletion(BaseLLM):
|
||||
tools: list[dict[str, Any]] | None = None,
|
||||
callbacks: list[Any] | None = None,
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str | Any:
|
||||
"""Call Google Gemini generate content API.
|
||||
@@ -296,15 +284,12 @@ class GeminiCompletion(BaseLLM):
|
||||
self.tools = tools
|
||||
config_params = {}
|
||||
|
||||
# Add system instruction if present
|
||||
if system_instruction:
|
||||
# Convert system instruction to Content format
|
||||
system_content = types.Content(
|
||||
role="user", parts=[types.Part.from_text(text=system_instruction)]
|
||||
)
|
||||
config_params["system_instruction"] = system_content
|
||||
|
||||
# Add generation config parameters
|
||||
if self.temperature is not None:
|
||||
config_params["temperature"] = self.temperature
|
||||
if self.top_p is not None:
|
||||
@@ -313,14 +298,13 @@ class GeminiCompletion(BaseLLM):
|
||||
config_params["top_k"] = self.top_k
|
||||
if self.max_output_tokens is not None:
|
||||
config_params["max_output_tokens"] = self.max_output_tokens
|
||||
if self.stop_sequences:
|
||||
config_params["stop_sequences"] = self.stop_sequences
|
||||
if self.stop:
|
||||
config_params["stop_sequences"] = self.stop
|
||||
|
||||
if response_model:
|
||||
config_params["response_mime_type"] = "application/json"
|
||||
config_params["response_schema"] = response_model.model_json_schema()
|
||||
|
||||
# Handle tools for supported models
|
||||
if tools and self.supports_tools:
|
||||
config_params["tools"] = self._convert_tools_for_interference(tools)
|
||||
|
||||
@@ -335,8 +319,6 @@ class GeminiCompletion(BaseLLM):
|
||||
"""Convert CrewAI tool format to Gemini function declaration format."""
|
||||
gemini_tools = []
|
||||
|
||||
from crewai.llms.providers.utils.common import safe_tool_conversion
|
||||
|
||||
for tool in tools:
|
||||
name, description, parameters = safe_tool_conversion(tool, "Gemini")
|
||||
|
||||
@@ -345,7 +327,6 @@ class GeminiCompletion(BaseLLM):
|
||||
description=description,
|
||||
)
|
||||
|
||||
# Add parameters if present - ensure parameters is a dict
|
||||
if parameters and isinstance(parameters, dict):
|
||||
function_declaration.parameters = parameters
|
||||
|
||||
@@ -381,16 +362,12 @@ class GeminiCompletion(BaseLLM):
|
||||
content = message.get("content", "")
|
||||
|
||||
if role == "system":
|
||||
# Extract system instruction - Gemini handles it separately
|
||||
if system_instruction:
|
||||
system_instruction += f"\n\n{content}"
|
||||
else:
|
||||
system_instruction = cast(str, content)
|
||||
else:
|
||||
# Convert role for Gemini (assistant -> model)
|
||||
gemini_role = "model" if role == "assistant" else "user"
|
||||
|
||||
# Create Content object
|
||||
gemini_content = types.Content(
|
||||
role=gemini_role, parts=[types.Part.from_text(text=content)]
|
||||
)
|
||||
@@ -404,8 +381,8 @@ class GeminiCompletion(BaseLLM):
|
||||
system_instruction: str | None,
|
||||
config: types.GenerateContentConfig,
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str | Any:
|
||||
"""Handle non-streaming content generation."""
|
||||
@@ -416,7 +393,7 @@ class GeminiCompletion(BaseLLM):
|
||||
}
|
||||
|
||||
try:
|
||||
response = self.client.models.generate_content(**api_params)
|
||||
response = self._client.models.generate_content(**api_params)
|
||||
|
||||
usage = self._extract_token_usage(response)
|
||||
except Exception as e:
|
||||
@@ -470,8 +447,8 @@ class GeminiCompletion(BaseLLM):
|
||||
contents: list[types.Content],
|
||||
config: types.GenerateContentConfig,
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str:
|
||||
"""Handle streaming content generation."""
|
||||
@@ -484,7 +461,7 @@ class GeminiCompletion(BaseLLM):
|
||||
"config": config,
|
||||
}
|
||||
|
||||
for chunk in self.client.models.generate_content_stream(**api_params):
|
||||
for chunk in self._client.models.generate_content_stream(**api_params):
|
||||
if hasattr(chunk, "text") and chunk.text:
|
||||
full_response += chunk.text
|
||||
self._emit_stream_chunk_event(
|
||||
@@ -507,13 +484,11 @@ class GeminiCompletion(BaseLLM):
|
||||
else {},
|
||||
}
|
||||
|
||||
# Handle completed function calls
|
||||
if function_calls and available_functions:
|
||||
for call_data in function_calls.values():
|
||||
function_name = call_data["name"]
|
||||
function_args = call_data["args"]
|
||||
|
||||
# Execute tool
|
||||
result = self._handle_tool_execution(
|
||||
function_name=function_name,
|
||||
function_args=function_args,
|
||||
@@ -547,7 +522,6 @@ class GeminiCompletion(BaseLLM):
|
||||
|
||||
def get_context_window_size(self) -> int:
|
||||
"""Get the context window size for the model."""
|
||||
from crewai.llm import CONTEXT_WINDOW_USAGE_RATIO, LLM_CONTEXT_WINDOW_SIZES
|
||||
|
||||
min_context = 1024
|
||||
max_context = 2097152
|
||||
@@ -574,13 +548,11 @@ class GeminiCompletion(BaseLLM):
|
||||
"gemma-3-27b": 128000,
|
||||
}
|
||||
|
||||
# Find the best match for the model name
|
||||
for model_prefix, size in context_windows.items():
|
||||
if self.model.startswith(model_prefix):
|
||||
return int(size * CONTEXT_WINDOW_USAGE_RATIO)
|
||||
|
||||
# Default context window size for Gemini models
|
||||
return int(1048576 * CONTEXT_WINDOW_USAGE_RATIO) # 1M tokens
|
||||
return int(1048576 * CONTEXT_WINDOW_USAGE_RATIO)
|
||||
|
||||
def _extract_token_usage(self, response: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Extract token usage from Gemini response."""
|
||||
@@ -6,16 +6,20 @@ import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import httpx
|
||||
from openai import APIConnectionError, NotFoundError, OpenAI
|
||||
from openai.types.chat import ChatCompletion, ChatCompletionChunk
|
||||
from openai.types.chat.chat_completion import Choice
|
||||
from openai.types.chat.chat_completion_chunk import ChoiceDelta
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field, PrivateAttr, model_validator
|
||||
from typing_extensions import Self
|
||||
|
||||
from crewai.events.types.llm_events import LLMCallType
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llms.hooks.transport import HTTPTransport
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.llm.core import CONTEXT_WINDOW_USAGE_RATIO, LLM_CONTEXT_WINDOW_SIZES
|
||||
from crewai.llm.hooks.transport import HTTPTransport
|
||||
from crewai.llm.providers.utils.common import safe_tool_conversion
|
||||
from crewai.utilities.agent_utils import is_context_length_exceeded
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededError,
|
||||
@@ -25,11 +29,13 @@ from crewai.utilities.types import LLMMessage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.task import Task
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class OpenAICompletion(BaseLLM):
|
||||
"""OpenAI native completion implementation.
|
||||
|
||||
@@ -37,60 +43,56 @@ class OpenAICompletion(BaseLLM):
|
||||
offering native structured outputs, function calling, and streaming support.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "gpt-4o",
|
||||
api_key: str | None = None,
|
||||
base_url: str | None = None,
|
||||
organization: str | None = None,
|
||||
project: str | None = None,
|
||||
timeout: float | None = None,
|
||||
max_retries: int = 2,
|
||||
default_headers: dict[str, str] | None = None,
|
||||
default_query: dict[str, Any] | None = None,
|
||||
client_params: dict[str, Any] | None = None,
|
||||
temperature: float | None = None,
|
||||
top_p: float | None = None,
|
||||
frequency_penalty: float | None = None,
|
||||
presence_penalty: float | None = None,
|
||||
max_tokens: int | None = None,
|
||||
max_completion_tokens: int | None = None,
|
||||
seed: int | None = None,
|
||||
stream: bool = False,
|
||||
response_format: dict[str, Any] | type[BaseModel] | None = None,
|
||||
logprobs: bool | None = None,
|
||||
top_logprobs: int | None = None,
|
||||
reasoning_effort: str | None = None,
|
||||
provider: str | None = None,
|
||||
interceptor: BaseInterceptor[httpx.Request, httpx.Response] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize OpenAI chat completion client."""
|
||||
# Client configuration fields
|
||||
organization: str | None = Field(default=None, description="OpenAI organization ID")
|
||||
project: str | None = Field(default=None, description="OpenAI project ID")
|
||||
max_retries: int = Field(default=2, description="Maximum number of retries")
|
||||
default_headers: dict[str, str] = Field(
|
||||
default_factory=dict, description="Default headers for requests"
|
||||
)
|
||||
default_query: dict[str, Any] = Field(
|
||||
default_factory=dict, description="Default query parameters"
|
||||
)
|
||||
client_params: dict[str, Any] = Field(
|
||||
default_factory=dict, description="Additional client parameters"
|
||||
)
|
||||
timeout: float | None = Field(default=None, description="Request timeout")
|
||||
api_base: str | None = Field(
|
||||
default=None, description="API base URL", deprecated=True
|
||||
)
|
||||
|
||||
if provider is None:
|
||||
provider = kwargs.pop("provider", "openai")
|
||||
# Completion parameters
|
||||
top_p: float | None = Field(default=None, description="Top-p sampling parameter")
|
||||
frequency_penalty: float | None = Field(
|
||||
default=None, description="Frequency penalty"
|
||||
)
|
||||
presence_penalty: float | None = Field(default=None, description="Presence penalty")
|
||||
max_tokens: int | None = Field(default=None, description="Maximum tokens")
|
||||
max_completion_tokens: int | None = Field(
|
||||
None, description="Maximum completion tokens"
|
||||
)
|
||||
seed: int | None = Field(default=None, description="Random seed")
|
||||
stream: bool = Field(default=False, description="Enable streaming")
|
||||
response_format: dict[str, Any] | type[BaseModel] | None = Field(
|
||||
default=None, description="Response format"
|
||||
)
|
||||
logprobs: bool | None = Field(default=None, description="Return log probabilities")
|
||||
top_logprobs: int | None = Field(
|
||||
default=None, description="Number of top log probabilities"
|
||||
)
|
||||
reasoning_effort: str | None = Field(
|
||||
default=None, description="Reasoning effort level"
|
||||
)
|
||||
|
||||
self.interceptor = interceptor
|
||||
# Client configuration attributes
|
||||
self.organization = organization
|
||||
self.project = project
|
||||
self.max_retries = max_retries
|
||||
self.default_headers = default_headers
|
||||
self.default_query = default_query
|
||||
self.client_params = client_params
|
||||
self.timeout = timeout
|
||||
self.base_url = base_url
|
||||
self.api_base = kwargs.pop("api_base", None)
|
||||
_client: OpenAI = PrivateAttr(default=None) # type: ignore[assignment]
|
||||
is_o1_model: bool = Field(default=False, description="Whether this is an O1 model")
|
||||
is_gpt4_model: bool = Field(
|
||||
default=False, description="Whether this is a GPT-4 model"
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
api_key=api_key or os.getenv("OPENAI_API_KEY"),
|
||||
base_url=base_url,
|
||||
timeout=timeout,
|
||||
provider=provider,
|
||||
**kwargs,
|
||||
)
|
||||
@model_validator(mode="after")
|
||||
def setup_client(self) -> Self:
|
||||
"""Initialize OpenAI client after model validation."""
|
||||
|
||||
client_config = self._get_client_params()
|
||||
if self.interceptor:
|
||||
@@ -98,31 +100,15 @@ class OpenAICompletion(BaseLLM):
|
||||
http_client = httpx.Client(transport=transport)
|
||||
client_config["http_client"] = http_client
|
||||
|
||||
self.client = OpenAI(**client_config)
|
||||
self._client = OpenAI(**client_config)
|
||||
|
||||
# Completion parameters
|
||||
self.top_p = top_p
|
||||
self.frequency_penalty = frequency_penalty
|
||||
self.presence_penalty = presence_penalty
|
||||
self.max_tokens = max_tokens
|
||||
self.max_completion_tokens = max_completion_tokens
|
||||
self.seed = seed
|
||||
self.stream = stream
|
||||
self.response_format = response_format
|
||||
self.logprobs = logprobs
|
||||
self.top_logprobs = top_logprobs
|
||||
self.reasoning_effort = reasoning_effort
|
||||
self.is_o1_model = "o1" in model.lower()
|
||||
self.is_gpt4_model = "gpt-4" in model.lower()
|
||||
self.is_o1_model = "o1" in self.model.lower()
|
||||
self.is_gpt4_model = "gpt-4" in self.model.lower()
|
||||
|
||||
return self
|
||||
|
||||
def _get_client_params(self) -> dict[str, Any]:
|
||||
"""Get OpenAI client parameters."""
|
||||
|
||||
if self.api_key is None:
|
||||
self.api_key = os.getenv("OPENAI_API_KEY")
|
||||
if self.api_key is None:
|
||||
raise ValueError("OPENAI_API_KEY is required")
|
||||
|
||||
base_params = {
|
||||
"api_key": self.api_key,
|
||||
"organization": self.organization,
|
||||
@@ -268,7 +254,6 @@ class OpenAICompletion(BaseLLM):
|
||||
self, tools: list[dict[str, BaseTool]]
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Convert CrewAI tool format to OpenAI function calling format."""
|
||||
from crewai.llms.providers.utils.common import safe_tool_conversion
|
||||
|
||||
openai_tools = []
|
||||
|
||||
@@ -296,14 +281,14 @@ class OpenAICompletion(BaseLLM):
|
||||
self,
|
||||
params: dict[str, Any],
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str | Any:
|
||||
"""Handle non-streaming chat completion."""
|
||||
try:
|
||||
if response_model:
|
||||
parsed_response = self.client.beta.chat.completions.parse(
|
||||
parsed_response = self._client.beta.chat.completions.parse(
|
||||
**params,
|
||||
response_format=response_model,
|
||||
)
|
||||
@@ -327,7 +312,7 @@ class OpenAICompletion(BaseLLM):
|
||||
)
|
||||
return structured_json
|
||||
|
||||
response: ChatCompletion = self.client.chat.completions.create(**params)
|
||||
response: ChatCompletion = self._client.chat.completions.create(**params)
|
||||
|
||||
usage = self._extract_openai_token_usage(response)
|
||||
|
||||
@@ -419,8 +404,8 @@ class OpenAICompletion(BaseLLM):
|
||||
self,
|
||||
params: dict[str, Any],
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
from_agent: Any | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str:
|
||||
"""Handle streaming chat completion."""
|
||||
@@ -429,7 +414,7 @@ class OpenAICompletion(BaseLLM):
|
||||
|
||||
if response_model:
|
||||
completion_stream: Iterator[ChatCompletionChunk] = (
|
||||
self.client.chat.completions.create(**params)
|
||||
self._client.chat.completions.create(**params)
|
||||
)
|
||||
|
||||
accumulated_content = ""
|
||||
@@ -472,7 +457,7 @@ class OpenAICompletion(BaseLLM):
|
||||
)
|
||||
return accumulated_content
|
||||
|
||||
stream: Iterator[ChatCompletionChunk] = self.client.chat.completions.create(
|
||||
stream: Iterator[ChatCompletionChunk] = self._client.chat.completions.create(
|
||||
**params
|
||||
)
|
||||
|
||||
@@ -560,7 +545,6 @@ class OpenAICompletion(BaseLLM):
|
||||
|
||||
def get_context_window_size(self) -> int:
|
||||
"""Get the context window size for the model."""
|
||||
from crewai.llm import CONTEXT_WINDOW_USAGE_RATIO, LLM_CONTEXT_WINDOW_SIZES
|
||||
|
||||
min_context = 1024
|
||||
max_context = 2097152
|
||||
@@ -1 +1,38 @@
|
||||
"""LLM implementations for crewAI."""
|
||||
"""LLM implementations for crewAI.
|
||||
|
||||
.. deprecated:: 1.4.0
|
||||
The `crewai.llms` package is deprecated. Use `crewai.llm` instead.
|
||||
|
||||
This package was reorganized from `crewai.llms.*` to `crewai.llm.*`.
|
||||
All submodules are redirected to their new locations in `crewai.llm.*`.
|
||||
|
||||
Migration guide:
|
||||
Old: from crewai.llms.base_llm import BaseLLM
|
||||
New: from crewai.llm.base_llm import BaseLLM
|
||||
|
||||
Old: from crewai.llms.hooks.base import BaseInterceptor
|
||||
New: from crewai.llm.hooks.base import BaseInterceptor
|
||||
|
||||
Old: from crewai.llms.constants import OPENAI_MODELS
|
||||
New: from crewai.llm.constants import OPENAI_MODELS
|
||||
|
||||
Or use top-level imports:
|
||||
from crewai import LLM, BaseLLM
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
from crewai.llm import LLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
|
||||
|
||||
# Issue deprecation warning when this module is imported
|
||||
warnings.warn(
|
||||
"The 'crewai.llms' package is deprecated and will be removed in a future version. "
|
||||
"Please use 'crewai.llm' (singular) instead. "
|
||||
"All submodules have been reorganized from 'crewai.llms.*' to 'crewai.llm.*'.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
__all__ = ["LLM", "BaseLLM"]
|
||||
|
||||
@@ -1,550 +1,15 @@
|
||||
"""Base LLM abstract class for CrewAI.
|
||||
"""Deprecated: Use crewai.llm.base_llm instead.
|
||||
|
||||
This module provides the abstract base class for all LLM implementations
|
||||
in CrewAI, including common functionality for native SDK implementations.
|
||||
.. deprecated:: 1.4.0
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import warnings
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any, Final
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from crewai.events.event_bus import crewai_event_bus
|
||||
from crewai.events.types.llm_events import (
|
||||
LLMCallCompletedEvent,
|
||||
LLMCallFailedEvent,
|
||||
LLMCallStartedEvent,
|
||||
LLMCallType,
|
||||
LLMStreamChunkEvent,
|
||||
warnings.warn(
|
||||
"crewai.llms.base_llm is deprecated. Use crewai.llm.base_llm instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
from crewai.events.types.tool_usage_events import (
|
||||
ToolUsageErrorEvent,
|
||||
ToolUsageFinishedEvent,
|
||||
ToolUsageStartedEvent,
|
||||
)
|
||||
from crewai.types.usage_metrics import UsageMetrics
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.task import Task
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.utilities.types import LLMMessage
|
||||
|
||||
|
||||
DEFAULT_CONTEXT_WINDOW_SIZE: Final[int] = 4096
|
||||
DEFAULT_SUPPORTS_STOP_WORDS: Final[bool] = True
|
||||
_JSON_EXTRACTION_PATTERN: Final[re.Pattern[str]] = re.compile(r"\{.*}", re.DOTALL)
|
||||
|
||||
|
||||
class BaseLLM(ABC):
|
||||
"""Abstract base class for LLM implementations.
|
||||
|
||||
This class defines the interface that all LLM implementations must follow.
|
||||
Users can extend this class to create custom LLM implementations that don't
|
||||
rely on litellm's authentication mechanism.
|
||||
|
||||
Custom LLM implementations should handle error cases gracefully, including
|
||||
timeouts, authentication failures, and malformed responses. They should also
|
||||
implement proper validation for input parameters and provide clear error
|
||||
messages when things go wrong.
|
||||
|
||||
Attributes:
|
||||
model: The model identifier/name.
|
||||
temperature: Optional temperature setting for response generation.
|
||||
stop: A list of stop sequences that the LLM should use to stop generation.
|
||||
additional_params: Additional provider-specific parameters.
|
||||
"""
|
||||
|
||||
is_litellm: bool = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model: str,
|
||||
temperature: float | None = None,
|
||||
api_key: str | None = None,
|
||||
base_url: str | None = None,
|
||||
provider: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize the BaseLLM with default attributes.
|
||||
|
||||
Args:
|
||||
model: The model identifier/name.
|
||||
temperature: Optional temperature setting for response generation.
|
||||
stop: Optional list of stop sequences for generation.
|
||||
**kwargs: Additional provider-specific parameters.
|
||||
"""
|
||||
if not model:
|
||||
raise ValueError("Model name is required and cannot be empty")
|
||||
|
||||
self.model = model
|
||||
self.temperature = temperature
|
||||
self.api_key = api_key
|
||||
self.base_url = base_url
|
||||
# Store additional parameters for provider-specific use
|
||||
self.additional_params = kwargs
|
||||
self._provider = provider or "openai"
|
||||
|
||||
stop = kwargs.pop("stop", None)
|
||||
if stop is None:
|
||||
self.stop: list[str] = []
|
||||
elif isinstance(stop, str):
|
||||
self.stop = [stop]
|
||||
elif isinstance(stop, list):
|
||||
self.stop = stop
|
||||
else:
|
||||
self.stop = []
|
||||
|
||||
self._token_usage = {
|
||||
"total_tokens": 0,
|
||||
"prompt_tokens": 0,
|
||||
"completion_tokens": 0,
|
||||
"successful_requests": 0,
|
||||
"cached_prompt_tokens": 0,
|
||||
}
|
||||
|
||||
@property
|
||||
def provider(self) -> str:
|
||||
"""Get the provider of the LLM."""
|
||||
return self._provider
|
||||
|
||||
@provider.setter
|
||||
def provider(self, value: str) -> None:
|
||||
"""Set the provider of the LLM."""
|
||||
self._provider = value
|
||||
|
||||
@abstractmethod
|
||||
def call(
|
||||
self,
|
||||
messages: str | list[LLMMessage],
|
||||
tools: list[dict[str, BaseTool]] | None = None,
|
||||
callbacks: list[Any] | None = None,
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> str | Any:
|
||||
"""Call the LLM with the given messages.
|
||||
|
||||
Args:
|
||||
messages: Input messages for the LLM.
|
||||
Can be a string or list of message dictionaries.
|
||||
If string, it will be converted to a single user message.
|
||||
If list, each dict must have 'role' and 'content' keys.
|
||||
tools: Optional list of tool schemas for function calling.
|
||||
Each tool should define its name, description, and parameters.
|
||||
callbacks: Optional list of callback functions to be executed
|
||||
during and after the LLM call.
|
||||
available_functions: Optional dict mapping function names to callables
|
||||
that can be invoked by the LLM.
|
||||
from_task: Optional task caller to be used for the LLM call.
|
||||
from_agent: Optional agent caller to be used for the LLM call.
|
||||
response_model: Optional response model to be used for the LLM call.
|
||||
|
||||
Returns:
|
||||
Either a text response from the LLM (str) or
|
||||
the result of a tool function call (Any).
|
||||
|
||||
Raises:
|
||||
ValueError: If the messages format is invalid.
|
||||
TimeoutError: If the LLM request times out.
|
||||
RuntimeError: If the LLM request fails for other reasons.
|
||||
"""
|
||||
|
||||
def _convert_tools_for_interference(
|
||||
self, tools: list[dict[str, BaseTool]]
|
||||
) -> list[dict[str, BaseTool]]:
|
||||
"""Convert tools to a format that can be used for interference.
|
||||
|
||||
Args:
|
||||
tools: List of tools to convert.
|
||||
|
||||
Returns:
|
||||
List of converted tools (default implementation returns as-is)
|
||||
"""
|
||||
return tools
|
||||
|
||||
def supports_stop_words(self) -> bool:
|
||||
"""Check if the LLM supports stop words.
|
||||
|
||||
Returns:
|
||||
True if the LLM supports stop words, False otherwise.
|
||||
"""
|
||||
return DEFAULT_SUPPORTS_STOP_WORDS
|
||||
|
||||
def _supports_stop_words_implementation(self) -> bool:
|
||||
"""Check if stop words are configured for this LLM instance.
|
||||
|
||||
Native providers can override supports_stop_words() to return this value
|
||||
to ensure consistent behavior based on whether stop words are actually configured.
|
||||
|
||||
Returns:
|
||||
True if stop words are configured and can be applied
|
||||
"""
|
||||
return bool(self.stop)
|
||||
|
||||
def _apply_stop_words(self, content: str) -> str:
|
||||
"""Apply stop words to truncate response content.
|
||||
|
||||
This method provides consistent stop word behavior across all native SDK providers.
|
||||
Native providers should call this method to post-process their responses.
|
||||
|
||||
Args:
|
||||
content: The raw response content from the LLM
|
||||
|
||||
Returns:
|
||||
Content truncated at the first occurrence of any stop word
|
||||
|
||||
Example:
|
||||
>>> llm = MyNativeLLM(stop=["Observation:", "Final Answer:"])
|
||||
>>> response = (
|
||||
... "I need to search.\\n\\nAction: search\\nObservation: Found results"
|
||||
... )
|
||||
>>> llm._apply_stop_words(response)
|
||||
"I need to search.\\n\\nAction: search"
|
||||
"""
|
||||
if not self.stop or not content:
|
||||
return content
|
||||
|
||||
# Find the earliest occurrence of any stop word
|
||||
earliest_stop_pos = len(content)
|
||||
found_stop_word = None
|
||||
|
||||
for stop_word in self.stop:
|
||||
stop_pos = content.find(stop_word)
|
||||
if stop_pos != -1 and stop_pos < earliest_stop_pos:
|
||||
earliest_stop_pos = stop_pos
|
||||
found_stop_word = stop_word
|
||||
|
||||
# Truncate at the stop word if found
|
||||
if found_stop_word is not None:
|
||||
truncated = content[:earliest_stop_pos].strip()
|
||||
logging.debug(
|
||||
f"Applied stop word '{found_stop_word}' at position {earliest_stop_pos}"
|
||||
)
|
||||
return truncated
|
||||
|
||||
return content
|
||||
|
||||
def get_context_window_size(self) -> int:
|
||||
"""Get the context window size for the LLM.
|
||||
|
||||
Returns:
|
||||
The number of tokens/characters the model can handle.
|
||||
"""
|
||||
# Default implementation - subclasses should override with model-specific values
|
||||
return DEFAULT_CONTEXT_WINDOW_SIZE
|
||||
|
||||
# Common helper methods for native SDK implementations
|
||||
|
||||
def _emit_call_started_event(
|
||||
self,
|
||||
messages: str | list[LLMMessage],
|
||||
tools: list[dict[str, BaseTool]] | None = None,
|
||||
callbacks: list[Any] | None = None,
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
) -> None:
|
||||
"""Emit LLM call started event."""
|
||||
if not hasattr(crewai_event_bus, "emit"):
|
||||
raise ValueError("crewai_event_bus does not have an emit method") from None
|
||||
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=LLMCallStartedEvent(
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
callbacks=callbacks,
|
||||
available_functions=available_functions,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
model=self.model,
|
||||
),
|
||||
)
|
||||
|
||||
def _emit_call_completed_event(
|
||||
self,
|
||||
response: Any,
|
||||
call_type: LLMCallType,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
messages: str | list[dict[str, Any]] | None = None,
|
||||
) -> None:
|
||||
"""Emit LLM call completed event."""
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=LLMCallCompletedEvent(
|
||||
messages=messages,
|
||||
response=response,
|
||||
call_type=call_type,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
model=self.model,
|
||||
),
|
||||
)
|
||||
|
||||
def _emit_call_failed_event(
|
||||
self,
|
||||
error: str,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
) -> None:
|
||||
"""Emit LLM call failed event."""
|
||||
if not hasattr(crewai_event_bus, "emit"):
|
||||
raise ValueError("crewai_event_bus does not have an emit method") from None
|
||||
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=LLMCallFailedEvent(
|
||||
error=error,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
),
|
||||
)
|
||||
|
||||
def _emit_stream_chunk_event(
|
||||
self,
|
||||
chunk: str,
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
tool_call: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Emit stream chunk event."""
|
||||
if not hasattr(crewai_event_bus, "emit"):
|
||||
raise ValueError("crewai_event_bus does not have an emit method") from None
|
||||
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=LLMStreamChunkEvent(
|
||||
chunk=chunk,
|
||||
tool_call=tool_call,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
),
|
||||
)
|
||||
|
||||
def _handle_tool_execution(
|
||||
self,
|
||||
function_name: str,
|
||||
function_args: dict[str, Any],
|
||||
available_functions: dict[str, Any],
|
||||
from_task: Task | None = None,
|
||||
from_agent: Agent | None = None,
|
||||
) -> str | None:
|
||||
"""Handle tool execution with proper event emission.
|
||||
|
||||
Args:
|
||||
function_name: Name of the function to execute
|
||||
function_args: Arguments to pass to the function
|
||||
available_functions: Dict of available functions
|
||||
from_task: Optional task object
|
||||
from_agent: Optional agent object
|
||||
|
||||
Returns:
|
||||
Result of function execution or None if function not found
|
||||
"""
|
||||
if function_name not in available_functions:
|
||||
logging.warning(
|
||||
f"Function '{function_name}' not found in available functions"
|
||||
)
|
||||
return None
|
||||
|
||||
try:
|
||||
# Emit tool usage started event
|
||||
started_at = datetime.now()
|
||||
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=ToolUsageStartedEvent(
|
||||
tool_name=function_name,
|
||||
tool_args=function_args,
|
||||
from_agent=from_agent,
|
||||
from_task=from_task,
|
||||
),
|
||||
)
|
||||
|
||||
# Execute the function
|
||||
fn = available_functions[function_name]
|
||||
result = fn(**function_args)
|
||||
|
||||
# Emit tool usage finished event
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=ToolUsageFinishedEvent(
|
||||
output=result,
|
||||
tool_name=function_name,
|
||||
tool_args=function_args,
|
||||
started_at=started_at,
|
||||
finished_at=datetime.now(),
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
),
|
||||
)
|
||||
|
||||
# Emit LLM call completed event for tool call
|
||||
self._emit_call_completed_event(
|
||||
response=result,
|
||||
call_type=LLMCallType.TOOL_CALL,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
)
|
||||
|
||||
return str(result)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error executing function '{function_name}': {e!s}"
|
||||
logging.error(error_msg)
|
||||
|
||||
# Emit tool usage error event
|
||||
if not hasattr(crewai_event_bus, "emit"):
|
||||
raise ValueError(
|
||||
"crewai_event_bus does not have an emit method"
|
||||
) from None
|
||||
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=ToolUsageErrorEvent(
|
||||
tool_name=function_name,
|
||||
tool_args=function_args,
|
||||
error=error_msg,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
),
|
||||
)
|
||||
|
||||
# Emit LLM call failed event
|
||||
self._emit_call_failed_event(
|
||||
error=error_msg,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def _format_messages(self, messages: str | list[LLMMessage]) -> list[LLMMessage]:
|
||||
"""Convert messages to standard format.
|
||||
|
||||
Args:
|
||||
messages: Input messages (string or list of message dicts)
|
||||
|
||||
Returns:
|
||||
List of message dictionaries with 'role' and 'content' keys
|
||||
|
||||
Raises:
|
||||
ValueError: If message format is invalid
|
||||
"""
|
||||
if isinstance(messages, str):
|
||||
return [{"role": "user", "content": messages}]
|
||||
|
||||
# Validate message format
|
||||
for i, msg in enumerate(messages):
|
||||
if not isinstance(msg, dict):
|
||||
raise ValueError(f"Message at index {i} must be a dictionary")
|
||||
if "role" not in msg or "content" not in msg:
|
||||
raise ValueError(
|
||||
f"Message at index {i} must have 'role' and 'content' keys"
|
||||
)
|
||||
|
||||
return messages
|
||||
|
||||
@staticmethod
|
||||
def _validate_structured_output(
|
||||
response: str,
|
||||
response_format: type[BaseModel] | None,
|
||||
) -> str | BaseModel:
|
||||
"""Validate and parse structured output.
|
||||
|
||||
Args:
|
||||
response: Raw response string
|
||||
response_format: Optional Pydantic model for structured output
|
||||
|
||||
Returns:
|
||||
Parsed response (BaseModel instance if response_format provided, otherwise string)
|
||||
|
||||
Raises:
|
||||
ValueError: If structured output validation fails
|
||||
"""
|
||||
if response_format is None:
|
||||
return response
|
||||
|
||||
try:
|
||||
# Try to parse as JSON first
|
||||
if response.strip().startswith("{") or response.strip().startswith("["):
|
||||
data = json.loads(response)
|
||||
return response_format.model_validate(data)
|
||||
|
||||
json_match = _JSON_EXTRACTION_PATTERN.search(response)
|
||||
if json_match:
|
||||
data = json.loads(json_match.group())
|
||||
return response_format.model_validate(data)
|
||||
|
||||
raise ValueError("No JSON found in response")
|
||||
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
logging.warning(f"Failed to parse structured output: {e}")
|
||||
raise ValueError(
|
||||
f"Failed to parse response into {response_format.__name__}: {e}"
|
||||
) from e
|
||||
|
||||
@staticmethod
|
||||
def _extract_provider(model: str) -> str:
|
||||
"""Extract provider from model string.
|
||||
|
||||
Args:
|
||||
model: Model string (e.g., 'openai/gpt-4' or 'gpt-4')
|
||||
|
||||
Returns:
|
||||
Provider name (e.g., 'openai')
|
||||
"""
|
||||
if "/" in model:
|
||||
return model.partition("/")[0]
|
||||
return "openai" # Default provider
|
||||
|
||||
def _track_token_usage_internal(self, usage_data: dict[str, Any]) -> None:
|
||||
"""Track token usage internally in the LLM instance.
|
||||
|
||||
Args:
|
||||
usage_data: Token usage data from the API response
|
||||
"""
|
||||
# Extract tokens in a provider-agnostic way
|
||||
prompt_tokens = (
|
||||
usage_data.get("prompt_tokens")
|
||||
or usage_data.get("prompt_token_count")
|
||||
or usage_data.get("input_tokens")
|
||||
or 0
|
||||
)
|
||||
|
||||
completion_tokens = (
|
||||
usage_data.get("completion_tokens")
|
||||
or usage_data.get("candidates_token_count")
|
||||
or usage_data.get("output_tokens")
|
||||
or 0
|
||||
)
|
||||
|
||||
cached_tokens = (
|
||||
usage_data.get("cached_tokens")
|
||||
or usage_data.get("cached_prompt_tokens")
|
||||
or 0
|
||||
)
|
||||
|
||||
self._token_usage["prompt_tokens"] += prompt_tokens
|
||||
self._token_usage["completion_tokens"] += completion_tokens
|
||||
self._token_usage["total_tokens"] += prompt_tokens + completion_tokens
|
||||
self._token_usage["successful_requests"] += 1
|
||||
self._token_usage["cached_prompt_tokens"] += cached_tokens
|
||||
|
||||
def get_token_usage_summary(self) -> UsageMetrics:
|
||||
"""Get summary of token usage for this LLM instance.
|
||||
|
||||
Returns:
|
||||
Dictionary with token usage totals
|
||||
"""
|
||||
return UsageMetrics(**self._token_usage)
|
||||
from crewai.llm.base_llm import * # noqa: E402, F403
|
||||
|
||||
@@ -1,558 +1,15 @@
|
||||
from typing import Literal, TypeAlias
|
||||
"""Deprecated: Use crewai.llm.constants instead.
|
||||
|
||||
.. deprecated:: 1.4.0
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
|
||||
OpenAIModels: TypeAlias = Literal[
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0125",
|
||||
"gpt-3.5-turbo-0301",
|
||||
"gpt-3.5-turbo-0613",
|
||||
"gpt-3.5-turbo-1106",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-3.5-turbo-instruct",
|
||||
"gpt-3.5-turbo-instruct-0914",
|
||||
"gpt-4",
|
||||
"gpt-4-0125-preview",
|
||||
"gpt-4-0314",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-32k",
|
||||
"gpt-4-32k-0314",
|
||||
"gpt-4-32k-0613",
|
||||
"gpt-4-turbo",
|
||||
"gpt-4-turbo-2024-04-09",
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-4-vision-preview",
|
||||
"gpt-4.1",
|
||||
"gpt-4.1-2025-04-14",
|
||||
"gpt-4.1-mini",
|
||||
"gpt-4.1-mini-2025-04-14",
|
||||
"gpt-4.1-nano",
|
||||
"gpt-4.1-nano-2025-04-14",
|
||||
"gpt-4o",
|
||||
"gpt-4o-2024-05-13",
|
||||
"gpt-4o-2024-08-06",
|
||||
"gpt-4o-2024-11-20",
|
||||
"gpt-4o-audio-preview",
|
||||
"gpt-4o-audio-preview-2024-10-01",
|
||||
"gpt-4o-audio-preview-2024-12-17",
|
||||
"gpt-4o-audio-preview-2025-06-03",
|
||||
"gpt-4o-mini",
|
||||
"gpt-4o-mini-2024-07-18",
|
||||
"gpt-4o-mini-audio-preview",
|
||||
"gpt-4o-mini-audio-preview-2024-12-17",
|
||||
"gpt-4o-mini-realtime-preview",
|
||||
"gpt-4o-mini-realtime-preview-2024-12-17",
|
||||
"gpt-4o-mini-search-preview",
|
||||
"gpt-4o-mini-search-preview-2025-03-11",
|
||||
"gpt-4o-mini-transcribe",
|
||||
"gpt-4o-mini-tts",
|
||||
"gpt-4o-realtime-preview",
|
||||
"gpt-4o-realtime-preview-2024-10-01",
|
||||
"gpt-4o-realtime-preview-2024-12-17",
|
||||
"gpt-4o-realtime-preview-2025-06-03",
|
||||
"gpt-4o-search-preview",
|
||||
"gpt-4o-search-preview-2025-03-11",
|
||||
"gpt-4o-transcribe",
|
||||
"gpt-4o-transcribe-diarize",
|
||||
"gpt-5",
|
||||
"gpt-5-2025-08-07",
|
||||
"gpt-5-chat",
|
||||
"gpt-5-chat-latest",
|
||||
"gpt-5-codex",
|
||||
"gpt-5-mini",
|
||||
"gpt-5-mini-2025-08-07",
|
||||
"gpt-5-nano",
|
||||
"gpt-5-nano-2025-08-07",
|
||||
"gpt-5-pro",
|
||||
"gpt-5-pro-2025-10-06",
|
||||
"gpt-5-search-api",
|
||||
"gpt-5-search-api-2025-10-14",
|
||||
"gpt-audio",
|
||||
"gpt-audio-2025-08-28",
|
||||
"gpt-audio-mini",
|
||||
"gpt-audio-mini-2025-10-06",
|
||||
"gpt-image-1",
|
||||
"gpt-image-1-mini",
|
||||
"gpt-realtime",
|
||||
"gpt-realtime-2025-08-28",
|
||||
"gpt-realtime-mini",
|
||||
"gpt-realtime-mini-2025-10-06",
|
||||
"o1",
|
||||
"o1-preview",
|
||||
"o1-2024-12-17",
|
||||
"o1-mini",
|
||||
"o1-mini-2024-09-12",
|
||||
"o1-pro",
|
||||
"o1-pro-2025-03-19",
|
||||
"o3-mini",
|
||||
"o3",
|
||||
"o4-mini",
|
||||
"whisper-1",
|
||||
]
|
||||
OPENAI_MODELS: list[OpenAIModels] = [
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0125",
|
||||
"gpt-3.5-turbo-0301",
|
||||
"gpt-3.5-turbo-0613",
|
||||
"gpt-3.5-turbo-1106",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-3.5-turbo-instruct",
|
||||
"gpt-3.5-turbo-instruct-0914",
|
||||
"gpt-4",
|
||||
"gpt-4-0125-preview",
|
||||
"gpt-4-0314",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-32k",
|
||||
"gpt-4-32k-0314",
|
||||
"gpt-4-32k-0613",
|
||||
"gpt-4-turbo",
|
||||
"gpt-4-turbo-2024-04-09",
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-4-vision-preview",
|
||||
"gpt-4.1",
|
||||
"gpt-4.1-2025-04-14",
|
||||
"gpt-4.1-mini",
|
||||
"gpt-4.1-mini-2025-04-14",
|
||||
"gpt-4.1-nano",
|
||||
"gpt-4.1-nano-2025-04-14",
|
||||
"gpt-4o",
|
||||
"gpt-4o-2024-05-13",
|
||||
"gpt-4o-2024-08-06",
|
||||
"gpt-4o-2024-11-20",
|
||||
"gpt-4o-audio-preview",
|
||||
"gpt-4o-audio-preview-2024-10-01",
|
||||
"gpt-4o-audio-preview-2024-12-17",
|
||||
"gpt-4o-audio-preview-2025-06-03",
|
||||
"gpt-4o-mini",
|
||||
"gpt-4o-mini-2024-07-18",
|
||||
"gpt-4o-mini-audio-preview",
|
||||
"gpt-4o-mini-audio-preview-2024-12-17",
|
||||
"gpt-4o-mini-realtime-preview",
|
||||
"gpt-4o-mini-realtime-preview-2024-12-17",
|
||||
"gpt-4o-mini-search-preview",
|
||||
"gpt-4o-mini-search-preview-2025-03-11",
|
||||
"gpt-4o-mini-transcribe",
|
||||
"gpt-4o-mini-tts",
|
||||
"gpt-4o-realtime-preview",
|
||||
"gpt-4o-realtime-preview-2024-10-01",
|
||||
"gpt-4o-realtime-preview-2024-12-17",
|
||||
"gpt-4o-realtime-preview-2025-06-03",
|
||||
"gpt-4o-search-preview",
|
||||
"gpt-4o-search-preview-2025-03-11",
|
||||
"gpt-4o-transcribe",
|
||||
"gpt-4o-transcribe-diarize",
|
||||
"gpt-5",
|
||||
"gpt-5-2025-08-07",
|
||||
"gpt-5-chat",
|
||||
"gpt-5-chat-latest",
|
||||
"gpt-5-codex",
|
||||
"gpt-5-mini",
|
||||
"gpt-5-mini-2025-08-07",
|
||||
"gpt-5-nano",
|
||||
"gpt-5-nano-2025-08-07",
|
||||
"gpt-5-pro",
|
||||
"gpt-5-pro-2025-10-06",
|
||||
"gpt-5-search-api",
|
||||
"gpt-5-search-api-2025-10-14",
|
||||
"gpt-audio",
|
||||
"gpt-audio-2025-08-28",
|
||||
"gpt-audio-mini",
|
||||
"gpt-audio-mini-2025-10-06",
|
||||
"gpt-image-1",
|
||||
"gpt-image-1-mini",
|
||||
"gpt-realtime",
|
||||
"gpt-realtime-2025-08-28",
|
||||
"gpt-realtime-mini",
|
||||
"gpt-realtime-mini-2025-10-06",
|
||||
"o1",
|
||||
"o1-preview",
|
||||
"o1-2024-12-17",
|
||||
"o1-mini",
|
||||
"o1-mini-2024-09-12",
|
||||
"o1-pro",
|
||||
"o1-pro-2025-03-19",
|
||||
"o3-mini",
|
||||
"o3",
|
||||
"o4-mini",
|
||||
"whisper-1",
|
||||
]
|
||||
warnings.warn(
|
||||
"crewai.llms.constants is deprecated. Use crewai.llm.constants instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
|
||||
AnthropicModels: TypeAlias = Literal[
|
||||
"claude-3-7-sonnet-latest",
|
||||
"claude-3-7-sonnet-20250219",
|
||||
"claude-3-5-haiku-latest",
|
||||
"claude-3-5-haiku-20241022",
|
||||
"claude-haiku-4-5",
|
||||
"claude-haiku-4-5-20251001",
|
||||
"claude-sonnet-4-20250514",
|
||||
"claude-sonnet-4-0",
|
||||
"claude-4-sonnet-20250514",
|
||||
"claude-sonnet-4-5",
|
||||
"claude-sonnet-4-5-20250929",
|
||||
"claude-3-5-sonnet-latest",
|
||||
"claude-3-5-sonnet-20241022",
|
||||
"claude-3-5-sonnet-20240620",
|
||||
"claude-opus-4-0",
|
||||
"claude-opus-4-20250514",
|
||||
"claude-4-opus-20250514",
|
||||
"claude-opus-4-1",
|
||||
"claude-opus-4-1-20250805",
|
||||
"claude-3-opus-latest",
|
||||
"claude-3-opus-20240229",
|
||||
"claude-3-sonnet-20240229",
|
||||
"claude-3-haiku-latest",
|
||||
"claude-3-haiku-20240307",
|
||||
]
|
||||
ANTHROPIC_MODELS: list[AnthropicModels] = [
|
||||
"claude-3-7-sonnet-latest",
|
||||
"claude-3-7-sonnet-20250219",
|
||||
"claude-3-5-haiku-latest",
|
||||
"claude-3-5-haiku-20241022",
|
||||
"claude-haiku-4-5",
|
||||
"claude-haiku-4-5-20251001",
|
||||
"claude-sonnet-4-20250514",
|
||||
"claude-sonnet-4-0",
|
||||
"claude-4-sonnet-20250514",
|
||||
"claude-sonnet-4-5",
|
||||
"claude-sonnet-4-5-20250929",
|
||||
"claude-3-5-sonnet-latest",
|
||||
"claude-3-5-sonnet-20241022",
|
||||
"claude-3-5-sonnet-20240620",
|
||||
"claude-opus-4-0",
|
||||
"claude-opus-4-20250514",
|
||||
"claude-4-opus-20250514",
|
||||
"claude-opus-4-1",
|
||||
"claude-opus-4-1-20250805",
|
||||
"claude-3-opus-latest",
|
||||
"claude-3-opus-20240229",
|
||||
"claude-3-sonnet-20240229",
|
||||
"claude-3-haiku-latest",
|
||||
"claude-3-haiku-20240307",
|
||||
]
|
||||
|
||||
GeminiModels: TypeAlias = Literal[
|
||||
"gemini-2.5-pro",
|
||||
"gemini-2.5-pro-preview-03-25",
|
||||
"gemini-2.5-pro-preview-05-06",
|
||||
"gemini-2.5-pro-preview-06-05",
|
||||
"gemini-2.5-flash",
|
||||
"gemini-2.5-flash-preview-05-20",
|
||||
"gemini-2.5-flash-preview-04-17",
|
||||
"gemini-2.5-flash-image",
|
||||
"gemini-2.5-flash-image-preview",
|
||||
"gemini-2.5-flash-lite",
|
||||
"gemini-2.5-flash-lite-preview-06-17",
|
||||
"gemini-2.5-flash-preview-09-2025",
|
||||
"gemini-2.5-flash-lite-preview-09-2025",
|
||||
"gemini-2.5-flash-preview-tts",
|
||||
"gemini-2.5-pro-preview-tts",
|
||||
"gemini-2.5-computer-use-preview-10-2025",
|
||||
"gemini-2.0-flash",
|
||||
"gemini-2.0-flash-001",
|
||||
"gemini-2.0-flash-exp",
|
||||
"gemini-2.0-flash-exp-image-generation",
|
||||
"gemini-2.0-flash-lite",
|
||||
"gemini-2.0-flash-lite-001",
|
||||
"gemini-2.0-flash-lite-preview",
|
||||
"gemini-2.0-flash-lite-preview-02-05",
|
||||
"gemini-2.0-flash-preview-image-generation",
|
||||
"gemini-2.0-flash-thinking-exp",
|
||||
"gemini-2.0-flash-thinking-exp-01-21",
|
||||
"gemini-2.0-flash-thinking-exp-1219",
|
||||
"gemini-2.0-pro-exp",
|
||||
"gemini-2.0-pro-exp-02-05",
|
||||
"gemini-exp-1206",
|
||||
"gemini-1.5-pro",
|
||||
"gemini-1.5-flash",
|
||||
"gemini-1.5-flash-8b",
|
||||
"gemini-flash-latest",
|
||||
"gemini-flash-lite-latest",
|
||||
"gemini-pro-latest",
|
||||
"gemini-2.0-flash-live-001",
|
||||
"gemini-live-2.5-flash-preview",
|
||||
"gemini-2.5-flash-live-preview",
|
||||
"gemini-robotics-er-1.5-preview",
|
||||
"gemini-gemma-2-27b-it",
|
||||
"gemini-gemma-2-9b-it",
|
||||
"gemma-3-1b-it",
|
||||
"gemma-3-4b-it",
|
||||
"gemma-3-12b-it",
|
||||
"gemma-3-27b-it",
|
||||
"gemma-3n-e2b-it",
|
||||
"gemma-3n-e4b-it",
|
||||
"learnlm-2.0-flash-experimental",
|
||||
]
|
||||
GEMINI_MODELS: list[GeminiModels] = [
|
||||
"gemini-2.5-pro",
|
||||
"gemini-2.5-pro-preview-03-25",
|
||||
"gemini-2.5-pro-preview-05-06",
|
||||
"gemini-2.5-pro-preview-06-05",
|
||||
"gemini-2.5-flash",
|
||||
"gemini-2.5-flash-preview-05-20",
|
||||
"gemini-2.5-flash-preview-04-17",
|
||||
"gemini-2.5-flash-image",
|
||||
"gemini-2.5-flash-image-preview",
|
||||
"gemini-2.5-flash-lite",
|
||||
"gemini-2.5-flash-lite-preview-06-17",
|
||||
"gemini-2.5-flash-preview-09-2025",
|
||||
"gemini-2.5-flash-lite-preview-09-2025",
|
||||
"gemini-2.5-flash-preview-tts",
|
||||
"gemini-2.5-pro-preview-tts",
|
||||
"gemini-2.5-computer-use-preview-10-2025",
|
||||
"gemini-2.0-flash",
|
||||
"gemini-2.0-flash-001",
|
||||
"gemini-2.0-flash-exp",
|
||||
"gemini-2.0-flash-exp-image-generation",
|
||||
"gemini-2.0-flash-lite",
|
||||
"gemini-2.0-flash-lite-001",
|
||||
"gemini-2.0-flash-lite-preview",
|
||||
"gemini-2.0-flash-lite-preview-02-05",
|
||||
"gemini-2.0-flash-preview-image-generation",
|
||||
"gemini-2.0-flash-thinking-exp",
|
||||
"gemini-2.0-flash-thinking-exp-01-21",
|
||||
"gemini-2.0-flash-thinking-exp-1219",
|
||||
"gemini-2.0-pro-exp",
|
||||
"gemini-2.0-pro-exp-02-05",
|
||||
"gemini-exp-1206",
|
||||
"gemini-1.5-pro",
|
||||
"gemini-1.5-flash",
|
||||
"gemini-1.5-flash-8b",
|
||||
"gemini-flash-latest",
|
||||
"gemini-flash-lite-latest",
|
||||
"gemini-pro-latest",
|
||||
"gemini-2.0-flash-live-001",
|
||||
"gemini-live-2.5-flash-preview",
|
||||
"gemini-2.5-flash-live-preview",
|
||||
"gemini-robotics-er-1.5-preview",
|
||||
"gemini-gemma-2-27b-it",
|
||||
"gemini-gemma-2-9b-it",
|
||||
"gemma-3-1b-it",
|
||||
"gemma-3-4b-it",
|
||||
"gemma-3-12b-it",
|
||||
"gemma-3-27b-it",
|
||||
"gemma-3n-e2b-it",
|
||||
"gemma-3n-e4b-it",
|
||||
"learnlm-2.0-flash-experimental",
|
||||
]
|
||||
|
||||
|
||||
AzureModels: TypeAlias = Literal[
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0301",
|
||||
"gpt-3.5-turbo-0613",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-35-turbo",
|
||||
"gpt-35-turbo-0125",
|
||||
"gpt-35-turbo-1106",
|
||||
"gpt-35-turbo-16k-0613",
|
||||
"gpt-35-turbo-instruct-0914",
|
||||
"gpt-4",
|
||||
"gpt-4-0314",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-0125-preview",
|
||||
"gpt-4-32k",
|
||||
"gpt-4-32k-0314",
|
||||
"gpt-4-32k-0613",
|
||||
"gpt-4-turbo",
|
||||
"gpt-4-turbo-2024-04-09",
|
||||
"gpt-4-vision",
|
||||
"gpt-4o",
|
||||
"gpt-4o-2024-05-13",
|
||||
"gpt-4o-2024-08-06",
|
||||
"gpt-4o-2024-11-20",
|
||||
"gpt-4o-mini",
|
||||
"gpt-5",
|
||||
"o1",
|
||||
"o1-mini",
|
||||
"o1-preview",
|
||||
"o3-mini",
|
||||
"o3",
|
||||
"o4-mini",
|
||||
]
|
||||
AZURE_MODELS: list[AzureModels] = [
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0301",
|
||||
"gpt-3.5-turbo-0613",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-35-turbo",
|
||||
"gpt-35-turbo-0125",
|
||||
"gpt-35-turbo-1106",
|
||||
"gpt-35-turbo-16k-0613",
|
||||
"gpt-35-turbo-instruct-0914",
|
||||
"gpt-4",
|
||||
"gpt-4-0314",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-0125-preview",
|
||||
"gpt-4-32k",
|
||||
"gpt-4-32k-0314",
|
||||
"gpt-4-32k-0613",
|
||||
"gpt-4-turbo",
|
||||
"gpt-4-turbo-2024-04-09",
|
||||
"gpt-4-vision",
|
||||
"gpt-4o",
|
||||
"gpt-4o-2024-05-13",
|
||||
"gpt-4o-2024-08-06",
|
||||
"gpt-4o-2024-11-20",
|
||||
"gpt-4o-mini",
|
||||
"gpt-5",
|
||||
"o1",
|
||||
"o1-mini",
|
||||
"o1-preview",
|
||||
"o3-mini",
|
||||
"o3",
|
||||
"o4-mini",
|
||||
]
|
||||
|
||||
|
||||
BedrockModels: TypeAlias = Literal[
|
||||
"ai21.jamba-1-5-large-v1:0",
|
||||
"ai21.jamba-1-5-mini-v1:0",
|
||||
"amazon.nova-lite-v1:0",
|
||||
"amazon.nova-lite-v1:0:24k",
|
||||
"amazon.nova-lite-v1:0:300k",
|
||||
"amazon.nova-micro-v1:0",
|
||||
"amazon.nova-micro-v1:0:128k",
|
||||
"amazon.nova-micro-v1:0:24k",
|
||||
"amazon.nova-premier-v1:0",
|
||||
"amazon.nova-premier-v1:0:1000k",
|
||||
"amazon.nova-premier-v1:0:20k",
|
||||
"amazon.nova-premier-v1:0:8k",
|
||||
"amazon.nova-premier-v1:0:mm",
|
||||
"amazon.nova-pro-v1:0",
|
||||
"amazon.nova-pro-v1:0:24k",
|
||||
"amazon.nova-pro-v1:0:300k",
|
||||
"amazon.titan-text-express-v1",
|
||||
"amazon.titan-text-express-v1:0:8k",
|
||||
"amazon.titan-text-lite-v1",
|
||||
"amazon.titan-text-lite-v1:0:4k",
|
||||
"amazon.titan-tg1-large",
|
||||
"anthropic.claude-3-5-haiku-20241022-v1:0",
|
||||
"anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
"anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
"anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0:200k",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0:48k",
|
||||
"anthropic.claude-3-opus-20240229-v1:0",
|
||||
"anthropic.claude-3-opus-20240229-v1:0:12k",
|
||||
"anthropic.claude-3-opus-20240229-v1:0:200k",
|
||||
"anthropic.claude-3-opus-20240229-v1:0:28k",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0:200k",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0:28k",
|
||||
"anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
"anthropic.claude-instant-v1:2:100k",
|
||||
"anthropic.claude-opus-4-1-20250805-v1:0",
|
||||
"anthropic.claude-opus-4-20250514-v1:0",
|
||||
"anthropic.claude-sonnet-4-20250514-v1:0",
|
||||
"anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
"anthropic.claude-v2:0:100k",
|
||||
"anthropic.claude-v2:0:18k",
|
||||
"anthropic.claude-v2:1:18k",
|
||||
"anthropic.claude-v2:1:200k",
|
||||
"cohere.command-r-plus-v1:0",
|
||||
"cohere.command-r-v1:0",
|
||||
"cohere.rerank-v3-5:0",
|
||||
"deepseek.r1-v1:0",
|
||||
"meta.llama3-1-70b-instruct-v1:0",
|
||||
"meta.llama3-1-8b-instruct-v1:0",
|
||||
"meta.llama3-2-11b-instruct-v1:0",
|
||||
"meta.llama3-2-1b-instruct-v1:0",
|
||||
"meta.llama3-2-3b-instruct-v1:0",
|
||||
"meta.llama3-2-90b-instruct-v1:0",
|
||||
"meta.llama3-3-70b-instruct-v1:0",
|
||||
"meta.llama3-70b-instruct-v1:0",
|
||||
"meta.llama3-8b-instruct-v1:0",
|
||||
"meta.llama4-maverick-17b-instruct-v1:0",
|
||||
"meta.llama4-scout-17b-instruct-v1:0",
|
||||
"mistral.mistral-7b-instruct-v0:2",
|
||||
"mistral.mistral-large-2402-v1:0",
|
||||
"mistral.mistral-small-2402-v1:0",
|
||||
"mistral.mixtral-8x7b-instruct-v0:1",
|
||||
"mistral.pixtral-large-2502-v1:0",
|
||||
"openai.gpt-oss-120b-1:0",
|
||||
"openai.gpt-oss-20b-1:0",
|
||||
"qwen.qwen3-32b-v1:0",
|
||||
"qwen.qwen3-coder-30b-a3b-v1:0",
|
||||
"twelvelabs.pegasus-1-2-v1:0",
|
||||
]
|
||||
BEDROCK_MODELS: list[BedrockModels] = [
|
||||
"ai21.jamba-1-5-large-v1:0",
|
||||
"ai21.jamba-1-5-mini-v1:0",
|
||||
"amazon.nova-lite-v1:0",
|
||||
"amazon.nova-lite-v1:0:24k",
|
||||
"amazon.nova-lite-v1:0:300k",
|
||||
"amazon.nova-micro-v1:0",
|
||||
"amazon.nova-micro-v1:0:128k",
|
||||
"amazon.nova-micro-v1:0:24k",
|
||||
"amazon.nova-premier-v1:0",
|
||||
"amazon.nova-premier-v1:0:1000k",
|
||||
"amazon.nova-premier-v1:0:20k",
|
||||
"amazon.nova-premier-v1:0:8k",
|
||||
"amazon.nova-premier-v1:0:mm",
|
||||
"amazon.nova-pro-v1:0",
|
||||
"amazon.nova-pro-v1:0:24k",
|
||||
"amazon.nova-pro-v1:0:300k",
|
||||
"amazon.titan-text-express-v1",
|
||||
"amazon.titan-text-express-v1:0:8k",
|
||||
"amazon.titan-text-lite-v1",
|
||||
"amazon.titan-text-lite-v1:0:4k",
|
||||
"amazon.titan-tg1-large",
|
||||
"anthropic.claude-3-5-haiku-20241022-v1:0",
|
||||
"anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
"anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
"anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0:200k",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0:48k",
|
||||
"anthropic.claude-3-opus-20240229-v1:0",
|
||||
"anthropic.claude-3-opus-20240229-v1:0:12k",
|
||||
"anthropic.claude-3-opus-20240229-v1:0:200k",
|
||||
"anthropic.claude-3-opus-20240229-v1:0:28k",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0:200k",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0:28k",
|
||||
"anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
"anthropic.claude-instant-v1:2:100k",
|
||||
"anthropic.claude-opus-4-1-20250805-v1:0",
|
||||
"anthropic.claude-opus-4-20250514-v1:0",
|
||||
"anthropic.claude-sonnet-4-20250514-v1:0",
|
||||
"anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
"anthropic.claude-v2:0:100k",
|
||||
"anthropic.claude-v2:0:18k",
|
||||
"anthropic.claude-v2:1:18k",
|
||||
"anthropic.claude-v2:1:200k",
|
||||
"cohere.command-r-plus-v1:0",
|
||||
"cohere.command-r-v1:0",
|
||||
"cohere.rerank-v3-5:0",
|
||||
"deepseek.r1-v1:0",
|
||||
"meta.llama3-1-70b-instruct-v1:0",
|
||||
"meta.llama3-1-8b-instruct-v1:0",
|
||||
"meta.llama3-2-11b-instruct-v1:0",
|
||||
"meta.llama3-2-1b-instruct-v1:0",
|
||||
"meta.llama3-2-3b-instruct-v1:0",
|
||||
"meta.llama3-2-90b-instruct-v1:0",
|
||||
"meta.llama3-3-70b-instruct-v1:0",
|
||||
"meta.llama3-70b-instruct-v1:0",
|
||||
"meta.llama3-8b-instruct-v1:0",
|
||||
"meta.llama4-maverick-17b-instruct-v1:0",
|
||||
"meta.llama4-scout-17b-instruct-v1:0",
|
||||
"mistral.mistral-7b-instruct-v0:2",
|
||||
"mistral.mistral-large-2402-v1:0",
|
||||
"mistral.mistral-small-2402-v1:0",
|
||||
"mistral.mixtral-8x7b-instruct-v0:1",
|
||||
"mistral.pixtral-large-2502-v1:0",
|
||||
"openai.gpt-oss-120b-1:0",
|
||||
"openai.gpt-oss-20b-1:0",
|
||||
"qwen.qwen3-32b-v1:0",
|
||||
"qwen.qwen3-coder-30b-a3b-v1:0",
|
||||
"twelvelabs.pegasus-1-2-v1:0",
|
||||
]
|
||||
from crewai.llm.constants import * # noqa: E402, F403
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
"""Interceptor contracts for crewai"""
|
||||
"""Deprecated: Use crewai.llm.hooks instead.
|
||||
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
.. deprecated:: 1.4.0
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
|
||||
__all__ = ["BaseInterceptor"]
|
||||
warnings.warn(
|
||||
"crewai.llms.hooks is deprecated. Use crewai.llm.hooks instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
from crewai.llm.hooks import * # noqa: E402, F403
|
||||
|
||||
@@ -1,133 +1,15 @@
|
||||
"""Base classes for LLM transport interceptors.
|
||||
"""Deprecated: Use crewai.llm.hooks.base instead.
|
||||
|
||||
This module provides abstract base classes for intercepting and modifying
|
||||
outbound and inbound messages at the transport level.
|
||||
.. deprecated:: 1.4.0
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
||||
|
||||
from pydantic_core import core_schema
|
||||
import warnings
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic import GetCoreSchemaHandler
|
||||
from pydantic_core import CoreSchema
|
||||
warnings.warn(
|
||||
"crewai.llms.hooks.base is deprecated. Use crewai.llm.hooks.base instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
|
||||
class BaseInterceptor(ABC, Generic[T, U]):
|
||||
"""Abstract base class for intercepting transport-level messages.
|
||||
|
||||
Provides hooks to intercept and modify outbound and inbound messages
|
||||
at the transport layer.
|
||||
|
||||
Type parameters:
|
||||
T: Outbound message type (e.g., httpx.Request)
|
||||
U: Inbound message type (e.g., httpx.Response)
|
||||
|
||||
Example:
|
||||
>>> import httpx
|
||||
>>> class CustomInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
... def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
... message.headers["X-Custom-Header"] = "value"
|
||||
... return message
|
||||
...
|
||||
... def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
... print(f"Status: {message.status_code}")
|
||||
... return message
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def on_outbound(self, message: T) -> T:
|
||||
"""Intercept outbound message before sending.
|
||||
|
||||
Args:
|
||||
message: Outbound message object.
|
||||
|
||||
Returns:
|
||||
Modified message object.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def on_inbound(self, message: U) -> U:
|
||||
"""Intercept inbound message after receiving.
|
||||
|
||||
Args:
|
||||
message: Inbound message object.
|
||||
|
||||
Returns:
|
||||
Modified message object.
|
||||
"""
|
||||
...
|
||||
|
||||
async def aon_outbound(self, message: T) -> T:
|
||||
"""Async version of on_outbound.
|
||||
|
||||
Args:
|
||||
message: Outbound message object.
|
||||
|
||||
Returns:
|
||||
Modified message object.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def aon_inbound(self, message: U) -> U:
|
||||
"""Async version of on_inbound.
|
||||
|
||||
Args:
|
||||
message: Inbound message object.
|
||||
|
||||
Returns:
|
||||
Modified message object.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def __get_pydantic_core_schema__(
|
||||
cls, _source_type: Any, _handler: GetCoreSchemaHandler
|
||||
) -> CoreSchema:
|
||||
"""Generate Pydantic core schema for BaseInterceptor.
|
||||
|
||||
This allows the generic BaseInterceptor to be used in Pydantic models
|
||||
without requiring arbitrary_types_allowed=True. The schema validates
|
||||
that the value is an instance of BaseInterceptor.
|
||||
|
||||
Args:
|
||||
_source_type: The source type being validated (unused).
|
||||
_handler: Handler for generating schemas (unused).
|
||||
|
||||
Returns:
|
||||
A Pydantic core schema that validates BaseInterceptor instances.
|
||||
"""
|
||||
return core_schema.no_info_plain_validator_function(
|
||||
_validate_interceptor,
|
||||
serialization=core_schema.plain_serializer_function_ser_schema(
|
||||
lambda x: x, return_schema=core_schema.any_schema()
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _validate_interceptor(value: Any) -> BaseInterceptor[T, U]:
|
||||
"""Validate that the value is a BaseInterceptor instance.
|
||||
|
||||
Args:
|
||||
value: The value to validate.
|
||||
|
||||
Returns:
|
||||
The validated BaseInterceptor instance.
|
||||
|
||||
Raises:
|
||||
ValueError: If the value is not a BaseInterceptor instance.
|
||||
"""
|
||||
if not isinstance(value, BaseInterceptor):
|
||||
raise ValueError(
|
||||
f"Expected BaseInterceptor instance, got {type(value).__name__}"
|
||||
)
|
||||
return value
|
||||
from crewai.llm.hooks.base import * # noqa: E402, F403
|
||||
|
||||
@@ -1,123 +1,15 @@
|
||||
"""HTTP transport implementations for LLM request/response interception.
|
||||
"""Deprecated: Use crewai.llm.hooks.transport instead.
|
||||
|
||||
This module provides internal transport classes that integrate with BaseInterceptor
|
||||
to enable request/response modification at the transport level.
|
||||
.. deprecated:: 1.4.0
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import warnings
|
||||
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING, TypedDict
|
||||
|
||||
from httpx import (
|
||||
AsyncHTTPTransport as _AsyncHTTPTransport,
|
||||
HTTPTransport as _HTTPTransport,
|
||||
warnings.warn(
|
||||
"crewai.llms.hooks.transport is deprecated. Use crewai.llm.hooks.transport instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
from typing_extensions import NotRequired, Unpack
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ssl import SSLContext
|
||||
|
||||
from httpx import Limits, Request, Response
|
||||
from httpx._types import CertTypes, ProxyTypes
|
||||
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
class HTTPTransportKwargs(TypedDict, total=False):
|
||||
"""Typed dictionary for httpx.HTTPTransport initialization parameters.
|
||||
|
||||
These parameters configure the underlying HTTP transport behavior including
|
||||
SSL verification, proxies, connection limits, and low-level socket options.
|
||||
"""
|
||||
|
||||
verify: bool | str | SSLContext
|
||||
cert: NotRequired[CertTypes]
|
||||
trust_env: bool
|
||||
http1: bool
|
||||
http2: bool
|
||||
limits: Limits
|
||||
proxy: NotRequired[ProxyTypes]
|
||||
uds: NotRequired[str]
|
||||
local_address: NotRequired[str]
|
||||
retries: int
|
||||
socket_options: NotRequired[
|
||||
Iterable[
|
||||
tuple[int, int, int]
|
||||
| tuple[int, int, bytes | bytearray]
|
||||
| tuple[int, int, None, int]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
class HTTPTransport(_HTTPTransport):
|
||||
"""HTTP transport that uses an interceptor for request/response modification.
|
||||
|
||||
This transport is used internally when a user provides a BaseInterceptor.
|
||||
Users should not instantiate this class directly - instead, pass an interceptor
|
||||
to the LLM client and this transport will be created automatically.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
interceptor: BaseInterceptor[Request, Response],
|
||||
**kwargs: Unpack[HTTPTransportKwargs],
|
||||
) -> None:
|
||||
"""Initialize transport with interceptor.
|
||||
|
||||
Args:
|
||||
interceptor: HTTP interceptor for modifying raw request/response objects.
|
||||
**kwargs: HTTPTransport configuration parameters (verify, cert, proxy, etc.).
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.interceptor = interceptor
|
||||
|
||||
def handle_request(self, request: Request) -> Response:
|
||||
"""Handle request with interception.
|
||||
|
||||
Args:
|
||||
request: The HTTP request to handle.
|
||||
|
||||
Returns:
|
||||
The HTTP response.
|
||||
"""
|
||||
request = self.interceptor.on_outbound(request)
|
||||
response = super().handle_request(request)
|
||||
return self.interceptor.on_inbound(response)
|
||||
|
||||
|
||||
class AsyncHTTPTransport(_AsyncHTTPTransport):
|
||||
"""Async HTTP transport that uses an interceptor for request/response modification.
|
||||
|
||||
This transport is used internally when a user provides a BaseInterceptor.
|
||||
Users should not instantiate this class directly - instead, pass an interceptor
|
||||
to the LLM client and this transport will be created automatically.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
interceptor: BaseInterceptor[Request, Response],
|
||||
**kwargs: Unpack[HTTPTransportKwargs],
|
||||
) -> None:
|
||||
"""Initialize async transport with interceptor.
|
||||
|
||||
Args:
|
||||
interceptor: HTTP interceptor for modifying raw request/response objects.
|
||||
**kwargs: HTTPTransport configuration parameters (verify, cert, proxy, etc.).
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.interceptor = interceptor
|
||||
|
||||
async def handle_async_request(self, request: Request) -> Response:
|
||||
"""Handle async request with interception.
|
||||
|
||||
Args:
|
||||
request: The HTTP request to handle.
|
||||
|
||||
Returns:
|
||||
The HTTP response.
|
||||
"""
|
||||
request = await self.interceptor.aon_outbound(request)
|
||||
response = await super().handle_async_request(request)
|
||||
return await self.interceptor.aon_inbound(response)
|
||||
from crewai.llm.hooks.transport import * # noqa: E402, F403
|
||||
|
||||
15
lib/crewai/src/crewai/llms/internal/__init__.py
Normal file
15
lib/crewai/src/crewai/llms/internal/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Deprecated: Use crewai.llm.internal instead.
|
||||
|
||||
.. deprecated:: 1.4.0
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
|
||||
warnings.warn(
|
||||
"crewai.llms.internal is deprecated. Use crewai.llm.internal instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
from crewai.llm.internal import * # noqa: E402, F403
|
||||
15
lib/crewai/src/crewai/llms/internal/constants.py
Normal file
15
lib/crewai/src/crewai/llms/internal/constants.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Deprecated: Use crewai.llm.internal.constants instead.
|
||||
|
||||
.. deprecated:: 1.4.0
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
|
||||
warnings.warn(
|
||||
"crewai.llms.internal.constants is deprecated. Use crewai.llm.internal.constants instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
from crewai.llm.internal.constants import * # noqa: E402, F403
|
||||
@@ -0,0 +1,15 @@
|
||||
"""Deprecated: Use crewai.llm.providers instead.
|
||||
|
||||
.. deprecated:: 1.4.0
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
|
||||
warnings.warn(
|
||||
"crewai.llms.providers is deprecated. Use crewai.llm.providers instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
from crewai.llm.providers import * # noqa: E402, F403
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""Third-party LLM implementations for crewAI."""
|
||||
@@ -8,7 +8,7 @@ Classes:
|
||||
|
||||
from typing import Any
|
||||
|
||||
from crewai.llm import LLM
|
||||
from crewai.llm.core import LLM
|
||||
from crewai.tasks.task_output import TaskOutput
|
||||
from crewai.utilities.logger import Logger
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from crewai.agent import Agent
|
||||
from crewai.lite_agent_output import LiteAgentOutput
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.tasks.task_output import TaskOutput
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ if TYPE_CHECKING:
|
||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||
from crewai.agents.tools_handler import ToolsHandler
|
||||
from crewai.lite_agent import LiteAgent
|
||||
from crewai.llm import LLM
|
||||
from crewai.llm.core import LLM
|
||||
from crewai.task import Task
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from crewai.agents.parser import (
|
||||
parse,
|
||||
)
|
||||
from crewai.cli.config import Settings
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.tools import BaseTool as CrewAITool
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.tools.structured_tool import CrewStructuredTool
|
||||
|
||||
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
|
||||
from crewai.agent import Agent
|
||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
|
||||
_JSON_PATTERN: Final[re.Pattern[str]] = re.compile(r"({.*})", re.DOTALL)
|
||||
_I18N = get_i18n()
|
||||
|
||||
@@ -11,7 +11,7 @@ from rich.table import Table
|
||||
from crewai.agent import Agent
|
||||
from crewai.events.event_bus import crewai_event_bus
|
||||
from crewai.events.types.crew_events import CrewTestResultEvent
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.task import Task
|
||||
from crewai.tasks.task_output import TaskOutput
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from crewai.utilities.logger_utils import suppress_warnings
|
||||
if TYPE_CHECKING:
|
||||
from crewai.agent import Agent
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.utilities.types import LLMMessage
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Any, Final
|
||||
|
||||
from crewai.cli.constants import DEFAULT_LLM_MODEL, ENV_VARS, LITELLM_PARAMS
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -5,7 +5,7 @@ import logging
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from crewai.agent import Agent
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.task import Task
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
|
||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||
from crewai.crew import Crew
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.task import Task
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from crewai.knowledge.knowledge_config import KnowledgeConfig
|
||||
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
|
||||
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.process import Process
|
||||
from crewai.tools.tool_calling import InstructorToolCalling
|
||||
from crewai.tools.tool_usage import ToolUsage
|
||||
|
||||
@@ -9,7 +9,7 @@ from crewai.events.types.agent_events import LiteAgentExecutionStartedEvent
|
||||
from crewai.events.types.tool_usage_events import ToolUsageStartedEvent
|
||||
from crewai.lite_agent import LiteAgent
|
||||
from crewai.lite_agent_output import LiteAgentOutput
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from pydantic import BaseModel, Field
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -590,7 +590,7 @@ interactions:
|
||||
"<function BaseTool.<lambda> at 0x107389260>", "result_as_answer": "False",
|
||||
"max_usage_count": "None", "current_usage_count": "0"}], "max_iter": 2, "agent_executor":
|
||||
"<crewai.agents.crew_agent_executor.CrewAgentExecutor object at 0x130de6540>",
|
||||
"llm": "<crewai.llms.providers.openai.completion.OpenAICompletion object at
|
||||
"llm": "<crewai.llm.providers.openai.completion.OpenAICompletion object at
|
||||
0x130db6de0>", "crew": {"parent_flow": null, "name": "crew", "cache": true,
|
||||
"tasks": ["{''used_tools'': 0, ''tools_errors'': 0, ''delegations'': 0, ''i18n'':
|
||||
{''prompt_file'': None}, ''name'': None, ''prompt_context'': '''', ''description'':
|
||||
@@ -605,7 +605,7 @@ interactions:
|
||||
''description_updated'': False, ''cache_function'': <function BaseTool.<lambda>
|
||||
at 0x107389260>, ''result_as_answer'': False, ''max_usage_count'': None, ''current_usage_count'':
|
||||
0}], ''max_iter'': 2, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x130de6540>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x130de6540>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x130db6de0>, ''crew'': Crew(id=991ac83f-9a29-411f-b0a0-0a335c7a2d0e,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -634,7 +634,7 @@ interactions:
|
||||
''abc.Learn_About_Ai''>, ''description_updated'': False, ''cache_function'':
|
||||
<function BaseTool.<lambda> at 0x107389260>, ''result_as_answer'': False, ''max_usage_count'':
|
||||
None, ''current_usage_count'': 0}], ''max_iter'': 2, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x130de6540>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x130de6540>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x130db6de0>, ''crew'': Crew(id=991ac83f-9a29-411f-b0a0-0a335c7a2d0e,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -657,7 +657,7 @@ interactions:
|
||||
{"fingerprint": {"metadata": "{}"}}, "callbacks": [], "adapted_agent": false,
|
||||
"knowledge_config": null, "max_execution_time": null, "agent_ops_agent_name":
|
||||
"test role", "agent_ops_agent_id": null, "step_callback": null, "use_system_prompt":
|
||||
true, "function_calling_llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
true, "function_calling_llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x130db7020>", "system_template": null, "prompt_template": null, "response_template":
|
||||
null, "allow_code_execution": false, "respect_context_window": true, "max_retry_limit":
|
||||
2, "multimodal": false, "inject_date": false, "date_format": "%Y-%m-%d", "code_execution_mode":
|
||||
@@ -1068,7 +1068,7 @@ interactions:
|
||||
"<function BaseTool.<lambda> at 0x107e394e0>", "result_as_answer": "False",
|
||||
"max_usage_count": "None", "current_usage_count": "0"}], "max_iter": 2, "agent_executor":
|
||||
"<crewai.agents.crew_agent_executor.CrewAgentExecutor object at 0x13b37c980>",
|
||||
"llm": "<crewai.llms.providers.openai.completion.OpenAICompletion object at
|
||||
"llm": "<crewai.llm.providers.openai.completion.OpenAICompletion object at
|
||||
0x13b7563c0>", "crew": {"parent_flow": null, "name": "crew", "cache": true,
|
||||
"tasks": ["{''used_tools'': 0, ''tools_errors'': 0, ''delegations'': 0, ''i18n'':
|
||||
{''prompt_file'': None}, ''name'': None, ''prompt_context'': '''', ''description'':
|
||||
@@ -1083,7 +1083,7 @@ interactions:
|
||||
''description_updated'': False, ''cache_function'': <function BaseTool.<lambda>
|
||||
at 0x107e394e0>, ''result_as_answer'': False, ''max_usage_count'': None, ''current_usage_count'':
|
||||
0}], ''max_iter'': 2, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13b37c980>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13b37c980>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13b7563c0>, ''crew'': Crew(id=f38365e9-3206-45b6-8754-950cb03fe57e,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1112,7 +1112,7 @@ interactions:
|
||||
''abc.Learn_About_Ai''>, ''description_updated'': False, ''cache_function'':
|
||||
<function BaseTool.<lambda> at 0x107e394e0>, ''result_as_answer'': False, ''max_usage_count'':
|
||||
None, ''current_usage_count'': 0}], ''max_iter'': 2, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13b37c980>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13b37c980>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13b7563c0>, ''crew'': Crew(id=f38365e9-3206-45b6-8754-950cb03fe57e,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1135,7 +1135,7 @@ interactions:
|
||||
{"fingerprint": {"metadata": "{}"}}, "callbacks": [], "adapted_agent": false,
|
||||
"knowledge_config": null, "max_execution_time": null, "agent_ops_agent_name":
|
||||
"test role", "agent_ops_agent_id": null, "step_callback": null, "use_system_prompt":
|
||||
true, "function_calling_llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
true, "function_calling_llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13b756690>", "system_template": null, "prompt_template": null, "response_template":
|
||||
null, "allow_code_execution": false, "respect_context_window": true, "max_retry_limit":
|
||||
2, "multimodal": false, "inject_date": false, "date_format": "%Y-%m-%d", "code_execution_mode":
|
||||
|
||||
@@ -1274,7 +1274,7 @@ interactions:
|
||||
"b6cf723e-04c8-40c5-a927-e2078cfbae59", "role": "test role", "goal": "test goal",
|
||||
"backstory": "test backstory", "cache": true, "verbose": true, "max_rpm": null,
|
||||
"allow_delegation": false, "tools": [], "max_iter": 6, "agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab0abd0>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0abd0>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>", "crew": {"parent_flow": null, "name": "crew", "cache":
|
||||
true, "tasks": ["{''used_tools'': 0, ''tools_errors'': 0, ''delegations'': 0,
|
||||
''i18n'': {''prompt_file'': None}, ''name'': None, ''prompt_context'': '''',
|
||||
@@ -1285,7 +1285,7 @@ interactions:
|
||||
''test role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': None, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 6, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>, ''crew'': Crew(id=004dd8a0-dd87-43fa-bdc8-07f449808028,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1309,7 +1309,7 @@ interactions:
|
||||
role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': None, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 6, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>, ''crew'': Crew(id=004dd8a0-dd87-43fa-bdc8-07f449808028,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1491,7 +1491,7 @@ interactions:
|
||||
"goal": "test goal", "backstory": "test backstory", "cache": true, "verbose":
|
||||
true, "max_rpm": null, "allow_delegation": false, "tools": [], "max_iter": 6,
|
||||
"agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor object
|
||||
at 0x13ab0abd0>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
at 0x13ab0abd0>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>", "crew": {"parent_flow": null, "name": "crew", "cache":
|
||||
true, "tasks": ["{''used_tools'': 2, ''tools_errors'': 0, ''delegations'': 0,
|
||||
''i18n'': {''prompt_file'': None}, ''name'': None, ''prompt_context'': '''',
|
||||
@@ -1502,7 +1502,7 @@ interactions:
|
||||
''test role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': None, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 6, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>, ''crew'': Crew(id=004dd8a0-dd87-43fa-bdc8-07f449808028,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1528,7 +1528,7 @@ interactions:
|
||||
role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': None, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 6, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>, ''crew'': Crew(id=004dd8a0-dd87-43fa-bdc8-07f449808028,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1660,7 +1660,7 @@ interactions:
|
||||
role", "goal": "test goal", "backstory": "test backstory", "cache": true, "verbose":
|
||||
true, "max_rpm": null, "allow_delegation": false, "tools": [], "max_iter": 6,
|
||||
"agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor object
|
||||
at 0x13ab0abd0>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
at 0x13ab0abd0>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>", "crew": {"parent_flow": null, "name": "crew", "cache":
|
||||
true, "tasks": ["{''used_tools'': 3, ''tools_errors'': 0, ''delegations'': 0,
|
||||
''i18n'': {''prompt_file'': None}, ''name'': None, ''prompt_context'': '''',
|
||||
@@ -1671,7 +1671,7 @@ interactions:
|
||||
''test role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': None, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 6, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>, ''crew'': Crew(id=004dd8a0-dd87-43fa-bdc8-07f449808028,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1698,7 +1698,7 @@ interactions:
|
||||
role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': None, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 6, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>, ''crew'': Crew(id=004dd8a0-dd87-43fa-bdc8-07f449808028,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1839,7 +1839,7 @@ interactions:
|
||||
"goal": "test goal", "backstory": "test backstory", "cache": true, "verbose":
|
||||
true, "max_rpm": null, "allow_delegation": false, "tools": [], "max_iter": 6,
|
||||
"agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor object
|
||||
at 0x13ab0abd0>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
at 0x13ab0abd0>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>", "crew": {"parent_flow": null, "name": "crew", "cache":
|
||||
true, "tasks": ["{''used_tools'': 4, ''tools_errors'': 0, ''delegations'': 0,
|
||||
''i18n'': {''prompt_file'': None}, ''name'': None, ''prompt_context'': '''',
|
||||
@@ -1850,7 +1850,7 @@ interactions:
|
||||
''test role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': None, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 6, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>, ''crew'': Crew(id=004dd8a0-dd87-43fa-bdc8-07f449808028,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1879,7 +1879,7 @@ interactions:
|
||||
role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': None, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 6, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>, ''crew'': Crew(id=004dd8a0-dd87-43fa-bdc8-07f449808028,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -2029,7 +2029,7 @@ interactions:
|
||||
"goal": "test goal", "backstory": "test backstory", "cache": true, "verbose":
|
||||
true, "max_rpm": null, "allow_delegation": false, "tools": [], "max_iter": 6,
|
||||
"agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor object
|
||||
at 0x13ab0abd0>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
at 0x13ab0abd0>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>", "crew": {"parent_flow": null, "name": "crew", "cache":
|
||||
true, "tasks": ["{''used_tools'': 5, ''tools_errors'': 0, ''delegations'': 0,
|
||||
''i18n'': {''prompt_file'': None}, ''name'': None, ''prompt_context'': '''',
|
||||
@@ -2040,7 +2040,7 @@ interactions:
|
||||
''test role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': None, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 6, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>, ''crew'': Crew(id=004dd8a0-dd87-43fa-bdc8-07f449808028,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -2070,7 +2070,7 @@ interactions:
|
||||
role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': None, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 6, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0abd0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab0b050>, ''crew'': Crew(id=004dd8a0-dd87-43fa-bdc8-07f449808028,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
|
||||
@@ -1082,7 +1082,7 @@ interactions:
|
||||
"role": "test role", "goal": "test goal", "backstory": "test backstory", "cache":
|
||||
true, "verbose": true, "max_rpm": 10, "allow_delegation": false, "tools": [],
|
||||
"max_iter": 4, "agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x133d41100>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x133d41100>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x133d40500>", "crew": {"parent_flow": null, "name": "crew", "cache":
|
||||
true, "tasks": ["{''used_tools'': 0, ''tools_errors'': 0, ''delegations'': 0,
|
||||
''i18n'': {''prompt_file'': None}, ''name'': None, ''prompt_context'': '''',
|
||||
@@ -1093,7 +1093,7 @@ interactions:
|
||||
''test role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': 10, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 4, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x133d41100>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x133d41100>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x133d40500>, ''crew'': Crew(id=4c6d502e-f6ec-446a-8f76-644563c4aa94,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1117,7 +1117,7 @@ interactions:
|
||||
''test role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': 10, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 4, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x133d41100>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x133d41100>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x133d40500>, ''crew'': Crew(id=4c6d502e-f6ec-446a-8f76-644563c4aa94,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1910,7 +1910,7 @@ interactions:
|
||||
"role": "test role", "goal": "test goal", "backstory": "test backstory", "cache":
|
||||
true, "verbose": true, "max_rpm": 10, "allow_delegation": false, "tools": [],
|
||||
"max_iter": 4, "agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x10308d610>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x10308d610>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x129201640>", "crew": {"parent_flow": null, "name": "crew", "cache":
|
||||
true, "tasks": ["{''used_tools'': 0, ''tools_errors'': 0, ''delegations'': 0,
|
||||
''i18n'': {''prompt_file'': None}, ''name'': None, ''prompt_context'': '''',
|
||||
@@ -1921,7 +1921,7 @@ interactions:
|
||||
''test role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': 10, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 4, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x10308d610>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x10308d610>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x129201640>, ''crew'': Crew(id=1a07d718-fed5-49fa-bee2-de2db91c9f33,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1945,7 +1945,7 @@ interactions:
|
||||
''test role'', ''goal'': ''test goal'', ''backstory'': ''test backstory'', ''cache'':
|
||||
True, ''verbose'': True, ''max_rpm'': 10, ''allow_delegation'': False, ''tools'':
|
||||
[], ''max_iter'': 4, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x10308d610>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x10308d610>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x129201640>, ''crew'': Crew(id=1a07d718-fed5-49fa-bee2-de2db91c9f33,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
|
||||
@@ -937,7 +937,7 @@ interactions:
|
||||
"description_updated": "False", "cache_function": "<function BaseTool.<lambda>
|
||||
at 0x10614d3a0>", "result_as_answer": "False", "max_usage_count": "None", "current_usage_count":
|
||||
"0"}], "max_iter": 25, "agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x10f6c3bc0>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x10f6c3bc0>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x10f6c27e0>", "crew": {"parent_flow": null, "name": "crew", "cache":
|
||||
true, "tasks": ["{''used_tools'': 0, ''tools_errors'': 0, ''delegations'': 0,
|
||||
''i18n'': {''prompt_file'': None}, ''name'': None, ''prompt_context'': '''',
|
||||
@@ -977,7 +977,7 @@ interactions:
|
||||
''description_updated'': False, ''cache_function'': <function BaseTool.<lambda>
|
||||
at 0x10614d3a0>, ''result_as_answer'': False, ''max_usage_count'': None, ''current_usage_count'':
|
||||
0}], ''max_iter'': 25, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x10f6c3bc0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x10f6c3bc0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x10f6c27e0>, ''crew'': Crew(id=49cbb747-f055-4636-bbca-9e8a450c05f6,
|
||||
process=Process.hierarchical, number_of_agents=2, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -996,7 +996,7 @@ interactions:
|
||||
''First Agent'', ''goal'': ''First goal'', ''backstory'': ''First backstory'',
|
||||
''cache'': True, ''verbose'': False, ''max_rpm'': None, ''allow_delegation'':
|
||||
False, ''tools'': [], ''max_iter'': 25, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x11059ca10>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x11059ca10>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x10f6e6ae0>, ''crew'': Crew(id=49cbb747-f055-4636-bbca-9e8a450c05f6,
|
||||
process=Process.hierarchical, number_of_agents=2, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1007,7 +1007,7 @@ interactions:
|
||||
''role'': ''Second Agent'', ''goal'': ''Second goal'', ''backstory'': ''Second
|
||||
backstory'', ''cache'': True, ''verbose'': False, ''max_rpm'': None, ''allow_delegation'':
|
||||
False, ''tools'': [], ''max_iter'': 25, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x10f6c3500>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x10f6c3500>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x10f6d2000>, ''crew'': Crew(id=49cbb747-f055-4636-bbca-9e8a450c05f6,
|
||||
process=Process.hierarchical, number_of_agents=2, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1017,7 +1017,7 @@ interactions:
|
||||
False, ''knowledge_config'': None}"], "process": "hierarchical", "verbose":
|
||||
false, "memory": false, "short_term_memory": null, "long_term_memory": null,
|
||||
"entity_memory": null, "external_memory": null, "embedder": null, "usage_metrics":
|
||||
null, "manager_llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
null, "manager_llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x10f6c27e0>", "manager_agent": {"id": "UUID(''b0898472-5e3b-45bb-bd90-05bad0b5a8ce'')",
|
||||
"role": "''Crew Manager''", "goal": "''Manage the team to complete the task
|
||||
in the best way possible.''", "backstory": "\"You are a seasoned manager with
|
||||
@@ -1053,7 +1053,7 @@ interactions:
|
||||
''description_updated'': False, ''cache_function'': <function BaseTool.<lambda>
|
||||
at 0x10614d3a0>, ''result_as_answer'': False, ''max_usage_count'': None, ''current_usage_count'':
|
||||
0}]", "max_iter": "25", "agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x10f6c3bc0>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x10f6c3bc0>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x10f6c27e0>", "crew": "Crew(id=49cbb747-f055-4636-bbca-9e8a450c05f6,
|
||||
process=Process.hierarchical, number_of_agents=2, number_of_tasks=1)", "i18n":
|
||||
"{''prompt_file'': None}", "cache_handler": "{}", "tools_handler": "<crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1805,7 +1805,7 @@ interactions:
|
||||
"description_updated": "False", "cache_function": "<function BaseTool.<lambda>
|
||||
at 0x107e394e0>", "result_as_answer": "False", "max_usage_count": "None", "current_usage_count":
|
||||
"0"}], "max_iter": 25, "agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x1388bedb0>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x1388bedb0>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x1388bf710>", "crew": {"parent_flow": null, "name": "crew", "cache":
|
||||
true, "tasks": ["{''used_tools'': 0, ''tools_errors'': 0, ''delegations'': 0,
|
||||
''i18n'': {''prompt_file'': None}, ''name'': None, ''prompt_context'': '''',
|
||||
@@ -1845,7 +1845,7 @@ interactions:
|
||||
''description_updated'': False, ''cache_function'': <function BaseTool.<lambda>
|
||||
at 0x107e394e0>, ''result_as_answer'': False, ''max_usage_count'': None, ''current_usage_count'':
|
||||
0}], ''max_iter'': 25, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x1388bedb0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x1388bedb0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x1388bf710>, ''crew'': Crew(id=4d744f3e-0589-4d1d-b1c1-6aa8b52478ac,
|
||||
process=Process.hierarchical, number_of_agents=2, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1864,7 +1864,7 @@ interactions:
|
||||
''First Agent'', ''goal'': ''First goal'', ''backstory'': ''First backstory'',
|
||||
''cache'': True, ''verbose'': False, ''max_rpm'': None, ''allow_delegation'':
|
||||
False, ''tools'': [], ''max_iter'': 25, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x1388d5c70>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x1388d5c70>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x1388bde80>, ''crew'': Crew(id=4d744f3e-0589-4d1d-b1c1-6aa8b52478ac,
|
||||
process=Process.hierarchical, number_of_agents=2, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1875,7 +1875,7 @@ interactions:
|
||||
''role'': ''Second Agent'', ''goal'': ''Second goal'', ''backstory'': ''Second
|
||||
backstory'', ''cache'': True, ''verbose'': False, ''max_rpm'': None, ''allow_delegation'':
|
||||
False, ''tools'': [], ''max_iter'': 25, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x1388bf7d0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x1388bf7d0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x1388bfb90>, ''crew'': Crew(id=4d744f3e-0589-4d1d-b1c1-6aa8b52478ac,
|
||||
process=Process.hierarchical, number_of_agents=2, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -1885,7 +1885,7 @@ interactions:
|
||||
False, ''knowledge_config'': None}"], "process": "hierarchical", "verbose":
|
||||
false, "memory": false, "short_term_memory": null, "long_term_memory": null,
|
||||
"entity_memory": null, "external_memory": null, "embedder": null, "usage_metrics":
|
||||
null, "manager_llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
null, "manager_llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x1388bf710>", "manager_agent": {"id": "UUID(''09794b42-447f-4b7a-b634-3a861f457357'')",
|
||||
"role": "''Crew Manager''", "goal": "''Manage the team to complete the task
|
||||
in the best way possible.''", "backstory": "\"You are a seasoned manager with
|
||||
@@ -1921,7 +1921,7 @@ interactions:
|
||||
''description_updated'': False, ''cache_function'': <function BaseTool.<lambda>
|
||||
at 0x107e394e0>, ''result_as_answer'': False, ''max_usage_count'': None, ''current_usage_count'':
|
||||
0}]", "max_iter": "25", "agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x1388bedb0>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x1388bedb0>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x1388bf710>", "crew": "Crew(id=4d744f3e-0589-4d1d-b1c1-6aa8b52478ac,
|
||||
process=Process.hierarchical, number_of_agents=2, number_of_tasks=1)", "i18n":
|
||||
"{''prompt_file'': None}", "cache_handler": "{}", "tools_handler": "<crewai.agents.tools_handler.ToolsHandler
|
||||
|
||||
@@ -126,7 +126,7 @@ interactions:
|
||||
a freelancer and is now working on doing research and analysis for a new customer.\",
|
||||
''cache'': True, ''verbose'': False, ''max_rpm'': None, ''allow_delegation'':
|
||||
False, ''tools'': [], ''max_iter'': 25, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x12b973fe0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b973fe0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b910290>, ''crew'': None, ''i18n'': {''prompt_file'': None}, ''cache_handler'':
|
||||
{}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler object at 0x12b9934d0>,
|
||||
''tools_results'': [], ''max_tokens'': None, ''knowledge'': None, ''knowledge_sources'':
|
||||
@@ -149,7 +149,7 @@ interactions:
|
||||
writing content for a new customer.\", ''cache'': True, ''verbose'': False,
|
||||
''max_rpm'': None, ''allow_delegation'': False, ''tools'': [], ''max_iter'':
|
||||
25, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x12b7bbbf0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b7bbbf0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b9903b0>, ''crew'': None, ''i18n'': {''prompt_file'': None}, ''cache_handler'':
|
||||
{}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler object at 0x12b631bb0>,
|
||||
''tools_results'': [], ''max_tokens'': None, ''knowledge'': None, ''knowledge_sources'':
|
||||
@@ -169,7 +169,7 @@ interactions:
|
||||
a freelancer and is now working on doing research and analysis for a new customer.\"",
|
||||
"cache": "True", "verbose": "False", "max_rpm": "None", "allow_delegation":
|
||||
"False", "tools": "[]", "max_iter": "25", "agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x12b973fe0>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b973fe0>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b910290>", "crew": "None", "i18n": "{''prompt_file'': None}",
|
||||
"cache_handler": "{}", "tools_handler": "<crewai.agents.tools_handler.ToolsHandler
|
||||
object at 0x12b9934d0>", "tools_results": "[]", "max_tokens": "None", "knowledge":
|
||||
@@ -182,7 +182,7 @@ interactions:
|
||||
You work as a freelancer and are now working on writing content for a new customer.\"",
|
||||
"cache": "True", "verbose": "False", "max_rpm": "None", "allow_delegation":
|
||||
"False", "tools": "[]", "max_iter": "25", "agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x12b7bbbf0>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b7bbbf0>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b9903b0>", "crew": "None", "i18n": "{''prompt_file'': None}",
|
||||
"cache_handler": "{}", "tools_handler": "<crewai.agents.tools_handler.ToolsHandler
|
||||
object at 0x12b631bb0>", "tools_results": "[]", "max_tokens": "None", "knowledge":
|
||||
@@ -214,7 +214,7 @@ interactions:
|
||||
a freelancer and is now working on doing research and analysis for a new customer.\",
|
||||
''cache'': True, ''verbose'': False, ''max_rpm'': None, ''allow_delegation'':
|
||||
False, ''tools'': [], ''max_iter'': 25, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x12b973fe0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b973fe0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b910290>, ''crew'': None, ''i18n'': {''prompt_file'': None}, ''cache_handler'':
|
||||
{}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler object at 0x12b9934d0>,
|
||||
''tools_results'': [], ''max_tokens'': None, ''knowledge'': None, ''knowledge_sources'':
|
||||
@@ -237,7 +237,7 @@ interactions:
|
||||
writing content for a new customer.\", ''cache'': True, ''verbose'': False,
|
||||
''max_rpm'': None, ''allow_delegation'': False, ''tools'': [], ''max_iter'':
|
||||
25, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x12b7bbbf0>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b7bbbf0>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b9903b0>, ''crew'': None, ''i18n'': {''prompt_file'': None}, ''cache_handler'':
|
||||
{}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler object at 0x12b631bb0>,
|
||||
''tools_results'': [], ''max_tokens'': None, ''knowledge'': None, ''knowledge_sources'':
|
||||
@@ -257,7 +257,7 @@ interactions:
|
||||
a freelancer and is now working on doing research and analysis for a new customer.\"",
|
||||
"cache": "True", "verbose": "False", "max_rpm": "None", "allow_delegation":
|
||||
"False", "tools": "[]", "max_iter": "25", "agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x12b973fe0>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b973fe0>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b910290>", "crew": "None", "i18n": "{''prompt_file'': None}",
|
||||
"cache_handler": "{}", "tools_handler": "<crewai.agents.tools_handler.ToolsHandler
|
||||
object at 0x12b9934d0>", "tools_results": "[]", "max_tokens": "None", "knowledge":
|
||||
@@ -270,7 +270,7 @@ interactions:
|
||||
You work as a freelancer and are now working on writing content for a new customer.\"",
|
||||
"cache": "True", "verbose": "False", "max_rpm": "None", "allow_delegation":
|
||||
"False", "tools": "[]", "max_iter": "25", "agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x12b7bbbf0>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b7bbbf0>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x12b9903b0>", "crew": "None", "i18n": "{''prompt_file'': None}",
|
||||
"cache_handler": "{}", "tools_handler": "<crewai.agents.tools_handler.ToolsHandler
|
||||
object at 0x12b631bb0>", "tools_results": "[]", "max_tokens": "None", "knowledge":
|
||||
|
||||
@@ -468,7 +468,7 @@ interactions:
|
||||
"description_updated": "False", "cache_function": "<function BaseTool.<lambda>
|
||||
at 0x107ff9440>", "result_as_answer": "True", "max_usage_count": "None", "current_usage_count":
|
||||
"0"}], "max_iter": 25, "agent_executor": "<crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab2e030>", "llm": "<crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab2e030>", "llm": "<crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab2e5d0>", "crew": {"parent_flow": null, "name": "crew", "cache":
|
||||
true, "tasks": ["{''used_tools'': 0, ''tools_errors'': 0, ''delegations'': 0,
|
||||
''i18n'': {''prompt_file'': None}, ''name'': None, ''prompt_context'': '''',
|
||||
@@ -484,7 +484,7 @@ interactions:
|
||||
<class ''abc.MyCustomToolSchema''>, ''description_updated'': False, ''cache_function'':
|
||||
<function BaseTool.<lambda> at 0x107ff9440>, ''result_as_answer'': True, ''max_usage_count'':
|
||||
None, ''current_usage_count'': 0}], ''max_iter'': 25, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab2e030>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab2e030>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab2e5d0>, ''crew'': Crew(id=f74956dd-60d0-402a-a703-2cc3d767397f,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
@@ -512,7 +512,7 @@ interactions:
|
||||
''description_updated'': False, ''cache_function'': <function BaseTool.<lambda>
|
||||
at 0x107ff9440>, ''result_as_answer'': True, ''max_usage_count'': None, ''current_usage_count'':
|
||||
0}], ''max_iter'': 25, ''agent_executor'': <crewai.agents.crew_agent_executor.CrewAgentExecutor
|
||||
object at 0x13ab2e030>, ''llm'': <crewai.llms.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab2e030>, ''llm'': <crewai.llm.providers.openai.completion.OpenAICompletion
|
||||
object at 0x13ab2e5d0>, ''crew'': Crew(id=f74956dd-60d0-402a-a703-2cc3d767397f,
|
||||
process=Process.sequential, number_of_agents=1, number_of_tasks=1), ''i18n'':
|
||||
{''prompt_file'': None}, ''cache_handler'': {}, ''tools_handler'': <crewai.agents.tools_handler.ToolsHandler
|
||||
|
||||
@@ -34,7 +34,7 @@ def test_anthropic_completion_is_used_when_claude_provider():
|
||||
"""
|
||||
llm = LLM(model="claude/claude-3-5-sonnet-20241022")
|
||||
|
||||
from crewai.llms.providers.anthropic.completion import AnthropicCompletion
|
||||
from crewai.llm.providers.anthropic.completion import AnthropicCompletion
|
||||
assert isinstance(llm, AnthropicCompletion)
|
||||
assert llm.provider == "anthropic"
|
||||
assert llm.model == "claude-3-5-sonnet-20241022"
|
||||
@@ -47,7 +47,7 @@ def test_anthropic_tool_use_conversation_flow():
|
||||
Test that the Anthropic completion properly handles tool use conversation flow
|
||||
"""
|
||||
from unittest.mock import Mock, patch
|
||||
from crewai.llms.providers.anthropic.completion import AnthropicCompletion
|
||||
from crewai.llm.providers.anthropic.completion import AnthropicCompletion
|
||||
from anthropic.types.tool_use_block import ToolUseBlock
|
||||
|
||||
# Create AnthropicCompletion instance
|
||||
@@ -60,7 +60,7 @@ def test_anthropic_tool_use_conversation_flow():
|
||||
available_functions = {"get_weather": mock_weather_tool}
|
||||
|
||||
# Mock the Anthropic client responses
|
||||
with patch.object(completion.client.messages, 'create') as mock_create:
|
||||
with patch.object(completion._client.messages, 'create') as mock_create:
|
||||
# Mock initial response with tool use - need to properly mock ToolUseBlock
|
||||
mock_tool_use = Mock(spec=ToolUseBlock)
|
||||
mock_tool_use.id = "tool_123"
|
||||
@@ -123,7 +123,7 @@ def test_anthropic_completion_module_is_imported():
|
||||
"""
|
||||
Test that the completion module is properly imported when using Anthropic provider
|
||||
"""
|
||||
module_name = "crewai.llms.providers.anthropic.completion"
|
||||
module_name = "crewai.llm.providers.anthropic.completion"
|
||||
|
||||
# Remove module from cache if it exists
|
||||
if module_name in sys.modules:
|
||||
@@ -175,7 +175,7 @@ def test_anthropic_completion_initialization_parameters():
|
||||
api_key="test-key"
|
||||
)
|
||||
|
||||
from crewai.llms.providers.anthropic.completion import AnthropicCompletion
|
||||
from crewai.llm.providers.anthropic.completion import AnthropicCompletion
|
||||
assert isinstance(llm, AnthropicCompletion)
|
||||
assert llm.model == "claude-3-5-sonnet-20241022"
|
||||
assert llm.temperature == 0.7
|
||||
@@ -195,12 +195,12 @@ def test_anthropic_specific_parameters():
|
||||
timeout=60
|
||||
)
|
||||
|
||||
from crewai.llms.providers.anthropic.completion import AnthropicCompletion
|
||||
from crewai.llm.providers.anthropic.completion import AnthropicCompletion
|
||||
assert isinstance(llm, AnthropicCompletion)
|
||||
assert llm.stop_sequences == ["Human:", "Assistant:"]
|
||||
assert llm.stop == ["Human:", "Assistant:"]
|
||||
assert llm.stream == True
|
||||
assert llm.client.max_retries == 5
|
||||
assert llm.client.timeout == 60
|
||||
assert llm._client.max_retries == 5
|
||||
assert llm._client.timeout == 60
|
||||
|
||||
|
||||
def test_anthropic_completion_call():
|
||||
@@ -390,7 +390,7 @@ def test_anthropic_raises_error_when_model_not_supported():
|
||||
"""Test that AnthropicCompletion raises ValueError when model not supported"""
|
||||
|
||||
# Mock the Anthropic client to raise an error
|
||||
with patch('crewai.llms.providers.anthropic.completion.Anthropic') as mock_anthropic_class:
|
||||
with patch('crewai.llm.providers.anthropic.completion.Anthropic') as mock_anthropic_class:
|
||||
mock_client = MagicMock()
|
||||
mock_anthropic_class.return_value = mock_client
|
||||
|
||||
@@ -427,7 +427,7 @@ def test_anthropic_client_params_setup():
|
||||
client_params=custom_client_params
|
||||
)
|
||||
|
||||
from crewai.llms.providers.anthropic.completion import AnthropicCompletion
|
||||
from crewai.llm.providers.anthropic.completion import AnthropicCompletion
|
||||
assert isinstance(llm, AnthropicCompletion)
|
||||
|
||||
assert llm.client_params == custom_client_params
|
||||
@@ -462,7 +462,7 @@ def test_anthropic_client_params_override_defaults():
|
||||
)
|
||||
|
||||
# Verify this is actually AnthropicCompletion, not LiteLLM fallback
|
||||
from crewai.llms.providers.anthropic.completion import AnthropicCompletion
|
||||
from crewai.llm.providers.anthropic.completion import AnthropicCompletion
|
||||
assert isinstance(llm, AnthropicCompletion)
|
||||
|
||||
merged_params = llm._get_client_params()
|
||||
@@ -487,7 +487,7 @@ def test_anthropic_client_params_none():
|
||||
client_params=None
|
||||
)
|
||||
|
||||
from crewai.llms.providers.anthropic.completion import AnthropicCompletion
|
||||
from crewai.llm.providers.anthropic.completion import AnthropicCompletion
|
||||
assert isinstance(llm, AnthropicCompletion)
|
||||
|
||||
assert llm.client_params is None
|
||||
@@ -515,7 +515,7 @@ def test_anthropic_client_params_empty_dict():
|
||||
client_params={}
|
||||
)
|
||||
|
||||
from crewai.llms.providers.anthropic.completion import AnthropicCompletion
|
||||
from crewai.llm.providers.anthropic.completion import AnthropicCompletion
|
||||
assert isinstance(llm, AnthropicCompletion)
|
||||
|
||||
assert llm.client_params == {}
|
||||
@@ -538,7 +538,7 @@ def test_anthropic_model_detection():
|
||||
|
||||
for model_name in anthropic_test_cases:
|
||||
llm = LLM(model=model_name)
|
||||
from crewai.llms.providers.anthropic.completion import AnthropicCompletion
|
||||
from crewai.llm.providers.anthropic.completion import AnthropicCompletion
|
||||
assert isinstance(llm, AnthropicCompletion), f"Failed for model: {model_name}"
|
||||
|
||||
|
||||
@@ -637,8 +637,8 @@ def test_anthropic_environment_variable_api_key():
|
||||
with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "test-anthropic-key"}):
|
||||
llm = LLM(model="anthropic/claude-3-5-sonnet-20241022")
|
||||
|
||||
assert llm.client is not None
|
||||
assert hasattr(llm.client, 'messages')
|
||||
assert llm._client is not None
|
||||
assert hasattr(llm._client, 'messages')
|
||||
|
||||
|
||||
def test_anthropic_token_usage_tracking():
|
||||
@@ -648,7 +648,7 @@ def test_anthropic_token_usage_tracking():
|
||||
llm = LLM(model="anthropic/claude-3-5-sonnet-20241022")
|
||||
|
||||
# Mock the Anthropic response with usage information
|
||||
with patch.object(llm.client.messages, 'create') as mock_create:
|
||||
with patch.object(llm._client.messages, 'create') as mock_create:
|
||||
mock_response = MagicMock()
|
||||
mock_response.content = [MagicMock(text="test response")]
|
||||
mock_response.usage = MagicMock(input_tokens=50, output_tokens=25)
|
||||
@@ -667,23 +667,21 @@ def test_anthropic_token_usage_tracking():
|
||||
|
||||
|
||||
def test_anthropic_stop_sequences_sync():
|
||||
"""Test that stop and stop_sequences attributes stay synchronized."""
|
||||
"""Test that stop sequences can be set and retrieved correctly."""
|
||||
llm = LLM(model="anthropic/claude-3-5-sonnet-20241022")
|
||||
|
||||
# Test setting stop as a list
|
||||
llm.stop = ["\nObservation:", "\nThought:"]
|
||||
assert llm.stop_sequences == ["\nObservation:", "\nThought:"]
|
||||
assert llm.stop == ["\nObservation:", "\nThought:"]
|
||||
|
||||
# Test setting stop as a string
|
||||
# Test setting stop as a string - note: setting via attribute doesn't go through validator
|
||||
# so it stays as a string
|
||||
llm.stop = "\nFinal Answer:"
|
||||
assert llm.stop_sequences == ["\nFinal Answer:"]
|
||||
assert llm.stop == ["\nFinal Answer:"]
|
||||
assert llm.stop == "\nFinal Answer:"
|
||||
|
||||
# Test setting stop as None
|
||||
llm.stop = None
|
||||
assert llm.stop_sequences == []
|
||||
assert llm.stop == []
|
||||
assert llm.stop is None
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization", "x-api-key"])
|
||||
|
||||
@@ -37,7 +37,7 @@ def test_azure_completion_is_used_when_azure_openai_provider():
|
||||
"""
|
||||
llm = LLM(model="azure_openai/gpt-4")
|
||||
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
assert isinstance(llm, AzureCompletion)
|
||||
assert llm.provider == "azure"
|
||||
assert llm.model == "gpt-4"
|
||||
@@ -47,7 +47,7 @@ def test_azure_tool_use_conversation_flow():
|
||||
"""
|
||||
Test that the Azure completion properly handles tool use conversation flow
|
||||
"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
from azure.ai.inference.models import ChatCompletionsToolCall
|
||||
|
||||
# Create AzureCompletion instance
|
||||
@@ -64,7 +64,7 @@ def test_azure_tool_use_conversation_flow():
|
||||
available_functions = {"get_weather": mock_weather_tool}
|
||||
|
||||
# Mock the Azure client responses
|
||||
with patch.object(completion.client, 'complete') as mock_complete:
|
||||
with patch.object(completion._client, 'complete') as mock_complete:
|
||||
# Mock tool call in response with proper type
|
||||
mock_tool_call = MagicMock(spec=ChatCompletionsToolCall)
|
||||
mock_tool_call.function.name = "get_weather"
|
||||
@@ -105,7 +105,7 @@ def test_azure_completion_module_is_imported():
|
||||
"""
|
||||
Test that the completion module is properly imported when using Azure provider
|
||||
"""
|
||||
module_name = "crewai.llms.providers.azure.completion"
|
||||
module_name = "crewai.llm.providers.azure.completion"
|
||||
|
||||
# Remove module from cache if it exists
|
||||
if module_name in sys.modules:
|
||||
@@ -160,7 +160,7 @@ def test_azure_completion_initialization_parameters():
|
||||
endpoint="https://test.openai.azure.com"
|
||||
)
|
||||
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
assert isinstance(llm, AzureCompletion)
|
||||
assert llm.model == "gpt-4"
|
||||
assert llm.temperature == 0.7
|
||||
@@ -182,7 +182,7 @@ def test_azure_specific_parameters():
|
||||
endpoint="https://test.openai.azure.com"
|
||||
)
|
||||
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
assert isinstance(llm, AzureCompletion)
|
||||
assert llm.stop == ["Human:", "Assistant:"]
|
||||
assert llm.stream == True
|
||||
@@ -374,7 +374,7 @@ def test_azure_completion_with_tools():
|
||||
|
||||
def test_azure_raises_error_when_endpoint_missing():
|
||||
"""Test that AzureCompletion raises ValueError when endpoint is missing"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
|
||||
# Clear environment variables
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
@@ -383,7 +383,7 @@ def test_azure_raises_error_when_endpoint_missing():
|
||||
|
||||
def test_azure_raises_error_when_api_key_missing():
|
||||
"""Test that AzureCompletion raises ValueError when API key is missing"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
|
||||
# Clear environment variables
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
@@ -400,7 +400,7 @@ def test_azure_endpoint_configuration():
|
||||
}):
|
||||
llm = LLM(model="azure/gpt-4")
|
||||
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
assert isinstance(llm, AzureCompletion)
|
||||
assert llm.endpoint == "https://test1.openai.azure.com/openai/deployments/gpt-4"
|
||||
|
||||
@@ -426,7 +426,7 @@ def test_azure_api_key_configuration():
|
||||
}):
|
||||
llm = LLM(model="azure/gpt-4")
|
||||
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
assert isinstance(llm, AzureCompletion)
|
||||
assert llm.api_key == "test-azure-key"
|
||||
|
||||
@@ -437,7 +437,7 @@ def test_azure_model_capabilities():
|
||||
"""
|
||||
# Test GPT-4 model (supports function calling)
|
||||
llm_gpt4 = LLM(model="azure/gpt-4")
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
assert isinstance(llm_gpt4, AzureCompletion)
|
||||
assert llm_gpt4.is_openai_model == True
|
||||
assert llm_gpt4.supports_function_calling() == True
|
||||
@@ -466,7 +466,7 @@ def test_azure_completion_params_preparation():
|
||||
max_tokens=1000
|
||||
)
|
||||
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
assert isinstance(llm, AzureCompletion)
|
||||
|
||||
messages = [{"role": "user", "content": "Hello"}]
|
||||
@@ -494,7 +494,7 @@ def test_azure_model_detection():
|
||||
|
||||
for model_name in azure_test_cases:
|
||||
llm = LLM(model=model_name)
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
assert isinstance(llm, AzureCompletion), f"Failed for model: {model_name}"
|
||||
|
||||
|
||||
@@ -602,7 +602,7 @@ def test_azure_environment_variable_endpoint():
|
||||
}):
|
||||
llm = LLM(model="azure/gpt-4")
|
||||
|
||||
assert llm.client is not None
|
||||
assert llm._client is not None
|
||||
assert llm.endpoint == "https://test.openai.azure.com/openai/deployments/gpt-4"
|
||||
|
||||
|
||||
@@ -613,7 +613,7 @@ def test_azure_token_usage_tracking():
|
||||
llm = LLM(model="azure/gpt-4")
|
||||
|
||||
# Mock the Azure response with usage information
|
||||
with patch.object(llm.client, 'complete') as mock_complete:
|
||||
with patch.object(llm._client, 'complete') as mock_complete:
|
||||
mock_message = MagicMock()
|
||||
mock_message.content = "test response"
|
||||
mock_message.tool_calls = None
|
||||
@@ -651,7 +651,7 @@ def test_azure_http_error_handling():
|
||||
llm = LLM(model="azure/gpt-4")
|
||||
|
||||
# Mock an HTTP error
|
||||
with patch.object(llm.client, 'complete') as mock_complete:
|
||||
with patch.object(llm._client, 'complete') as mock_complete:
|
||||
mock_complete.side_effect = HttpResponseError(message="Rate limit exceeded", response=MagicMock(status_code=429))
|
||||
|
||||
with pytest.raises(HttpResponseError):
|
||||
@@ -662,13 +662,13 @@ def test_azure_streaming_completion():
|
||||
"""
|
||||
Test that streaming completions work properly
|
||||
"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
from azure.ai.inference.models import StreamingChatCompletionsUpdate
|
||||
|
||||
llm = LLM(model="azure/gpt-4", stream=True)
|
||||
|
||||
# Mock streaming response
|
||||
with patch.object(llm.client, 'complete') as mock_complete:
|
||||
with patch.object(llm._client, 'complete') as mock_complete:
|
||||
# Create mock streaming updates with proper type
|
||||
mock_updates = []
|
||||
for chunk in ["Hello", " ", "world", "!"]:
|
||||
@@ -698,7 +698,7 @@ def test_azure_api_version_default():
|
||||
"""
|
||||
llm = LLM(model="azure/gpt-4")
|
||||
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
assert isinstance(llm, AzureCompletion)
|
||||
# Should use default or environment variable
|
||||
assert llm.api_version is not None
|
||||
@@ -721,7 +721,7 @@ def test_azure_openai_endpoint_url_construction():
|
||||
"""
|
||||
Test that Azure OpenAI endpoint URLs are automatically constructed correctly
|
||||
"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
|
||||
with patch.dict(os.environ, {
|
||||
"AZURE_API_KEY": "test-key",
|
||||
@@ -738,7 +738,7 @@ def test_azure_openai_endpoint_url_with_trailing_slash():
|
||||
"""
|
||||
Test that trailing slashes are handled correctly in endpoint URLs
|
||||
"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
|
||||
with patch.dict(os.environ, {
|
||||
"AZURE_API_KEY": "test-key",
|
||||
@@ -804,7 +804,7 @@ def test_non_azure_openai_model_parameter_included():
|
||||
"""
|
||||
Test that model parameter IS included for non-Azure OpenAI endpoints
|
||||
"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
|
||||
with patch.dict(os.environ, {
|
||||
"AZURE_API_KEY": "test-key",
|
||||
@@ -824,7 +824,7 @@ def test_azure_message_formatting_with_role():
|
||||
"""
|
||||
Test that messages are formatted with both 'role' and 'content' fields
|
||||
"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
|
||||
llm = LLM(model="azure/gpt-4")
|
||||
|
||||
@@ -886,12 +886,12 @@ def test_azure_improved_error_messages():
|
||||
"""
|
||||
Test that improved error messages are provided for common HTTP errors
|
||||
"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
from azure.core.exceptions import HttpResponseError
|
||||
|
||||
llm = LLM(model="azure/gpt-4")
|
||||
|
||||
with patch.object(llm.client, 'complete') as mock_complete:
|
||||
with patch.object(llm._client, 'complete') as mock_complete:
|
||||
error_401 = HttpResponseError(message="Unauthorized")
|
||||
error_401.status_code = 401
|
||||
mock_complete.side_effect = error_401
|
||||
@@ -918,7 +918,7 @@ def test_azure_api_version_properly_passed():
|
||||
"""
|
||||
Test that api_version is properly passed to the client
|
||||
"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
|
||||
with patch.dict(os.environ, {
|
||||
"AZURE_API_KEY": "test-key",
|
||||
@@ -940,7 +940,7 @@ def test_azure_timeout_and_max_retries_stored():
|
||||
"""
|
||||
Test that timeout and max_retries parameters are stored
|
||||
"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
|
||||
with patch.dict(os.environ, {
|
||||
"AZURE_API_KEY": "test-key",
|
||||
@@ -960,7 +960,7 @@ def test_azure_complete_params_include_optional_params():
|
||||
"""
|
||||
Test that optional parameters are included in completion params when set
|
||||
"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
|
||||
with patch.dict(os.environ, {
|
||||
"AZURE_API_KEY": "test-key",
|
||||
@@ -992,7 +992,7 @@ def test_azure_endpoint_validation_with_azure_prefix():
|
||||
"""
|
||||
Test that 'azure/' prefix is properly stripped when constructing endpoint
|
||||
"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
|
||||
with patch.dict(os.environ, {
|
||||
"AZURE_API_KEY": "test-key",
|
||||
@@ -1009,7 +1009,7 @@ def test_azure_message_formatting_preserves_all_roles():
|
||||
"""
|
||||
Test that all message roles (system, user, assistant) are preserved correctly
|
||||
"""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from crewai.llm.providers.azure.completion import AzureCompletion
|
||||
|
||||
llm = LLM(model="azure/gpt-4")
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ def mock_aws_credentials():
|
||||
"AWS_DEFAULT_REGION": "us-east-1"
|
||||
}):
|
||||
# Mock boto3 Session to prevent actual AWS connections
|
||||
with patch('crewai.llms.providers.bedrock.completion.Session') as mock_session_class:
|
||||
with patch('crewai.llm.providers.bedrock.completion.Session') as mock_session_class:
|
||||
# Create mock session instance
|
||||
mock_session_instance = MagicMock()
|
||||
mock_client = MagicMock()
|
||||
@@ -67,7 +67,7 @@ def test_bedrock_completion_module_is_imported():
|
||||
"""
|
||||
Test that the completion module is properly imported when using Bedrock provider
|
||||
"""
|
||||
module_name = "crewai.llms.providers.bedrock.completion"
|
||||
module_name = "crewai.llm.providers.bedrock.completion"
|
||||
|
||||
# Remove module from cache if it exists
|
||||
if module_name in sys.modules:
|
||||
@@ -124,7 +124,7 @@ def test_bedrock_completion_initialization_parameters():
|
||||
region_name="us-west-2"
|
||||
)
|
||||
|
||||
from crewai.llms.providers.bedrock.completion import BedrockCompletion
|
||||
from crewai.llm.providers.bedrock.completion import BedrockCompletion
|
||||
assert isinstance(llm, BedrockCompletion)
|
||||
assert llm.model == "anthropic.claude-3-5-sonnet-20241022-v2:0"
|
||||
assert llm.temperature == 0.7
|
||||
@@ -145,9 +145,9 @@ def test_bedrock_specific_parameters():
|
||||
region_name="us-east-1"
|
||||
)
|
||||
|
||||
from crewai.llms.providers.bedrock.completion import BedrockCompletion
|
||||
from crewai.llm.providers.bedrock.completion import BedrockCompletion
|
||||
assert isinstance(llm, BedrockCompletion)
|
||||
assert llm.stop_sequences == ["Human:", "Assistant:"]
|
||||
assert llm.stop == ["Human:", "Assistant:"]
|
||||
assert llm.stream == True
|
||||
assert llm.region_name == "us-east-1"
|
||||
|
||||
@@ -369,7 +369,7 @@ def test_bedrock_aws_credentials_configuration():
|
||||
}):
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0")
|
||||
|
||||
from crewai.llms.providers.bedrock.completion import BedrockCompletion
|
||||
from crewai.llm.providers.bedrock.completion import BedrockCompletion
|
||||
assert isinstance(llm, BedrockCompletion)
|
||||
assert llm.region_name == "us-east-1"
|
||||
|
||||
@@ -390,7 +390,7 @@ def test_bedrock_model_capabilities():
|
||||
"""
|
||||
# Test Claude model
|
||||
llm_claude = LLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0")
|
||||
from crewai.llms.providers.bedrock.completion import BedrockCompletion
|
||||
from crewai.llm.providers.bedrock.completion import BedrockCompletion
|
||||
assert isinstance(llm_claude, BedrockCompletion)
|
||||
assert llm_claude.is_claude_model == True
|
||||
assert llm_claude.supports_tools == True
|
||||
@@ -413,7 +413,7 @@ def test_bedrock_inference_config():
|
||||
max_tokens=1000
|
||||
)
|
||||
|
||||
from crewai.llms.providers.bedrock.completion import BedrockCompletion
|
||||
from crewai.llm.providers.bedrock.completion import BedrockCompletion
|
||||
assert isinstance(llm, BedrockCompletion)
|
||||
|
||||
# Test config preparation
|
||||
@@ -444,7 +444,7 @@ def test_bedrock_model_detection():
|
||||
|
||||
for model_name in bedrock_test_cases:
|
||||
llm = LLM(model=model_name)
|
||||
from crewai.llms.providers.bedrock.completion import BedrockCompletion
|
||||
from crewai.llm.providers.bedrock.completion import BedrockCompletion
|
||||
assert isinstance(llm, BedrockCompletion), f"Failed for model: {model_name}"
|
||||
|
||||
|
||||
@@ -579,7 +579,7 @@ def test_bedrock_token_usage_tracking():
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0")
|
||||
|
||||
# Mock the Bedrock response with usage information
|
||||
with patch.object(llm.client, 'converse') as mock_converse:
|
||||
with patch.object(llm._client, 'converse') as mock_converse:
|
||||
mock_response = {
|
||||
'output': {
|
||||
'message': {
|
||||
@@ -624,7 +624,7 @@ def test_bedrock_tool_use_conversation_flow():
|
||||
available_functions = {"get_weather": mock_weather_tool}
|
||||
|
||||
# Mock the Bedrock client responses
|
||||
with patch.object(llm.client, 'converse') as mock_converse:
|
||||
with patch.object(llm._client, 'converse') as mock_converse:
|
||||
# First response: tool use request
|
||||
tool_use_response = {
|
||||
'output': {
|
||||
@@ -710,7 +710,7 @@ def test_bedrock_client_error_handling():
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0")
|
||||
|
||||
# Test ValidationException
|
||||
with patch.object(llm.client, 'converse') as mock_converse:
|
||||
with patch.object(llm._client, 'converse') as mock_converse:
|
||||
error_response = {
|
||||
'Error': {
|
||||
'Code': 'ValidationException',
|
||||
@@ -724,7 +724,7 @@ def test_bedrock_client_error_handling():
|
||||
assert "validation" in str(exc_info.value).lower()
|
||||
|
||||
# Test ThrottlingException
|
||||
with patch.object(llm.client, 'converse') as mock_converse:
|
||||
with patch.object(llm._client, 'converse') as mock_converse:
|
||||
error_response = {
|
||||
'Error': {
|
||||
'Code': 'ThrottlingException',
|
||||
@@ -739,23 +739,19 @@ def test_bedrock_client_error_handling():
|
||||
|
||||
|
||||
def test_bedrock_stop_sequences_sync():
|
||||
"""Test that stop and stop_sequences attributes stay synchronized."""
|
||||
"""Test that stop sequences can be set and retrieved correctly."""
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0")
|
||||
|
||||
# Test setting stop as a list
|
||||
llm.stop = ["\nObservation:", "\nThought:"]
|
||||
assert list(llm.stop_sequences) == ["\nObservation:", "\nThought:"]
|
||||
assert llm.stop == ["\nObservation:", "\nThought:"]
|
||||
|
||||
# Test setting stop as a string
|
||||
llm.stop = "\nFinal Answer:"
|
||||
assert list(llm.stop_sequences) == ["\nFinal Answer:"]
|
||||
assert llm.stop == ["\nFinal Answer:"]
|
||||
llm2 = LLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0", stop_sequences="\nFinal Answer:")
|
||||
assert llm2.stop == ["\nFinal Answer:"]
|
||||
|
||||
# Test setting stop as None
|
||||
llm.stop = None
|
||||
assert list(llm.stop_sequences) == []
|
||||
assert llm.stop == []
|
||||
assert llm.stop is None
|
||||
|
||||
|
||||
def test_bedrock_stop_sequences_sent_to_api():
|
||||
@@ -766,7 +762,7 @@ def test_bedrock_stop_sequences_sent_to_api():
|
||||
llm.stop = ["\nObservation:", "\nThought:"]
|
||||
|
||||
# Patch the API call to capture parameters without making real call
|
||||
with patch.object(llm.client, 'converse') as mock_converse:
|
||||
with patch.object(llm._client, 'converse') as mock_converse:
|
||||
mock_response = {
|
||||
'output': {
|
||||
'message': {
|
||||
|
||||
@@ -34,7 +34,7 @@ def test_gemini_completion_is_used_when_gemini_provider():
|
||||
"""
|
||||
llm = LLM(model="gemini/gemini-2.0-flash-001")
|
||||
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
from crewai.llm.providers.gemini.completion import GeminiCompletion
|
||||
assert isinstance(llm, GeminiCompletion)
|
||||
assert llm.provider == "gemini"
|
||||
assert llm.model == "gemini-2.0-flash-001"
|
||||
@@ -47,7 +47,7 @@ def test_gemini_tool_use_conversation_flow():
|
||||
Test that the Gemini completion properly handles tool use conversation flow
|
||||
"""
|
||||
from unittest.mock import Mock, patch
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
from crewai.llm.providers.gemini.completion import GeminiCompletion
|
||||
|
||||
# Create GeminiCompletion instance
|
||||
completion = GeminiCompletion(model="gemini-2.0-flash-001")
|
||||
@@ -59,7 +59,7 @@ def test_gemini_tool_use_conversation_flow():
|
||||
available_functions = {"get_weather": mock_weather_tool}
|
||||
|
||||
# Mock the Google Gemini client responses
|
||||
with patch.object(completion.client.models, 'generate_content') as mock_generate:
|
||||
with patch.object(completion._client.models, 'generate_content') as mock_generate:
|
||||
# Mock function call in response
|
||||
mock_function_call = Mock()
|
||||
mock_function_call.name = "get_weather"
|
||||
@@ -102,7 +102,7 @@ def test_gemini_completion_module_is_imported():
|
||||
"""
|
||||
Test that the completion module is properly imported when using Google provider
|
||||
"""
|
||||
module_name = "crewai.llms.providers.gemini.completion"
|
||||
module_name = "crewai.llm.providers.gemini.completion"
|
||||
|
||||
# Remove module from cache if it exists
|
||||
if module_name in sys.modules:
|
||||
@@ -159,7 +159,7 @@ def test_gemini_completion_initialization_parameters():
|
||||
api_key="test-key"
|
||||
)
|
||||
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
from crewai.llm.providers.gemini.completion import GeminiCompletion
|
||||
assert isinstance(llm, GeminiCompletion)
|
||||
assert llm.model == "gemini-2.0-flash-001"
|
||||
assert llm.temperature == 0.7
|
||||
@@ -186,9 +186,9 @@ def test_gemini_specific_parameters():
|
||||
location="us-central1"
|
||||
)
|
||||
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
from crewai.llm.providers.gemini.completion import GeminiCompletion
|
||||
assert isinstance(llm, GeminiCompletion)
|
||||
assert llm.stop_sequences == ["Human:", "Assistant:"]
|
||||
assert llm.stop == ["Human:", "Assistant:"]
|
||||
assert llm.stream == True
|
||||
assert llm.safety_settings == safety_settings
|
||||
assert llm.project == "test-project"
|
||||
@@ -382,7 +382,7 @@ def test_gemini_raises_error_when_model_not_supported():
|
||||
"""Test that GeminiCompletion raises ValueError when model not supported"""
|
||||
|
||||
# Mock the Google client to raise an error
|
||||
with patch('crewai.llms.providers.gemini.completion.genai') as mock_genai:
|
||||
with patch('crewai.llm.providers.gemini.completion.genai') as mock_genai:
|
||||
mock_client = MagicMock()
|
||||
mock_genai.Client.return_value = mock_client
|
||||
|
||||
@@ -420,7 +420,7 @@ def test_gemini_vertex_ai_setup():
|
||||
location="us-west1"
|
||||
)
|
||||
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
from crewai.llm.providers.gemini.completion import GeminiCompletion
|
||||
assert isinstance(llm, GeminiCompletion)
|
||||
|
||||
assert llm.project == "test-project"
|
||||
@@ -435,7 +435,7 @@ def test_gemini_api_key_configuration():
|
||||
with patch.dict(os.environ, {"GOOGLE_API_KEY": "test-google-key"}):
|
||||
llm = LLM(model="google/gemini-2.0-flash-001")
|
||||
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
from crewai.llm.providers.gemini.completion import GeminiCompletion
|
||||
assert isinstance(llm, GeminiCompletion)
|
||||
assert llm.api_key == "test-google-key"
|
||||
|
||||
@@ -453,7 +453,7 @@ def test_gemini_model_capabilities():
|
||||
"""
|
||||
# Test Gemini 2.0 model
|
||||
llm_2_0 = LLM(model="google/gemini-2.0-flash-001")
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
from crewai.llm.providers.gemini.completion import GeminiCompletion
|
||||
assert isinstance(llm_2_0, GeminiCompletion)
|
||||
assert llm_2_0.is_gemini_2 == True
|
||||
assert llm_2_0.supports_tools == True
|
||||
@@ -477,7 +477,7 @@ def test_gemini_generation_config():
|
||||
max_output_tokens=1000
|
||||
)
|
||||
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
from crewai.llm.providers.gemini.completion import GeminiCompletion
|
||||
assert isinstance(llm, GeminiCompletion)
|
||||
|
||||
# Test config preparation
|
||||
@@ -504,7 +504,7 @@ def test_gemini_model_detection():
|
||||
|
||||
for model_name in gemini_test_cases:
|
||||
llm = LLM(model=model_name)
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
from crewai.llm.providers.gemini.completion import GeminiCompletion
|
||||
assert isinstance(llm, GeminiCompletion), f"Failed for model: {model_name}"
|
||||
|
||||
|
||||
@@ -614,8 +614,8 @@ def test_gemini_environment_variable_api_key():
|
||||
with patch.dict(os.environ, {"GOOGLE_API_KEY": "test-google-key"}):
|
||||
llm = LLM(model="google/gemini-2.0-flash-001")
|
||||
|
||||
assert llm.client is not None
|
||||
assert hasattr(llm.client, 'models')
|
||||
assert llm._client is not None
|
||||
assert hasattr(llm._client, 'models')
|
||||
assert llm.api_key == "test-google-key"
|
||||
|
||||
|
||||
@@ -626,7 +626,7 @@ def test_gemini_token_usage_tracking():
|
||||
llm = LLM(model="google/gemini-2.0-flash-001")
|
||||
|
||||
# Mock the Gemini response with usage information
|
||||
with patch.object(llm.client.models, 'generate_content') as mock_generate:
|
||||
with patch.object(llm._client.models, 'generate_content') as mock_generate:
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = "test response"
|
||||
mock_response.candidates = []
|
||||
@@ -651,23 +651,20 @@ def test_gemini_token_usage_tracking():
|
||||
|
||||
|
||||
def test_gemini_stop_sequences_sync():
|
||||
"""Test that stop and stop_sequences attributes stay synchronized."""
|
||||
"""Test that stop sequences can be set and retrieved correctly."""
|
||||
llm = LLM(model="google/gemini-2.0-flash-001")
|
||||
|
||||
# Test setting stop as a list
|
||||
llm.stop = ["\nObservation:", "\nThought:"]
|
||||
assert llm.stop_sequences == ["\nObservation:", "\nThought:"]
|
||||
assert llm.stop == ["\nObservation:", "\nThought:"]
|
||||
|
||||
# Test setting stop as a string
|
||||
llm.stop = "\nFinal Answer:"
|
||||
assert llm.stop_sequences == ["\nFinal Answer:"]
|
||||
assert llm.stop == ["\nFinal Answer:"]
|
||||
assert llm.stop == "\nFinal Answer:"
|
||||
|
||||
# Test setting stop as None
|
||||
llm.stop = None
|
||||
assert llm.stop_sequences == []
|
||||
assert llm.stop == []
|
||||
assert llm.stop is None
|
||||
|
||||
|
||||
def test_gemini_stop_sequences_sent_to_api():
|
||||
@@ -678,7 +675,7 @@ def test_gemini_stop_sequences_sent_to_api():
|
||||
llm.stop = ["\nObservation:", "\nThought:"]
|
||||
|
||||
# Patch the API call to capture parameters without making real call
|
||||
with patch.object(llm.client.models, 'generate_content') as mock_generate:
|
||||
with patch.object(llm._client.models, 'generate_content') as mock_generate:
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = "Hello"
|
||||
mock_response.candidates = []
|
||||
|
||||
@@ -6,7 +6,7 @@ import httpx
|
||||
import pytest
|
||||
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.llm.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.llm.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
class SimpleInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
|
||||
@@ -4,7 +4,7 @@ import httpx
|
||||
import pytest
|
||||
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.llm.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
class OpenAITestInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
|
||||
@@ -5,8 +5,8 @@ from unittest.mock import Mock
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.llms.hooks.transport import AsyncHTTPTransport, HTTPTransport
|
||||
from crewai.llm.hooks.base import BaseInterceptor
|
||||
from crewai.llm.hooks.transport import AsyncHTTPTransport, HTTPTransport
|
||||
|
||||
|
||||
class TrackingInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
|
||||
@@ -6,7 +6,7 @@ import httpx
|
||||
import pytest
|
||||
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.llm.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
||||
@@ -6,7 +6,7 @@ import openai
|
||||
import pytest
|
||||
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.providers.openai.completion import OpenAICompletion
|
||||
from crewai.llm.providers.openai.completion import OpenAICompletion
|
||||
from crewai.crew import Crew
|
||||
from crewai.agent import Agent
|
||||
from crewai.task import Task
|
||||
@@ -29,7 +29,7 @@ def test_openai_completion_is_used_when_no_provider_prefix():
|
||||
"""
|
||||
llm = LLM(model="gpt-4o")
|
||||
|
||||
from crewai.llms.providers.openai.completion import OpenAICompletion
|
||||
from crewai.llm.providers.openai.completion import OpenAICompletion
|
||||
assert isinstance(llm, OpenAICompletion)
|
||||
assert llm.provider == "openai"
|
||||
assert llm.model == "gpt-4o"
|
||||
@@ -63,7 +63,7 @@ def test_openai_completion_module_is_imported():
|
||||
"""
|
||||
Test that the completion module is properly imported when using OpenAI provider
|
||||
"""
|
||||
module_name = "crewai.llms.providers.openai.completion"
|
||||
module_name = "crewai.llm.providers.openai.completion"
|
||||
|
||||
# Remove module from cache if it exists
|
||||
if module_name in sys.modules:
|
||||
@@ -114,7 +114,7 @@ def test_openai_completion_initialization_parameters():
|
||||
api_key="test-key"
|
||||
)
|
||||
|
||||
from crewai.llms.providers.openai.completion import OpenAICompletion
|
||||
from crewai.llm.providers.openai.completion import OpenAICompletion
|
||||
assert isinstance(llm, OpenAICompletion)
|
||||
assert llm.model == "gpt-4o"
|
||||
assert llm.temperature == 0.7
|
||||
@@ -335,7 +335,7 @@ def test_openai_completion_call_returns_usage_metrics():
|
||||
def test_openai_raises_error_when_model_not_supported():
|
||||
"""Test that OpenAICompletion raises ValueError when model not supported"""
|
||||
|
||||
with patch('crewai.llms.providers.openai.completion.OpenAI') as mock_openai_class:
|
||||
with patch('crewai.llm.providers.openai.completion.OpenAI') as mock_openai_class:
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
|
||||
@@ -369,11 +369,11 @@ def test_openai_client_setup_with_extra_arguments():
|
||||
assert llm.top_p == 0.5
|
||||
|
||||
# Check that client parameters are properly configured
|
||||
assert llm.client.max_retries == 3
|
||||
assert llm.client.timeout == 30
|
||||
assert llm._client.max_retries == 3
|
||||
assert llm._client.timeout == 30
|
||||
|
||||
# Test that parameters are properly used in API calls
|
||||
with patch.object(llm.client.chat.completions, 'create') as mock_create:
|
||||
with patch.object(llm._client.chat.completions, 'create') as mock_create:
|
||||
mock_create.return_value = MagicMock(
|
||||
choices=[MagicMock(message=MagicMock(content="test response", tool_calls=None))],
|
||||
usage=MagicMock(prompt_tokens=10, completion_tokens=20, total_tokens=30)
|
||||
@@ -394,7 +394,7 @@ def test_extra_arguments_are_passed_to_openai_completion():
|
||||
"""
|
||||
llm = LLM(model="gpt-4o", temperature=0.7, max_tokens=1000, top_p=0.5, max_retries=3)
|
||||
|
||||
with patch.object(llm.client.chat.completions, 'create') as mock_create:
|
||||
with patch.object(llm._client.chat.completions, 'create') as mock_create:
|
||||
mock_create.return_value = MagicMock(
|
||||
choices=[MagicMock(message=MagicMock(content="test response", tool_calls=None))],
|
||||
usage=MagicMock(prompt_tokens=10, completion_tokens=20, total_tokens=30)
|
||||
@@ -501,7 +501,7 @@ def test_openai_streaming_with_response_model():
|
||||
|
||||
llm = LLM(model="openai/gpt-4o", stream=True)
|
||||
|
||||
with patch.object(llm.client.chat.completions, "create") as mock_create:
|
||||
with patch.object(llm._client.chat.completions, "create") as mock_create:
|
||||
mock_chunk1 = MagicMock()
|
||||
mock_chunk1.choices = [
|
||||
MagicMock(delta=MagicMock(content='{"answer": "test", ', tool_calls=None))
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
import pytest
|
||||
from crewai import Agent, Crew, Process, Task
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.utilities.llm_utils import create_llm
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from crewai.events.event_types import (
|
||||
ToolUsageFinishedEvent,
|
||||
ToolUsageStartedEvent,
|
||||
)
|
||||
from crewai.llm import CONTEXT_WINDOW_USAGE_RATIO, LLM
|
||||
from crewai.llm.core import CONTEXT_WINDOW_USAGE_RATIO, LLM
|
||||
from crewai.utilities.token_counter_callback import TokenCalcHandler
|
||||
from pydantic import BaseModel
|
||||
import pytest
|
||||
@@ -229,7 +229,7 @@ def test_validate_call_params_supported():
|
||||
a: int
|
||||
|
||||
# Patch supports_response_schema to simulate a supported model.
|
||||
with patch("crewai.llm.supports_response_schema", return_value=True):
|
||||
with patch("crewai.llm.core.supports_response_schema", return_value=True):
|
||||
llm = LLM(
|
||||
model="openrouter/deepseek/deepseek-chat", response_format=DummyResponse
|
||||
)
|
||||
@@ -242,7 +242,7 @@ def test_validate_call_params_not_supported():
|
||||
a: int
|
||||
|
||||
# Patch supports_response_schema to simulate an unsupported model.
|
||||
with patch("crewai.llm.supports_response_schema", return_value=False):
|
||||
with patch("crewai.llm.core.supports_response_schema", return_value=False):
|
||||
llm = LLM(model="gemini/gemini-1.5-pro", response_format=DummyResponse, is_litellm=True)
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
llm._validate_call_params()
|
||||
@@ -342,7 +342,7 @@ def test_context_window_validation():
|
||||
# Test invalid window size
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with patch.dict(
|
||||
"crewai.llm.LLM_CONTEXT_WINDOW_SIZES",
|
||||
"crewai.llm.core.LLM_CONTEXT_WINDOW_SIZES",
|
||||
{"test-model": 500}, # Below minimum
|
||||
clear=True,
|
||||
):
|
||||
@@ -702,8 +702,8 @@ def test_ollama_does_not_modify_when_last_is_user(ollama_llm):
|
||||
|
||||
def test_native_provider_raises_error_when_supported_but_fails():
|
||||
"""Test that when a native provider is in SUPPORTED_NATIVE_PROVIDERS but fails to instantiate, we raise the error."""
|
||||
with patch("crewai.llm.SUPPORTED_NATIVE_PROVIDERS", ["openai"]):
|
||||
with patch("crewai.llm.LLM._get_native_provider") as mock_get_native:
|
||||
with patch("crewai.llm.internal.meta.SUPPORTED_NATIVE_PROVIDERS", ["openai"]):
|
||||
with patch("crewai.llm.internal.meta.LLMMeta._get_native_provider") as mock_get_native:
|
||||
# Mock that provider exists but throws an error when instantiated
|
||||
mock_provider = MagicMock()
|
||||
mock_provider.side_effect = ValueError("Native provider initialization failed")
|
||||
@@ -718,7 +718,7 @@ def test_native_provider_raises_error_when_supported_but_fails():
|
||||
|
||||
def test_native_provider_falls_back_to_litellm_when_not_in_supported_list():
|
||||
"""Test that when a provider is not in SUPPORTED_NATIVE_PROVIDERS, we fall back to LiteLLM."""
|
||||
with patch("crewai.llm.SUPPORTED_NATIVE_PROVIDERS", ["openai", "anthropic"]):
|
||||
with patch("crewai.llm.internal.meta.SUPPORTED_NATIVE_PROVIDERS", ["openai", "anthropic"]):
|
||||
# Using a provider not in the supported list
|
||||
llm = LLM(model="groq/llama-3.1-70b-versatile", is_litellm=False)
|
||||
|
||||
|
||||
@@ -743,7 +743,7 @@ def test_llm_emits_call_failed_event():
|
||||
error_message = "OpenAI API call failed: Simulated API failure"
|
||||
|
||||
with patch(
|
||||
"crewai.llms.providers.openai.completion.OpenAICompletion._handle_completion"
|
||||
"crewai.llm.providers.openai.completion.OpenAICompletion._handle_completion"
|
||||
) as mock_handle_completion:
|
||||
mock_handle_completion.side_effect = Exception("Simulated API failure")
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from unittest.mock import patch
|
||||
|
||||
from crewai.cli.constants import DEFAULT_LLM_MODEL
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llm.base_llm import BaseLLM
|
||||
from crewai.utilities.llm_utils import create_llm
|
||||
import pytest
|
||||
|
||||
|
||||
Reference in New Issue
Block a user