I have made a ai agent that can help me. but right now it can only call one tool or 2 at the time. but for example for a question like "age of bratt pid multiplied by 300" it needs to search for the age and after it did that use the multiply tool. 
import gradio as gr
from langchain_ollama import ChatOllama
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain import hub
from langchain.tools import DuckDuckGoSearchRun, tool
from langchain.prompts import ChatPromptTemplate
import math
from typing import Dict, Any
from langsmith import traceable
import os

os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "<TRUFFLEHOG_REDACTED_LANGSMITH>"
os.environ["LANGCHAIN_PROJECT"] = "ollama tool call"

# Initialize the Ollama model
llm = ChatOllama(model="llama3.1", temperature=1)

# Create the DuckDuckGo search tool
search_tool = DuckDuckGoSearchRun()

# Create a math tool
@tool
def multiply(a: int, b: int) -> int:
    """Multiply a and b."""
    return a * b

# Create the tools list
tools = [search_tool, multiply]

# Get the OpenAI tools agent prompt
# prompt = hub.pull("hwchase17/openai-tools-agent")
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "You are a helpful assistant. You fulfill any request the user asks!"),
        ("placeholder", "{chat_history}"),
        ("human", "{input}"),
        ("placeholder", "{agent_scratchpad}"),
    ]
)
# prompt = "you are a helpful assistant"

# Create the agent
agent = create_openai_tools_agent(llm, tools, prompt)

# Create the agent executor
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# Function to run the agent
@traceable
def run_agent(input_text: str) -> Dict[str, Any]:
    try:
        response = agent_executor.invoke({"input": input_text})
        return {"output": response["output"]}
    except Exception as e:
        return {"output": f"An error occurred: {str(e)}"}

# Create the Gradio interface
iface = gr.Interface(
    fn=run_agent,
    inputs=gr.Textbox(lines=2, placeholder="Enter your question here..."),
    outputs=gr.Textbox(label="Agent Response"),
    title="Ollama Agent with Tool Calling",
    description="Ask a question, and the agent will use Ollama (llama3.1 model) with tool calling to answer.",
)

# Launch the interface
iface.launch()



But to have a work around that I want to implement a langgraph node workflow for the llm. For you to understand the key concepts of langgraph I have a example code right here:
import json
import ast
from langchain_core.runnables import RunnableLambda
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
from langchain_core.messages import HumanMessage
from models.openai_models import get_open_ai_json
from langgraph.checkpoint.sqlite import SqliteSaver
from agents.agents import (
    PlannerAgent,
    SelectorAgent,
    ReporterAgent,
    ReviewerAgent,
    RouterAgent,
    FinalReportAgent,
    EndNodeAgent
)
from prompts.prompts import (
    reviewer_prompt_template, 
    planner_prompt_template, 
    selector_prompt_template, 
    reporter_prompt_template,
    router_prompt_template,
    reviewer_guided_json,
    selector_guided_json,
    planner_guided_json,
    router_guided_json

)
from tools.google_serper import get_google_serper
from tools.basic_scraper import scrape_website
from states.state import AgentGraphState, get_agent_graph_state, state

def create_graph(server=None, model=None, stop=None, model_endpoint=None, temperature=0):
    graph = StateGraph(AgentGraphState)

    graph.add_node(
        "planner", 
        lambda state: PlannerAgent(
            state=state,
            model=model,
            server=server,
            guided_json=planner_guided_json,
            stop=stop,
            model_endpoint=model_endpoint,
            temperature=temperature
        ).invoke(
            research_question=state["research_question"],
            feedback=lambda: get_agent_graph_state(state=state, state_key="reviewer_latest"),
            # previous_plans=lambda: get_agent_graph_state(state=state, state_key="planner_all"),
            prompt=planner_prompt_template
        )
    )

    graph.add_node(
        "selector",
        lambda state: SelectorAgent(
            state=state,
            model=model,
            server=server,
            guided_json=selector_guided_json,
            stop=stop,
            model_endpoint=model_endpoint,
            temperature=temperature
        ).invoke(
            research_question=state["research_question"],
            feedback=lambda: get_agent_graph_state(state=state, state_key="reviewer_latest"),
            previous_selections=lambda: get_agent_graph_state(state=state, state_key="selector_all"),
            serp=lambda: get_agent_graph_state(state=state, state_key="serper_latest"),
            prompt=selector_prompt_template,
        )
    )

    graph.add_node(
        "reporter", 
        lambda state: ReporterAgent(
            state=state,
            model=model,
            server=server,
            stop=stop,
            model_endpoint=model_endpoint,
            temperature=temperature
        ).invoke(
            research_question=state["research_question"],
            feedback=lambda: get_agent_graph_state(state=state, state_key="reviewer_latest"),
            previous_reports=lambda: get_agent_graph_state(state=state, state_key="reporter_all"),
            research=lambda: get_agent_graph_state(state=state, state_key="scraper_latest"),
            prompt=reporter_prompt_template
        )
    )

    graph.add_node(
        "reviewer", 
        lambda state: ReviewerAgent(
            state=state,
            model=model,
            server=server,
            guided_json=reviewer_guided_json,
            stop=stop,
            model_endpoint=model_endpoint,
            temperature=temperature
        ).invoke(
            research_question=state["research_question"],
            feedback=lambda: get_agent_graph_state(state=state, state_key="reviewer_all"),
            # planner=lambda: get_agent_graph_state(state=state, state_key="planner_latest"),
            # selector=lambda: get_agent_graph_state(state=state, state_key="selector_latest"),
            reporter=lambda: get_agent_graph_state(state=state, state_key="reporter_latest"),
            # planner_agent=planner_prompt_template,
            # selector_agent=selector_prompt_template,
            # reporter_agent=reporter_prompt_template,
            # serp=lambda: get_agent_graph_state(state=state, state_key="serper_latest"),
            prompt=reviewer_prompt_template
        )
    )

    graph.add_node(
        "router", 
        lambda state: RouterAgent(
            state=state,
            model=model,
            server=server,
            guided_json=router_guided_json,
            stop=stop,
            model_endpoint=model_endpoint,
            temperature=temperature
        ).invoke(
            research_question=state["research_question"],
            feedback=lambda: get_agent_graph_state(state=state, state_key="reviewer_all"),
            # planner=lambda: get_agent_graph_state(state=state, state_key="planner_latest"),
            # selector=lambda: get_agent_graph_state(state=state, state_key="selector_latest"),
            # reporter=lambda: get_agent_graph_state(state=state, state_key="reporter_latest"),
            # planner_agent=planner_prompt_template,
            # selector_agent=selector_prompt_template,
            # reporter_agent=reporter_prompt_template,
            # serp=lambda: get_agent_graph_state(state=state, state_key="serper_latest"),
            prompt=router_prompt_template
        )
    )


    graph.add_node(
        "serper_tool",
        lambda state: get_google_serper(
            state=state,
            plan=lambda: get_agent_graph_state(state=state, state_key="planner_latest")
        )
    )

    graph.add_node(
        "scraper_tool",
        lambda state: scrape_website(
            state=state,
            research=lambda: get_agent_graph_state(state=state, state_key="selector_latest")
        )
    )

    graph.add_node(
        "final_report", 
        lambda state: FinalReportAgent(
            state=state
        ).invoke(
            final_response=lambda: get_agent_graph_state(state=state, state_key="reporter_latest")
        )
    )

    graph.add_node("end", lambda state: EndNodeAgent(state).invoke())

    # Define the edges in the agent graph
    def pass_review(state: AgentGraphState):
        review_list = state["router_response"]
        if review_list:
            review = review_list[-1]
        else:
            review = "No review"

        if review != "No review":
            if isinstance(review, HumanMessage):
                review_content = review.content
            else:
                review_content = review
            
            review_data = json.loads(review_content)
            next_agent = review_data["next_agent"]
        else:
            next_agent = "end"

        return next_agent

    # Add edges to the graph
    graph.set_entry_point("planner")
    graph.set_finish_point("end")
    graph.add_edge("planner", "serper_tool")
    graph.add_edge("serper_tool", "selector")
    graph.add_edge("selector", "scraper_tool")
    graph.add_edge("scraper_tool", "reporter")
    graph.add_edge("reporter", "reviewer")
    graph.add_edge("reviewer", "router")

    graph.add_conditional_edges(
        "router",
        lambda state: pass_review(state=state),
    )

    graph.add_edge("final_report", "end")

    return graph

def compile_workflow(graph):
    workflow = graph.compile()
    return workflow
	
	
	
	
	
	
and to solve my issue I want to make some kind of intelligent graph workflow with a planner a reviewer and a few other steps that utilze the tools. Reply with a full working implementation of my react ollama agent using my already made tool for duckduckgo serach and multiplying and make sure to keep the tracing stuff and the api keys from my code I gave you! Reply with full working code and explaination
Important is that I want to have a general react agent that utilzes his tool to get the best possible answer to the user. I dont want it to specifically focus on my example prompt with the age multiplication.