LangChain Create_React_Agent Tool Callings always use the same input

from langchain.agents import AgentExecutor, create_react_agent
from langchain_google_vertexai import ChatVertexAI
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate

agent_prompt_2 = \
"""
Persona
You are a highly accurate and efficient response selection agent. Your operation is silent and precise, focused exclusively on matching user intent to the correct pre-defined template based on the conversation context. You are equipped with a tool called get_template that allows you to search a database of pre-defined templates using a concise query string.
You have the access of the following tool: {tools}
Objective
Your sole objective is to analyze the user's input and the recent conversation history, find the single most relevant, complete, and ready-to-use template using the available tool, and return only its text as the final answer.
Core Directives
Analyze Context: Carefully analyze the user's latest input string ({input}) AND the provided conversation history ({messages}) to understand the full context, primary intent, and language.
Formulate & Execute Query: Formulate a concise search query in the same language as the user's input. Call the get_template tool with this query.
Grade Results: Critically evaluate the templates returned by the tool based on three primary criteria, in this order of importance:
Placeholder Check: The template must not contain any placeholders (e.g., {{variable_name}}, [placeholder]). A template with placeholders is always a "Bad" match and must be rejected.
Language Match: The template must be in the same language as the user's input. A template in a different language is always a "Bad" match.
Relevance: The template must be a direct and complete answer to the user's specific need.
Assign a mental grade based on these criteria: Good, Partial, or Bad.
Decide Next Step (Refine Query if Necessary):
If the grade is Good, proceed to select the single best template and provide the Final Answer.
If the grade is Partial or Bad, you must attempt a second, refined search. To do this:
In a new Thought step, explain why the first search failed (e.g., "results were too generic," "results contained placeholders").
Formulate a new Action Input for the get_template tool. This new input must be a modified version of your original query designed for better searching.
Modification Strategies: Improve your query by adding specific context from the conversation history (like product names), using more precise keywords from the user's latest message, or rephrasing the query entirely.
You may only attempt this refined search once.
Final Output: If a good template is found (either on the first or second attempt), output its full, unaltered text in the Final Answer. If both attempts fail, you must use the fallback response.
Boundaries
Your Final Answer must only contain the selected template text.
Do not select a template if it contains placeholders like {{}} or [].
Do not modify, summarize, or alter the selected template in any way.
Do not add any conversational filler, greetings, or explanations to your Final Answer.
Do not return more than one template in the Final Answer.
Do not use Markdown or any other formatting in your Final Answer.
Output Format
You must always use the ReAct (Reasoning and Acting) format. Your output must follow this exact sequence, repeating the Thought/Action/Action Input/Observation cycle a maximum of two times.
Thought: Your reasoning about the user's input, the conversation history, and your plan to use the tool.
Action: The action to take, which must be in [{tool_names}].
Action Input: The string you will use to search for the template.
Observation: The result returned from the get_template tool.
Thought: Your reasoning for selecting the best template, concluding the results are inadequate and need refinement, or concluding that no template fits.
(Optional) Action -> Action Input -> Observation -> Thought
Final Answer: The full, unaltered text of the selected template.
The User Question you are going to handle: {input}
Recent chat: {messages}
Thought:{agent_scratchpad}
Examples
User Input (Language Selection):
Conversation History: []
Current Input: ¿Cuál es el horario de atención?
Ideal Agent Output:
Thought: The user is asking for business hours in Spanish. I need to find a template for business hours that is in Spanish and has no placeholders.
Action: get_template
Action Input: horario de atención
Observation: ["Template ES: Nuestro horario de atención es de lunes a viernes de 9:00 a 18:00.", "Template EN: Our business hours are Monday to Friday from 9:00 AM to 6:00 PM."]
Thought: The tool returned two templates. The Spanish template has no placeholders and matches the user's language. The English template is in the wrong language. Therefore, the Spanish template is a "Good" match.
Final Answer: Nuestro horario de atención es de lunes a viernes de 9:00 a 18:00.
User Input (Requires Refinement):
Conversation History: User: I'm having trouble with my recent order #A-123., Agent: I see that order #A-123 was for a 'Smart Toaster Pro'. What seems to be the issue?
Current Input: it's not working
Ideal Agent Output:
Thought: The user says "it's not working". The context from the conversation history is that "it" refers to the 'Smart Toaster Pro'. The initial query should be about troubleshooting this product.
Action: get_template
Action Input: product not working
Observation: ["Template 1: General troubleshooting steps for all smart devices.", "Template 2: How to check your WiFi connection."]
Thought: The results are too generic. This is a "Partial" match. I need to refine my Action Input to be more specific by using the product name from the conversation history. I will try a second time with a more targeted query.
Action: get_template
Action Input: Smart Toaster Pro troubleshooting
Observation: ["To troubleshoot your Smart Toaster Pro, first ensure it is plugged into a working outlet. Then, try performing a factory reset by holding the 'reset' button for 10 seconds. For more detailed steps, visit our guide at [link]."]
Thought: This result is a perfect match ("Good"). It provides specific troubleshooting steps for the 'Smart Toaster Pro'. I now know the final answer.
Final Answer: To troubleshoot your Smart Toaster Pro, first ensure it is plugged into a working outlet. Then, try performing a factory reset by holding the 'reset' button for 10 seconds. For more detailed steps, visit our guide at [link].
Fallback Responses
If the get_template tool returns no relevant, complete, and placeholder-free results after your final attempt, your final thought should explain this, and the Final Answer must be the exact text: NO_TEMPLATE_FOUND
"""

agent_llm = ChatVertexAI(model="gemini-2.5-flash", location="europe-central2", temperature=0.5, seed=42)
tools = [get_template]
agent_prompt = PromptTemplate.from_template(
    template=agent_prompt_2
)

agent = create_react_agent(agent_llm, tools, agent_prompt, )
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, max_iterations=3, early_stopping_method="force", return_intermediate_steps=True)

def agent_node(state: WorkflowState, config: RunnableConfig) -> Dict:
    print("🤖 Agent Node: Starting agent execution loop...")
    recent_msgs = conv_to_str(state["messages"][-10:])  # Get the latest user message
    # Ensure intermediate_steps exists
    
    prompt_manager = get_prompt_manager()
        
    intermediate_steps = state.get("intermediate_steps", [])
    question = state["messages"][-1].content
        
    # Prepare initial input for agent
    agent_input = {
        "input": question,  # First question
        "messages": recent_msgs,
        "agent_scratchpad": format_intermediate_steps(intermediate_steps),
        # "tools": tools,
        # "tool_names": ", ".join([tool.name for tool in tools]),
    }
    last_tool_result = state.get("last_tool_result", "")  # Initialize from state
    print(f"Agent input: {agent_input}")
    try:
        # Invoke agent
        result = agent_executor.invoke(agent_input, config)
        # print(f"Agent result type: {type(result)}, value: {result}")
        print(f"Agent result: {result}")
        
        if 'output' in result and result['output'] != "Agent stopped due to iteration limit or time limit.":
            return {
                "current_answer": result['output'],
                "current_research_agent": "agent_node",
                "research_attempts": state.get("research_attempts", []) + ["agent_node"],

            }
        else:
            last_step = result.get("intermediate_steps", [])[-1] if result.get("intermediate_steps") else None
            if last_step:
                last_action, last_tool_result = last_step # last tool result is a list of templates in format of "Template Heading": , Template Content: 
                print(f"Last action: {last_action}, Last tool result: {last_tool_result}")
                question = result['input']
                prompt_manager = get_prompt_manager()                
                rerank_prompt = prompt_manager.get_prompt(
                    PromptType.RERANKING,
                    config.get('metadata', {}).get('branch_id', '1'),
                    config.get('metadata', {}).get('database_name', 'PHARMCARE_DEMO')
                )
                prompt = ChatPromptTemplate([
                    ("system", rerank_prompt),
                    ("human", """User Question: {question}
                    
                    Input Questions:
                    {contexts}
                    Language: {language}
                    """)
                ])
                language = config.get('metadata', {}).get('language', 'zh-TW')
                rerank_chain = prompt | logic_llm | StrOutputParser()
                ranking_result = rerank_chain.invoke({
                    "question": question,
                    "contexts": "\n".join(last_tool_result),
                    "language": language
                })
                
                # Parse ranking result
                rankings = [int(x.strip()) - 1 for x in ranking_result.split("|") if x.strip().isdigit()]
                
                # Validate rankings
                valid_rankings = [r for r in rankings if 0 <= r < len(last_tool_result)]
                
                # Add any missing contexts at the end
                missing = [i for i in range(len(last_tool_result)) if i not in valid_rankings]
                valid_rankings.extend(missing)

                reranked_tool_output = [last_tool_result[i] for i in valid_rankings]
                
                candidate_template = reranked_tool_output[0]
                candidate_answer = candidate_template.split("Template Content:")[-1].strip() if "Template Content:" in candidate_template else candidate_template
                return {
                    "current_answer": candidate_answer,
                    "current_research_agent": "agent_node",
                    "research_attempts": state.get("research_attempts", []) + ["agent_node"],
                }
            else:
                print("⚠️ No intermediate steps found from agent execution")
                return {
                    "current_answer": "I'm sorry, I couldn't find a suitable answer to your question.",
                    "current_research_agent": "agent_node",
                    "research_attempts": state.get("research_attempts", []) + ["agent_node"],
                }

    except Exception as e:
        print(f"⚠️ Agent execution error: {e}")
        return {
            "current_answer": "I'm sorry, I encountered an error while processing your request.",
            "current_research_agent": "agent_node",
            "research_attempts": state.get("research_attempts", []) + ["agent_node"],
        }

I’m using langchain==0.3.27, and I’ve noticed something unexpected with my agent. When the agent calls a tool (in this case, get_template), the action_input passed to the tool is always the same in every iteration.

For example, during three consecutive iterations inside agent_executor, the agent calls the get_template tool three times — but with the exact same input each time, instead of adjusting based on previous outputs or context.

Is this expected behavior? Am I missing something in the setup that would allow the agent to update the tool input between iterations?

I believe the issue you are encountering is due to the seed parameter on this line. If you remove seed, you will likely begin to get different results.

From the vertex docs:

When seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn’t guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used.