From 67d0284dfb258834b31525bf8d97849473f6309f Mon Sep 17 00:00:00 2001 From: Kasper Marinus Date: Thu, 20 Nov 2025 17:32:19 +0100 Subject: [PATCH 1/3] chore: remove metadata field and jid attribute These weren't used. --- src/control_backend/core/agent_system.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/control_backend/core/agent_system.py b/src/control_backend/core/agent_system.py index f0b8ea6..37ca9c8 100644 --- a/src/control_backend/core/agent_system.py +++ b/src/control_backend/core/agent_system.py @@ -1,8 +1,7 @@ import asyncio import logging from abc import ABC, abstractmethod -from dataclasses import dataclass, field -from typing import Any +from dataclasses import dataclass # Central directory to resolve agent names to instances _agent_directory: dict[str, "BaseAgent"] = {} @@ -14,7 +13,6 @@ class InternalMessage: sender: str body: str thread: str | None = None - metadata: dict[str, Any] = field(default_factory=dict) class AgentDirectory: @@ -32,7 +30,6 @@ class BaseAgent(ABC): def __init__(self, name: str): self.name = name - self.jid = name # present for backwards compatibility self.inbox: asyncio.Queue[InternalMessage] = asyncio.Queue() self._tasks: set[asyncio.Task] = set() self._running = False @@ -83,5 +80,3 @@ class BaseAgent(ABC): task = asyncio.create_task(coro) self._tasks.add(task) task.add_done_callback(self._tasks.discard) - - # await asyncio.sleep(1) From 92fc73d45b37a07760e9baf0c02a4503b3ab54bb Mon Sep 17 00:00:00 2001 From: Kasper Date: Fri, 21 Nov 2025 10:02:50 +0100 Subject: [PATCH 2/3] chore: add back agentspeak dependency This was removed with the removal of SPADE. --- pyproject.toml | 1 + uv.lock | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3680173..54a4a20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.13" dependencies = [ + "agentspeak>=0.2.2", "colorlog>=6.10.1", "fastapi[all]>=0.115.6", "mlx-whisper>=0.4.3 ; sys_platform == 'darwin'", diff --git a/uv.lock b/uv.lock index 4ba76f4..c2a4f21 100644 --- a/uv.lock +++ b/uv.lock @@ -6,6 +6,18 @@ resolution-markers = [ "python_full_version < '3.14'", ] +[[package]] +name = "agentspeak" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/a3/f8e9292cfd47aa5558f4578c498ca12c068a3a1d60ddfd0af13a87c1e47a/agentspeak-0.2.2.tar.gz", hash = "sha256:7c7fcf689fd54460597be1798ce11535f42a60c3d79af59381af3e13ef7a41bb", size = 59628, upload-time = "2024-03-21T11:55:39.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/b5/e95cbd9d9e999ac8dc4e0bb7a940112a2751cf98880b4ff0626e53d14249/agentspeak-0.2.2-py3-none-any.whl", hash = "sha256:9b454bc0adf63cb0d73fb4a3a9a489e7d892d5fbf17f750de532670736c0c4dd", size = 61628, upload-time = "2024-03-21T11:55:36.741Z" }, +] + [[package]] name = "alabaster" version = "0.7.16" @@ -975,6 +987,7 @@ name = "pepperplus-cb" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "agentspeak" }, { name = "colorlog" }, { name = "fastapi", extra = ["all"] }, { name = "mlx-whisper", marker = "sys_platform == 'darwin'" }, @@ -1016,6 +1029,7 @@ test = [ [package.metadata] requires-dist = [ + { name = "agentspeak", specifier = ">=0.2.2" }, { name = "colorlog", specifier = ">=6.10.1" }, { name = "fastapi", extras = ["all"], specifier = ">=0.115.6" }, { name = "mlx-whisper", marker = "sys_platform == 'darwin'", specifier = ">=0.4.3" }, From 1c510c661e2337287317b6d0827749745deb8c18 Mon Sep 17 00:00:00 2001 From: Kasper Date: Fri, 21 Nov 2025 12:08:53 +0100 Subject: [PATCH 3/3] feat: more robust belief management ref: N25B-316 --- .logging_config.yaml | 2 +- .../bdi/bdi_core_agent/bdi_core_agent.py | 75 +++++++++++++++---- .../agents/bdi/bdi_core_agent/rules.asl | 6 +- .../agents/perception/vad_agent.py | 4 +- 4 files changed, 68 insertions(+), 19 deletions(-) diff --git a/.logging_config.yaml b/.logging_config.yaml index 0403c77..a244917 100644 --- a/.logging_config.yaml +++ b/.logging_config.yaml @@ -8,7 +8,7 @@ formatters: # Console output colored: (): "colorlog.ColoredFormatter" - format: "{log_color}{asctime} | {levelname:11} | {name:70} | {message}" + format: "{log_color}{asctime}.{msecs:03.0f} | {levelname:11} | {name:70} | {message}" style: "{" datefmt: "%H:%M:%S" diff --git a/src/control_backend/agents/bdi/bdi_core_agent/bdi_core_agent.py b/src/control_backend/agents/bdi/bdi_core_agent/bdi_core_agent.py index 8de6204..73d187a 100644 --- a/src/control_backend/agents/bdi/bdi_core_agent/bdi_core_agent.py +++ b/src/control_backend/agents/bdi/bdi_core_agent/bdi_core_agent.py @@ -13,11 +13,12 @@ from control_backend.schemas.ri_message import SpeechCommand class BDICoreAgent(BaseAgent): + bdi_agent: agentspeak.runtime.Agent + def __init__(self, name: str, asl: str): super().__init__(name) self.asl_file = asl self.env = agentspeak.runtime.Environment() - self.bdi_agent = None self.actions = agentspeak.stdlib.actions async def setup(self) -> None: @@ -42,7 +43,6 @@ class BDICoreAgent(BaseAgent): async def _bdi_loop(self): """Runs the AgentSpeak BDI loop.""" while self._running: - assert self.bdi_agent is not None self.bdi_agent.step() await asyncio.sleep(0.01) @@ -74,30 +74,72 @@ class BDICoreAgent(BaseAgent): ) await self.send(out_msg) - # TODO: test way of adding beliefs def _add_beliefs(self, beliefs: dict[str, list[str]]): if not beliefs: return - for belief_name, args in beliefs.items(): - self._add_belief(belief_name, args) + for name, args in beliefs.items(): + self._add_belief(name, args) - if belief_name == "user_said": - self._add_belief("new_message") - - def _add_belief(self, belief_name: str, arguments: Iterable[str] = []): - args = (agentspeak.Literal(arg) for arg in arguments) - literal_belief = agentspeak.Literal(belief_name, args) + def _add_belief(self, name: str, args: Iterable[str] = []): + new_args = (agentspeak.Literal(arg) for arg in args) + term = agentspeak.Literal(name, new_args) assert self.bdi_agent is not None self.bdi_agent.call( agentspeak.Trigger.addition, agentspeak.GoalType.belief, - literal_belief, + term, agentspeak.runtime.Intention(), ) - self.logger.debug(f"Added belief {belief_name}({','.join(arguments)})") + self.logger.debug(f"Added belief {self.format_belief_string(name, args)}") + + def _remove_belief(self, name: str, args: Iterable[str]): + """ + Removes a specific belief (with arguments), if it exists. + """ + new_args = (agentspeak.Literal(arg) for arg in args) + term = agentspeak.Literal(name, new_args) + + assert self.bdi_agent is not None + + result = self.bdi_agent.call( + agentspeak.Trigger.removal, + agentspeak.GoalType.belief, + term, + agentspeak.runtime.Intention(), + ) + + if result: + self.logger.debug(f"Removed belief {self.format_belief_string(name, args)}") + else: + self.logger.debug("Failed to remove belief (it was not in the belief base).") + + # TODO: decide if this is needed + def _remove_all_with_name(self, name: str): + """ + Removes all beliefs that match the given `name`. + """ + assert self.bdi_agent is not None + + relevant_groups = [] + for key in self.bdi_agent.beliefs: + if key[0] == name: + relevant_groups.append(key) + + removed_count = 0 + for group in relevant_groups: + for belief in self.bdi_agent.beliefs[group]: + self.bdi_agent.call( + agentspeak.Trigger.removal, + agentspeak.GoalType.belief, + belief, + agentspeak.runtime.Intention(), + ) + removed_count += 1 + + self.logger.debug(f"Removed {removed_count} beliefs.") def _add_custom_actions(self) -> None: """Add any custom actions here.""" @@ -119,3 +161,10 @@ class BDICoreAgent(BaseAgent): msg = InternalMessage(to=settings.agent_settings.llm_name, sender=self.name, body=text) await self.send(msg) self.logger.info("Message sent to LLM agent: %s", text) + + @staticmethod + def format_belief_string(name: str, args: Iterable[str] = []): + """ + Given a belief's name and its args, return a string of the form "name(*args)" + """ + return f"{name}{'(' if args else ''}{','.join(args)}{')' if args else ''}" diff --git a/src/control_backend/agents/bdi/bdi_core_agent/rules.asl b/src/control_backend/agents/bdi/bdi_core_agent/rules.asl index 0001d3c..d88858d 100644 --- a/src/control_backend/agents/bdi/bdi_core_agent/rules.asl +++ b/src/control_backend/agents/bdi/bdi_core_agent/rules.asl @@ -1,3 +1,3 @@ -+new_message : user_said(Message) <- - -new_message; - .reply(Message). ++user_said(NewMessage) <- + -user_said(NewMessage); + .reply(NewMessage). diff --git a/src/control_backend/agents/perception/vad_agent.py b/src/control_backend/agents/perception/vad_agent.py index 37117c2..667d6db 100644 --- a/src/control_backend/agents/perception/vad_agent.py +++ b/src/control_backend/agents/perception/vad_agent.py @@ -67,7 +67,7 @@ class VADAgent(BaseAgent): self.model = None async def setup(self): - self.logger.info("Setting up %s", self.jid) + self.logger.info("Setting up %s", self.name) self._connect_audio_in_socket() @@ -99,7 +99,7 @@ class VADAgent(BaseAgent): transcriber = TranscriptionAgent(audio_out_address) await transcriber.start() - self.logger.info("Finished setting up %s", self.jid) + self.logger.info("Finished setting up %s", self.name) async def stop(self): """