Skip to content

Commit 0076ea7

Browse files
committed
Adding ability to remember instruction after using too many tools
1 parent e79da7b commit 0076ea7

File tree

9 files changed

+1906
-11
lines changed

9 files changed

+1906
-11
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
[tool.poetry]
33
name = "crewai"
4-
version = "0.8.0"
4+
version = "0.10.0"
55
description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks."
66
authors = ["Joao Moura <joao@crewai.com>"]
77
readme = "README.md"

src/crewai/agent.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,14 @@ def execute_task(
150150
tools = tools or self.tools
151151
self.agent_executor.tools = tools
152152
self.agent_executor.task = task
153+
self.agent_executor.tools_description = (render_text_description(tools),)
154+
self.agent_executor.tools_names = self.__tools_names(tools)
153155

154156
result = self.agent_executor.invoke(
155157
{
156158
"input": task_prompt,
157-
"tool_names": self.__tools_names(tools),
158-
"tools": render_text_description(tools),
159+
"tool_names": self.agent_executor.tools_names,
160+
"tools": self.agent_executor.tools_description,
159161
}
160162
)["output"]
161163

src/crewai/agents/executor.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ class CrewAgentExecutor(AgentExecutor):
2121
i18n: I18N = I18N()
2222
llm: Any = None
2323
iterations: int = 0
24+
task: Any = None
25+
tools_description: str = ""
26+
tools_names: str = ""
2427
request_within_rpm_limit: Any = None
2528
tools_handler: InstanceOf[ToolsHandler] = None
2629
max_iterations: Optional[int] = 15
@@ -187,7 +190,12 @@ def _iter_next_step(
187190
if return_direct:
188191
tool_run_kwargs["llm_prefix"] = ""
189192
observation = ToolUsage(
190-
tools_handler=self.tools_handler, tools=self.tools, llm=self.llm
193+
tools_handler=self.tools_handler,
194+
tools=self.tools,
195+
tools_description=self.tools_description,
196+
tools_names=self.tools_names,
197+
llm=self.llm,
198+
task=self.task,
191199
).use(agent_action.log)
192200
else:
193201
tool_run_kwargs = self.agent.tool_run_logging_kwargs()

src/crewai/tools/tool_usage.py

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,55 @@
1010
from crewai.utilities import I18N, Printer
1111

1212

13+
class ToolUsageErrorException(Exception):
14+
"""Exception raised for errors in the tool usage."""
15+
16+
def __init__(self, message: str) -> None:
17+
self.message = message
18+
super().__init__(self.message)
19+
20+
1321
class ToolUsage:
1422
"""
1523
Class that represents the usage of a tool by an agent.
1624
1725
Attributes:
26+
task: Task being executed.
1827
tools_handler: Tools handler that will manage the tool usage.
1928
tools: List of tools available for the agent.
29+
tools_description: Description of the tools available for the agent.
30+
tools_names: Names of the tools available for the agent.
2031
llm: Language model to be used for the tool usage.
2132
"""
2233

2334
def __init__(
24-
self, tools_handler: ToolsHandler, tools: List[BaseTool], llm: Any
35+
self,
36+
tools_handler: ToolsHandler,
37+
tools: List[BaseTool],
38+
tools_description: str,
39+
tools_names: str,
40+
task: Any,
41+
llm: Any,
2542
) -> None:
2643
self._i18n: I18N = I18N()
2744
self._printer: Printer = Printer()
2845
self._telemetry: Telemetry = Telemetry()
2946
self._run_attempts: int = 1
3047
self._max_parsing_attempts: int = 3
48+
self._remeber_format_after_usages: int = 3
49+
self.tools_description = tools_description
50+
self.tools_names = tools_names
3151
self.tools_handler = tools_handler
3252
self.tools = tools
53+
self.task = task
3354
self.llm = llm
3455

3556
def use(self, tool_string: str):
3657
calling = self._tool_calling(tool_string)
58+
if isinstance(calling, ToolUsageErrorException):
59+
error = calling.message
60+
self._printer.print(content=f"\n\n{error}\n", color="yellow")
61+
return error
3762
tool = self._select_tool(calling.function_name)
3863
return self._use(tool=tool, calling=calling)
3964

@@ -57,6 +82,24 @@ def _use(self, tool: BaseTool, calling: ToolCalling) -> None:
5782
self._telemetry.tool_usage(
5883
llm=self.llm, tool_name=tool.name, attempts=self._run_attempts
5984
)
85+
86+
result = self._format_result(result=result)
87+
return result
88+
89+
def _format_result(self, result: Any) -> None:
90+
self.task.used_tools += 1
91+
if self._should_remember_format():
92+
result = self._remember_format(result=result)
93+
return result
94+
95+
def _should_remember_format(self) -> None:
96+
return self.task.used_tools % self._remeber_format_after_usages == 0
97+
98+
def _remember_format(self, result: str) -> None:
99+
result = str(result)
100+
result += "\n\n" + self._i18n.slice("tools").format(
101+
tools=self.tools_description, tool_names=self.tools_names
102+
)
60103
return result
61104

62105
def _check_tool_repeated_usage(self, calling: ToolCalling) -> None:
@@ -102,11 +145,11 @@ def _tool_calling(self, tool_string: str) -> ToolCalling:
102145
chain = prompt | self.llm | parser
103146
calling = chain.invoke({"tool_string": tool_string})
104147

105-
except Exception as e:
148+
except Exception:
106149
self._run_attempts += 1
107150
if self._run_attempts > self._max_parsing_attempts:
108151
self._telemetry.tool_usage_error(llm=self.llm)
109-
raise e
152+
return ToolUsageErrorException(self._i18n.errors("tool_usage_error"))
110153
return self._tool_calling(tool_string)
111154

112155
return calling

src/crewai/translations/el.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"errors": {
1717
"force_final_answer": "Στην πραγματικότητα, χρησιμοποίησα πάρα πολλά εργαλεία, οπότε θα σταματήσω τώρα και θα σας δώσω την απόλυτη ΚΑΛΥΤΕΡΗ τελική μου απάντηση ΤΩΡΑ, χρησιμοποιώντας την αναμενόμενη μορφή: ```\nΣκέφτηκα: Χρειάζεται να χρησιμοποιήσω ένα εργαλείο; Όχι\nΤελική απάντηση: [η απάντησή σας εδώ]```",
1818
"agent_tool_unexsiting_coworker": "\nΣφάλμα κατά την εκτέλεση του εργαλείου. Ο συνάδελφος που αναφέρεται στο Ενέργεια προς εισαγωγή δεν βρέθηκε, πρέπει να είναι μία από τις ακόλουθες επιλογές: {coworkers}.\n",
19-
"task_repeated_usage": "Μόλις χρησιμοποίησα το εργαλείο {tool} με είσοδο {tool_input}. Άρα ξέρω ήδη το αποτέλεσμα αυτού και δεν χρειάζεται να το χρησιμοποιήσω ξανά τώρα.\n"
19+
"task_repeated_usage": "Μόλις χρησιμοποίησα το εργαλείο {tool} με είσοδο {tool_input}. Άρα ξέρω ήδη το αποτέλεσμα αυτού και δεν χρειάζεται να το χρησιμοποιήσω ξανά τώρα.\n",
20+
"tool_usage_error": "Φαίνεται ότι αντιμετωπίσαμε ένα απροσδόκητο σφάλμα κατά την προσπάθεια χρήσης του εργαλείου."
2021
},
2122
"tools": {
2223
"delegate_work": "Αναθέστε μια συγκεκριμένη εργασία σε έναν από τους παρακάτω συναδέλφους: {coworkers}. Η είσοδος σε αυτό το εργαλείο θα πρέπει να είναι ο ρόλος του συναδέλφου, η εργασία που θέλετε να κάνει και ΟΛΟ το απαραίτητο πλαίσιο για την εκτέλεση της εργασίας, δεν γνωρίζουν τίποτα για την εργασία, επομένως μοιραστείτε απολύτως όλα όσα γνωρίζετε, μην αναφέρετε πράγματα, αλλά αντί να τους εξηγήσεις.",

src/crewai/translations/en.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
"expected_output": "Your final answer must be: {expected_output}"
1515
},
1616
"errors": {
17-
"force_final_answer": "Actually, I used too many tools, so I'll stop now and give you my absolute BEST Final answer NOW, using the expected format: ```\nThought: Do I need to use a tool? No\nFinal Answer: [your response here]```",
17+
"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]```",
1818
"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: {coworkers}.\n",
19-
"task_repeated_usage": "I just used the {tool} tool with input {tool_input}. So I already know the result of that and don't need to use it again now.\n"
19+
"task_repeated_usage": "I just used the {tool} tool with input {tool_input}. So I already know the result of that and don't need to use it again now. \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",
20+
"tool_usage_error": "It seems we encountered an unexpected error while trying to use the tool."
2021
},
2122
"tools": {
2223
"delegate_work": "Delegate a specific task to one of the following co-workers: {coworkers}. The input to this tool should be the role of the coworker, the task you want them to do, and ALL necessary context to exectue the task, they know nothing about the task, so share absolute everything you know, don't reference things but instead explain them.",

tests/agent_test.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from crewai.agents.cache import CacheHandler
1111
from crewai.agents.executor import CrewAgentExecutor
1212
from crewai.tools.tool_calling import ToolCalling
13+
from crewai.tools.tool_usage import ToolUsage
1314
from crewai.utilities import RPMController
1415

1516

@@ -256,7 +257,7 @@ def get_final_answer(numbers) -> float:
256257

257258
captured = capsys.readouterr()
258259
assert (
259-
"I just used the get_final_answer tool with input {'numbers': 42}. So I already know the result of that and don't need to use it again now."
260+
"I just used the get_final_answer tool with input {'numbers': 42}. So I already know the result of that and don't need to use it again now. \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"
260261
in captured.out
261262
)
262263

@@ -414,6 +415,78 @@ def get_final_answer(numbers) -> float:
414415
moveon.assert_called_once()
415416

416417

418+
@pytest.mark.vcr(filter_headers=["authorization"])
419+
def test_agent_error_on_parsing_tool(capsys):
420+
from unittest.mock import patch
421+
422+
from langchain.tools import tool
423+
424+
@tool
425+
def get_final_answer(numbers) -> float:
426+
"""Get the final answer but don't give it yet, just re-use this
427+
tool non-stop."""
428+
return 42
429+
430+
agent1 = Agent(
431+
role="test role",
432+
goal="test goal",
433+
backstory="test backstory",
434+
verbose=True,
435+
)
436+
tasks = [
437+
Task(
438+
description="Use the get_final_answer tool.",
439+
agent=agent1,
440+
tools=[get_final_answer],
441+
)
442+
]
443+
444+
crew = Crew(agents=[agent1], tasks=tasks, verbose=2)
445+
446+
with patch.object(ToolUsage, "_render") as force_exception:
447+
force_exception.side_effect = Exception("Error on parsing tool.")
448+
crew.kickoff()
449+
captured = capsys.readouterr()
450+
assert (
451+
"It seems we encountered an unexpected error while trying to use the tool"
452+
in captured.out
453+
)
454+
455+
456+
@pytest.mark.vcr(filter_headers=["authorization"])
457+
def test_agent_remembers_output_format_after_using_tools_too_many_times():
458+
from unittest.mock import patch
459+
460+
from langchain.tools import tool
461+
462+
@tool
463+
def get_final_answer(numbers) -> float:
464+
"""Get the final answer but don't give it yet, just re-use this
465+
tool non-stop."""
466+
return 42
467+
468+
agent1 = Agent(
469+
role="test role",
470+
goal="test goal",
471+
backstory="test backstory",
472+
max_iter=4,
473+
verbose=True,
474+
)
475+
tasks = [
476+
Task(
477+
description="Never give the final answer. Use the get_final_answer tool in a loop.",
478+
agent=agent1,
479+
tools=[get_final_answer],
480+
)
481+
]
482+
483+
crew = Crew(agents=[agent1], tasks=tasks, verbose=2)
484+
485+
with patch.object(ToolUsage, "_remember_format") as remember_format:
486+
crew.kickoff()
487+
remember_format.assert_called()
488+
489+
417490
@pytest.mark.vcr(filter_headers=["authorization"])
418491
def test_agent_use_specific_tasks_output_as_context(capsys):
419492
agent1 = Agent(role="test role", goal="test goal", backstory="test backstory")

0 commit comments

Comments
 (0)