vgtc-api / src /hermes /reasoning /react.py
vora-sonnet's picture
Upload folder using huggingface_hub
0d3f7cc verified
Raw
History Blame Contribute Delete
4.75 kB
"""ReAct reasoning pattern implementation."""
from __future__ import annotations
import json
import logging
from typing import Any
from hermes.core.types import AgentStrategy
logger = logging.getLogger(__name__)
class ReActReasoner:
"""Implements the ReAct (Reasoning + Acting) pattern."""
def __init__(self) -> None:
self.strategy = AgentStrategy.REACT
def create_prompt(
self, task: str, tools: list[dict[str, Any]], history: list[dict[str, str]]
) -> str:
"""Create ReAct prompt with tool descriptions."""
tool_descriptions = "\n".join(
f"- {t['name']}: {t['description']}" for t in tools
)
history_text = ""
if history:
history_text = "\nPrevious steps:\n"
for h in history:
if "thought" in h:
history_text += f"Thought: {h['thought']}\n"
if "action" in h:
history_text += f"Action: {h['action']}\n"
if "observation" in h:
history_text += f"Observation: {h['observation']}\n"
return f"""You are an AI agent that uses the ReAct pattern to solve tasks.
Task: {task}
Available tools:
{tool_descriptions}
{history_text}
Think step by step. Use the following format:
Thought: [your reasoning about what to do next]
Action: [tool_name with arguments as JSON]
Observation: [this will be provided after action execution]
When you have enough information to answer, use:
Thought: I now have enough information to provide a final answer.
Final Answer: [your complete answer]
Important:
- Always start with a Thought
- Use only the available tools
- Provide clear reasoning in your thoughts
- When done, provide a Final Answer"""
def parse_response(self, response: str) -> dict[str, Any]:
"""Parse ReAct response into components."""
result: dict[str, Any] = {"thought": "", "action": None, "final_answer": None}
lines = response.strip().split("\n")
current_key = None
current_value: list[str] = []
for line in lines:
stripped = line.strip()
if stripped.startswith("Thought:"):
if current_key and current_value:
result[current_key] = "\n".join(current_value).strip()
current_key = "thought"
current_value = [stripped[len("Thought:"):].strip()]
elif stripped.startswith("Action:"):
if current_key and current_value:
result[current_key] = "\n".join(current_value).strip()
current_key = "action"
current_value = [stripped[len("Action:"):].strip()]
elif stripped.startswith("Observation:"):
if current_key and current_value:
result[current_key] = "\n".join(current_value).strip()
current_key = "observation"
current_value = [stripped[len("Observation:"):].strip()]
elif stripped.startswith("Final Answer:"):
if current_key and current_value:
result[current_key] = "\n".join(current_value).strip()
current_key = "final_answer"
current_value = [stripped[len("Final Answer:"):].strip()]
elif current_key:
current_value.append(stripped)
if current_key and current_value:
result[current_key] = "\n".join(current_value).strip()
if result["action"]:
try:
action_str = result["action"]
if "(" in action_str and action_str.endswith(")"):
tool_name = action_str[: action_str.index("(")]
args_str = action_str[action_str.index("(") + 1 : -1]
try:
args = json.loads(args_str) if args_str.strip() else {}
except json.JSONDecodeError:
args = {"input": args_str}
result["action"] = {"tool": tool_name, "arguments": args}
else:
result["action"] = {"tool": action_str, "arguments": {}}
except Exception:
result["action"] = {"tool": result["action"], "arguments": {}}
return result
def should_continue(self, parsed: dict[str, Any], max_steps: int, current_step: int) -> bool:
"""Determine if the agent should continue."""
if parsed.get("final_answer"):
return False
if current_step >= max_steps:
return False
return bool(parsed.get("action"))