Skip to content

Commit 47cdfcf

Browse files
feat: delete support for older LLM models
- removed support for FuctionMessage
1 parent caa98ca commit 47cdfcf

File tree

2 files changed

+54
-75
lines changed

2 files changed

+54
-75
lines changed

src/agent/graph.py

Lines changed: 14 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
from copy import copy
21
from langgraph.graph import StateGraph, MessagesState, START, END
3-
from langchain_core.messages import FunctionMessage, SystemMessage, ToolMessage
2+
from langchain_core.messages import SystemMessage, ToolMessage, BaseMessage, AnyMessage
43
from langchain_core.runnables import RunnableConfig
5-
# from langchain_ollama import ChatOllama
64
from langchain_google_genai import ChatGoogleGenerativeAI
75

86
from agent.tools import tools
97
from agent.utils import (
108
has_too_many_consecutive_tool_calls,
119
message_has_tool_calls,
12-
process_new_format_tool_calls,
13-
process_old_format_function_call
10+
process_tool_calls,
11+
filter_empty_content_messages
1412
)
1513

1614

@@ -23,11 +21,6 @@ class Configuration:
2321

2422
)
2523

26-
# llm_name = "llama3.2:3b-instruct-fp16"
27-
# llm = ChatOllama(
28-
# model=llm_name,
29-
# temperature=0.1,
30-
# )
3124
llm_name = "gemini-2.5-flash"
3225
llm = ChatGoogleGenerativeAI(model=llm_name)
3326
llm_with_tools = llm.bind_tools(tools)
@@ -45,26 +38,8 @@ def agent_node(state: AgentState, config: RunnableConfig):
4538
if not messages or not isinstance(messages[0], SystemMessage):
4639
messages = [SYSTEM_MESSAGE] + messages
4740

48-
# Ensure no message has completely empty content (which causes Gemini errors)
49-
processed_messages = []
50-
for message in messages:
51-
content = getattr(message, "content", "")
52-
53-
# If content is empty or just whitespace, provide minimal content
54-
if not content or (isinstance(content, str) and not content.strip()):
55-
# Create a copy with minimal non-empty content
56-
new_message = copy(message)
57-
if isinstance(message, (ToolMessage, FunctionMessage)):
58-
new_message.content = "Completed"
59-
elif isinstance(message, SystemMessage):
60-
# Keep system message as is (it should have content)
61-
processed_messages.append(message)
62-
continue
63-
else:
64-
new_message.content = "..."
65-
processed_messages.append(new_message)
66-
else:
67-
processed_messages.append(message)
41+
# Filter messages to ensure no empty content (prevents Gemini errors)
42+
processed_messages = filter_empty_content_messages(messages)
6843

6944
response = llm_with_tools.invoke(processed_messages)
7045
return {"messages": [response]}
@@ -73,7 +48,7 @@ def agent_node(state: AgentState, config: RunnableConfig):
7348
def should_continue(state: AgentState) -> str:
7449
"""Determine if we should continue or end."""
7550
messages = state["messages"]
76-
last_message = messages[-1]
51+
last_message: AnyMessage = messages[-1]
7752

7853
# Prevent infinite loops by checking recent tool usage
7954
if has_too_many_consecutive_tool_calls(messages):
@@ -87,24 +62,20 @@ def should_continue(state: AgentState) -> str:
8762

8863

8964
def tools_node(state: AgentState, config: RunnableConfig):
90-
"""Handle tool execution for both new and old tool calling formats."""
91-
last_message = state["messages"][-1]
65+
"""Handle tool execution using the modern tool calling format."""
66+
last_message: AnyMessage = state["messages"][-1]
9267

93-
# Handle new tool_calls format (preferred)
68+
# Handle tool_calls format
9469
tool_calls = getattr(last_message, "tool_calls", None)
9570
if tool_calls:
96-
messages_to_add = process_new_format_tool_calls(tool_calls, tools)
71+
messages_to_add = process_tool_calls(tool_calls, tools)
9772
return {"messages": messages_to_add}
9873

99-
# Handle old function_call format (fallback)
100-
additional_kwargs = getattr(last_message, "additional_kwargs", {})
101-
function_call = additional_kwargs.get("function_call")
102-
if function_call:
103-
message = process_old_format_function_call(function_call, tools)
104-
return {"messages": [message]}
74+
# log a warning for observation
75+
print("Warning: No tool calls found in the last message. This may indicate an issue.")
10576

106-
# No tool calls found
107-
return {"messages": [FunctionMessage(content="No tool calls found", name="error")]}
77+
# No tool calls found - this shouldn't happen with modern LLMs
78+
return {"messages": [ToolMessage(content="No tool calls found", tool_call_id="error")]}
10879

10980

11081
workflow = StateGraph(AgentState)

src/agent/utils.py

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
"""Utility functions for the agent graph."""
22

3-
import json
4-
from typing import List, Dict, Any, Optional
5-
from langchain_core.messages import ToolMessage, FunctionMessage
3+
from copy import copy
4+
from typing import List, Dict, Any, Sequence
5+
from langchain_core.messages import BaseMessage, ToolMessage, AnyMessage, SystemMessage
66

77

8-
def has_too_many_consecutive_tool_calls(messages: list, max_calls: int = 3, look_back: int = 10) -> bool:
8+
def has_too_many_consecutive_tool_calls(
9+
messages: Sequence[AnyMessage],
10+
max_calls: int = 3,
11+
look_back: int = 10
12+
) -> bool:
913
"""Check if there are too many consecutive tool calls to prevent infinite loops."""
1014
recent_messages = messages[-look_back:] if len(
1115
messages) > look_back else messages
@@ -22,23 +26,16 @@ def has_too_many_consecutive_tool_calls(messages: list, max_calls: int = 3, look
2226
return consecutive_tool_calls >= max_calls
2327

2428

25-
def message_has_tool_calls(message) -> bool:
26-
"""Check if a message contains tool calls in either format."""
27-
# Check new tool_calls format
28-
if hasattr(message, "tool_calls") and getattr(message, "tool_calls", None):
29-
return True
29+
def message_has_tool_calls(message: AnyMessage) -> bool:
30+
"""Check if a message contains tool calls."""
31+
# Check for tool_calls format (modern standard)
32+
tool_calls = getattr(message, "tool_calls", None)
33+
return bool(tool_calls)
3034

31-
# Check old function_call format
32-
if hasattr(message, "additional_kwargs"):
33-
additional_kwargs = getattr(message, "additional_kwargs", {})
34-
return "function_call" in additional_kwargs
3535

36-
return False
37-
38-
39-
def is_tool_response(message) -> bool:
40-
"""Check if a message is a tool or function response."""
41-
return hasattr(message, "__class__") and message.__class__.__name__ in ["ToolMessage", "FunctionMessage"]
36+
def is_tool_response(message: AnyMessage) -> bool:
37+
"""Check if a message is a tool response."""
38+
return isinstance(message, ToolMessage)
4239

4340

4441
def execute_tool(tool, tool_name: str, tool_input: Dict[str, Any]) -> str:
@@ -61,8 +58,8 @@ def find_tool_by_name(tools: List, tool_name: str):
6158
return next((t for t in tools if t.name == tool_name), None)
6259

6360

64-
def process_new_format_tool_calls(tool_calls: List[Dict], tools: List) -> List[ToolMessage]:
65-
"""Process tool calls in the new format and return ToolMessage list."""
61+
def process_tool_calls(tool_calls: List[Dict], tools: List) -> List[ToolMessage]:
62+
"""Process tool calls and return ToolMessage list."""
6663
messages_to_add = []
6764

6865
for tool_call in tool_calls:
@@ -82,15 +79,26 @@ def process_new_format_tool_calls(tool_calls: List[Dict], tools: List) -> List[T
8279
return messages_to_add
8380

8481

85-
def process_old_format_function_call(function_call: Dict, tools: List) -> FunctionMessage:
86-
"""Process function call in the old format and return FunctionMessage."""
87-
tool_name = function_call["name"]
88-
tool_input = json.loads(function_call["arguments"])
89-
90-
tool = find_tool_by_name(tools, tool_name)
91-
if tool:
92-
result_content = execute_tool(tool, tool_name, tool_input)
93-
else:
94-
result_content = f"Tool '{tool_name}' not found"
82+
def filter_empty_content_messages(messages: List[AnyMessage]) -> List[AnyMessage]:
83+
"""Filter messages to ensure no message has empty content (prevents Gemini errors)."""
84+
processed_messages = []
85+
for message in messages:
86+
content = getattr(message, "content", "")
87+
88+
# If content is empty or just whitespace, provide minimal content
89+
if not content or (isinstance(content, str) and not content.strip()):
90+
# Create a copy with minimal non-empty content
91+
new_message = copy(message)
92+
if isinstance(message, ToolMessage):
93+
new_message.content = "Completed"
94+
elif isinstance(message, SystemMessage):
95+
# Keep system message as is (it should have content)
96+
processed_messages.append(message)
97+
continue
98+
else:
99+
new_message.content = "..."
100+
processed_messages.append(new_message)
101+
else:
102+
processed_messages.append(message)
95103

96-
return FunctionMessage(content=result_content, name=tool_name)
104+
return processed_messages

0 commit comments

Comments
 (0)