Adding new tool usage and parsing logic

This commit is contained in:
João Moura
2024-02-19 22:43:10 -03:00
parent 6da94c1bba
commit 3cfc8dd4e0
9 changed files with 256 additions and 101 deletions

View File

@@ -1,12 +1,13 @@
import os import os
import uuid import uuid
from typing import Any, List, Optional from typing import Any, List, Optional, Tuple
from crewai_tools import BaseTool as CrewAITool
from langchain.agents.agent import RunnableAgent from langchain.agents.agent import RunnableAgent
from langchain.agents.format_scratchpad import format_log_to_str from langchain.agents.tools import tool as LangChainTool
from langchain.agents.output_parsers import ReActSingleInputOutputParser
from langchain.memory import ConversationSummaryMemory from langchain.memory import ConversationSummaryMemory
from langchain.tools.render import render_text_description from langchain.tools.render import render_text_description
from langchain_core.agents import AgentAction
from langchain_openai import ChatOpenAI from langchain_openai import ChatOpenAI
from pydantic import ( from pydantic import (
UUID4, UUID4,
@@ -20,7 +21,7 @@ from pydantic import (
) )
from pydantic_core import PydanticCustomError from pydantic_core import PydanticCustomError
from crewai.agents import CacheHandler, CrewAgentExecutor, ToolsHandler from crewai.agents import CacheHandler, CrewAgentExecutor, CrewAgentParser, ToolsHandler
from crewai.utilities import I18N, Logger, Prompts, RPMController from crewai.utilities import I18N, Logger, Prompts, RPMController
@@ -73,7 +74,7 @@ class Agent(BaseModel):
allow_delegation: bool = Field( allow_delegation: bool = Field(
default=True, description="Allow delegation of tasks to agents" default=True, description="Allow delegation of tasks to agents"
) )
tools: List[Any] = Field( tools: Optional[List[Any]] = Field(
default_factory=list, description="Tools at agents disposal" default_factory=list, description="Tools at agents disposal"
) )
max_iter: Optional[int] = Field( max_iter: Optional[int] = Field(
@@ -151,7 +152,7 @@ class Agent(BaseModel):
task=task_prompt, context=context task=task_prompt, context=context
) )
tools = tools or self.tools tools = self._parse_tools(tools or self.tools)
self.agent_executor.tools = tools self.agent_executor.tools = tools
self.agent_executor.task = task self.agent_executor.task = task
self.agent_executor.tools_description = render_text_description(tools) self.agent_executor.tools_description = render_text_description(tools)
@@ -200,12 +201,15 @@ class Agent(BaseModel):
"input": lambda x: x["input"], "input": lambda x: x["input"],
"tools": lambda x: x["tools"], "tools": lambda x: x["tools"],
"tool_names": lambda x: x["tool_names"], "tool_names": lambda x: x["tool_names"],
"agent_scratchpad": lambda x: format_log_to_str(x["intermediate_steps"]), "agent_scratchpad": lambda x: self.format_log_to_str(
x["intermediate_steps"]
),
} }
executor_args = { executor_args = {
"llm": self.llm, "llm": self.llm,
"i18n": self.i18n, "i18n": self.i18n,
"tools": self.tools, "tools": self._parse_tools(self.tools),
"verbose": self.verbose, "verbose": self.verbose,
"handle_parsing_errors": True, "handle_parsing_errors": True,
"max_iterations": self.max_iter, "max_iterations": self.max_iter,
@@ -225,9 +229,11 @@ class Agent(BaseModel):
) )
executor_args["memory"] = summary_memory executor_args["memory"] = summary_memory
agent_args["chat_history"] = lambda x: x["chat_history"] agent_args["chat_history"] = lambda x: x["chat_history"]
prompt = Prompts(i18n=self.i18n).task_execution_with_memory() prompt = Prompts(
i18n=self.i18n, tools=self.tools
).task_execution_with_memory()
else: else:
prompt = Prompts(i18n=self.i18n).task_execution() prompt = Prompts(i18n=self.i18n, tools=self.tools).task_execution()
execution_prompt = prompt.partial( execution_prompt = prompt.partial(
goal=self.goal, goal=self.goal,
@@ -236,13 +242,34 @@ class Agent(BaseModel):
) )
bind = self.llm.bind(stop=[self.i18n.slice("observation")]) bind = self.llm.bind(stop=[self.i18n.slice("observation")])
inner_agent = ( inner_agent = agent_args | execution_prompt | bind | CrewAgentParser()
agent_args | execution_prompt | bind | ReActSingleInputOutputParser()
)
self.agent_executor = CrewAgentExecutor( self.agent_executor = CrewAgentExecutor(
agent=RunnableAgent(runnable=inner_agent), **executor_args agent=RunnableAgent(runnable=inner_agent), **executor_args
) )
def _parse_tools(self, tools: List[Any]) -> List[LangChainTool]:
"""Parse tools to be used for the task."""
tools_list = []
for tool in tools:
if isinstance(tool, CrewAITool):
tools_list.append(tool.to_langchain())
else:
tools_list.append(tool)
return tools_list
def format_log_to_str(
self,
intermediate_steps: List[Tuple[AgentAction, str]],
observation_prefix: str = "Result: ",
llm_prefix: str = "Thought: ",
) -> str:
"""Construct the scratchpad that lets the agent continue its thought process."""
thoughts = ""
for action, observation in intermediate_steps:
thoughts += action.log
thoughts += f"\n{observation_prefix}{observation}\n{llm_prefix}"
return thoughts
@staticmethod @staticmethod
def __tools_names(tools) -> str: def __tools_names(tools) -> str:
return ", ".join([t.name for t in tools]) return ", ".join([t.name for t in tools])

View File

@@ -1,3 +1,4 @@
from .cache.cache_handler import CacheHandler from .cache.cache_handler import CacheHandler
from .executor import CrewAgentExecutor from .executor import CrewAgentExecutor
from .parser import CrewAgentParser
from .tools_handler import ToolsHandler from .tools_handler import ToolsHandler

View File

@@ -18,7 +18,7 @@ from crewai.utilities import I18N
class CrewAgentExecutor(AgentExecutor): class CrewAgentExecutor(AgentExecutor):
i18n: I18N = I18N() _i18n: I18N = I18N()
llm: Any = None llm: Any = None
iterations: int = 0 iterations: int = 0
task: Any = None task: Any = None
@@ -105,14 +105,12 @@ class CrewAgentExecutor(AgentExecutor):
""" """
try: try:
intermediate_steps = self._prepare_intermediate_steps(intermediate_steps) intermediate_steps = self._prepare_intermediate_steps(intermediate_steps)
# Call the LLM to see what to do. # Call the LLM to see what to do.
output = self.agent.plan( output = self.agent.plan(
intermediate_steps, intermediate_steps,
callbacks=run_manager.get_child() if run_manager else None, callbacks=run_manager.get_child() if run_manager else None,
**inputs, **inputs,
) )
if self._should_force_answer(): if self._should_force_answer():
if isinstance(output, AgentAction) or isinstance(output, AgentFinish): if isinstance(output, AgentAction) or isinstance(output, AgentFinish):
output = output output = output
@@ -121,7 +119,7 @@ class CrewAgentExecutor(AgentExecutor):
f"Unexpected output type from agent: {type(output)}" f"Unexpected output type from agent: {type(output)}"
) )
yield AgentStep( yield AgentStep(
action=output, observation=self.i18n.errors("force_final_answer") action=output, observation=self._i18n.errors("force_final_answer")
) )
return return
@@ -140,14 +138,14 @@ class CrewAgentExecutor(AgentExecutor):
text = str(e) text = str(e)
if isinstance(self.handle_parsing_errors, bool): if isinstance(self.handle_parsing_errors, bool):
if e.send_to_llm: if e.send_to_llm:
observation = str(e.observation) observation = f"\n{str(e.observation)}"
text = str(e.llm_output) text = str(e.llm_output)
else: else:
observation = "Invalid or incomplete response" observation = "Invalid or incomplete response"
elif isinstance(self.handle_parsing_errors, str): elif isinstance(self.handle_parsing_errors, str):
observation = self.handle_parsing_errors observation = f"\n{self.handle_parsing_errors}"
elif callable(self.handle_parsing_errors): elif callable(self.handle_parsing_errors):
observation = self.handle_parsing_errors(e) observation = f"\n{self.handle_parsing_errors(e)}"
else: else:
raise ValueError("Got unexpected type of `handle_parsing_errors`") raise ValueError("Got unexpected type of `handle_parsing_errors`")
output = AgentAction("_Exception", observation, text) output = AgentAction("_Exception", observation, text)
@@ -164,7 +162,7 @@ class CrewAgentExecutor(AgentExecutor):
if self._should_force_answer(): if self._should_force_answer():
yield AgentStep( yield AgentStep(
action=output, observation=self.i18n.errors("force_final_answer") action=output, observation=self._i18n.errors("force_final_answer")
) )
return return
@@ -183,27 +181,26 @@ class CrewAgentExecutor(AgentExecutor):
if run_manager: if run_manager:
run_manager.on_agent_action(agent_action, color="green") run_manager.on_agent_action(agent_action, color="green")
# Otherwise we lookup the tool # Otherwise we lookup the tool
if agent_action.tool in name_to_tool_map: tool_usage = ToolUsage(
tool = name_to_tool_map[agent_action.tool] tools_handler=self.tools_handler,
return_direct = tool.return_direct tools=self.tools,
color_mapping[agent_action.tool] tools_description=self.tools_description,
tool_run_kwargs = self.agent.tool_run_logging_kwargs() tools_names=self.tools_names,
if return_direct: function_calling_llm=self.function_calling_llm,
tool_run_kwargs["llm_prefix"] = "" llm=self.llm,
observation = ToolUsage( task=self.task,
tools_handler=self.tools_handler, )
tools=self.tools, tool_calling = tool_usage.parse(agent_action.log)
tools_description=self.tools_description,
tools_names=self.tools_names, if tool_calling.tool_name.lower().strip() in [
function_calling_llm=self.function_calling_llm, name.lower().strip() for name in name_to_tool_map
llm=self.llm, ]:
task=self.task, observation = tool_usage.use(tool_calling, agent_action.log)
).use(agent_action.log)
else: else:
tool_run_kwargs = self.agent.tool_run_logging_kwargs() tool_run_kwargs = self.agent.tool_run_logging_kwargs()
observation = InvalidTool().run( observation = InvalidTool().run(
{ {
"requested_tool_name": agent_action.tool, "requested_tool_name": tool_calling.tool_name,
"available_tool_names": list(name_to_tool_map.keys()), "available_tool_names": list(name_to_tool_map.keys()),
}, },
verbose=self.verbose, verbose=self.verbose,

View File

@@ -0,0 +1,61 @@
from typing import Union
from langchain.agents.output_parsers import ReActSingleInputOutputParser
from langchain_core.agents import AgentAction, AgentFinish
from langchain_core.exceptions import OutputParserException
from crewai.utilities import I18N
TOOL_USAGE_SECTION = "Use Tool:"
FINAL_ANSWER_ACTION = "Final Answer:"
FINAL_ANSWER_AND_TOOL_ERROR_MESSAGE = "You are trying to use a tool and give a final answer at the same time, choose only one."
class CrewAgentParser(ReActSingleInputOutputParser):
"""Parses Crew-style LLM calls that have a single tool input.
Expects output to be in one of two formats.
If the output signals that an action should be taken,
should be in the below format. This will result in an AgentAction
being returned.
```
Use Tool: All context for using the tool here
```
If the output signals that a final answer should be given,
should be in the below format. This will result in an AgentFinish
being returned.
```
Final Answer: The temperature is 100 degrees
```
"""
_i18n: I18N = I18N()
def parse(self, text: str) -> Union[AgentAction, AgentFinish]:
includes_answer = FINAL_ANSWER_ACTION in text
includes_tool = TOOL_USAGE_SECTION in text
if includes_tool:
if includes_answer:
raise OutputParserException(f"{FINAL_ANSWER_AND_TOOL_ERROR_MESSAGE}")
return AgentAction("", "", text)
elif includes_answer:
return AgentFinish(
{"output": text.split(FINAL_ANSWER_ACTION)[-1].strip()}, text
)
error = self._i18n.errors("unexpected_format")
format = self._i18n.slice("format_without_tools")
error = f"{error}\n{format}"
raise OutputParserException(
error,
observation=error,
llm_output=text,
send_to_llm=True,
)

View File

@@ -1,6 +1,6 @@
from textwrap import dedent
from typing import Any, List, Union from typing import Any, List, Union
import instructor
from langchain.prompts import PromptTemplate from langchain.prompts import PromptTemplate
from langchain_core.tools import BaseTool from langchain_core.tools import BaseTool
from langchain_openai import ChatOpenAI from langchain_openai import ChatOpenAI
@@ -9,7 +9,9 @@ from crewai.agents.tools_handler import ToolsHandler
from crewai.telemtry import Telemetry from crewai.telemtry import Telemetry
from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling
from crewai.tools.tool_output_parser import ToolOutputParser from crewai.tools.tool_output_parser import ToolOutputParser
from crewai.utilities import I18N, Printer from crewai.utilities import I18N, Instructor, Printer
OPENAI_BIGGER_MODELS = ["gpt-4"]
class ToolUsageErrorException(Exception): class ToolUsageErrorException(Exception):
@@ -31,6 +33,7 @@ class ToolUsage:
tools_description: Description of the tools available for the agent. tools_description: Description of the tools available for the agent.
tools_names: Names of the tools available for the agent. tools_names: Names of the tools available for the agent.
llm: Language model to be used for the tool usage. llm: Language model to be used for the tool usage.
function_calling_llm: Language model to be used for the tool usage.
""" """
def __init__( def __init__(
@@ -47,18 +50,28 @@ class ToolUsage:
self._printer: Printer = Printer() self._printer: Printer = Printer()
self._telemetry: Telemetry = Telemetry() self._telemetry: Telemetry = Telemetry()
self._run_attempts: int = 1 self._run_attempts: int = 1
self._max_parsing_attempts: int = 2 self._max_parsing_attempts: int = 3
self._remeber_format_after_usages: int = 3 self._remeber_format_after_usages: int = 3
self.tools_description = tools_description self.tools_description = tools_description
self.tools_names = tools_names self.tools_names = tools_names
self.tools_handler = tools_handler self.tools_handler = tools_handler
self.tools = tools self.tools = tools
self.task = task self.task = task
self.llm = llm self.llm = function_calling_llm or llm
self.function_calling_llm = function_calling_llm
def use(self, tool_string: str): # Set the maximum parsing attempts for bigger models
calling = self._tool_calling(tool_string) if (isinstance(self.llm, ChatOpenAI)) and (self.llm.openai_api_base == None):
if self.llm.model_name in OPENAI_BIGGER_MODELS:
self._max_parsing_attempts = 2
self._remeber_format_after_usages = 4
def parse(self, tool_string: str):
"""Parse the tool string and return the tool calling."""
return self._tool_calling(tool_string)
def use(
self, calling: Union[ToolCalling, InstructorToolCalling], tool_string: str
) -> str:
if isinstance(calling, ToolUsageErrorException): if isinstance(calling, ToolUsageErrorException):
error = calling.message error = calling.message
self._printer.print(content=f"\n\n{error}\n", color="red") self._printer.print(content=f"\n\n{error}\n", color="red")
@@ -69,7 +82,7 @@ class ToolUsage:
error = getattr(e, "message", str(e)) error = getattr(e, "message", str(e))
self._printer.print(content=f"\n\n{error}\n", color="red") self._printer.print(content=f"\n\n{error}\n", color="red")
return error return error
return self._use(tool_string=tool_string, tool=tool, calling=calling) return f"{self._use(tool_string=tool_string, tool=tool, calling=calling)}\n{self._i18n.slice('final_answer_format')}"
def _use( def _use(
self, self,
@@ -106,11 +119,11 @@ class ToolUsage:
if self._run_attempts > self._max_parsing_attempts: if self._run_attempts > self._max_parsing_attempts:
self._telemetry.tool_usage_error(llm=self.llm) self._telemetry.tool_usage_error(llm=self.llm)
error = ToolUsageErrorException( error = ToolUsageErrorException(
self._i18n.errors("tool_usage_exception").format(error=e) f'{self._i18n.errors("tool_usage_exception").format(error=e)}.\n{self._i18n.slice("format").format(tool_names=self.tools_names)}'
).message ).message
self._printer.print(content=f"\n\n{error}\n", color="red") self._printer.print(content=f"\n\n{error}\n", color="red")
return error return error
return self.use(tool_string=tool_string) return self.use(calling=calling, tool_string=tool_string)
self.tools_handler.on_tool_use(calling=calling, output=result) self.tools_handler.on_tool_use(calling=calling, output=result)
@@ -174,69 +187,55 @@ class ToolUsage:
self, tool_string: str self, tool_string: str
) -> Union[ToolCalling, InstructorToolCalling]: ) -> Union[ToolCalling, InstructorToolCalling]:
try: try:
tool_string = tool_string.replace( if (isinstance(self.llm, ChatOpenAI)) and (
"Thought: Do I need to use a tool? Yes", "" self.llm.openai_api_base == None
) ):
tool_string = tool_string.replace("Action:", "Tool Name:") instructor = Instructor(
tool_string = tool_string.replace("Action Input:", "Tool Arguments:") llm=self.llm,
model=InstructorToolCalling,
content=f"Tools available:\n\n{self._render()}\n\nReturn a valid schema for the tool, the tool name must be equal one of the options, use this text to inform a valid ouput schema:\n{tool_string}```",
instructions=dedent(
"""\
The schema should have the following structure, only two keys:
- tool_name: str
- arguments: dict (with all arguments being passed)
llm = self.function_calling_llm or self.llm Example:
{"tool_name": "tool_name", "arguments": {"arg_name1": "value", "arg_name2": 2}}
if (isinstance(llm, ChatOpenAI)) and (llm.openai_api_base == None): """
client = instructor.patch( ),
llm.client._client,
mode=instructor.Mode.FUNCTIONS,
)
calling = client.chat.completions.create(
model=llm.model_name,
messages=[
{
"role": "system",
"content": """
The schema should have the following structure, only two key:
- tool_name: str
- arguments: dict (with all arguments being passed)
Example:
{"tool_name": "tool_name", "arguments": {"arg_name1": "value", "arg_name2": 2}}
""",
},
{
"role": "user",
"content": f"Tools available:\n\n{self._render()}\n\nReturn a valid schema for the tool, use this text to inform a valid ouput schema:\n{tool_string}```",
},
],
response_model=InstructorToolCalling,
) )
calling = instructor.to_pydantic()
else: else:
parser = ToolOutputParser(pydantic_object=ToolCalling) parser = ToolOutputParser(pydantic_object=ToolCalling)
prompt = PromptTemplate( prompt = PromptTemplate(
template="Tools available:\n\n{available_tools}\n\nReturn a valid schema for the tool, use this text to inform a valid ouput schema:\n{tool_string}\n\n{format_instructions}\n```", template="Tools available:\n\n{available_tools}\n\nReturn a valid schema for the tool, the tool name must be equal one of the options, use this text to inform a valid ouput schema:\n{tool_string}\n\n{format_instructions}\n```",
input_variables=["tool_string"], input_variables=["tool_string"],
partial_variables={ partial_variables={
"available_tools": self._render(), "available_tools": self._render(),
"format_instructions": """ "format_instructions": dedent(
The schema should have the following structure, only two key: """\
The schema should have the following structure, only two keys:
- tool_name: str - tool_name: str
- arguments: dict (with all arguments being passed) - arguments: dict (with all arguments being passed)
Example: Example:
{"tool_name": "tool_name", "arguments": {"arg_name1": "value", "arg_name2": 2}} {"tool_name": "tool_name", "arguments": {"arg_name1": "value", "arg_name2": 2}}
""", """
),
}, },
) )
chain = prompt | llm | parser chain = prompt | self.llm | parser
calling = chain.invoke({"tool_string": tool_string}) calling = chain.invoke({"tool_string": tool_string})
except Exception as e: except Exception as e:
self._run_attempts += 1 self._run_attempts += 1
if self._run_attempts > self._max_parsing_attempts: if self._run_attempts > self._max_parsing_attempts:
self._telemetry.tool_usage_error(llm=llm) self._telemetry.tool_usage_error(llm=self.llm)
error = ToolUsageErrorException( self._printer.print(content=f"\n\n{e}\n", color="red")
self._i18n.errors("tool_usage_exception").format(error=e) return ToolUsageErrorException(
).message f'{self._i18n.errors("tool_usage_error")}.\n{self._i18n.slice("format").format(tool_names=self.tools_names)}'
self._printer.print(content=f"\n\n{error}\n", color="red") )
return error
return self._tool_calling(tool_string) return self._tool_calling(tool_string)
return calling return calling

View File

@@ -5,18 +5,23 @@
"backstory": "You are a seasoned manager with a knack for getting the best out of your team.\nYou are also known for your ability to delegate work to the right people, and to ask the right questions to get the best out of your team.\nEven though you don't perform tasks by yourself, you have a lot of experience in the field, which allows you to properly evaluate the work of your team members." "backstory": "You are a seasoned manager with a knack for getting the best out of your team.\nYou are also known for your ability to delegate work to the right people, and to ask the right questions to get the best out of your team.\nEven though you don't perform tasks by yourself, you have a lot of experience in the field, which allows you to properly evaluate the work of your team members."
}, },
"slices": { "slices": {
"observation": "\nObservation", "observation": "\nResult",
"task": "Begin! This is VERY important to you, your job depends on it!\n\nCurrent Task: {input}", "task": "Begin! This is VERY important to you, your job depends on it!\n\nCurrent Task: {input}",
"memory": "This is the summary of your work so far:\n{chat_history}", "memory": "This is the summary of your work so far:\n{chat_history}",
"role_playing": "You are {role}.\n{backstory}\n\nYour personal goal is: {goal}", "role_playing": "You are {role}.\n{backstory}\n\nYour personal goal is: {goal}",
"tools": "TOOLS:\n------\nYou have access to only the following tools:\n\n{tools}\n\nTo use a tool, please use the exact following format:\n\n```\nThought: Do I need to use a tool? Yes\nAction: the tool you wanna use, should be one of [{tool_names}], just the name.\nAction Input: Any and all relevant information input and context for using the tool\nObservation: the result of using the tool\n```\n\nWhen you have a response for your task, or if you do not need to use a tool, you MUST use the format:\n\n```\nThought: Do I need to use a tool? No\nFinal Answer: [your response here]```", "tools": "You have access to ONLY the following tools, use one at time:\n\n{tools}\n\nTo use a tool you MUST use the exact following format:\n\n```\nUse Tool: the tool you wanna use, should be one of [{tool_names}] and absolute all relevant input and context for using the tool, you must use only one tool at once.\nResult: [result of the tool]\n```\n\nTo complete the task you MUST follow the format:\n\n```\nFinal Answer: [THE MOST COMPLETE ANSWE WITH ALL CONTEXT, DO NOT LEAVE ANYTHING OUT]\n``` You must use these formats, my life depends on it.",
"no_tools": "To complete the task you MUST follow the format:\n\n```\nFinal Answer: [your most complete final answer goes here]\n``` You must use these formats, my life depends on it.",
"format": "To use a tool you MUST use the exact following format:\n\n```\nUse Tool: the tool you wanna use, should be one of [{tool_names}] and absolute all relevant input and context for using the tool, you must use only one tool at once.\nResult: [result of the tool]\n```\n\nTo complete the task you MUST follow the format:\n\n```\nFinal Answer: [your most complete final answer goes here]\n``` You must use these formats, my life depends on it.",
"final_answer_format": "If you don't need to use any more tools, use the correct format for your final answer:\n\n```Final Answer: [your most complete final answer goes here]```",
"format_without_tools": "To use a tool you MUST use the exact following format:\n\n```\nUse Tool: the tool you wanna use, and absolute all relevant input and context for using the tool, you must use only one tool at once.\nResult: [result of the tool]\n```\n\nTo complete the task you MUST follow the format:\n\n```\nFinal Answer: [your most complete final answer goes here]\n``` You must use these formats, my life depends on it.",
"task_with_context": "{task}\nThis is the context you're working with:\n{context}", "task_with_context": "{task}\nThis is the context you're working with:\n{context}",
"expected_output": "Your final answer must be: {expected_output}" "expected_output": "Your final answer must be: {expected_output}"
}, },
"errors": { "errors": {
"force_final_answer": "Actually, I used too many tools, so I'll stop now and give you my absolute BEST Final answer NOW, using exaclty the expected format bellow: \n```\nThought: Do I need to use a tool? No\nFinal Answer: [your response here]```", "unexpected_format": "You didn't use the expected format, you MUST use a tool or give your best final answer.",
"force_final_answer": "Actually, I used too many tools, so I'll stop now and give you my absolute BEST Final answer NOW, using exaclty the expected format bellow:\n\n```\nFinal Answer: [your most complete final answer goes here]\n``` You must use these formats, my life depends on it.",
"agent_tool_unexsiting_coworker": "\nError executing tool. Co-worker mentioned on the Action Input not found, it must to be one of the following options:\n{coworkers}.\n", "agent_tool_unexsiting_coworker": "\nError executing tool. Co-worker mentioned on the Action Input not found, it must to be one of the following options:\n{coworkers}.\n",
"task_repeated_usage": "I just used the {tool} tool with input {tool_input}. So I already know that and must stop using it in a row with the same input. \nI could give my final answer if I'm ready, using exaclty the expected format bellow: \n\nThought: Do I need to use a tool? No\nFinal Answer: [your response here]\n", "task_repeated_usage": "I already used the {tool} tool with input {tool_input}. So I already know that and must stop using it with same input. \nI could give my best complete final answer if I'm ready, using exaclty the expected format bellow:\n\n```\nFinal Answer: [your most complete final answer goes here]\n``` You must use these formats, my life depends on it.",
"tool_usage_error": "It seems we encountered an unexpected error while trying to use the tool.", "tool_usage_error": "It seems we encountered an unexpected error while trying to use the tool.",
"tool_usage_exception": "It seems we encountered an unexpected error while trying to use the tool. This was the error: {error}" "tool_usage_exception": "It seems we encountered an unexpected error while trying to use the tool. This was the error: {error}"
}, },

View File

@@ -1,4 +1,5 @@
from .i18n import I18N from .i18n import I18N
from .instructor import Instructor
from .logger import Logger from .logger import Logger
from .printer import Printer from .printer import Printer
from .prompts import Prompts from .prompts import Prompts

View File

@@ -0,0 +1,51 @@
from typing import Any, Optional, Type
import instructor
from pydantic import BaseModel, Field, PrivateAttr, model_validator
class Instructor(BaseModel):
"""Class that wraps an agent llm with instructor."""
_client: Any = PrivateAttr()
content: str = Field(description="Content to be sent to the instructor.")
agent: Optional[Any] = Field(
description="The agent that needs to use instructor.", default=None
)
llm: Optional[Any] = Field(
description="The agent that needs to use instructor.", default=None
)
instructions: Optional[str] = Field(
description="Instructions to be sent to the instructor.",
default=None,
)
model: Type[BaseModel] = Field(
description="Pydantic model to be used to create an output."
)
@model_validator(mode="after")
def set_instructor(self):
"""Set instructor."""
if self.agent and not self.llm:
self.llm = self.agent.function_calling_llm or self.agent.llm
self._client = instructor.patch(
self.llm.client._client,
mode=instructor.Mode.TOOLS,
)
return self
def to_json(self):
model = self.to_pydantic()
return model.model_dump_json(indent=2)
def to_pydantic(self):
messages = [{"role": "user", "content": self.content}]
if self.instructions:
messages.append({"role": "system", "content": self.instructions})
model = self._client.chat.completions.create(
model=self.llm.model_name, response_model=self.model, messages=messages
)
return model

View File

@@ -1,6 +1,6 @@
from typing import ClassVar from typing import Any, ClassVar
from langchain.prompts import PromptTemplate, BasePromptTemplate from langchain.prompts import BasePromptTemplate, PromptTemplate
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from crewai.utilities import I18N from crewai.utilities import I18N
@@ -10,12 +10,18 @@ class Prompts(BaseModel):
"""Manages and generates prompts for a generic agent with support for different languages.""" """Manages and generates prompts for a generic agent with support for different languages."""
i18n: I18N = Field(default=I18N()) i18n: I18N = Field(default=I18N())
tools: list[Any] = Field(default=[])
SCRATCHPAD_SLICE: ClassVar[str] = "\n{agent_scratchpad}" SCRATCHPAD_SLICE: ClassVar[str] = "\n{agent_scratchpad}"
def task_execution_with_memory(self) -> BasePromptTemplate: def task_execution_with_memory(self) -> BasePromptTemplate:
"""Generate a prompt for task execution with memory components.""" """Generate a prompt for task execution with memory components."""
return self._build_prompt(["role_playing", "tools", "memory", "task"]) slices = ["role_playing"]
if len(self.tools) > 0:
slices.append("tools")
else:
slices.append("no_tools")
slices.extend(["memory", "task"])
return self._build_prompt(slices)
def task_execution_without_tools(self) -> BasePromptTemplate: def task_execution_without_tools(self) -> BasePromptTemplate:
"""Generate a prompt for task execution without tools components.""" """Generate a prompt for task execution without tools components."""
@@ -23,10 +29,17 @@ class Prompts(BaseModel):
def task_execution(self) -> BasePromptTemplate: def task_execution(self) -> BasePromptTemplate:
"""Generate a standard prompt for task execution.""" """Generate a standard prompt for task execution."""
return self._build_prompt(["role_playing", "tools", "task"]) slices = ["role_playing"]
if len(self.tools) > 0:
slices.append("tools")
else:
slices.append("no_tools")
slices.append("task")
return self._build_prompt(slices)
def _build_prompt(self, components: list[str]) -> BasePromptTemplate: def _build_prompt(self, components: list[str]) -> BasePromptTemplate:
"""Constructs a prompt string from specified components.""" """Constructs a prompt string from specified components."""
prompt_parts = [self.i18n.slice(component) for component in components] prompt_parts = [self.i18n.slice(component) for component in components]
prompt_parts.append(self.SCRATCHPAD_SLICE) prompt_parts.append(self.SCRATCHPAD_SLICE)
return PromptTemplate.from_template("".join(prompt_parts)) prompt = PromptTemplate.from_template("".join(prompt_parts))
return prompt