Merge remote-tracking branch 'origin/feat/extra-agentspeak-functionality' into feat/monitoringpage-cb
This commit is contained in:
@@ -33,7 +33,7 @@ class RobotGestureAgent(BaseAgent):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
address=settings.zmq_settings.ri_command_address,
|
||||
address: str,
|
||||
bind=False,
|
||||
gesture_data=None,
|
||||
single_gesture_data=None,
|
||||
|
||||
@@ -86,6 +86,7 @@ class BDIProgramManager(BaseAgent):
|
||||
self._phase = phase
|
||||
|
||||
self._send_beliefs_to_semantic_belief_extractor()
|
||||
self._send_goals_to_semantic_belief_extractor()
|
||||
|
||||
# Notify user interaction agent
|
||||
msg = InternalMessage(
|
||||
@@ -132,12 +133,10 @@ class BDIProgramManager(BaseAgent):
|
||||
|
||||
await self.send(message)
|
||||
|
||||
@staticmethod
|
||||
def _extract_goals_from_program(program: Program) -> list[Goal]:
|
||||
def _extract_current_goals(self) -> list[Goal]:
|
||||
"""
|
||||
Extract all goals from the program, including subgoals.
|
||||
|
||||
:param program: The program received from the API.
|
||||
:return: A list of Goal objects.
|
||||
"""
|
||||
goals: list[Goal] = []
|
||||
@@ -149,19 +148,16 @@ class BDIProgramManager(BaseAgent):
|
||||
goals_.extend(extract_goals_from_goal(plan))
|
||||
return goals_
|
||||
|
||||
for phase in program.phases:
|
||||
for goal in phase.goals:
|
||||
goals.extend(extract_goals_from_goal(goal))
|
||||
for goal in self._phase.goals:
|
||||
goals.extend(extract_goals_from_goal(goal))
|
||||
|
||||
return goals
|
||||
|
||||
async def _send_goals_to_semantic_belief_extractor(self, program: Program):
|
||||
async def _send_goals_to_semantic_belief_extractor(self):
|
||||
"""
|
||||
Extract goals from the program and send them to the Semantic Belief Extractor Agent.
|
||||
|
||||
:param program: The program received from the API.
|
||||
Extract goals for the current phase and send them to the Semantic Belief Extractor Agent.
|
||||
"""
|
||||
goals = GoalList(goals=self._extract_goals_from_program(program))
|
||||
goals = GoalList(goals=self._extract_current_goals())
|
||||
|
||||
message = InternalMessage(
|
||||
to=settings.agent_settings.text_belief_extractor_name,
|
||||
@@ -172,12 +168,34 @@ class BDIProgramManager(BaseAgent):
|
||||
|
||||
await self.send(message)
|
||||
|
||||
async def _send_clear_llm_history(self):
|
||||
"""
|
||||
Clear the LLM Agent's conversation history.
|
||||
|
||||
Sends an empty history to the LLM Agent to reset its state.
|
||||
"""
|
||||
message = InternalMessage(
|
||||
to=settings.agent_settings.llm_name,
|
||||
body="clear_history",
|
||||
)
|
||||
await self.send(message)
|
||||
self.logger.debug("Sent message to LLM agent to clear history.")
|
||||
|
||||
extractor_msg = InternalMessage(
|
||||
to=settings.agent_settings.text_belief_extractor_name,
|
||||
thread="conversation_history",
|
||||
body="reset",
|
||||
)
|
||||
await self.send(extractor_msg)
|
||||
self.logger.debug("Sent message to extractor agent to clear history.")
|
||||
|
||||
async def _receive_programs(self):
|
||||
"""
|
||||
Continuous loop that receives program updates from the HTTP endpoint.
|
||||
|
||||
It listens to the ``program`` topic on the internal ZMQ SUB socket.
|
||||
When a program is received, it is validated and forwarded to BDI via :meth:`_send_to_bdi`.
|
||||
Additionally, the LLM history is cleared via :meth:`_send_clear_llm_history`.
|
||||
"""
|
||||
while True:
|
||||
topic, body = await self.sub_socket.recv_multipart()
|
||||
@@ -190,10 +208,12 @@ class BDIProgramManager(BaseAgent):
|
||||
|
||||
self._initialize_internal_state(program)
|
||||
|
||||
await self._send_clear_llm_history()
|
||||
|
||||
await asyncio.gather(
|
||||
self._create_agentspeak_and_send_to_bdi(program),
|
||||
self._send_beliefs_to_semantic_belief_extractor(),
|
||||
self._send_goals_to_semantic_belief_extractor(program),
|
||||
self._send_goals_to_semantic_belief_extractor(),
|
||||
)
|
||||
|
||||
async def setup(self):
|
||||
|
||||
@@ -116,9 +116,19 @@ class TextBeliefExtractorAgent(BaseAgent):
|
||||
self._handle_beliefs_message(msg)
|
||||
case "goals":
|
||||
self._handle_goals_message(msg)
|
||||
case "conversation_history":
|
||||
if msg.body == "reset":
|
||||
self._reset()
|
||||
case _:
|
||||
self.logger.warning("Received unexpected message from %s", msg.sender)
|
||||
|
||||
def _reset(self):
|
||||
self.conversation = ChatHistory(messages=[])
|
||||
self.belief_inferrer.available_beliefs.clear()
|
||||
self._current_beliefs = BeliefState()
|
||||
self.goal_inferrer.goals.clear()
|
||||
self._current_goal_completions = {}
|
||||
|
||||
def _handle_beliefs_message(self, msg: InternalMessage):
|
||||
try:
|
||||
belief_list = BeliefList.model_validate_json(msg.body)
|
||||
|
||||
@@ -38,7 +38,7 @@ class RICommunicationAgent(BaseAgent):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
address=settings.zmq_settings.ri_command_address,
|
||||
address=settings.zmq_settings.ri_communication_address,
|
||||
bind=False,
|
||||
):
|
||||
super().__init__(name)
|
||||
@@ -168,7 +168,7 @@ class RICommunicationAgent(BaseAgent):
|
||||
bind = port_data["bind"]
|
||||
|
||||
if not bind:
|
||||
addr = f"tcp://localhost:{port}"
|
||||
addr = f"tcp://{settings.ri_host}:{port}"
|
||||
else:
|
||||
addr = f"tcp://*:{port}"
|
||||
|
||||
|
||||
@@ -57,8 +57,12 @@ class LLMAgent(BaseAgent):
|
||||
self.history.append({"role": "assistant", "content": msg.body})
|
||||
case "user_message":
|
||||
self.history.append({"role": "user", "content": msg.body})
|
||||
elif msg.sender == settings.agent_settings.bdi_program_manager_name:
|
||||
if msg.body == "clear_history":
|
||||
self.logger.debug("Clearing conversation history.")
|
||||
self.history.clear()
|
||||
else:
|
||||
self.logger.debug("Message ignored (not from BDI core.")
|
||||
self.logger.debug("Message ignored.")
|
||||
|
||||
async def _process_bdi_message(self, message: LLMPromptMessage):
|
||||
"""
|
||||
|
||||
@@ -103,12 +103,11 @@ class VADAgent(BaseAgent):
|
||||
|
||||
self._connect_audio_in_socket()
|
||||
|
||||
audio_out_port = self._connect_audio_out_socket()
|
||||
if audio_out_port is None:
|
||||
audio_out_address = self._connect_audio_out_socket()
|
||||
if audio_out_address is None:
|
||||
self.logger.error("Could not bind output socket, stopping.")
|
||||
await self.stop()
|
||||
return
|
||||
audio_out_address = f"tcp://localhost:{audio_out_port}"
|
||||
|
||||
# Connect to internal communication socket
|
||||
self.program_sub_socket = azmq.Context.instance().socket(zmq.SUB)
|
||||
@@ -161,13 +160,14 @@ class VADAgent(BaseAgent):
|
||||
self.audio_in_socket.connect(self.audio_in_address)
|
||||
self.audio_in_poller = SocketPoller[bytes](self.audio_in_socket)
|
||||
|
||||
def _connect_audio_out_socket(self) -> int | None:
|
||||
def _connect_audio_out_socket(self) -> str | None:
|
||||
"""
|
||||
Returns the port bound, or None if binding failed.
|
||||
Returns the address that was bound to, or None if binding failed.
|
||||
"""
|
||||
try:
|
||||
self.audio_out_socket = azmq.Context.instance().socket(zmq.PUB)
|
||||
return self.audio_out_socket.bind_to_random_port("tcp://localhost", max_tries=100)
|
||||
self.audio_out_socket.bind(settings.zmq_settings.vad_pub_address)
|
||||
return settings.zmq_settings.vad_pub_address
|
||||
except zmq.ZMQBindError:
|
||||
self.logger.error("Failed to bind an audio output socket after 100 tries.")
|
||||
self.audio_out_socket = None
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
"""
|
||||
An exhaustive overview of configurable options. All of these can be set using environment variables
|
||||
by nesting with double underscores (__). Start from the ``Settings`` class.
|
||||
|
||||
For example, ``settings.ri_host`` becomes ``RI_HOST``, and
|
||||
``settings.zmq_settings.ri_communication_address`` becomes
|
||||
``ZMQ_SETTINGS__RI_COMMUNICATION_ADDRESS``.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
@@ -8,16 +17,17 @@ class ZMQSettings(BaseModel):
|
||||
|
||||
:ivar internal_pub_address: Address for the internal PUB socket.
|
||||
:ivar internal_sub_address: Address for the internal SUB socket.
|
||||
:ivar ri_command_address: Address for sending commands to the Robot Interface.
|
||||
:ivar ri_communication_address: Address for receiving communication from the Robot Interface.
|
||||
:ivar vad_agent_address: Address for the Voice Activity Detection (VAD) agent.
|
||||
:ivar ri_communication_address: Address for the endpoint that the Robot Interface connects to.
|
||||
:ivar vad_pub_address: Address that the VAD agent binds to and publishes audio segments to.
|
||||
"""
|
||||
|
||||
# ATTENTION: When adding/removing settings, make sure to update the .env.example file
|
||||
|
||||
internal_pub_address: str = "tcp://localhost:5560"
|
||||
internal_sub_address: str = "tcp://localhost:5561"
|
||||
ri_command_address: str = "tcp://localhost:0000"
|
||||
ri_communication_address: str = "tcp://*:5555"
|
||||
internal_gesture_rep_adress: str = "tcp://localhost:7788"
|
||||
vad_pub_address: str = "inproc://vad_stream"
|
||||
|
||||
|
||||
class AgentSettings(BaseModel):
|
||||
@@ -36,6 +46,8 @@ class AgentSettings(BaseModel):
|
||||
:ivar robot_speech_name: Name of the Robot Speech Agent.
|
||||
"""
|
||||
|
||||
# ATTENTION: When adding/removing settings, make sure to update the .env.example file
|
||||
|
||||
# agent names
|
||||
bdi_core_name: str = "bdi_core_agent"
|
||||
bdi_belief_collector_name: str = "belief_collector_agent"
|
||||
@@ -68,6 +80,8 @@ class BehaviourSettings(BaseModel):
|
||||
:ivar conversation_history_length_limit: The maximum amount of messages to extract beliefs from.
|
||||
"""
|
||||
|
||||
# ATTENTION: When adding/removing settings, make sure to update the .env.example file
|
||||
|
||||
sleep_s: float = 1.0
|
||||
comm_setup_max_retries: int = 5
|
||||
socket_poller_timeout_ms: int = 100
|
||||
@@ -99,6 +113,8 @@ class LLMSettings(BaseModel):
|
||||
:ivar n_parallel: The number of parallel calls allowed to be made to the LLM.
|
||||
"""
|
||||
|
||||
# ATTENTION: When adding/removing settings, make sure to update the .env.example file
|
||||
|
||||
local_llm_url: str = "http://localhost:1234/v1/chat/completions"
|
||||
local_llm_model: str = "gpt-oss"
|
||||
chat_temperature: float = 1.0
|
||||
@@ -115,6 +131,8 @@ class VADSettings(BaseModel):
|
||||
:ivar sample_rate_hz: Sample rate in Hz for the VAD model.
|
||||
"""
|
||||
|
||||
# ATTENTION: When adding/removing settings, make sure to update the .env.example file
|
||||
|
||||
repo_or_dir: str = "snakers4/silero-vad"
|
||||
model_name: str = "silero_vad"
|
||||
sample_rate_hz: int = 16000
|
||||
@@ -128,6 +146,8 @@ class SpeechModelSettings(BaseModel):
|
||||
:ivar openai_model_name: Model name for OpenAI-based speech recognition.
|
||||
"""
|
||||
|
||||
# ATTENTION: When adding/removing settings, make sure to update the .env.example file
|
||||
|
||||
# model identifiers for speech recognition
|
||||
mlx_model_name: str = "mlx-community/whisper-small.en-mlx"
|
||||
openai_model_name: str = "small.en"
|
||||
@@ -139,6 +159,7 @@ class Settings(BaseSettings):
|
||||
|
||||
:ivar app_title: Title of the application.
|
||||
:ivar ui_url: URL of the frontend UI.
|
||||
:ivar ri_host: The hostname of the Robot Interface.
|
||||
:ivar zmq_settings: ZMQ configuration.
|
||||
:ivar agent_settings: Agent name configuration.
|
||||
:ivar behaviour_settings: Behavior configuration.
|
||||
@@ -151,6 +172,8 @@ class Settings(BaseSettings):
|
||||
|
||||
ui_url: str = "http://localhost:5173"
|
||||
|
||||
ri_host: str = "localhost"
|
||||
|
||||
zmq_settings: ZMQSettings = ZMQSettings()
|
||||
|
||||
agent_settings: AgentSettings = AgentSettings()
|
||||
|
||||
Reference in New Issue
Block a user