Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions backend/apps/chatbot/agent/react_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from langgraph.graph.message import add_messages
from langgraph.managed import IsLastStep, RemainingSteps
from langgraph.prebuilt import ToolNode
from loguru import logger

from chatbot.agents.utils import async_delete_checkpoints, delete_checkpoints

Expand Down Expand Up @@ -75,6 +76,10 @@ def _call_model(self, state: State, config: RunnableConfig) -> dict[str, list[Ba

response: AIMessage = self.model_runnable.invoke(messages, config)

if not response.content and not response.tool_calls:
logger.warning("Received empty response from model, skipping message list update")
return {"messages": []}

if is_last_step and response.tool_calls or remaining_steps < 2 and response.tool_calls:
return {
"messages": [
Expand Down Expand Up @@ -108,6 +113,10 @@ async def _acall_model(

response: AIMessage = await self.model_runnable.ainvoke(messages, config)

if not response.content and not response.tool_calls:
logger.warning("Received empty response from model, skipping message list update")
return {"messages": []}

if is_last_step and response.tool_calls or remaining_steps < 2 and response.tool_calls:
return {
"messages": [
Expand Down
31 changes: 19 additions & 12 deletions backend/apps/chatbot/utils/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ def _truncate_dict(data: dict, max_bytes: int) -> dict:
Returns:
dict: Truncated dictionary that fits within the byte limit.
"""
items = data.items()
items = list(data.items())

left, right = 0, len(items)
best_size = 0

while left <= right:
mid = (left + right) // 2
test_dict = dict(items[:mid])
size = len(json.dumps(test_dict, ensure_ascii=False, indent=2).encode("utf-8"))
sub_dict = dict(items[:mid])
size = len(json.dumps(sub_dict, ensure_ascii=False, indent=2).encode("utf-8"))

if size <= max_bytes:
best_size = mid
Expand All @@ -119,8 +119,8 @@ def _truncate_list(data: list, max_bytes: int) -> list:

while left <= right:
mid = (left + right) // 2
test_list = data[:mid]
size = len(json.dumps(test_list, ensure_ascii=False, indent=2).encode("utf-8"))
sub_list = data[:mid]
size = len(json.dumps(sub_list, ensure_ascii=False, indent=2).encode("utf-8"))

if size <= max_bytes:
best_size = mid
Expand All @@ -146,7 +146,14 @@ def process_chunk(chunk: dict[str, Any]) -> StreamEvent | None:
- None for ignored chunks
"""
if "agent" in chunk:
message: AIMessage = chunk["agent"]["messages"][0]
ai_messages: list[AIMessage] = chunk["agent"]["messages"]

# If no messages are returned, the model returned an empty response
# with no tool calls. This also counts as a final (but empty) answer.
if not ai_messages:
return StreamEvent(type="final_answer", data=EventData(content=""))

message = ai_messages[0]

if message.tool_calls:
tool_calls = [
Expand All @@ -161,12 +168,12 @@ def process_chunk(chunk: dict[str, Any]) -> StreamEvent | None:

return StreamEvent(type=event_type, data=event_data)
elif "tools" in chunk:
messages: list[ToolMessage] = chunk["tools"]["messages"]
tool_messages: list[ToolMessage] = chunk["tools"]["messages"]

tool_outputs = []

for msg in messages:
content, truncated = _truncate_content(msg.content, MAX_BYTES)
for message in tool_messages:
content, truncated = _truncate_content(message.content, MAX_BYTES)

if truncated:
metadata = {"truncated": True}
Expand All @@ -175,9 +182,9 @@ def process_chunk(chunk: dict[str, Any]) -> StreamEvent | None:

tool_outputs.append(
ToolOutput(
status=msg.status,
tool_call_id=msg.tool_call_id,
tool_name=msg.name,
status=message.status,
tool_call_id=message.tool_call_id,
tool_name=message.name,
output=content,
metadata=metadata,
)
Expand Down
Loading