Adding new LLM class

This commit is contained in:
João Moura
2024-09-23 03:59:05 -03:00
parent 355338767c
commit b440d143ed
9 changed files with 124 additions and 93 deletions

View File

@@ -1,6 +1,6 @@
import os import os
from inspect import signature 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 pydantic import Field, InstanceOf, PrivateAttr, model_validator
from crewai.agents import CacheHandler 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.constants import TRAINED_AGENTS_DATA_FILE, TRAINING_DATA_FILE
from crewai.utilities.training_handler import CrewTrainingHandler from crewai.utilities.training_handler import CrewTrainingHandler
from crewai.utilities.token_counter_callback import TokenCalcHandler from crewai.utilities.token_counter_callback import TokenCalcHandler
from crewai.llm import LLM
def mock_agent_ops_provider(): def mock_agent_ops_provider():
@@ -81,8 +82,8 @@ class Agent(BaseAgent):
default=True, default=True,
description="Use system prompt for the agent.", description="Use system prompt for the agent.",
) )
llm: Any = Field( llm: Union[str, InstanceOf[LLM], Any] = Field(
description="Language model that will run the agent.", default="gpt-4o-mini" description="Language model that will run the agent.", default=None
) )
function_calling_llm: Optional[Any] = Field( function_calling_llm: Optional[Any] = Field(
description="Language model that will run the agent.", default=None description="Language model that will run the agent.", default=None
@@ -118,17 +119,58 @@ class Agent(BaseAgent):
@model_validator(mode="after") @model_validator(mode="after")
def post_init_setup(self): def post_init_setup(self):
self.agent_ops_agent_name = self.role self.agent_ops_agent_name = self.role
self.llm = (
getattr(self.llm, "model_name", None) # Handle different cases for self.llm
or getattr(self.llm, "deployment_name", None) if isinstance(self.llm, str):
or self.llm # If it's a string, create an LLM instance
or os.environ.get("OPENAI_MODEL_NAME") self.llm = LLM(model=self.llm)
) elif isinstance(self.llm, LLM):
self.function_calling_llm = ( # If it's already an LLM instance, keep it as is
getattr(self.function_calling_llm, "model_name", None) pass
or getattr(self.function_calling_llm, "deployment_name", None) elif self.llm is None:
or self.function_calling_llm # 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: if not self.agent_executor:
self._setup_agent_executor() self._setup_agent_executor()

View File

@@ -13,7 +13,6 @@ from crewai.utilities.exceptions.context_window_exceeding_exception import (
) )
from crewai.utilities.logger import Logger from crewai.utilities.logger import Logger
from crewai.utilities.training_handler import CrewTrainingHandler from crewai.utilities.training_handler import CrewTrainingHandler
from crewai.llm import LLM
from crewai.agents.parser import ( from crewai.agents.parser import (
AgentAction, AgentAction,
AgentFinish, AgentFinish,
@@ -104,23 +103,10 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
try: try:
while not isinstance(formatted_answer, AgentFinish): while not isinstance(formatted_answer, AgentFinish):
if not self.request_within_rpm_limit or self.request_within_rpm_limit(): if not self.request_within_rpm_limit or self.request_within_rpm_limit():
if isinstance(self.llm, str): answer = self.llm.call(
llm = LLM( self.messages,
model=self.llm, callbacks=self.callbacks,
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)
if not self.use_stop_words: if not self.use_stop_words:
try: try:
@@ -139,6 +125,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
action_result = self._use_tool(formatted_answer) action_result = self._use_tool(formatted_answer)
formatted_answer.text += f"\nObservation: {action_result}" formatted_answer.text += f"\nObservation: {action_result}"
formatted_answer.result = action_result formatted_answer.result = action_result
print("formatted_answer", formatted_answer)
self._show_logs(formatted_answer) self._show_logs(formatted_answer)
if self.step_callback: if self.step_callback:
@@ -194,7 +181,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
if isinstance(formatted_answer, AgentAction): if isinstance(formatted_answer, AgentAction):
thought = re.sub(r"\n+", "\n", formatted_answer.thought) thought = re.sub(r"\n+", "\n", formatted_answer.thought)
formatted_json = json.dumps( formatted_json = json.dumps(
json.loads(formatted_answer.tool_input), formatted_answer.tool_input,
indent=2, indent=2,
ensure_ascii=False, ensure_ascii=False,
) )
@@ -253,16 +240,6 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
return tool_result return tool_result
def _summarize_messages(self) -> None: 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 = [] messages_groups = []
for message in self.messages: for message in self.messages:
@@ -272,7 +249,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
summarized_contents = [] summarized_contents = []
for group in messages_groups: for group in messages_groups:
summary = llm.call( summary = self.llm.call(
[ [
self._format_msg( self._format_msg(
self._i18n.slices("summarizer_system_message"), role="system" self._i18n.slices("summarizer_system_message"), role="system"
@@ -280,7 +257,8 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
self._format_msg( self._format_msg(
self._i18n.errors("sumamrize_instruction").format(group=group), self._i18n.errors("sumamrize_instruction").format(group=group),
), ),
] ],
callbacks=self.callbacks,
) )
summarized_contents.append(summary) summarized_contents.append(summary)

View File

@@ -22,6 +22,7 @@ from crewai.agent import Agent
from crewai.agents.agent_builder.base_agent import BaseAgent from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.agents.cache import CacheHandler from crewai.agents.cache import CacheHandler
from crewai.crews.crew_output import CrewOutput from crewai.crews.crew_output import CrewOutput
from crewai.llm import LLM
from crewai.memory.entity.entity_memory import EntityMemory from crewai.memory.entity.entity_memory import EntityMemory
from crewai.memory.long_term.long_term_memory import LongTermMemory from crewai.memory.long_term.long_term_memory import LongTermMemory
from crewai.memory.short_term.short_term_memory import ShortTermMemory from crewai.memory.short_term.short_term_memory import ShortTermMemory
@@ -211,11 +212,15 @@ class Crew(BaseModel):
if self.output_log_file: if self.output_log_file:
self._file_handler = FileHandler(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._rpm_controller = RPMController(max_rpm=self.max_rpm, logger=self._logger)
self.function_calling_llm = ( if self.function_calling_llm:
getattr(self.function_calling_llm, "model_name", None) if isinstance(self.function_calling_llm, str):
or getattr(self.function_calling_llm, "deployment_name", None) self.function_calling_llm = LLM(model=self.function_calling_llm)
or 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 = Telemetry()
self._telemetry.set_tracer() self._telemetry.set_tracer()
return self return self

View File

@@ -85,6 +85,3 @@ class LLM:
except Exception as e: except Exception as e:
logging.error(f"LiteLLM call failed: {str(e)}") logging.error(f"LiteLLM call failed: {str(e)}")
raise # Re-raise the exception after logging raise # Re-raise the exception after logging
def __getattr__(self, name):
return self.kwargs.get(name)

View File

@@ -117,8 +117,10 @@ class Telemetry:
"max_iter": agent.max_iter, "max_iter": agent.max_iter,
"max_rpm": agent.max_rpm, "max_rpm": agent.max_rpm,
"i18n": agent.i18n.prompt_file, "i18n": agent.i18n.prompt_file,
"function_calling_llm": agent.function_calling_llm, "function_calling_llm": agent.function_calling_llm.model
"llm": agent.llm, if agent.function_calling_llm
else "",
"llm": agent.llm.model,
"delegation_enabled?": agent.allow_delegation, "delegation_enabled?": agent.allow_delegation,
"allow_code_execution?": agent.allow_code_execution, "allow_code_execution?": agent.allow_code_execution,
"max_retry_limit": agent.max_retry_limit, "max_retry_limit": agent.max_retry_limit,
@@ -182,8 +184,10 @@ class Telemetry:
"verbose?": agent.verbose, "verbose?": agent.verbose,
"max_iter": agent.max_iter, "max_iter": agent.max_iter,
"max_rpm": agent.max_rpm, "max_rpm": agent.max_rpm,
"function_calling_llm": agent.function_calling_llm, "function_calling_llm": agent.function_calling_llm.model
"llm": agent.llm, if agent.function_calling_llm
else "",
"llm": agent.llm.model,
"delegation_enabled?": agent.allow_delegation, "delegation_enabled?": agent.allow_delegation,
"allow_code_execution?": agent.allow_code_execution, "allow_code_execution?": agent.allow_code_execution,
"max_retry_limit": agent.max_retry_limit, "max_retry_limit": agent.max_retry_limit,
@@ -488,7 +492,7 @@ class Telemetry:
"max_iter": agent.max_iter, "max_iter": agent.max_iter,
"max_rpm": agent.max_rpm, "max_rpm": agent.max_rpm,
"i18n": agent.i18n.prompt_file, "i18n": agent.i18n.prompt_file,
"llm": agent.llm, "llm": agent.llm.model,
"delegation_enabled?": agent.allow_delegation, "delegation_enabled?": agent.allow_delegation,
"tools_names": [ "tools_names": [
tool.name.casefold() for tool in agent.tools or [] tool.name.casefold() for tool in agent.tools or []

View File

@@ -72,7 +72,8 @@ class ToolUsage:
# Set the maximum parsing attempts for bigger models # Set the maximum parsing attempts for bigger models
if ( 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 and self.function_calling_llm in OPENAI_BIGGER_MODELS
): ):
self._max_parsing_attempts = 2 self._max_parsing_attempts = 2
@@ -85,6 +86,7 @@ class ToolUsage:
def use( def use(
self, calling: Union[ToolCalling, InstructorToolCalling], tool_string: str self, calling: Union[ToolCalling, InstructorToolCalling], tool_string: str
) -> str: ) -> str:
print("calling", calling)
if isinstance(calling, ToolUsageErrorException): if isinstance(calling, ToolUsageErrorException):
error = calling.message error = calling.message
if self.agent.verbose: if self.agent.verbose:
@@ -299,9 +301,9 @@ class ToolUsage:
def _is_gpt(self, llm) -> bool: def _is_gpt(self, llm) -> bool:
return ( return (
"gpt" in str(llm).lower() "gpt" in str(llm.model).lower()
or "o1-preview" in str(llm).lower() or "o1-preview" in str(llm.model).lower()
or "o1-mini" in str(llm).lower() or "o1-mini" in str(llm.model).lower()
) )
def _tool_calling( def _tool_calling(
@@ -309,11 +311,16 @@ class ToolUsage:
) -> Union[ToolCalling, InstructorToolCalling]: ) -> Union[ToolCalling, InstructorToolCalling]:
try: try:
if self.function_calling_llm: if self.function_calling_llm:
print("self.function_calling_llm")
model = ( model = (
InstructorToolCalling InstructorToolCalling
if self._is_gpt(self.function_calling_llm) if self._is_gpt(self.function_calling_llm)
else ToolCalling else ToolCalling
) )
print("model", model)
print(
"self.function_calling_llm.model", self.function_calling_llm.model
)
converter = Converter( 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}", 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, llm=self.function_calling_llm,
@@ -329,7 +336,15 @@ class ToolUsage:
), ),
max_attempts=1, 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): if isinstance(calling, ConverterError):
raise calling raise calling

View File

@@ -27,8 +27,7 @@ class Converter(OutputConverter):
if self.is_gpt: if self.is_gpt:
return self._create_instructor().to_pydantic() return self._create_instructor().to_pydantic()
else: else:
llm = self._create_llm() return self.llm.call(
return llm.call(
[ [
{"role": "system", "content": self.instructions}, {"role": "system", "content": self.instructions},
{"role": "user", "content": self.text}, {"role": "user", "content": self.text},
@@ -47,9 +46,8 @@ class Converter(OutputConverter):
if self.is_gpt: if self.is_gpt:
return self._create_instructor().to_json() return self._create_instructor().to_json()
else: else:
llm = self._create_llm()
return json.dumps( return json.dumps(
llm.call( self.llm.call(
[ [
{"role": "system", "content": self.instructions}, {"role": "system", "content": self.instructions},
{"role": "user", "content": self.text}, {"role": "user", "content": self.text},
@@ -61,19 +59,6 @@ class Converter(OutputConverter):
return self.to_json(current_attempt + 1) return self.to_json(current_attempt + 1)
return ConverterError(f"Failed to convert text into JSON, error: {e}.") 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): def _create_instructor(self):
"""Create an instructor.""" """Create an instructor."""
from crewai.utilities import InternalInstructor from crewai.utilities import InternalInstructor
@@ -93,7 +78,7 @@ class Converter(OutputConverter):
) )
parser = CrewPydanticOutputParser(pydantic_object=self.model) parser = CrewPydanticOutputParser(pydantic_object=self.model)
result = LLM(model=self.llm).call( result = self.llm.call(
[ [
{"role": "system", "content": self.instructions}, {"role": "system", "content": self.instructions},
{"role": "user", "content": self.text}, {"role": "user", "content": self.text},
@@ -105,9 +90,9 @@ class Converter(OutputConverter):
def is_gpt(self) -> bool: def is_gpt(self) -> bool:
"""Return if llm provided is of gpt from openai.""" """Return if llm provided is of gpt from openai."""
return ( return (
"gpt" in str(self.llm).lower() "gpt" in str(self.llm.model).lower()
or "o1-preview" in str(self.llm).lower() or "o1-preview" in str(self.llm.model).lower()
or "o1-mini" in str(self.llm).lower() or "o1-mini" in str(self.llm.model).lower()
) )
@@ -157,6 +142,7 @@ def handle_partial_json(
converter_cls: Optional[Type[Converter]] = None, converter_cls: Optional[Type[Converter]] = None,
) -> Union[dict, BaseModel, str]: ) -> Union[dict, BaseModel, str]:
match = re.search(r"({.*})", result, re.DOTALL) match = re.search(r"({.*})", result, re.DOTALL)
print("handle_partial_json")
if match: if match:
try: try:
exported_result = model.model_validate_json(match.group(0)) exported_result = model.model_validate_json(match.group(0))
@@ -185,8 +171,11 @@ def convert_with_instructions(
agent: Any, agent: Any,
converter_cls: Optional[Type[Converter]] = None, converter_cls: Optional[Type[Converter]] = None,
) -> Union[dict, BaseModel, str]: ) -> Union[dict, BaseModel, str]:
print("convert_with_instructions")
llm = agent.function_calling_llm or agent.llm llm = agent.function_calling_llm or agent.llm
print("llm", llm)
instructions = get_conversion_instructions(model, llm) instructions = get_conversion_instructions(model, llm)
print("instructions", instructions)
converter = create_converter( converter = create_converter(
agent=agent, agent=agent,
converter_cls=converter_cls, converter_cls=converter_cls,
@@ -195,10 +184,11 @@ def convert_with_instructions(
model=model, model=model,
instructions=instructions, instructions=instructions,
) )
print("converter", converter)
exported_result = ( exported_result = (
converter.to_pydantic() if not is_json_output else converter.to_json() converter.to_pydantic() if not is_json_output else converter.to_json()
) )
print("exported_result", exported_result)
if isinstance(exported_result, ConverterError): if isinstance(exported_result, ConverterError):
Printer().print( Printer().print(
@@ -218,12 +208,12 @@ def get_conversion_instructions(model: Type[BaseModel], llm: Any) -> str:
return instructions return instructions
def is_gpt(llm: Any) -> bool: def is_gpt(llm: LLM) -> bool:
"""Return if llm provided is of gpt from openai.""" """Return if llm provided is of gpt from openai."""
return ( return (
"gpt" in str(llm).lower() "gpt" in str(llm.model).lower()
or "o1-preview" in str(llm).lower() or "o1-preview" in str(llm.model).lower()
or "o1-mini" in str(llm).lower() or "o1-mini" in str(llm.model).lower()
) )

View File

@@ -93,9 +93,9 @@ class TaskEvaluator:
def _is_gpt(self, llm) -> bool: def _is_gpt(self, llm) -> bool:
return ( return (
"gpt" in str(self.llm).lower() "gpt" in str(self.llm.model).lower()
or "o1-preview" in str(self.llm).lower() or "o1-preview" in str(self.llm.model).lower()
or "o1-mini" in str(self.llm).lower() or "o1-mini" in str(self.llm.model).lower()
) )
def evaluate_training_data( def evaluate_training_data(

View File

@@ -42,6 +42,6 @@ class InternalInstructor:
if self.instructions: if self.instructions:
messages.append({"role": "system", "content": self.instructions}) messages.append({"role": "system", "content": self.instructions})
model = self._client.chat.completions.create( 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 return model