Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add react agent, max_loops handler prompt #7

Merged
merged 4 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions dynamiq/nodes/agents/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,14 @@ class InvalidActionException(RecoverableAgentException):
pass


class AgentMaxLoopsReached(RecoverableAgentException):
class MaxLoopsExceededException(RecoverableAgentException):
"""
Exception raised when the agent reaches the maximum number of loops without a final answer.
Exception raised when the agent exceeds the maximum number of allowed loops.

This exception is recoverable, meaning the agent can continue after catching this exception.
"""

pass
def __init__(
self, message: str = "Maximum number of loops reached without finding a final answer.", recoverable: bool = True
):
super().__init__(message, recoverable=recoverable)
64 changes: 60 additions & 4 deletions dynamiq/nodes/agents/react.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import json
import re
from enum import Enum
from typing import Any

from pydantic import Field

from dynamiq.nodes.agents.base import Agent, AgentIntermediateStep, AgentIntermediateStepModelObservation
from dynamiq.nodes.agents.exceptions import ActionParsingException, AgentMaxLoopsReached, RecoverableAgentException
from dynamiq.nodes.agents.exceptions import ActionParsingException, MaxLoopsExceededException, RecoverableAgentException
from dynamiq.nodes.node import NodeDependency
from dynamiq.nodes.types import InferenceMode
from dynamiq.prompts import Message, Prompt
from dynamiq.runnables import RunnableConfig, RunnableStatus
from dynamiq.utils.logger import logger


class BehaviorOnMaxLoops(str, Enum):
olbychos marked this conversation as resolved.
Show resolved Hide resolved
RAISE = "raise"
RETURN = "return"


REACT_BLOCK_TOOLS = """
You have access to a variety of tools, and you are responsible for using them in any order you choose to complete the task:
{tools_desc}
Expand Down Expand Up @@ -163,6 +170,31 @@
REACT_BLOCK_CONTEXT = "Below is the conversation: {context}"


REACT_MAX_LOOPS_PROMPT = """
You are tasked with providing a final answer based on information gathered during a process that has reached its maximum number of loops.
Your goal is to analyze the given context and formulate a clear, concise response.
First, carefully review the following context, which contains thoughts and information gathered during the process:
<context>
{context}
</context>
Analyze the context to identify key information, patterns, or partial answers that can contribute to a final response. Pay attention to any progress made, obstacles encountered, or partial results obtained.
Based on your analysis, attempt to formulate a final answer to the original question or task. Your answer should be:
1. Fully supported by the information found in the context
2. Clear and concise
3. Directly addressing the original question or task, if possible
If you cannot provide a full answer based on the given context, explain that due to limitations in the number of steps or potential issues with the tools used, you are unable to fully answer the question. In this case, suggest one or more of the following:
1. Increasing the maximum number of loops for the agent setup
2. Reviewing the tools settings
3. Revising the input task description
Important: Do not mention specific errors in tools, exact steps, environments, code, or search results. Keep your response general and focused on the task at hand.
Provide your final answer or explanation within <answer> tags.
Your response should be clear, concise, and professional.
<answer>
[Your final answer or explanation goes here]
</answer>
""" # noqa: E501


def function_calling_schema(tool_names):
return [
{
Expand Down Expand Up @@ -245,8 +277,12 @@ class ReActAgent(Agent):
"""Agent that uses the ReAct strategy for processing tasks by interacting with tools in a loop."""

name: str = "React"
max_loops: int = Field(default=15, ge=1)
max_loops: int = Field(default=15, ge=2)
inference_mode: InferenceMode = InferenceMode.DEFAULT
behaviour_on_max_loops: BehaviorOnMaxLoops = Field(
default=BehaviorOnMaxLoops.RAISE,
description="Define behavior when max loops are exceeded. Options are 'raise' or 'return'.",
)

def parse_xml_content(self, text: str, tag: str) -> str:
"""Extract content from XML-like tags."""
Expand Down Expand Up @@ -455,8 +491,28 @@ def _run_agent(self, config: RunnableConfig | None = None, **kwargs) -> str:
logger.error(f"Agent {self.name} - {self.id}:Loop {loop_num + 1}. failed with error: {str(e)}")
previous_responses.append(f"{type(e).__name__}: {e}")
continue
logger.error(f"Agent {self.name} - {self.id}: Maximum number of loops reached.")
raise AgentMaxLoopsReached(f"Agent {self.name} - {self.id}: Maximum number of loops reached.")
logger.warning(f"Agent {self.name} - {self.id}: Maximum number of loops reached.")
if self.behaviour_on_max_loops == BehaviorOnMaxLoops.RAISE:
error_message = (
f"Agent {self.name} (ID: {self.id}) has reached the maximum loop limit of {self.max_loops} without finding a final answer. " # noqa: E501
f"Consider increasing the maximum number of loops or reviewing the task complexity to ensure completion." # noqa: E501
)
raise MaxLoopsExceededException(message=error_message)
else:
return self._handle_max_loops_exceeded(previous_responses, config, kwargs)

def _handle_max_loops_exceeded(
self, previous_responses: list, config: RunnableConfig | None = None, **kwargs
) -> str:
"""
Handle the case where max loops are exceeded by crafting a thoughtful response.
"""
final_attempt_prompt = REACT_MAX_LOOPS_PROMPT.format(context="\n".join(previous_responses))
llm_final_attempt = self._run_llm(final_attempt_prompt, config=config, **kwargs)
self._run_depends = [NodeDependency(node=self.llm).to_dict()]
final_answer = self.parse_xml_content(llm_final_attempt, "answer")

return f"Max loops reached but here's my final attempt:\n{final_answer}"

def _extract_final_answer_xml(self, llm_output: str) -> str:
"""Extract the final answer from the LLM output."""
Expand Down
1 change: 0 additions & 1 deletion examples/agents_use_cases/agent_financial.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
tools=[tool_code],
role=AGENT_ROLE,
goal=AGENT_GOAL,
max_loops=10,
inference_mode=InferenceMode.XML,
)

Expand Down
4 changes: 1 addition & 3 deletions examples/orchestrators/adaptive_coding.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
In results provide initial and optimized loss of model.
also include the equation of the model.
""" # noqa: E501
INPUT_TASK = "Use code skills to gather data about NVIDIA and INTEL stocks prices for last 10 years, calulate average per year for each company and createa atable per me. Then craft a report and ad conclusion, what would be better if I could invest 100$ 10 yeasr ago. Use yahoo finance." # noqa: E501
# INPUT_TASK = "Add the first 10 numbers and tell if the result is prime"
# INPUT_TASK = "Use code skills to gather data about NVIDIA and INTEL stocks prices for last 10 years, calulate average per year for each company and createa atable per me. Then craft a report and ad conclusion, what would be better if I could invest 100$ 10 yeasr ago. Use yahoo finance." # noqa: E501

if __name__ == "__main__":
python_tool = E2BInterpreterTool(
Expand All @@ -35,7 +34,6 @@
tools=[python_tool],
role="Expert Agent with high programming skills, he can solve any problem using coding skills",
goal="provide the best solution for request, using all his algorithmic knowledge and coding skills",
max_loops=15,
inference_mode=InferenceMode.XML,
)

Expand Down
Loading