Bringing support to o1 family back + any models that don't support stop words

This commit is contained in:
João Moura
2024-09-24 22:18:20 -03:00
parent a1023739e0
commit cbb858d375
104 changed files with 56051 additions and 65319 deletions

View File

@@ -74,10 +74,6 @@ class Agent(BaseAgent):
default=None,
description="Callback to be executed after each step of the agent execution.",
)
use_stop_words: bool = Field(
default=True,
description="Use stop words for the agent.",
)
use_system_prompt: Optional[bool] = Field(
default=True,
description="Use system prompt for the agent.",
@@ -291,7 +287,6 @@ class Agent(BaseAgent):
stop_words=stop_words,
max_iter=self.max_iter,
tools_handler=self.tools_handler,
use_stop_words=self.use_stop_words,
tools_names=self.__tools_names(parsed_tools),
tools_description=self._render_text_description_and_args(parsed_tools),
step_callback=self.step_callback,

View File

@@ -34,7 +34,6 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
max_iter: int,
tools: List[Any],
tools_names: str,
use_stop_words: bool,
stop_words: List[str],
tools_description: str,
tools_handler: ToolsHandler,
@@ -60,7 +59,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
self.tools_handler = tools_handler
self.original_tools = original_tools
self.step_callback = step_callback
self.use_stop_words = use_stop_words
self.use_stop_words = self.llm.supports_stop_words()
self.tools_description = tools_description
self.function_calling_llm = function_calling_llm
self.respect_context_window = respect_context_window
@@ -146,6 +145,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
self.messages.append(
self._format_msg(formatted_answer.text, role="user")
)
except OutputParserException as e:
self.messages.append({"role": "user", "content": e.error})
return self._invoke_loop(formatted_answer)

View File

@@ -78,6 +78,7 @@ class LLM:
"api_key": self.api_key,
**self.kwargs,
}
# Remove None values to avoid passing unnecessary parameters
params = {k: v for k, v in params.items() if v is not None}
@@ -94,3 +95,11 @@ class LLM:
except Exception as e:
logging.error(f"Failed to get supported params: {str(e)}")
return False
def supports_stop_words(self) -> bool:
try:
params = get_supported_openai_params(model=self.model)
return "stop" in params
except Exception as e:
logging.error(f"Failed to get supported params: {str(e)}")
return False

View File

@@ -301,7 +301,7 @@ class Telemetry:
self._add_attribute(span, "tool_name", tool_name)
self._add_attribute(span, "attempts", attempts)
if llm:
self._add_attribute(span, "llm", llm)
self._add_attribute(span, "llm", llm.model)
span.set_status(Status(StatusCode.OK))
span.end()
except Exception:
@@ -321,7 +321,7 @@ class Telemetry:
self._add_attribute(span, "tool_name", tool_name)
self._add_attribute(span, "attempts", attempts)
if llm:
self._add_attribute(span, "llm", llm)
self._add_attribute(span, "llm", llm.model)
span.set_status(Status(StatusCode.OK))
span.end()
except Exception:
@@ -339,7 +339,7 @@ class Telemetry:
pkg_resources.get_distribution("crewai").version,
)
if llm:
self._add_attribute(span, "llm", llm)
self._add_attribute(span, "llm", llm.model)
span.set_status(Status(StatusCode.OK))
span.end()
except Exception:

View File

@@ -80,6 +80,7 @@ class ToolUsage:
def parse(self, tool_string: str):
"""Parse the tool string and return the tool calling."""
print("tool_string", tool_string)
return self._tool_calling(tool_string)
def use(
@@ -297,59 +298,78 @@ class ToolUsage:
)
return "\n--\n".join(descriptions)
def _function_calling(self, tool_string: str):
model = (
InstructorToolCalling
if self.function_calling_llm.supports_function_calling()
else ToolCalling
)
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,
model=model,
instructions=dedent(
"""\
The schema should have the following structure, only two keys:
- tool_name: str
- arguments: dict (always a dictionary, with all arguments being passed)
Example:
{"tool_name": "tool name", "arguments": {"arg_name1": "value", "arg_name2": 2}}""",
),
max_attempts=1,
)
tool_object = converter.to_pydantic()
calling = ToolCalling(
tool_name=tool_object["tool_name"],
arguments=tool_object["arguments"],
log=tool_string, # type: ignore
)
if isinstance(calling, ConverterError):
raise calling
return calling
def _original_tool_calling(self, tool_string: str, raise_error: bool = False):
tool_name = self.action.tool
tool = self._select_tool(tool_name)
try:
tool_input = self._validate_tool_input(self.action.tool_input)
arguments = ast.literal_eval(tool_input)
except Exception:
if raise_error:
raise
else:
return ToolUsageErrorException( # type: ignore # Incompatible return value type (got "ToolUsageErrorException", expected "ToolCalling | InstructorToolCalling")
f'{self._i18n.errors("tool_arguments_error")}'
)
if not isinstance(arguments, dict):
if raise_error:
raise
else:
return ToolUsageErrorException( # type: ignore # Incompatible return value type (got "ToolUsageErrorException", expected "ToolCalling | InstructorToolCalling")
f'{self._i18n.errors("tool_arguments_error")}'
)
return ToolCalling(
tool_name=tool.name,
arguments=arguments,
log=tool_string, # type: ignore
)
def _tool_calling(
self, tool_string: str
) -> Union[ToolCalling, InstructorToolCalling]:
try:
if self.function_calling_llm:
model = (
InstructorToolCalling
if self.function_calling_llm.supports_function_calling()
else ToolCalling
)
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,
model=model,
instructions=dedent(
"""\
The schema should have the following structure, only two keys:
- tool_name: str
- arguments: dict (with all arguments being passed)
Example:
{"tool_name": "tool name", "arguments": {"arg_name1": "value", "arg_name2": 2}}""",
),
max_attempts=1,
)
tool_object = converter.to_pydantic()
calling = ToolCalling(
tool_name=tool_object["tool_name"],
arguments=tool_object["arguments"],
log=tool_string, # type: ignore
)
if isinstance(calling, ConverterError):
raise calling
else:
tool_name = self.action.tool
tool = self._select_tool(tool_name)
try:
tool_input = self._validate_tool_input(self.action.tool_input)
arguments = ast.literal_eval(tool_input)
except Exception:
return ToolUsageErrorException( # type: ignore # Incompatible return value type (got "ToolUsageErrorException", expected "ToolCalling | InstructorToolCalling")
f'{self._i18n.errors("tool_arguments_error")}'
)
if not isinstance(arguments, dict):
return ToolUsageErrorException( # type: ignore # Incompatible return value type (got "ToolUsageErrorException", expected "ToolCalling | InstructorToolCalling")
f'{self._i18n.errors("tool_arguments_error")}'
)
calling = ToolCalling(
tool_name=tool.name,
arguments=arguments,
log=tool_string, # type: ignore
)
try:
return self._original_tool_calling(tool_string, raise_error=True)
except Exception:
if self.function_calling_llm:
return self._function_calling(tool_string)
else:
return self._original_tool_calling(tool_string)
except Exception as e:
self._run_attempts += 1
if self._run_attempts > self._max_parsing_attempts:
@@ -362,8 +382,6 @@ class ToolUsage:
)
return self._tool_calling(tool_string)
return calling
def _validate_tool_input(self, tool_input: str) -> str:
try:
ast.literal_eval(tool_input)