from openai import OpenAI from typing import List, Dict import json import os from .tools.base import Tool REACT_AGENT_SYSTEM_PROMPT = """ Answer the following questions as best you can. You have access to the following tools: {tools} Use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [{tool_names}] Action Input: the input to the action Observation: the result of the action ... (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! """ class AgentPro: def __init__(self, llm = None, tools: List[Tool] = [], system_prompt: str = None, react_prompt: str = REACT_AGENT_SYSTEM_PROMPT): super().__init__() self.client = llm if llm else OpenAI() self.tools = self.format_tools(tools) self.react_prompt = react_prompt.format( tools="\n\n".join(map(lambda tool: tool.get_tool_description(), tools)), tool_names=", ".join(map(lambda tool: tool.name, tools))) self.messages = [] if system_prompt: self.messages.append({"role": "system", "content": system_prompt}) self.messages.append({"role": "system", "content": self.react_prompt}) def format_tools(self, tools: List[Tool]) -> Dict: tool_names = list(map(lambda tool: tool.name, tools)) return dict(zip(tool_names, tools)) def parse_action_string(self, text): """ Parses action and action input from a string containing thoughts and actions. Handles multi-line actions and optional observations. """ lines = text.split('\n') action = None action_input = [] is_action_input = False for line in lines: if line.startswith('Action:'): action = line.replace('Action:', '').strip() continue if line.startswith('Action Input:'): is_action_input = True # Handle single-line action input input_text = line.replace('Action Input:', '').strip() if input_text: action_input.append(input_text) continue if line.startswith('Observation:'): is_action_input = False continue # Collect multi-line action input if is_action_input and line.strip(): action_input.append(line.strip()) # Join multi-line action input action_input = '\n'.join(action_input) try: action_input = json.loads(action_input) except Exception as e: pass return action, action_input def tool_call(self, response): action, action_input = self.parse_action_string(response) try: if action.strip().lower() in self.tools: tool_observation = self.tools[action].run(action_input) return f"Observation: {tool_observation}" return f"Observation: Tool '{action}' not found. Available tools: {list(self.tools.keys())}" except Exception as e: return f"Observation: There was an error executing the tool\nError: {e}" #def __call__(self, prompt): # self.messages.append({"role": "user", "content": prompt}) # response = "" # while True: # response = self.client.chat.completions.create( # model="gpt-4o-mini", # SET GPT-4o-mini AS DEFAULT, BUT VARIABLE W/OPEN ROUTER MODELS # messages=self.messages, # max_tokens=8000 # ).choices[0].message.content.strip() # self.messages.append({"role":"assistant", "content": response}) # print("="*80) # print(response) # print("="*80) # if "Final Answer" in response: # return response.split("Final Answer:")[-1].strip() # if "Action" in response and "Action Input" in response: # observation = self.tool_call(response) # self.messages.append({"role": "assistant", "content": observation}) def __call__(self, prompt): self.messages.append({"role": "user", "content": prompt}) response = "" openrouter_api_key = os.environ.get("OPENROUTER_API_KEY") model_name = os.environ.get("MODEL_NAME", "gpt-4o-mini") # Default to gpt-4o-mini if MODEL_NAME is not set try: if openrouter_api_key: print(f"Using OpenRouter with model: {model_name} for agent conversation") client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=openrouter_api_key) while True: response = client.chat.completions.create( model=model_name, messages=self.messages, max_tokens=8000 ).choices[0].message.content.strip() self.messages.append({"role":"assistant", "content": response}) print("="*80) print(response) print("="*80) if "Final Answer" in response: return response.split("Final Answer:")[-1].strip() if "Action" in response and "Action Input" in response: observation = self.tool_call(response) self.messages.append({"role": "assistant", "content": observation}) else: # Fall back to default OpenAI client print("OpenRouter API key not found, using default OpenAI client with gpt-4o-mini") while True: response = self.client.chat.completions.create( model="gpt-4o-mini", messages=self.messages, max_tokens=8000 ).choices[0].message.content.strip() self.messages.append({"role":"assistant", "content": response}) print("="*80) print(response) print("="*80) if "Final Answer" in response: return response.split("Final Answer:")[-1].strip() if "Action" in response and "Action Input" in response: observation = self.tool_call(response) self.messages.append({"role": "assistant", "content": observation}) except Exception as e: print(f"Error with primary model: {e}") print("Falling back to default OpenAI client with gpt-4o-mini") try: while True: response = self.client.chat.completions.create( model="gpt-4o-mini", messages=self.messages, max_tokens=8000 ).choices[0].message.content.strip() self.messages.append({"role":"assistant", "content": response}) print("="*80) print(response) print("="*80) if "Final Answer" in response: return response.split("Final Answer:")[-1].strip() if "Action" in response and "Action Input" in response: observation = self.tool_call(response) self.messages.append({"role": "assistant", "content": observation}) except Exception as e2: print(f"Critical error with all models: {e2}") return f"Error: Failed to generate response with both primary and fallback models. Details: {str(e2)}"