mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-10 16:48:30 +00:00
Adding new tool usage and parsing logic
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
import os
|
||||
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.format_scratchpad import format_log_to_str
|
||||
from langchain.agents.output_parsers import ReActSingleInputOutputParser
|
||||
from langchain.agents.tools import tool as LangChainTool
|
||||
from langchain.memory import ConversationSummaryMemory
|
||||
from langchain.tools.render import render_text_description
|
||||
from langchain_core.agents import AgentAction
|
||||
from langchain_openai import ChatOpenAI
|
||||
from pydantic import (
|
||||
UUID4,
|
||||
@@ -20,7 +21,7 @@ from pydantic import (
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
@@ -73,7 +74,7 @@ class Agent(BaseModel):
|
||||
allow_delegation: bool = Field(
|
||||
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"
|
||||
)
|
||||
max_iter: Optional[int] = Field(
|
||||
@@ -151,7 +152,7 @@ class Agent(BaseModel):
|
||||
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.task = task
|
||||
self.agent_executor.tools_description = render_text_description(tools)
|
||||
@@ -200,12 +201,15 @@ class Agent(BaseModel):
|
||||
"input": lambda x: x["input"],
|
||||
"tools": lambda x: x["tools"],
|
||||
"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 = {
|
||||
"llm": self.llm,
|
||||
"i18n": self.i18n,
|
||||
"tools": self.tools,
|
||||
"tools": self._parse_tools(self.tools),
|
||||
"verbose": self.verbose,
|
||||
"handle_parsing_errors": True,
|
||||
"max_iterations": self.max_iter,
|
||||
@@ -225,9 +229,11 @@ class Agent(BaseModel):
|
||||
)
|
||||
executor_args["memory"] = summary_memory
|
||||
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:
|
||||
prompt = Prompts(i18n=self.i18n).task_execution()
|
||||
prompt = Prompts(i18n=self.i18n, tools=self.tools).task_execution()
|
||||
|
||||
execution_prompt = prompt.partial(
|
||||
goal=self.goal,
|
||||
@@ -236,13 +242,34 @@ class Agent(BaseModel):
|
||||
)
|
||||
|
||||
bind = self.llm.bind(stop=[self.i18n.slice("observation")])
|
||||
inner_agent = (
|
||||
agent_args | execution_prompt | bind | ReActSingleInputOutputParser()
|
||||
)
|
||||
inner_agent = agent_args | execution_prompt | bind | CrewAgentParser()
|
||||
self.agent_executor = CrewAgentExecutor(
|
||||
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
|
||||
def __tools_names(tools) -> str:
|
||||
return ", ".join([t.name for t in tools])
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from .cache.cache_handler import CacheHandler
|
||||
from .executor import CrewAgentExecutor
|
||||
from .parser import CrewAgentParser
|
||||
from .tools_handler import ToolsHandler
|
||||
|
||||
@@ -18,7 +18,7 @@ from crewai.utilities import I18N
|
||||
|
||||
|
||||
class CrewAgentExecutor(AgentExecutor):
|
||||
i18n: I18N = I18N()
|
||||
_i18n: I18N = I18N()
|
||||
llm: Any = None
|
||||
iterations: int = 0
|
||||
task: Any = None
|
||||
@@ -105,14 +105,12 @@ class CrewAgentExecutor(AgentExecutor):
|
||||
"""
|
||||
try:
|
||||
intermediate_steps = self._prepare_intermediate_steps(intermediate_steps)
|
||||
|
||||
# Call the LLM to see what to do.
|
||||
output = self.agent.plan(
|
||||
intermediate_steps,
|
||||
callbacks=run_manager.get_child() if run_manager else None,
|
||||
**inputs,
|
||||
)
|
||||
|
||||
if self._should_force_answer():
|
||||
if isinstance(output, AgentAction) or isinstance(output, AgentFinish):
|
||||
output = output
|
||||
@@ -121,7 +119,7 @@ class CrewAgentExecutor(AgentExecutor):
|
||||
f"Unexpected output type from agent: {type(output)}"
|
||||
)
|
||||
yield AgentStep(
|
||||
action=output, observation=self.i18n.errors("force_final_answer")
|
||||
action=output, observation=self._i18n.errors("force_final_answer")
|
||||
)
|
||||
return
|
||||
|
||||
@@ -140,14 +138,14 @@ class CrewAgentExecutor(AgentExecutor):
|
||||
text = str(e)
|
||||
if isinstance(self.handle_parsing_errors, bool):
|
||||
if e.send_to_llm:
|
||||
observation = str(e.observation)
|
||||
observation = f"\n{str(e.observation)}"
|
||||
text = str(e.llm_output)
|
||||
else:
|
||||
observation = "Invalid or incomplete response"
|
||||
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):
|
||||
observation = self.handle_parsing_errors(e)
|
||||
observation = f"\n{self.handle_parsing_errors(e)}"
|
||||
else:
|
||||
raise ValueError("Got unexpected type of `handle_parsing_errors`")
|
||||
output = AgentAction("_Exception", observation, text)
|
||||
@@ -164,7 +162,7 @@ class CrewAgentExecutor(AgentExecutor):
|
||||
|
||||
if self._should_force_answer():
|
||||
yield AgentStep(
|
||||
action=output, observation=self.i18n.errors("force_final_answer")
|
||||
action=output, observation=self._i18n.errors("force_final_answer")
|
||||
)
|
||||
return
|
||||
|
||||
@@ -183,27 +181,26 @@ class CrewAgentExecutor(AgentExecutor):
|
||||
if run_manager:
|
||||
run_manager.on_agent_action(agent_action, color="green")
|
||||
# Otherwise we lookup the tool
|
||||
if agent_action.tool in name_to_tool_map:
|
||||
tool = name_to_tool_map[agent_action.tool]
|
||||
return_direct = tool.return_direct
|
||||
color_mapping[agent_action.tool]
|
||||
tool_run_kwargs = self.agent.tool_run_logging_kwargs()
|
||||
if return_direct:
|
||||
tool_run_kwargs["llm_prefix"] = ""
|
||||
observation = ToolUsage(
|
||||
tools_handler=self.tools_handler,
|
||||
tools=self.tools,
|
||||
tools_description=self.tools_description,
|
||||
tools_names=self.tools_names,
|
||||
function_calling_llm=self.function_calling_llm,
|
||||
llm=self.llm,
|
||||
task=self.task,
|
||||
).use(agent_action.log)
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=self.tools_handler,
|
||||
tools=self.tools,
|
||||
tools_description=self.tools_description,
|
||||
tools_names=self.tools_names,
|
||||
function_calling_llm=self.function_calling_llm,
|
||||
llm=self.llm,
|
||||
task=self.task,
|
||||
)
|
||||
tool_calling = tool_usage.parse(agent_action.log)
|
||||
|
||||
if tool_calling.tool_name.lower().strip() in [
|
||||
name.lower().strip() for name in name_to_tool_map
|
||||
]:
|
||||
observation = tool_usage.use(tool_calling, agent_action.log)
|
||||
else:
|
||||
tool_run_kwargs = self.agent.tool_run_logging_kwargs()
|
||||
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()),
|
||||
},
|
||||
verbose=self.verbose,
|
||||
|
||||
61
src/crewai/agents/parser.py
Normal file
61
src/crewai/agents/parser.py
Normal 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,
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
from textwrap import dedent
|
||||
from typing import Any, List, Union
|
||||
|
||||
import instructor
|
||||
from langchain.prompts import PromptTemplate
|
||||
from langchain_core.tools import BaseTool
|
||||
from langchain_openai import ChatOpenAI
|
||||
@@ -9,7 +9,9 @@ from crewai.agents.tools_handler import ToolsHandler
|
||||
from crewai.telemtry import Telemetry
|
||||
from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling
|
||||
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):
|
||||
@@ -31,6 +33,7 @@ class ToolUsage:
|
||||
tools_description: Description 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.
|
||||
function_calling_llm: Language model to be used for the tool usage.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -47,18 +50,28 @@ class ToolUsage:
|
||||
self._printer: Printer = Printer()
|
||||
self._telemetry: Telemetry = Telemetry()
|
||||
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.tools_description = tools_description
|
||||
self.tools_names = tools_names
|
||||
self.tools_handler = tools_handler
|
||||
self.tools = tools
|
||||
self.task = task
|
||||
self.llm = llm
|
||||
self.function_calling_llm = function_calling_llm
|
||||
self.llm = function_calling_llm or llm
|
||||
|
||||
def use(self, tool_string: str):
|
||||
calling = self._tool_calling(tool_string)
|
||||
# Set the maximum parsing attempts for bigger models
|
||||
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):
|
||||
error = calling.message
|
||||
self._printer.print(content=f"\n\n{error}\n", color="red")
|
||||
@@ -69,7 +82,7 @@ class ToolUsage:
|
||||
error = getattr(e, "message", str(e))
|
||||
self._printer.print(content=f"\n\n{error}\n", color="red")
|
||||
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(
|
||||
self,
|
||||
@@ -106,11 +119,11 @@ class ToolUsage:
|
||||
if self._run_attempts > self._max_parsing_attempts:
|
||||
self._telemetry.tool_usage_error(llm=self.llm)
|
||||
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
|
||||
self._printer.print(content=f"\n\n{error}\n", color="red")
|
||||
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)
|
||||
|
||||
@@ -174,69 +187,55 @@ class ToolUsage:
|
||||
self, tool_string: str
|
||||
) -> Union[ToolCalling, InstructorToolCalling]:
|
||||
try:
|
||||
tool_string = tool_string.replace(
|
||||
"Thought: Do I need to use a tool? Yes", ""
|
||||
)
|
||||
tool_string = tool_string.replace("Action:", "Tool Name:")
|
||||
tool_string = tool_string.replace("Action Input:", "Tool Arguments:")
|
||||
if (isinstance(self.llm, ChatOpenAI)) and (
|
||||
self.llm.openai_api_base == None
|
||||
):
|
||||
instructor = Instructor(
|
||||
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
|
||||
|
||||
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,
|
||||
Example:
|
||||
{"tool_name": "tool_name", "arguments": {"arg_name1": "value", "arg_name2": 2}}
|
||||
"""
|
||||
),
|
||||
)
|
||||
calling = instructor.to_pydantic()
|
||||
else:
|
||||
parser = ToolOutputParser(pydantic_object=ToolCalling)
|
||||
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"],
|
||||
partial_variables={
|
||||
"available_tools": self._render(),
|
||||
"format_instructions": """
|
||||
The schema should have the following structure, only two key:
|
||||
"format_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}}
|
||||
""",
|
||||
"""
|
||||
),
|
||||
},
|
||||
)
|
||||
chain = prompt | llm | parser
|
||||
chain = prompt | self.llm | parser
|
||||
calling = chain.invoke({"tool_string": tool_string})
|
||||
|
||||
except Exception as e:
|
||||
self._run_attempts += 1
|
||||
if self._run_attempts > self._max_parsing_attempts:
|
||||
self._telemetry.tool_usage_error(llm=llm)
|
||||
error = ToolUsageErrorException(
|
||||
self._i18n.errors("tool_usage_exception").format(error=e)
|
||||
).message
|
||||
self._printer.print(content=f"\n\n{error}\n", color="red")
|
||||
return error
|
||||
self._telemetry.tool_usage_error(llm=self.llm)
|
||||
self._printer.print(content=f"\n\n{e}\n", color="red")
|
||||
return ToolUsageErrorException(
|
||||
f'{self._i18n.errors("tool_usage_error")}.\n{self._i18n.slice("format").format(tool_names=self.tools_names)}'
|
||||
)
|
||||
return self._tool_calling(tool_string)
|
||||
|
||||
return calling
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
"slices": {
|
||||
"observation": "\nObservation",
|
||||
"observation": "\nResult",
|
||||
"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}",
|
||||
"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}",
|
||||
"expected_output": "Your final answer must be: {expected_output}"
|
||||
},
|
||||
"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",
|
||||
"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_exception": "It seems we encountered an unexpected error while trying to use the tool. This was the error: {error}"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from .i18n import I18N
|
||||
from .instructor import Instructor
|
||||
from .logger import Logger
|
||||
from .printer import Printer
|
||||
from .prompts import Prompts
|
||||
|
||||
51
src/crewai/utilities/instructor.py
Normal file
51
src/crewai/utilities/instructor.py
Normal 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
|
||||
@@ -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 crewai.utilities import I18N
|
||||
@@ -10,12 +10,18 @@ class Prompts(BaseModel):
|
||||
"""Manages and generates prompts for a generic agent with support for different languages."""
|
||||
|
||||
i18n: I18N = Field(default=I18N())
|
||||
|
||||
tools: list[Any] = Field(default=[])
|
||||
SCRATCHPAD_SLICE: ClassVar[str] = "\n{agent_scratchpad}"
|
||||
|
||||
def task_execution_with_memory(self) -> BasePromptTemplate:
|
||||
"""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:
|
||||
"""Generate a prompt for task execution without tools components."""
|
||||
@@ -23,10 +29,17 @@ class Prompts(BaseModel):
|
||||
|
||||
def task_execution(self) -> BasePromptTemplate:
|
||||
"""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:
|
||||
"""Constructs a prompt string from specified components."""
|
||||
prompt_parts = [self.i18n.slice(component) for component in components]
|
||||
prompt_parts.append(self.SCRATCHPAD_SLICE)
|
||||
return PromptTemplate.from_template("".join(prompt_parts))
|
||||
prompt = PromptTemplate.from_template("".join(prompt_parts))
|
||||
return prompt
|
||||
|
||||
Reference in New Issue
Block a user