diff --git a/src/control_backend/agents/__init__.py b/src/control_backend/agents/__init__.py index 1618d55..85f4aad 100644 --- a/src/control_backend/agents/__init__.py +++ b/src/control_backend/agents/__init__.py @@ -1 +1,5 @@ +""" +This package contains all agent implementations for the PepperPlus Control Backend. +""" + from .base import BaseAgent as BaseAgent diff --git a/src/control_backend/agents/actuation/__init__.py b/src/control_backend/agents/actuation/__init__.py index 8ff7e7f..9a8d81b 100644 --- a/src/control_backend/agents/actuation/__init__.py +++ b/src/control_backend/agents/actuation/__init__.py @@ -1,2 +1,6 @@ +""" +Agents responsible for controlling the robot's physical actions, such as speech and gestures. +""" + from .robot_gesture_agent import RobotGestureAgent as RobotGestureAgent from .robot_speech_agent import RobotSpeechAgent as RobotSpeechAgent diff --git a/src/control_backend/agents/bdi/__init__.py b/src/control_backend/agents/bdi/__init__.py index d6f5124..2f7d976 100644 --- a/src/control_backend/agents/bdi/__init__.py +++ b/src/control_backend/agents/bdi/__init__.py @@ -1,3 +1,8 @@ +""" +Agents and utilities for the BDI (Belief-Desire-Intention) reasoning system, +implementing AgentSpeak(L) logic. +""" + from control_backend.agents.bdi.bdi_core_agent import BDICoreAgent as BDICoreAgent from .text_belief_extractor_agent import ( diff --git a/src/control_backend/agents/bdi/agentspeak_ast.py b/src/control_backend/agents/bdi/agentspeak_ast.py index 68be531..19f48e2 100644 --- a/src/control_backend/agents/bdi/agentspeak_ast.py +++ b/src/control_backend/agents/bdi/agentspeak_ast.py @@ -80,7 +80,7 @@ class AstTerm(AstExpression, ABC): @dataclass(eq=False) class AstAtom(AstTerm): """ - Grounded expression in all lowercase. + Represents a grounded atom in AgentSpeak (e.g., lowercase constants). """ value: str @@ -92,7 +92,7 @@ class AstAtom(AstTerm): @dataclass(eq=False) class AstVar(AstTerm): """ - Ungrounded variable expression. First letter capitalized. + Represents an ungrounded variable in AgentSpeak (e.g., capitalized names). """ name: str @@ -103,6 +103,10 @@ class AstVar(AstTerm): @dataclass(eq=False) class AstNumber(AstTerm): + """ + Represents a numeric constant in AgentSpeak. + """ + value: int | float def _to_agentspeak(self) -> str: @@ -111,6 +115,10 @@ class AstNumber(AstTerm): @dataclass(eq=False) class AstString(AstTerm): + """ + Represents a string literal in AgentSpeak. + """ + value: str def _to_agentspeak(self) -> str: @@ -119,6 +127,10 @@ class AstString(AstTerm): @dataclass(eq=False) class AstLiteral(AstTerm): + """ + Represents a literal (functor and terms) in AgentSpeak. + """ + functor: str terms: list[AstTerm] = field(default_factory=list) @@ -142,6 +154,10 @@ class BinaryOperatorType(StrEnum): @dataclass class AstBinaryOp(AstExpression): + """ + Represents a binary logical or relational operation in AgentSpeak. + """ + left: AstExpression operator: BinaryOperatorType right: AstExpression @@ -167,6 +183,10 @@ class AstBinaryOp(AstExpression): @dataclass class AstLogicalExpression(AstExpression): + """ + Represents a logical expression, potentially negated, in AgentSpeak. + """ + expression: AstExpression negated: bool = False @@ -208,6 +228,10 @@ class AstStatement(AstNode): @dataclass class AstRule(AstNode): + """ + Represents an inference rule in AgentSpeak. If there is no condition, it always holds. + """ + result: AstExpression condition: AstExpression | None = None @@ -231,6 +255,10 @@ class TriggerType(StrEnum): @dataclass class AstPlan(AstNode): + """ + Represents a plan in AgentSpeak, consisting of a trigger, context, and body. + """ + type: TriggerType trigger_literal: AstExpression context: list[AstExpression] @@ -260,6 +288,10 @@ class AstPlan(AstNode): @dataclass class AstProgram(AstNode): + """ + Represents a full AgentSpeak program, consisting of rules and plans. + """ + rules: list[AstRule] = field(default_factory=list) plans: list[AstPlan] = field(default_factory=list) diff --git a/src/control_backend/agents/bdi/agentspeak_generator.py b/src/control_backend/agents/bdi/agentspeak_generator.py index 524f980..2fe12e3 100644 --- a/src/control_backend/agents/bdi/agentspeak_generator.py +++ b/src/control_backend/agents/bdi/agentspeak_generator.py @@ -40,9 +40,23 @@ from control_backend.schemas.program import ( class AgentSpeakGenerator: + """ + Generator class that translates a high-level :class:`~control_backend.schemas.program.Program` + into AgentSpeak(L) source code. + + It handles the conversion of phases, norms, goals, and triggers into AgentSpeak rules and plans, + ensuring the robot follows the defined behavioral logic. + """ + _asp: AstProgram def generate(self, program: Program) -> str: + """ + Translates a Program object into an AgentSpeak source string. + + :param program: The behavior program to translate. + :return: The generated AgentSpeak code as a string. + """ self._asp = AstProgram() if program.phases: diff --git a/src/control_backend/agents/bdi/text_belief_extractor_agent.py b/src/control_backend/agents/bdi/text_belief_extractor_agent.py index b5fd266..362dfbf 100644 --- a/src/control_backend/agents/bdi/text_belief_extractor_agent.py +++ b/src/control_backend/agents/bdi/text_belief_extractor_agent.py @@ -18,6 +18,12 @@ type JSONLike = None | bool | int | float | str | list["JSONLike"] | dict[str, " class BeliefState(BaseModel): + """ + Represents the state of inferred semantic beliefs. + + Maintains sets of beliefs that are currently considered true or false. + """ + true: set[InternalBelief] = set() false: set[InternalBelief] = set() @@ -338,7 +344,7 @@ class TextBeliefExtractorAgent(BaseAgent): class SemanticBeliefInferrer: """ - Class that handles only prompting an LLM for semantic beliefs. + Infers semantic beliefs from conversation history using an LLM. """ def __init__( @@ -464,6 +470,10 @@ Respond with a JSON similar to the following, but with the property names as giv class GoalAchievementInferrer(SemanticBeliefInferrer): + """ + Infers whether specific conversational goals have been achieved using an LLM. + """ + def __init__(self, llm: TextBeliefExtractorAgent.LLM): super().__init__(llm) self.goals: set[BaseGoal] = set() diff --git a/src/control_backend/agents/communication/__init__.py b/src/control_backend/agents/communication/__init__.py index 2aa1535..3dde6cf 100644 --- a/src/control_backend/agents/communication/__init__.py +++ b/src/control_backend/agents/communication/__init__.py @@ -1 +1,5 @@ +""" +Agents responsible for external communication and service discovery. +""" + from .ri_communication_agent import RICommunicationAgent as RICommunicationAgent diff --git a/src/control_backend/agents/llm/__init__.py b/src/control_backend/agents/llm/__init__.py index e12ff29..519812f 100644 --- a/src/control_backend/agents/llm/__init__.py +++ b/src/control_backend/agents/llm/__init__.py @@ -1 +1,5 @@ +""" +Agents that interface with Large Language Models for natural language processing and generation. +""" + from .llm_agent import LLMAgent as LLMAgent diff --git a/src/control_backend/agents/perception/__init__.py b/src/control_backend/agents/perception/__init__.py index e18361a..5a46671 100644 --- a/src/control_backend/agents/perception/__init__.py +++ b/src/control_backend/agents/perception/__init__.py @@ -1,3 +1,8 @@ +""" +Agents responsible for processing sensory input, such as audio transcription and voice activity +detection. +""" + from .transcription_agent.transcription_agent import ( TranscriptionAgent as TranscriptionAgent, ) diff --git a/src/control_backend/agents/perception/transcription_agent/transcription_agent.py b/src/control_backend/agents/perception/transcription_agent/transcription_agent.py index 765d7ac..795623d 100644 --- a/src/control_backend/agents/perception/transcription_agent/transcription_agent.py +++ b/src/control_backend/agents/perception/transcription_agent/transcription_agent.py @@ -74,7 +74,7 @@ class TranscriptionAgent(BaseAgent): def _connect_audio_in_socket(self): """ - Helper to connect the ZMQ SUB socket for audio input. + Connects the ZMQ SUB socket for receiving audio data. """ self.audio_in_socket = azmq.Context.instance().socket(zmq.SUB) self.audio_in_socket.setsockopt_string(zmq.SUBSCRIBE, "") diff --git a/src/control_backend/agents/user_interrupt/user_interrupt_agent.py b/src/control_backend/agents/user_interrupt/user_interrupt_agent.py index cf72ce5..6a4c9b0 100644 --- a/src/control_backend/agents/user_interrupt/user_interrupt_agent.py +++ b/src/control_backend/agents/user_interrupt/user_interrupt_agent.py @@ -50,10 +50,8 @@ class UserInterruptAgent(BaseAgent): async def setup(self): """ - Initialize the agent. - - Connects the internal ZMQ SUB socket and subscribes to the 'button_pressed' topic. - Starts the background behavior to receive the user interrupts. + Initialize the agent by setting up ZMQ sockets for receiving button events and + publishing updates. """ context = Context.instance() @@ -68,18 +66,15 @@ class UserInterruptAgent(BaseAgent): async def _receive_button_event(self): """ - The behaviour of the UserInterruptAgent. - Continuous loop that receives button_pressed events from the button_pressed HTTP endpoint. - These events contain a type and a context. + Main loop to receive and process button press events from the UI. - These are the different types and contexts: - - type: "speech", context: string that the robot has to say. - - type: "gesture", context: single gesture name that the robot has to perform. - - type: "override", context: id that belongs to the goal/trigger/conditional norm. - - type: "override_unachieve", context: id that belongs to the conditional norm to unachieve. - - type: "next_phase", context: None, indicates to the BDI Core to - - type: "pause", context: boolean indicating whether to pause - - type: "reset_phase", context: None, indicates to the BDI Core to + Handles different event types: + - `speech`: Triggers immediate robot speech. + - `gesture`: Triggers an immediate robot gesture. + - `override`: Forces a belief, trigger, or goal completion in the BDI core. + - `override_unachieve`: Removes a belief from the BDI core. + - `pause`: Toggles the system's pause state. + - `next_phase` / `reset_phase`: Controls experiment flow. """ while True: topic, body = await self.sub_socket.recv_multipart() @@ -172,7 +167,10 @@ class UserInterruptAgent(BaseAgent): async def handle_message(self, msg: InternalMessage): """ - Handle commands received from other internal Python agents. + Handles internal messages from other agents, such as program updates or trigger + notifications. + + :param msg: The incoming :class:`~control_backend.core.agent_system.InternalMessage`. """ match msg.thread: case "new_program": @@ -217,8 +215,9 @@ class UserInterruptAgent(BaseAgent): async def _broadcast_cond_norms(self, active_slugs: list[str]): """ - Sends the current state of all conditional norms to the UI. - :param active_slugs: A list of slugs (strings) currently active in the BDI core. + Broadcasts the current activation state of all conditional norms to the UI. + + :param active_slugs: A list of sluggified norm names currently active in the BDI core. """ updates = [] for asl_slug, ui_id in self._cond_norm_reverse_map.items(): @@ -235,7 +234,9 @@ class UserInterruptAgent(BaseAgent): def _create_mapping(self, program_json: str): """ - Create mappings between UI IDs and ASL slugs for triggers, goals, and conditional norms + Creates a bidirectional mapping between UI identifiers and AgentSpeak slugs. + + :param program_json: The JSON representation of the behavioral program. """ try: program = Program.model_validate_json(program_json) @@ -277,8 +278,10 @@ class UserInterruptAgent(BaseAgent): async def _send_experiment_update(self, data, should_log: bool = True): """ - Sends an update to the 'experiment' topic. - The SSE endpoint will pick this up and push it to the UI. + Publishes an experiment state update to the internal ZMQ bus for the UI. + + :param data: The update payload. + :param should_log: Whether to log the update. """ if self.pub_socket: topic = b"experiment" diff --git a/src/control_backend/api/v1/endpoints/sse.py b/src/control_backend/api/v1/endpoints/sse.py deleted file mode 100644 index c660aa5..0000000 --- a/src/control_backend/api/v1/endpoints/sse.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import APIRouter, Request - -router = APIRouter() - - -# TODO: implement -@router.get("/sse") -async def sse(request: Request): - """ - Placeholder for future Server-Sent Events endpoint. - """ - pass diff --git a/src/control_backend/core/agent_system.py b/src/control_backend/core/agent_system.py index e3c8dc4..267f072 100644 --- a/src/control_backend/core/agent_system.py +++ b/src/control_backend/core/agent_system.py @@ -22,10 +22,22 @@ class AgentDirectory: @staticmethod def register(name: str, agent: "BaseAgent"): + """ + Registers an agent instance with a unique name. + + :param name: The name of the agent. + :param agent: The :class:`BaseAgent` instance. + """ _agent_directory[name] = agent @staticmethod def get(name: str) -> "BaseAgent | None": + """ + Retrieves a registered agent instance by name. + + :param name: The name of the agent to retrieve. + :return: The :class:`BaseAgent` instance, or None if not found. + """ return _agent_directory.get(name) diff --git a/src/control_backend/schemas/belief_list.py b/src/control_backend/schemas/belief_list.py index f3d6818..841a4ed 100644 --- a/src/control_backend/schemas/belief_list.py +++ b/src/control_backend/schemas/belief_list.py @@ -16,4 +16,10 @@ class BeliefList(BaseModel): class GoalList(BaseModel): + """ + Represents a list of goals, used for communicating multiple goals between agents. + + :ivar goals: The list of goals. + """ + goals: list[BaseGoal] diff --git a/src/control_backend/schemas/chat_history.py b/src/control_backend/schemas/chat_history.py index 52fc224..8fd1e72 100644 --- a/src/control_backend/schemas/chat_history.py +++ b/src/control_backend/schemas/chat_history.py @@ -2,9 +2,22 @@ from pydantic import BaseModel class ChatMessage(BaseModel): + """ + Represents a single message in a conversation. + + :ivar role: The role of the speaker (e.g., 'user', 'assistant'). + :ivar content: The text content of the message. + """ + role: str content: str class ChatHistory(BaseModel): + """ + Represents a sequence of chat messages, forming a conversation history. + + :ivar messages: An ordered list of :class:`ChatMessage` objects. + """ + messages: list[ChatMessage] diff --git a/src/control_backend/schemas/events.py b/src/control_backend/schemas/events.py index 46967f7..a01b668 100644 --- a/src/control_backend/schemas/events.py +++ b/src/control_backend/schemas/events.py @@ -2,5 +2,13 @@ from pydantic import BaseModel class ButtonPressedEvent(BaseModel): + """ + Represents a button press event from the UI. + + :ivar type: The type of event (e.g., 'speech', 'gesture', 'override'). + :ivar context: Additional data associated with the event (e.g., speech text, gesture name, + or ID). + """ + type: str context: str diff --git a/src/control_backend/schemas/program.py b/src/control_backend/schemas/program.py index d04abbb..283e17d 100644 --- a/src/control_backend/schemas/program.py +++ b/src/control_backend/schemas/program.py @@ -20,6 +20,10 @@ class ProgramElement(BaseModel): class LogicalOperator(Enum): + """ + Logical operators for combining beliefs. + """ + AND = "AND" OR = "OR" @@ -30,9 +34,9 @@ type BasicBelief = KeywordBelief | SemanticBelief class KeywordBelief(ProgramElement): """ - Represents a belief that is set when the user spoken text contains a certain keyword. + Represents a belief that is activated when a specific keyword is detected in the user's speech. - :ivar keyword: The keyword on which this belief gets set. + :ivar keyword: The string to look for in the transcription. """ name: str = "" @@ -41,9 +45,11 @@ class KeywordBelief(ProgramElement): class SemanticBelief(ProgramElement): """ - Represents a belief that is set by semantic LLM validation. + Represents a belief whose truth value is determined by an LLM analyzing the conversation + context. - :ivar description: Description of how to form the belief, used by the LLM. + :ivar description: A natural language description of what this belief represents, + used as a prompt for the LLM. """ description: str @@ -51,13 +57,11 @@ class SemanticBelief(ProgramElement): class InferredBelief(ProgramElement): """ - Represents a belief that gets formed by combining two beliefs with a logical AND or OR. + Represents a belief derived from other beliefs using logical operators. - These beliefs can also be :class:`InferredBelief`, leading to arbitrarily deep nesting. - - :ivar operator: The logical operator to apply. - :ivar left: The left part of the logical expression. - :ivar right: The right part of the logical expression. + :ivar operator: The :class:`LogicalOperator` (AND/OR) to apply. + :ivar left: The left operand (another belief). + :ivar right: The right operand (another belief). """ name: str = "" @@ -67,6 +71,13 @@ class InferredBelief(ProgramElement): class Norm(ProgramElement): + """ + Base class for behavioral norms that guide the robot's interactions. + + :ivar norm: The textual description of the norm. + :ivar critical: Whether this norm is considered critical and should be strictly enforced. + """ + name: str = "" norm: str critical: bool = False @@ -74,10 +85,7 @@ class Norm(ProgramElement): class BasicNorm(Norm): """ - Represents a behavioral norm. - - :ivar norm: The actual norm text describing the behavior. - :ivar critical: When true, this norm should absolutely not be violated (checked separately). + A simple behavioral norm that is always considered for activation when its phase is active. """ pass @@ -85,9 +93,9 @@ class BasicNorm(Norm): class ConditionalNorm(Norm): """ - Represents a norm that is only active when a condition is met (i.e., a certain belief holds). + A behavioral norm that is only active when a specific condition (belief) is met. - :ivar condition: When to activate this norm. + :ivar condition: The :class:`Belief` that must hold for this norm to be active. """ condition: Belief @@ -140,9 +148,9 @@ type Action = SpeechAction | GestureAction | LLMAction class SpeechAction(ProgramElement): """ - Represents the action of the robot speaking a literal text. + An action where the robot speaks a predefined literal text. - :ivar text: The text to speak. + :ivar text: The text content to be spoken. """ name: str = "" @@ -151,11 +159,10 @@ class SpeechAction(ProgramElement): class Gesture(BaseModel): """ - Represents a gesture to be performed. Can be either a single gesture, - or a random gesture from a category (tag). + Defines a physical gesture for the robot to perform. - :ivar type: The type of the gesture, "tag" or "single". - :ivar name: The name of the single gesture or tag. + :ivar type: Whether to use a specific "single" gesture or a random one from a "tag" category. + :ivar name: The identifier for the gesture or tag. """ type: Literal["tag", "single"] @@ -164,9 +171,9 @@ class Gesture(BaseModel): class GestureAction(ProgramElement): """ - Represents the action of the robot performing a gesture. + An action where the robot performs a physical gesture. - :ivar gesture: The gesture to perform. + :ivar gesture: The :class:`Gesture` definition. """ name: str = "" @@ -175,10 +182,9 @@ class GestureAction(ProgramElement): class LLMAction(ProgramElement): """ - Represents the action of letting an LLM generate a reply based on its chat history - and an additional goal added in the prompt. + An action that triggers an LLM-generated conversational response. - :ivar goal: The extra (temporary) goal to add to the LLM. + :ivar goal: A temporary conversational goal to guide the LLM's response generation. """ name: str = "" @@ -187,10 +193,10 @@ class LLMAction(ProgramElement): class Trigger(ProgramElement): """ - Represents a belief-based trigger. When a belief is set, the corresponding plan is executed. + Defines a reactive behavior: when the condition (belief) is met, the plan is executed. - :ivar condition: When to activate the trigger. - :ivar plan: The plan to execute. + :ivar condition: The :class:`Belief` that triggers this behavior. + :ivar plan: The :class:`Plan` to execute upon activation. """ condition: Belief @@ -199,11 +205,11 @@ class Trigger(ProgramElement): class Phase(ProgramElement): """ - A distinct phase within a program, containing norms, goals, and triggers. + A logical stage in the interaction program, grouping norms, goals, and triggers. - :ivar norms: List of norms active in this phase. - :ivar goals: List of goals to pursue in this phase. - :ivar triggers: List of triggers that define transitions out of this phase. + :ivar norms: List of norms active during this phase. + :ivar goals: List of goals the robot pursues in this phase. + :ivar triggers: List of reactive behaviors defined for this phase. """ name: str = "" @@ -214,9 +220,9 @@ class Phase(ProgramElement): class Program(BaseModel): """ - Represents a complete interaction program, consisting of a sequence or set of phases. + The top-level container for a complete robot behavior definition. - :ivar phases: The list of phases that make up the program. + :ivar phases: An ordered list of :class:`Phase` objects defining the interaction flow. """ phases: list[Phase]