Tool calling agent enters infinite loop with custom function

Agent keeps calling the same tool repeatedly

I have a strange issue with my Langchain agent setup. When I inquire about network details, it should only trigger my custom tool once and yield the response. However, it continuously invokes the same function, appearing to be stuck in a loop.

The tool executes correctly and provides the right information each time, yet the agent fails to recognize that it has obtained the information it requires. Has anyone else experienced this type of issue before?

Here’s my code:

from langchain_ollama import ChatOllama
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.tools import tool

from network_utils import fetch_user_ip

model = ChatOllama(
    model="llama3.2",
    temperature=0.2,
)

system_prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            """You are a network assistant. Keep responses under 25 words.
            When users ask for their IP, use the get_current_ip tool.
            Sample interaction:
            User: Show me my IP
            Assistant: Your IP is 192.168.1.100""",
        ),
        MessagesPlaceholder(variable_name="chat_history"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),
        ("user", "{input}"),
    ]
)

session_store = {}

@tool
def get_current_ip():
    """Retrieves the current IPv4 address of the user."""
    return fetch_user_ip()

available_tools = [get_current_ip]

network_agent = create_tool_calling_agent(model, available_tools, system_prompt)

executor = AgentExecutor(
    agent=network_agent,
    tools=available_tools,
    verbose=True,
)

def retrieve_chat_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in session_store:
        session_store[session_id] = ChatMessageHistory()
    return session_store[session_id]

def query_with_stream(user_query, session_id):
    agent_with_history = RunnableWithMessageHistory(
        executor,
        retrieve_chat_history,
        input_messages_key="input",
        history_messages_key="chat_history",
    )
    return agent_with_history.stream({"input": user_query}, config={"configurable": {"session_id": session_id}})

def query_direct(user_query, session_id):
    agent_with_history = RunnableWithMessageHistory(
        executor,
        retrieve_chat_history,
        input_messages_key="input",
        history_messages_key="chat_history",
    )
    return agent_with_history.invoke({"input": user_query}, config={"configurable": {"session_id": session_id}})

Debug output shows:

Starting new AgentExecutor chain…

Calling: get_current_ip with {}

Retrieving network address…
10.0.1.45
Calling: get_current_ip with {}

Retrieving network address…
10.0.1.45
Calling: get_current_ip with {}

This loop happens when the model can’t tell it’s done with the tool. Your 25-word limit is probably messing with the agent’s ability to reason through completion. Drop that constraint and let the agent decide when it’s finished. Also check if fetch_user_ip() is throwing exceptions or returning None - even silent failures can make the agent think it needs to keep retrying.

your agent doesn’t know when to stop. add return_direct=True to your @tool decorator - it’ll use the tool result as the final answer instead of thinking it needs more info.

Been there with tool calling loops. Usually happens when the agent can’t parse what your tool returns.

Try wrapping your response in a cleaner format:

@tool
def get_current_ip():
    """Retrieves the current IPv4 address of the user."""
    ip = fetch_user_ip()
    return f"Current IP address: {ip}"

Check if fetch_user_ip() returns clean data. No weird characters, trailing whitespace, or funky response types. I’ve seen agents choke on that stuff.

Also - 25 words might be too tight for the agent to reason through tool calls properly. Try 50 words instead.

Your system prompt probably isn’t telling the agent when to stop after getting the tool result. The agent doesn’t know it’s done once it has what it needs. Update your system prompt to be super explicit: “After calling get_current_ip tool, immediately respond with the IP address and stop.” Also make sure your tool always returns a string - not None or anything else. I’ve found that explicit stop conditions in the prompt work way better than messing with tool parameters.

I’ve hit this exact issue with Ollama and tool calling. llama3.2 probably isn’t formatting the tool response correctly, so the agent doesn’t know when it’s done. Quick fixes: add max_iterations=3 to your AgentExecutor to stop infinite loops, and double-check that fetch_user_ip() returns a clean string - agents can be picky about response formats. Honestly though, if you can swing it, GPT or Claude handle tool calling way better than llama3.2.