Repository: langchain
Issue: Issue with Ollama Function – Only Agent Response, No Tool Calls
Labels: 🤖:bug

### Checked other resources

- [X] I added a very descriptive title to this issue.
- [X] I searched the [LangGraph](https://langchain-ai.github.io/langgraph/)/LangChain documentation with the integrated search.
- [X] I used the GitHub search to find a similar question and didn't find it.
- [X] I am sure that this is a bug in LangGraph/LangChain rather than my code.
- [X] I am sure this is better as an issue [rather than a GitHub discussion](https://github.com/langchain-ai/langgraph/discussions/new/choose), since this is a LangGraph bug and not a design question.

### Example Code

```python
from langchain_core.messages import AIMessage
from langgraph.graph import END, StateGraph
from typing import TypedDict
from langchain_core.tools import tool
from langchain_experimental.llms.ollama_functions import OllamaFunctions
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain.prompts import PromptTemplate
from termcolor import colored
import json
# Import tools from tools.py
from tools import get_current_weather, get_system_time

# using OllamaFunctions from experimental because it supports function binding with llms
model = OllamaFunctions(
    base_url="http://localhost:11436",
    model="llama3.1",
    format="json"
    )

model_with_tools = model.bind_tools(
    tools=[get_current_weather, get_system_time],
)

tool_mapping = {
    'get_current_weather': get_current_weather,
    'get_system_time': get_system_time,
}

# Define Agent Prompt template for llama3
agent_request_generator_prompt = PromptTemplate(
    template=
    """
    

    
system

        You are a Smart Agent. 
        You are a master at understanding what a customer wants.
        You evaluate every request and utilize available tools if you have to.
    

    
user

    Conduct a comprehensive analysis of the request provided\

    USER REQUEST:\n\n {initial_request} \n\n

    

    
assistant

    """,
    input_variables=["initial_request"],
)

agent_request_generator = agent_request_generator_prompt | model_with_tools
# result = agent_request_generator.invoke({"initial_request": "What is the weather in woodbury in MN?"})
# print(result)
# input("...")

# Pydantic Schema for structured response
class Evaluation(BaseModel):
    result: bool = Field(description="True or False", required=True)

# Prompt template llama3
category_generator_prompt = PromptTemplate(
    template=
    """
    

    
system

        You are a Smart Router Agent. You are a master at reviewing whether the original question that customer asked was answered in the tool response.
        You understand the context and question below and return your answer in JSON.
    

    
user

    CONTEXT: Conduct a comprehensive analysis of the Initial Request from user and Tool Response and route the request into boolean true or false:
        True - used when INITIAL REQUEST appears to be answered by TOOL RESPONSE. \
        False - used when INITIAL REQUEST is not answered by TOOL RESPONSE or when TOOL RESPONSE is empty \

            Output either True or False \
            eg:
            'True' \n\n
    INITIAL REQUEST:\n\n {research_question} \n\n
    TOOL RESPONSE:\n\n {tool_response} \n\n

    JSON:
    

    
assistant

    """,
 input_variables=["research_question", "tool_response"],
)

structured_llm = model.with_structured_output(Evaluation)
category_generator = category_generator_prompt | structured_llm

# result = category_generator.invoke({"research_question": "What is the weather in woodbury in MN?", "tool_response":"65C, Sunny"})
# print(result)
# input("...")


class AgentState(TypedDict):
    research_question: str
    tool_response: str
    agent_response: AIMessage
    agent_call_count: int = 0
    tool_call_count: int = 0

  
def agent(state: AgentState):
    print(colored("STATE at agent start:", "magenta"), colored(state, "cyan"))
    input("Paused ... Hit Enter to Execute Agent Logic...")
    last_ai_message = agent_request_generator.invoke({"initial_request": state["research_question"]})
    state["agent_call_count"] += 1
    #append the response to the agent_response list in the state
    if last_ai_message is not None:
        state["agent_response"] = last_ai_message 
        if last_ai_message.content is not None and last_ai_message.content != "" :
            state["tool_response"]=last_ai_message.content
    print(colored("STATE at agent end:", "magenta"), colored(state, "cyan"))
    input("Paused Hit Enter to go to Should Continue Logic...")    
    return state
   
    
def should_continue(state: AgentState):
    print(colored("STATE at should_continue start:", "magenta"), colored(state, "cyan"))
    input("Paused at Should Continue Start")
    print(colored("Evaluating whether the Question is Answered by the tool response or not... Please wait...", "red"))
    result = category_generator.invoke({"research_question": state["research_question"],
                                         "tool_response":state["tool_response"]
                                        })
    if isinstance(result, Evaluation):
        # Access the 'result' attribute from Evaluation
        print(colored("Is tool response good and should the flow go to END node? ", "cyan"), colored(result.result, "yellow"))
        input("Paused at Should Continue Mid")
        
        if result.result:  # If result is True
            print(colored("Return end", "red"))
            return "end"
        else:  # If result is False
            print(colored("Return continue", "green"))
            return "continue"
    else:
        print("Result is not an Evaluation instance, returning 'end' as default.")
        return "end"

def call_tool(state: AgentState):
    print(colored("STATE at call_tool start:", "magenta"), colored(state, "cyan"))
    input("Paused at call_tool Start")
    agent_response = state["agent_response"]
    
    if hasattr(agent_response, 'tool_calls') and len(agent_response.tool_calls) > 0:
        tool_call = agent_response.tool_calls[0]
        tool = tool_mapping[tool_call["name"].lower()]
        try:
            tool_output = tool.invoke(tool_call["args"])
            state["tool_call_count"] += 1
            print(colored("Tool output:", "magenta"), colored(tool_output, "green"))
            if tool_output is not None:
                state["tool_response"] = tool_output
        except Exception as e:
            print(f"Error invoking tool: {e}")
            # Handle the error or log it as needed
    else:
        print("No tool calls found in agent response.")
    print(colored("STATE at call_tool end:", "magenta"), colored(state, "cyan"))
    input("Paused at call_tool End")
    return state

workflow = StateGraph(AgentState)

workflow.add_node("agent", agent)
workflow.add_node("action", call_tool)

workflow.set_entry_point("agent")

workflow.add_conditional_edges(
    "agent",
    should_continue,
    {
        "continue": "action",
        "end": END,
    },
)
workflow.add_edge("action", "agent")

app = workflow.compile()

#helper method to visualize graph
def save_graph_to_file(runnable_graph, output_file_path):
    png_bytes = runnable_graph.get_graph().draw_mermaid_png()
    with open(output_file_path, 'wb') as file:
        file.write(png_bytes)

save_graph_to_file(app, "output-05.png")


# research_question = "What's the current system time?"
research_question = "Tell me a joke?"
# research_question = "How is the weather in Woodbury MN today?"
# research_question = "What is the cause of earthquakes?"



state : AgentState = {"research_question": research_question,
                      "tool_response": [] ,
                      "agent_response": [],
                      "agent_call_count": 0,
                      "tool_call_count": 0
                      }
result = app.invoke(state)
print("\n")
print(colored("FINAL STATE at end:", "magenta"), colored(result, "cyan"))

print(colored("FINAL RESPONSE at end:", "magenta"), colored(result["tool_response"], "cyan"))
```


### Error Message and Stack Trace (if applicable)

```shell
STATE at agent start: {'research_question': 'Tell me a joke?', 'tool_response': [], 'agent_response': [], 'agent_call_count': 0, 'tool_call_count': 0}
Paused ... Hit Enter to Execute Agent Logic...
STATE at agent end: {'research_question': 'Tell me a joke?', 'tool_response': "Here's one: Why couldn't the bicycle stand up by itself? Because it was two-tired!", 'agent_response': AIMessage(content="Here's one: Why couldn't the bicycle stand up by itself? Because it was two-tired!", additional_kwargs={}, response_metadata={}, id='run-409504c1-afb2-49f7-8ace-bc08c89fdd33-0'), 'agent_call_count': 1, 'tool_call_count': 0}
Paused Hit Enter to go to Should Continue Logic...
STATE at should_continue start: {'research_question': 'Tell me a joke?', 'tool_response': "Here's one: Why couldn't the bicycle stand up by itself? Because it was two-tired!", 'agent_response': AIMessage(content="Here's one: Why couldn't the bicycle stand up by itself? Because it was two-tired!", additional_kwargs={}, response_metadata={}, id='run-409504c1-afb2-49f7-8ace-bc08c89fdd33-0'), 'agent_call_count': 1, 'tool_call_count': 0}
Paused at Should Continue Start
Evaluating whether the Question is Answered by the tool response or not... Please wait...
Traceback (most recent call last):
  File "/home/poc/agentic-workflow/langgraph-learning/tutorials/example_1.py", line 203, in <module>
    result = app.invoke(state)
             ^^^^^^^^^^^^^^^^^
  File "/home/miniconda3/lib/python3.11/site-packages/langgraph/pregel/__init__.py", line 1749, in invoke
    for chunk in self.stream(
  File "/home/miniconda3/lib/python3.11/site-packages/langgraph/pregel/__init__.py", line 1477, in stream
    for _ in runner.tick(
  File "/home/miniconda3/lib/python3.11/site-packages/langgraph/pregel/runner.py", line 58, in tick
    run_with_retry(t, retry_policy)
  File "/home/miniconda3/lib/python3.11/site-packages/langgraph/pregel/retry.py", line 29, in run_with_retry
    task.proc.invoke(task.input, config)
  File "/home/miniconda3/lib/python3.11/site-packages/langgraph/utils/runnable.py", line 412, in invoke
    input = context.run(step.invoke, input, config)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/miniconda3/lib/python3.11/site-packages/langgraph/utils/runnable.py", line 184, in invoke
    ret = context.run(self.func, input, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/miniconda3/lib/python3.11/site-packages/langgraph/graph/graph.py", line 95, in _route
    result = self.path.invoke(value, config)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/miniconda3/lib/python3.11/site-packages/langgraph/utils/runnable.py", line 176, in invoke
    ret = context.run(self.func, input, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/poc/agentic-workflow/langgraph-learning/tutorials/example_1.py", line 121, in should_continue
    result = category_generator.invoke({"research_question": state["research_question"],
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/miniconda3/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 3024, in invoke
    input = context.run(step.invoke, input, config)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/miniconda3/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 5354, in invoke
    return self.bound.invoke(
           ^^^^^^^^^^^^^^^^^^
  File "/home/miniconda3/lib/python3.11/site-packages/langchain_core/language_models/chat_models.py", line 286, in invoke
    self.generate_prompt(
  File "/home/miniconda3/lib/python3.11/site-packages/langchain_core/language_models/chat_models.py", line 786, in generate_prompt
    return self.generate(prompt_messages, stop=stop, callbacks=callbacks, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/miniconda3/lib/python3.11/site-packages/langchain_core/language_models/chat_models.py", line 643, in generate
    raise e
  File "/home/miniconda3/lib/python3.11/site-packages/langchain_core/language_models/chat_models.py", line 633, in generate
    self._generate_with_cache(
  File "/home/miniconda3/lib/python3.11/site-packages/langchain_core/language_models/chat_models.py", line 851, in _generate_with_cache
    result = self._generate(
             ^^^^^^^^^^^^^^^
  File "/home/miniconda3/lib/python3.11/site-packages/langchain_experimental/llms/ollama_functions.py", line 305, in _generate
    functions = [convert_to_ollama_tool(fn) for fn in functions]
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/miniconda3/lib/python3.11/site-packages/langchain_experimental/llms/ollama_functions.py", line 305, in <listcomp>
    functions = [convert_to_ollama_tool(fn) for fn in functions]
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/miniconda3/lib/python3.11/site-packages/langchain_experimental/llms/ollama_functions.py", line 86, in convert_to_ollama_tool
    schema = tool.model_construct().model_json_schema()
             ^^^^^^^^^^^^^^^^^^^^
AttributeError: type object 'Evaluation' has no attribute 'model_construct'
```


### Description

I have encountered an issue where the ollama function returns only the agent's response but does not invoke any associated tools, as expected. I have ensured that all required libraries and dependencies are properly installed, but this behavior persists.

### System Info

"pip install langgraph httpx ipython graphviz langchain langchain_openai langchain_experimental langchain-ollama termcolor rich ollama openai docker"

platform (linux)
python version 3.11

langgraph==0.2.46
langchain==0.2.17
langchain-experimental==0.0.65
langchain-core==0.2.43