Compare commits

..

5 Commits

Author SHA1 Message Date
Twirre Meulenbelt
9248eaadbc fix: default belief false, user interrupt chat role, vad initial silence, unused import
ref: N25B-453
2026-01-26 14:36:10 +01:00
Twirre Meulenbelt
3095cb437b feat: rollover experiment logs when new program starts
ref: N25B-401
2026-01-22 11:27:36 +01:00
Twirre Meulenbelt
482c6b1082 Merge remote-tracking branch 'origin/demo' into demo 2026-01-20 12:53:49 +01:00
Twirre Meulenbelt
b3721322b9 Merge remote-tracking branch 'origin/demo' into demo 2026-01-20 12:31:51 +01:00
Twirre Meulenbelt
4fb10730a4 Merge remote-tracking branch 'origin/feat/visual-emotion-recognition' into demo 2026-01-20 12:20:24 +01:00
4 changed files with 31 additions and 18 deletions

View File

@@ -1,10 +1,12 @@
import asyncio
import json
import logging
import zmq
from pydantic import ValidationError
from zmq.asyncio import Context
import control_backend
from control_backend.agents import BaseAgent
from control_backend.agents.bdi.agentspeak_generator import AgentSpeakGenerator
from control_backend.core.config import settings
@@ -19,6 +21,8 @@ from control_backend.schemas.program import (
Program,
)
experiment_logger = logging.getLogger(settings.logging_settings.experiment_logger_name)
class BDIProgramManager(BaseAgent):
"""
@@ -241,6 +245,18 @@ class BDIProgramManager(BaseAgent):
await self.send(extractor_msg)
self.logger.debug("Sent message to extractor agent to clear history.")
@staticmethod
def _rollover_experiment_logs():
"""
A new experiment program started; make a new experiment log file.
"""
handlers = logging.getLogger("experiment").handlers
for handler in handlers:
if isinstance(handler, control_backend.logging.DatedFileHandler):
experiment_logger.action("Doing rollover...")
handler.do_rollover()
experiment_logger.debug("Finished rollover.")
async def _receive_programs(self):
"""
Continuous loop that receives program updates from the HTTP endpoint.
@@ -261,6 +277,7 @@ class BDIProgramManager(BaseAgent):
self._initialize_internal_state(program)
await self._send_program_to_user_interrupt(program)
await self._send_clear_llm_history()
self._rollover_experiment_logs()
await asyncio.gather(
self._create_agentspeak_and_send_to_bdi(program),

View File

@@ -150,6 +150,9 @@ class TextBeliefExtractorAgent(BaseAgent):
return
available_beliefs = [b for b in belief_list.beliefs if isinstance(b, SemanticBelief)]
self._current_beliefs = BeliefState(
false={InternalBelief(name=b.name, arguments=None) for b in available_beliefs},
)
self.belief_inferrer.available_beliefs = available_beliefs
self.logger.debug(
"Received %d semantic beliefs from the program manager: %s",
@@ -170,6 +173,9 @@ class TextBeliefExtractorAgent(BaseAgent):
available_goals = {g for g in goals_list.goals if g.can_fail}
available_goals -= self._force_completed_goals
self.goal_inferrer.goals = available_goals
self._current_goal_completions = {
f"achieved_{AgentSpeakGenerator.slugify(goal)}": False for goal in available_goals
}
self.logger.debug(
"Received %d failable goals from the program manager: %s",
len(available_goals),

View File

@@ -285,9 +285,10 @@ class VADAgent(BaseAgent):
assert self.audio_out_socket is not None
await self.audio_out_socket.send(self.audio_buffer[: -2 * len(chunk)].tobytes())
# At this point, we know that the speech has ended.
# Prepend the last chunk that had no speech, for a more fluent boundary
self.audio_buffer = chunk
# At this point, we know that there is no speech.
# Prepend the last few chunks that had no speech, for a more fluent boundary.
self.audio_buffer = np.append(self.audio_buffer, chunk)
self.audio_buffer = self.audio_buffer[-begin_silence_length * len(chunk) :]
async def handle_message(self, msg: InternalMessage):
"""

View File

@@ -9,7 +9,7 @@ from control_backend.agents.bdi.agentspeak_generator import AgentSpeakGenerator
from control_backend.core.agent_system import InternalMessage
from control_backend.core.config import settings
from control_backend.schemas.belief_message import Belief, BeliefMessage
from control_backend.schemas.program import ConditionalNorm, Goal, Program
from control_backend.schemas.program import ConditionalNorm, Program
from control_backend.schemas.ri_message import (
GestureCommand,
PauseCommand,
@@ -250,18 +250,6 @@ class UserInterruptAgent(BaseAgent):
self._cond_norm_map = {}
self._cond_norm_reverse_map = {}
def _register_goal(goal: Goal):
"""Recursively register goals and their subgoals."""
slug = AgentSpeakGenerator.slugify(goal)
self._goal_map[str(goal.id)] = slug
self._goal_reverse_map[slug] = str(goal.id)
# Recursively check steps for subgoals
if goal.plan and goal.plan.steps:
for step in goal.plan.steps:
if isinstance(step, Goal):
_register_goal(step)
for phase in program.phases:
for trigger in phase.triggers:
slug = AgentSpeakGenerator.slugify(trigger)
@@ -269,7 +257,8 @@ class UserInterruptAgent(BaseAgent):
self._trigger_reverse_map[slug] = str(trigger.id)
for goal in phase.goals:
_register_goal(goal)
self._goal_map[str(goal.id)] = AgentSpeakGenerator.slugify(goal)
self._goal_reverse_map[AgentSpeakGenerator.slugify(goal)] = str(goal.id)
for goal, id in self._goal_reverse_map.items():
self.logger.debug(f"Goal mapping: UI ID {goal} -> {id}")
@@ -311,7 +300,7 @@ class UserInterruptAgent(BaseAgent):
:param text_to_say: The string that the robot has to say.
"""
experiment_logger.chat(text_to_say, extra={"role": "user"})
experiment_logger.chat(text_to_say, extra={"role": "assistant"})
cmd = SpeechCommand(data=text_to_say, is_priority=True)
out_msg = InternalMessage(
to=settings.agent_settings.robot_speech_name,