Feat: Implement belief collector #14
@@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from spade.behaviour import CyclicBehaviour
|
from spade.behaviour import CyclicBehaviour
|
||||||
from spade.message import Message
|
from spade.agent import Message
|
||||||
from control_backend.core.config import settings
|
from control_backend.core.config import settings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -62,20 +62,29 @@ class ContinuousBeliefCollector(CyclicBehaviour):
|
|||||||
Expected payload:
|
Expected payload:
|
||||||
{
|
{
|
||||||
"type": "belief_extraction_text",
|
"type": "belief_extraction_text",
|
||||||
"beliefs": [
|
"beliefs": {"user_said": [["hello","test"],["Can you help me?"],["stop talking to me"],["No"],["Pepper do a dance"]]}
|
||||||
{"user_said": [["hello"],["Can you help me?"],["stop talking to me"],["No"],["Pepper do a dance"]]}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
beliefs = payload.get("beliefs", [])
|
beliefs = payload.get("beliefs", dict)
|
||||||
if not isinstance(beliefs, list):
|
|
||||||
logger.warning("BeliefCollector: 'beliefs' is not a list: %r", beliefs)
|
if not beliefs:
|
||||||
|
logger.info("BeliefCollector: no beliefs to process.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not isinstance(beliefs, dict):
|
||||||
|
logger.warning("BeliefCollector: 'beliefs' is not a dict: %r", beliefs)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not all(isinstance(v, list) for v in beliefs.values()):
|
||||||
|
logger.warning("BeliefCollector: 'beliefs' values are not all lists: %r", beliefs)
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info("BeliefCollector: forwarding %d beliefs.", len(beliefs))
|
logger.info("BeliefCollector: forwarding %d beliefs.", len(beliefs))
|
||||||
for b in beliefs:
|
for belief_name, belief_lists in beliefs.items():
|
||||||
logger.info(" - %s", b)
|
for args in belief_lists:
|
||||||
|
logger.info(" - %s %s", belief_name, " ".join(map(str, args)))
|
||||||
|
|
||||||
await self._send_beliefs_to_bdi(beliefs, origin=origin)
|
await self._send_beliefs_to_bdi(beliefs, origin=origin)
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ class BeliefTextAgent(Agent):
|
|||||||
# Send multiple beliefs in one JSON payload
|
# Send multiple beliefs in one JSON payload
|
||||||
payload = {
|
payload = {
|
||||||
"type": "belief_extraction_text",
|
"type": "belief_extraction_text",
|
||||||
"beliefs": [
|
"beliefs": {"user_said": [["hello","test"],["Can you help me?"],["stop talking to me"],["No"],["Pepper do a dance"]]}
|
||||||
{"user_said": [["hello"],["Can you help me?"],["stop talking to me"],["No"],["Pepper do a dance"]]}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msg = Message(to=to_jid)
|
msg = Message(to=to_jid)
|
||||||
|
|||||||
90
test/unit/agents/bdi/behaviours/test_continuous_collect.py
Normal file
90
test/unit/agents/bdi/behaviours/test_continuous_collect.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from unittest.mock import MagicMock, AsyncMock, call
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from control_backend.agents.belief_collector.behaviours.continuous_collect import ContinuousBeliefCollector
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_agent(mocker):
|
||||||
|
"""Fixture to create a mock Agent."""
|
||||||
|
agent = MagicMock()
|
||||||
|
agent.jid = "belief_collector_agent@test"
|
||||||
|
return agent
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def continuous_collector(mock_agent, mocker):
|
||||||
|
"""Fixture to create an instance of ContinuousBeliefCollector with a mocked agent."""
|
||||||
|
# Patch asyncio.sleep to prevent tests from actually waiting
|
||||||
|
mocker.patch("asyncio.sleep", return_value=None)
|
||||||
|
|
||||||
|
collector = ContinuousBeliefCollector()
|
||||||
|
collector.agent = mock_agent
|
||||||
|
# Mock the receive method, we will control its return value in each test
|
||||||
|
collector.receive = AsyncMock()
|
||||||
|
return collector
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_run_no_message_received(continuous_collector, mocker):
|
||||||
|
"""
|
||||||
|
Test that when no message is received, _process_message is not called.
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
continuous_collector.receive.return_value = None
|
||||||
|
mocker.patch.object(continuous_collector, "_process_message")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
await continuous_collector.run()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
continuous_collector._process_message.assert_not_called()
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_run_message_received(continuous_collector, mocker):
|
||||||
|
"""
|
||||||
|
Test that when a message is received, _process_message is called with that message.
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
mock_msg = MagicMock()
|
||||||
|
continuous_collector.receive.return_value = mock_msg
|
||||||
|
mocker.patch.object(continuous_collector, "_process_message")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
await continuous_collector.run()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
continuous_collector._process_message.assert_awaited_once_with(mock_msg)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_process_message_invalid(continuous_collector, mocker):
|
||||||
|
"""
|
||||||
|
Test that when an invalid JSON message is received, a warning is logged and processing stops.
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
invalid_json = "this is not json"
|
||||||
|
msg = MagicMock()
|
||||||
|
msg.body = invalid_json
|
||||||
|
msg.sender = "belief_text_agent_mock@test"
|
||||||
|
|
||||||
|
logger_mock = mocker.patch("control_backend.agents.belief_collector.behaviours.continuous_collect.logger")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
await continuous_collector._process_message(msg)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
logger_mock.warning.assert_called_once()
|
||||||
|
|
||||||
|
def test_get_sender_from_message(continuous_collector):
|
||||||
|
"""
|
||||||
|
Test that _sender_node correctly extracts the sender node from the message JID.
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
msg = MagicMock()
|
||||||
|
msg.sender = "agent_node@host/resource"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
sender_node = continuous_collector._sender_node(msg)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert sender_node == "agent_node"
|
||||||
Reference in New Issue
Block a user