From b440d143edd8b3dbd03f791a07ae61d3b902e198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Mon, 23 Sep 2024 03:59:05 -0300 Subject: [PATCH] Adding new LLM class --- src/crewai/agent.py | 70 +++++++++++++++---- src/crewai/agents/crew_agent_executor.py | 40 +++-------- src/crewai/crew.py | 15 ++-- src/crewai/llm.py | 3 - src/crewai/telemetry/telemetry.py | 14 ++-- src/crewai/tools/tool_usage.py | 25 +++++-- src/crewai/utilities/converter.py | 42 +++++------ .../utilities/evaluators/task_evaluator.py | 6 +- src/crewai/utilities/internal_instructor.py | 2 +- 9 files changed, 124 insertions(+), 93 deletions(-) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index b29c62f3a..a943c994d 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -1,6 +1,6 @@ import os from inspect import signature -from typing import Any, List, Optional +from typing import Any, List, Optional, Union from pydantic import Field, InstanceOf, PrivateAttr, model_validator from crewai.agents import CacheHandler @@ -12,6 +12,7 @@ from crewai.memory.contextual.contextual_memory import ContextualMemory from crewai.utilities.constants import TRAINED_AGENTS_DATA_FILE, TRAINING_DATA_FILE from crewai.utilities.training_handler import CrewTrainingHandler from crewai.utilities.token_counter_callback import TokenCalcHandler +from crewai.llm import LLM def mock_agent_ops_provider(): @@ -81,8 +82,8 @@ class Agent(BaseAgent): default=True, description="Use system prompt for the agent.", ) - llm: Any = Field( - description="Language model that will run the agent.", default="gpt-4o-mini" + llm: Union[str, InstanceOf[LLM], Any] = Field( + description="Language model that will run the agent.", default=None ) function_calling_llm: Optional[Any] = Field( description="Language model that will run the agent.", default=None @@ -118,17 +119,58 @@ class Agent(BaseAgent): @model_validator(mode="after") def post_init_setup(self): self.agent_ops_agent_name = self.role - self.llm = ( - getattr(self.llm, "model_name", None) - or getattr(self.llm, "deployment_name", None) - or self.llm - or os.environ.get("OPENAI_MODEL_NAME") - ) - self.function_calling_llm = ( - getattr(self.function_calling_llm, "model_name", None) - or getattr(self.function_calling_llm, "deployment_name", None) - or self.function_calling_llm - ) + + # Handle different cases for self.llm + if isinstance(self.llm, str): + # If it's a string, create an LLM instance + self.llm = LLM(model=self.llm) + elif isinstance(self.llm, LLM): + # If it's already an LLM instance, keep it as is + pass + elif self.llm is None: + # If it's None, use environment variables or default + model_name = os.environ.get("OPENAI_MODEL_NAME", "gpt-4o-mini") + llm_params = {"model": model_name} + + api_base = os.environ.get("OPENAI_API_BASE") + if api_base: + llm_params["base_url"] = api_base + + api_key = os.environ.get("OPENAI_API_KEY") + if api_key: + llm_params["api_key"] = api_key + + self.llm = LLM(**llm_params) + else: + # For any other type, attempt to extract relevant attributes + llm_params = { + "model": getattr(self.llm, "model_name", None) + or getattr(self.llm, "deployment_name", None) + or str(self.llm), + "temperature": getattr(self.llm, "temperature", None), + "max_tokens": getattr(self.llm, "max_tokens", None), + "logprobs": getattr(self.llm, "logprobs", None), + "timeout": getattr(self.llm, "timeout", None), + "max_retries": getattr(self.llm, "max_retries", None), + "api_key": getattr(self.llm, "api_key", None), + "base_url": getattr(self.llm, "base_url", None), + "organization": getattr(self.llm, "organization", None), + } + # Remove None values to avoid passing unnecessary parameters + llm_params = {k: v for k, v in llm_params.items() if v is not None} + self.llm = LLM(**llm_params) + + # Similar handling for function_calling_llm + if self.function_calling_llm: + if isinstance(self.function_calling_llm, str): + self.function_calling_llm = LLM(model=self.function_calling_llm) + elif not isinstance(self.function_calling_llm, LLM): + self.function_calling_llm = LLM( + model=getattr(self.function_calling_llm, "model_name", None) + or getattr(self.function_calling_llm, "deployment_name", None) + or str(self.function_calling_llm) + ) + if not self.agent_executor: self._setup_agent_executor() diff --git a/src/crewai/agents/crew_agent_executor.py b/src/crewai/agents/crew_agent_executor.py index f7bf57e8c..e256b56c6 100644 --- a/src/crewai/agents/crew_agent_executor.py +++ b/src/crewai/agents/crew_agent_executor.py @@ -13,7 +13,6 @@ from crewai.utilities.exceptions.context_window_exceeding_exception import ( ) from crewai.utilities.logger import Logger from crewai.utilities.training_handler import CrewTrainingHandler -from crewai.llm import LLM from crewai.agents.parser import ( AgentAction, AgentFinish, @@ -104,23 +103,10 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): try: while not isinstance(formatted_answer, AgentFinish): if not self.request_within_rpm_limit or self.request_within_rpm_limit(): - if isinstance(self.llm, str): - llm = LLM( - model=self.llm, - stop=self.stop if self.use_stop_words else None, - callbacks=self.callbacks, - ) - elif isinstance(self.llm, LLM): - llm = self.llm - else: - llm = LLM( - model=self.llm.model, - provider=getattr(self.llm, "provider", "litellm"), - stop=self.stop if self.use_stop_words else None, - callbacks=self.callbacks, - **getattr(self.llm, "llm_kwargs", {}), - ) - answer = llm.call(self.messages) + answer = self.llm.call( + self.messages, + callbacks=self.callbacks, + ) if not self.use_stop_words: try: @@ -139,6 +125,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): action_result = self._use_tool(formatted_answer) formatted_answer.text += f"\nObservation: {action_result}" formatted_answer.result = action_result + print("formatted_answer", formatted_answer) self._show_logs(formatted_answer) if self.step_callback: @@ -194,7 +181,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): if isinstance(formatted_answer, AgentAction): thought = re.sub(r"\n+", "\n", formatted_answer.thought) formatted_json = json.dumps( - json.loads(formatted_answer.tool_input), + formatted_answer.tool_input, indent=2, ensure_ascii=False, ) @@ -253,16 +240,6 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): return tool_result def _summarize_messages(self) -> None: - if isinstance(self.llm, str): - llm = LLM(model=self.llm) - elif isinstance(self.llm, LLM): - llm = self.llm - else: - llm = LLM( - model=self.llm.model, - provider=getattr(self.llm, "provider", "litellm"), - **getattr(self.llm, "llm_kwargs", {}), - ) messages_groups = [] for message in self.messages: @@ -272,7 +249,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): summarized_contents = [] for group in messages_groups: - summary = llm.call( + summary = self.llm.call( [ self._format_msg( self._i18n.slices("summarizer_system_message"), role="system" @@ -280,7 +257,8 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): self._format_msg( self._i18n.errors("sumamrize_instruction").format(group=group), ), - ] + ], + callbacks=self.callbacks, ) summarized_contents.append(summary) diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 91382c8ac..00d2c4b2a 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -22,6 +22,7 @@ from crewai.agent import Agent from crewai.agents.agent_builder.base_agent import BaseAgent from crewai.agents.cache import CacheHandler from crewai.crews.crew_output import CrewOutput +from crewai.llm import LLM from crewai.memory.entity.entity_memory import EntityMemory from crewai.memory.long_term.long_term_memory import LongTermMemory from crewai.memory.short_term.short_term_memory import ShortTermMemory @@ -211,11 +212,15 @@ class Crew(BaseModel): if self.output_log_file: self._file_handler = FileHandler(self.output_log_file) self._rpm_controller = RPMController(max_rpm=self.max_rpm, logger=self._logger) - self.function_calling_llm = ( - getattr(self.function_calling_llm, "model_name", None) - or getattr(self.function_calling_llm, "deployment_name", None) - or self.function_calling_llm - ) + if self.function_calling_llm: + if isinstance(self.function_calling_llm, str): + self.function_calling_llm = LLM(model=self.function_calling_llm) + elif not isinstance(self.function_calling_llm, LLM): + self.function_calling_llm = LLM( + model=getattr(self.function_calling_llm, "model_name", None) + or getattr(self.function_calling_llm, "deployment_name", None) + or str(self.function_calling_llm) + ) self._telemetry = Telemetry() self._telemetry.set_tracer() return self diff --git a/src/crewai/llm.py b/src/crewai/llm.py index 006733cd8..a1df0eeef 100644 --- a/src/crewai/llm.py +++ b/src/crewai/llm.py @@ -85,6 +85,3 @@ class LLM: except Exception as e: logging.error(f"LiteLLM call failed: {str(e)}") raise # Re-raise the exception after logging - - def __getattr__(self, name): - return self.kwargs.get(name) diff --git a/src/crewai/telemetry/telemetry.py b/src/crewai/telemetry/telemetry.py index b49391407..14a3ec865 100644 --- a/src/crewai/telemetry/telemetry.py +++ b/src/crewai/telemetry/telemetry.py @@ -117,8 +117,10 @@ class Telemetry: "max_iter": agent.max_iter, "max_rpm": agent.max_rpm, "i18n": agent.i18n.prompt_file, - "function_calling_llm": agent.function_calling_llm, - "llm": agent.llm, + "function_calling_llm": agent.function_calling_llm.model + if agent.function_calling_llm + else "", + "llm": agent.llm.model, "delegation_enabled?": agent.allow_delegation, "allow_code_execution?": agent.allow_code_execution, "max_retry_limit": agent.max_retry_limit, @@ -182,8 +184,10 @@ class Telemetry: "verbose?": agent.verbose, "max_iter": agent.max_iter, "max_rpm": agent.max_rpm, - "function_calling_llm": agent.function_calling_llm, - "llm": agent.llm, + "function_calling_llm": agent.function_calling_llm.model + if agent.function_calling_llm + else "", + "llm": agent.llm.model, "delegation_enabled?": agent.allow_delegation, "allow_code_execution?": agent.allow_code_execution, "max_retry_limit": agent.max_retry_limit, @@ -488,7 +492,7 @@ class Telemetry: "max_iter": agent.max_iter, "max_rpm": agent.max_rpm, "i18n": agent.i18n.prompt_file, - "llm": agent.llm, + "llm": agent.llm.model, "delegation_enabled?": agent.allow_delegation, "tools_names": [ tool.name.casefold() for tool in agent.tools or [] diff --git a/src/crewai/tools/tool_usage.py b/src/crewai/tools/tool_usage.py index ff8a57351..cbf070758 100644 --- a/src/crewai/tools/tool_usage.py +++ b/src/crewai/tools/tool_usage.py @@ -72,7 +72,8 @@ class ToolUsage: # Set the maximum parsing attempts for bigger models if ( - self._is_gpt(self.function_calling_llm) + self.function_calling_llm + and self._is_gpt(self.function_calling_llm) and self.function_calling_llm in OPENAI_BIGGER_MODELS ): self._max_parsing_attempts = 2 @@ -85,6 +86,7 @@ class ToolUsage: def use( self, calling: Union[ToolCalling, InstructorToolCalling], tool_string: str ) -> str: + print("calling", calling) if isinstance(calling, ToolUsageErrorException): error = calling.message if self.agent.verbose: @@ -299,9 +301,9 @@ class ToolUsage: def _is_gpt(self, llm) -> bool: return ( - "gpt" in str(llm).lower() - or "o1-preview" in str(llm).lower() - or "o1-mini" in str(llm).lower() + "gpt" in str(llm.model).lower() + or "o1-preview" in str(llm.model).lower() + or "o1-mini" in str(llm.model).lower() ) def _tool_calling( @@ -309,11 +311,16 @@ class ToolUsage: ) -> Union[ToolCalling, InstructorToolCalling]: try: if self.function_calling_llm: + print("self.function_calling_llm") model = ( InstructorToolCalling if self._is_gpt(self.function_calling_llm) else ToolCalling ) + print("model", model) + print( + "self.function_calling_llm.model", self.function_calling_llm.model + ) converter = Converter( text=f"Only tools available:\n###\n{self._render()}\n\nReturn a valid schema for the tool, the tool name must be exactly equal one of the options, use this text to inform the valid output schema:\n\n### TEXT \n{tool_string}", llm=self.function_calling_llm, @@ -329,7 +336,15 @@ class ToolUsage: ), max_attempts=1, ) - calling = converter.to_pydantic() + print("converter", converter) + tool_object = converter.to_pydantic() + print("tool_object", tool_object) + calling = ToolCalling( + tool_name=tool_object["tool_name"], + arguments=tool_object["arguments"], + log=tool_string, # type: ignore + ) + print("calling", calling) if isinstance(calling, ConverterError): raise calling diff --git a/src/crewai/utilities/converter.py b/src/crewai/utilities/converter.py index 7a1664784..368f7607f 100644 --- a/src/crewai/utilities/converter.py +++ b/src/crewai/utilities/converter.py @@ -27,8 +27,7 @@ class Converter(OutputConverter): if self.is_gpt: return self._create_instructor().to_pydantic() else: - llm = self._create_llm() - return llm.call( + return self.llm.call( [ {"role": "system", "content": self.instructions}, {"role": "user", "content": self.text}, @@ -47,9 +46,8 @@ class Converter(OutputConverter): if self.is_gpt: return self._create_instructor().to_json() else: - llm = self._create_llm() return json.dumps( - llm.call( + self.llm.call( [ {"role": "system", "content": self.instructions}, {"role": "user", "content": self.text}, @@ -61,19 +59,6 @@ class Converter(OutputConverter): return self.to_json(current_attempt + 1) return ConverterError(f"Failed to convert text into JSON, error: {e}.") - def _create_llm(self): - """Create an LLM instance.""" - if isinstance(self.llm, str): - return LLM(model=self.llm) - elif isinstance(self.llm, LLM): - return self.llm - else: - return LLM( - model=self.llm.model, - provider=getattr(self.llm, "provider", "litellm"), - **getattr(self.llm, "llm_kwargs", {}), - ) - def _create_instructor(self): """Create an instructor.""" from crewai.utilities import InternalInstructor @@ -93,7 +78,7 @@ class Converter(OutputConverter): ) parser = CrewPydanticOutputParser(pydantic_object=self.model) - result = LLM(model=self.llm).call( + result = self.llm.call( [ {"role": "system", "content": self.instructions}, {"role": "user", "content": self.text}, @@ -105,9 +90,9 @@ class Converter(OutputConverter): def is_gpt(self) -> bool: """Return if llm provided is of gpt from openai.""" return ( - "gpt" in str(self.llm).lower() - or "o1-preview" in str(self.llm).lower() - or "o1-mini" in str(self.llm).lower() + "gpt" in str(self.llm.model).lower() + or "o1-preview" in str(self.llm.model).lower() + or "o1-mini" in str(self.llm.model).lower() ) @@ -157,6 +142,7 @@ def handle_partial_json( converter_cls: Optional[Type[Converter]] = None, ) -> Union[dict, BaseModel, str]: match = re.search(r"({.*})", result, re.DOTALL) + print("handle_partial_json") if match: try: exported_result = model.model_validate_json(match.group(0)) @@ -185,8 +171,11 @@ def convert_with_instructions( agent: Any, converter_cls: Optional[Type[Converter]] = None, ) -> Union[dict, BaseModel, str]: + print("convert_with_instructions") llm = agent.function_calling_llm or agent.llm + print("llm", llm) instructions = get_conversion_instructions(model, llm) + print("instructions", instructions) converter = create_converter( agent=agent, converter_cls=converter_cls, @@ -195,10 +184,11 @@ def convert_with_instructions( model=model, instructions=instructions, ) - + print("converter", converter) exported_result = ( converter.to_pydantic() if not is_json_output else converter.to_json() ) + print("exported_result", exported_result) if isinstance(exported_result, ConverterError): Printer().print( @@ -218,12 +208,12 @@ def get_conversion_instructions(model: Type[BaseModel], llm: Any) -> str: return instructions -def is_gpt(llm: Any) -> bool: +def is_gpt(llm: LLM) -> bool: """Return if llm provided is of gpt from openai.""" return ( - "gpt" in str(llm).lower() - or "o1-preview" in str(llm).lower() - or "o1-mini" in str(llm).lower() + "gpt" in str(llm.model).lower() + or "o1-preview" in str(llm.model).lower() + or "o1-mini" in str(llm.model).lower() ) diff --git a/src/crewai/utilities/evaluators/task_evaluator.py b/src/crewai/utilities/evaluators/task_evaluator.py index ce19f3630..9206ef4a6 100644 --- a/src/crewai/utilities/evaluators/task_evaluator.py +++ b/src/crewai/utilities/evaluators/task_evaluator.py @@ -93,9 +93,9 @@ class TaskEvaluator: def _is_gpt(self, llm) -> bool: return ( - "gpt" in str(self.llm).lower() - or "o1-preview" in str(self.llm).lower() - or "o1-mini" in str(self.llm).lower() + "gpt" in str(self.llm.model).lower() + or "o1-preview" in str(self.llm.model).lower() + or "o1-mini" in str(self.llm.model).lower() ) def evaluate_training_data( diff --git a/src/crewai/utilities/internal_instructor.py b/src/crewai/utilities/internal_instructor.py index 5a213dcdc..0d9deaa24 100644 --- a/src/crewai/utilities/internal_instructor.py +++ b/src/crewai/utilities/internal_instructor.py @@ -42,6 +42,6 @@ class InternalInstructor: if self.instructions: messages.append({"role": "system", "content": self.instructions}) model = self._client.chat.completions.create( - model=self.llm, response_model=self.model, messages=messages + model=self.llm.model, response_model=self.model, messages=messages ) return model