Skip to content
Open
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 mesa_llm/tools/inbuilt_tools.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
from typing import TYPE_CHECKING, Any

from mesa.discrete_space import (
Expand All @@ -15,6 +16,8 @@
if TYPE_CHECKING:
from mesa_llm.llm_agent import LLMAgent

logger = logging.getLogger(__name__)

# Mapping directions to (dx, dy) for Cartesian-style spaces.
direction_map_xy = {
"North": (0, 1),
Expand Down Expand Up @@ -208,6 +211,12 @@ def speak_to(
]

for recipient in listener_agents:
if not hasattr(recipient, "memory"):
logger.warning(
"Agent %s has no memory attribute; skipping speak_to.",
recipient.unique_id,
)
continue
recipient.memory.add_to_memory(
type="message",
content={
Expand Down
44 changes: 44 additions & 0 deletions tests/test_tools/test_inbuilt_tools.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import logging
from types import SimpleNamespace

import pytest
Expand Down Expand Up @@ -582,3 +583,46 @@ class _DummyOrthogonalGrid(OrthogonalMooreGrid):

assert agent.cell is wrapped_cell
assert result == "agent 29 moved to (2, 2)."


def test_speak_to_skips_non_llm_recipient(mocker):
"""
speak_to must not crash when a recipient has no memory attribute.

This covers the case where a non-LLM (rule-based) agent is listed as a
recipient.
"""
model = DummyModel()

sender = DummyAgent(unique_id=1, model=model)
llm_recipient = DummyAgent(unique_id=2, model=model)
rule_recipient = DummyAgent(unique_id=3, model=model)

llm_recipient.memory = SimpleNamespace(add_to_memory=mocker.Mock())

model.agents = [sender, llm_recipient, rule_recipient]

ret = speak_to(sender, [2, 3], "Hello both")

llm_recipient.memory.add_to_memory.assert_called_once()
call_kwargs = llm_recipient.memory.add_to_memory.call_args[1]
assert call_kwargs["type"] == "message"
assert call_kwargs["content"]["message"] == "Hello both"

assert "2" in ret and "3" in ret


def test_speak_to_warns_for_non_llm_recipient(mocker, caplog):
model = DummyModel()
sender = DummyAgent(unique_id=10, model=model)
rule_recipient = DummyAgent(unique_id=11, model=model) # no .memory

model.agents = [sender, rule_recipient]

with caplog.at_level(logging.WARNING, logger="mesa_llm.tools.inbuilt_tools"):
speak_to(sender, [11], "Test message")

assert any(
"11" in record.message and "memory" in record.message
for record in caplog.records
)
Loading