from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser
from langchain.prompts import StringPromptTemplate
from langchain import OpenAI, SerpAPIWrapper, LLMChain
from typing import List, Union
from langchain.schema import AgentAction, AgentFinish
import re
from langchain.utilities import BashProcess
from langchain.tools.human.tool import HumanInputRun
from langchain_tools.cwtool import CloudWatchInsightQuery
import sys

# Set up a prompt template
class CustomPromptTemplate(StringPromptTemplate):
    # The template to use
    template: str
    # The list of tools available
    tools: List[Tool]
    
    def format(self, **kwargs) -> str:
        # Get the intermediate steps (AgentAction, Observation tuples)
        # Format them in a particular way
        intermediate_steps = kwargs.pop("intermediate_steps")
        thoughts = ""
        for action, observation in intermediate_steps:
            thoughts += action.log
            thoughts += f"\nObservation: {observation}\nThought: "
        # Set the agent_scratchpad variable to that value
        kwargs["agent_scratchpad"] = thoughts
        # Create a tools variable from the list of tools provided
        kwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools])
        # Create a list of tool names for the tools provided
        kwargs["tool_names"] = ", ".join([tool.name for tool in self.tools])
        return self.template.format(**kwargs)

class CustomOutputParser(AgentOutputParser):
    
    def parse(self, llm_output: str) -> Union[AgentAction, AgentFinish]:
        # Check if agent should finish
        if "Final Answer:" in llm_output:
            return AgentFinish(
                # Return values is generally always a dictionary with a single `output` key
                # It is not recommended to try anything else at the moment :)
                return_values={"output": llm_output.split("Final Answer:")[-1].strip()},
                log=llm_output,
            )
        # Parse out the action and action input
        regex = r"Action: (.*?)[\n]*Action Input:[\s]*(.*)"
        match = re.search(regex, llm_output, re.DOTALL)
        if not match:
            raise ValueError(f"Could not parse LLM output: `{llm_output}`")
        action = match.group(1).strip()
        action_input = match.group(2)
        # Return the action and action input
        return AgentAction(tool=action, tool_input=action_input.strip(" ").strip('"'), log=llm_output)

def agent_run(user_input) -> None:

    # Define which tools the agent can use to answer user queries
    search = SerpAPIWrapper()
    bash = BashProcess()
    human = HumanInputRun()
    cloudwatch = CloudWatchInsightQuery()

    tools = [
        Tool(
            name = "Search",
            func=search.run,
            description="useful for when you need to answer questions about current events"
        ),
        Tool(
            name = "human",
            func=human.run,
            description="useful for when you need to ask the human for input"
        ),
        Tool(
            name = "cloudwatch",
            func=cloudwatch.run,
            description="useful for when you need run an AWS cloudwatch insight query or search logs in AWS"
        ),
        Tool(
            name = "terminal",
            func=bash.run,
            description="useful for when you need to run commands in a terminal"
        )
    ]

    # Set up the base template
    template = """You are a cyber Security professional. You will take steps to achieve the requested task. 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

    Question: {input}
    {agent_scratchpad}"""

    prompt = CustomPromptTemplate(template=template,tools=tools,input_variables=["input", "intermediate_steps"])
    output_parser = CustomOutputParser()
    llm = OpenAI(temperature=0)
    llm_chain = LLMChain(llm=llm, prompt=prompt)

    tool_names = [tool.name for tool in tools]
    agent = LLMSingleActionAgent(
        llm_chain=llm_chain, 
        output_parser=output_parser,
        stop=["\nObservation:"], 
        allowed_tools=tool_names
    )

    agent_executor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
    agent_executor.run(user_input)

langchain.agents.AgentExecutor.from_agent_and_tools langchain.agents.LLMSingleActionAgent langchain_tools.cwtool.CloudWatchInsightQuery langchain.LLMChain langchain.tools.human.tool.HumanInputRun langchain.utilities.BashProcess langchain.SerpAPIWrapper langchain.agents.Tool langchain.OpenAI
[((2630, 2646), 'langchain.SerpAPIWrapper', 'SerpAPIWrapper', ([], {}), '()\n', (2644, 2646), False, 'from langchain import OpenAI, SerpAPIWrapper, LLMChain\n'), ((2658, 2671), 'langchain.utilities.BashProcess', 'BashProcess', ([], {}), '()\n', (2669, 2671), False, 'from langchain.utilities import BashProcess\n'), ((2684, 2699), 'langchain.tools.human.tool.HumanInputRun', 'HumanInputRun', ([], {}), '()\n', (2697, 2699), False, 'from langchain.tools.human.tool import HumanInputRun\n'), ((2717, 2741), 'langchain_tools.cwtool.CloudWatchInsightQuery', 'CloudWatchInsightQuery', ([], {}), '()\n', (2739, 2741), False, 'from langchain_tools.cwtool import CloudWatchInsightQuery\n'), ((4330, 4351), 'langchain.OpenAI', 'OpenAI', ([], {'temperature': '(0)'}), '(temperature=0)\n', (4336, 4351), False, 'from langchain import OpenAI, SerpAPIWrapper, LLMChain\n'), ((4368, 4400), 'langchain.LLMChain', 'LLMChain', ([], {'llm': 'llm', 'prompt': 'prompt'}), '(llm=llm, prompt=prompt)\n', (4376, 4400), False, 'from langchain import OpenAI, SerpAPIWrapper, LLMChain\n'), ((4461, 4587), 'langchain.agents.LLMSingleActionAgent', 'LLMSingleActionAgent', ([], {'llm_chain': 'llm_chain', 'output_parser': 'output_parser', 'stop': "['\\nObservation:']", 'allowed_tools': 'tool_names'}), "(llm_chain=llm_chain, output_parser=output_parser, stop\n    =['\\nObservation:'], allowed_tools=tool_names)\n", (4481, 4587), False, 'from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser\n'), ((4645, 4719), 'langchain.agents.AgentExecutor.from_agent_and_tools', 'AgentExecutor.from_agent_and_tools', ([], {'agent': 'agent', 'tools': 'tools', 'verbose': '(True)'}), '(agent=agent, tools=tools, verbose=True)\n', (4679, 4719), False, 'from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser\n'), ((2150, 2189), 're.search', 're.search', (['regex', 'llm_output', 're.DOTALL'], {}), '(regex, llm_output, re.DOTALL)\n', (2159, 2189), False, 'import re\n'), ((2765, 2887), 'langchain.agents.Tool', 'Tool', ([], {'name': '"""Search"""', 'func': 'search.run', 'description': '"""useful for when you need to answer questions about current events"""'}), "(name='Search', func=search.run, description=\n    'useful for when you need to answer questions about current events')\n", (2769, 2887), False, 'from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser\n'), ((2940, 3046), 'langchain.agents.Tool', 'Tool', ([], {'name': '"""human"""', 'func': 'human.run', 'description': '"""useful for when you need to ask the human for input"""'}), "(name='human', func=human.run, description=\n    'useful for when you need to ask the human for input')\n", (2944, 3046), False, 'from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser\n'), ((3099, 3251), 'langchain.agents.Tool', 'Tool', ([], {'name': '"""cloudwatch"""', 'func': 'cloudwatch.run', 'description': '"""useful for when you need run an AWS cloudwatch insight query or search logs in AWS"""'}), "(name='cloudwatch', func=cloudwatch.run, description=\n    'useful for when you need run an AWS cloudwatch insight query or search logs in AWS'\n    )\n", (3103, 3251), False, 'from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser\n'), ((3299, 3410), 'langchain.agents.Tool', 'Tool', ([], {'name': '"""terminal"""', 'func': 'bash.run', 'description': '"""useful for when you need to run commands in a terminal"""'}), "(name='terminal', func=bash.run, description=\n    'useful for when you need to run commands in a terminal')\n", (3303, 3410), False, 'from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser\n')]