docs: add docstrings to AgentSpeak stuff
ref: N25B-449
This commit is contained in:
@@ -8,31 +8,78 @@ from enum import StrEnum
|
|||||||
class AstNode(ABC):
|
class AstNode(ABC):
|
||||||
"""
|
"""
|
||||||
Abstract base class for all elements of an AgentSpeak program.
|
Abstract base class for all elements of an AgentSpeak program.
|
||||||
|
|
||||||
|
This class serves as the foundation for all AgentSpeak abstract syntax tree (AST) nodes.
|
||||||
|
It defines the core interface that all AST nodes must implement to generate AgentSpeak code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _to_agentspeak(self) -> str:
|
def _to_agentspeak(self) -> str:
|
||||||
"""
|
"""
|
||||||
Generates the AgentSpeak code string.
|
Generates the AgentSpeak code string.
|
||||||
|
|
||||||
|
This method converts the AST node into its corresponding
|
||||||
|
AgentSpeak source code representation.
|
||||||
|
|
||||||
|
:return: The AgentSpeak code string representation of this node.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns the string representation of this AST node.
|
||||||
|
|
||||||
|
This method provides a convenient way to get the AgentSpeak code representation
|
||||||
|
by delegating to the _to_agentspeak method.
|
||||||
|
|
||||||
|
:return: The AgentSpeak code string representation of this node.
|
||||||
|
"""
|
||||||
return self._to_agentspeak()
|
return self._to_agentspeak()
|
||||||
|
|
||||||
|
|
||||||
class AstExpression(AstNode, ABC):
|
class AstExpression(AstNode, ABC):
|
||||||
"""
|
"""
|
||||||
Intermediate class for anything that can be used in a logical expression.
|
Intermediate class for anything that can be used in a logical expression.
|
||||||
|
|
||||||
|
This class extends AstNode to provide common functionality for all expressions
|
||||||
|
that can be used in logical operations within AgentSpeak programs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __and__(self, other: ExprCoalescible) -> AstBinaryOp:
|
def __and__(self, other: ExprCoalescible) -> AstBinaryOp:
|
||||||
|
"""
|
||||||
|
Creates a logical AND operation between this expression and another.
|
||||||
|
|
||||||
|
This method allows for operator overloading of the & operator to create
|
||||||
|
binary logical operations in a more intuitive syntax.
|
||||||
|
|
||||||
|
:param other: The right-hand side expression to combine with this one.
|
||||||
|
:return: A new AstBinaryOp representing the logical AND operation.
|
||||||
|
"""
|
||||||
return AstBinaryOp(self, BinaryOperatorType.AND, _coalesce_expr(other))
|
return AstBinaryOp(self, BinaryOperatorType.AND, _coalesce_expr(other))
|
||||||
|
|
||||||
def __or__(self, other: ExprCoalescible) -> AstBinaryOp:
|
def __or__(self, other: ExprCoalescible) -> AstBinaryOp:
|
||||||
|
"""
|
||||||
|
Creates a logical OR operation between this expression and another.
|
||||||
|
|
||||||
|
This method allows for operator overloading of the | operator to create
|
||||||
|
binary logical operations in a more intuitive syntax.
|
||||||
|
|
||||||
|
:param other: The right-hand side expression to combine with this one.
|
||||||
|
:return: A new AstBinaryOp representing the logical OR operation.
|
||||||
|
"""
|
||||||
return AstBinaryOp(self, BinaryOperatorType.OR, _coalesce_expr(other))
|
return AstBinaryOp(self, BinaryOperatorType.OR, _coalesce_expr(other))
|
||||||
|
|
||||||
def __invert__(self) -> AstLogicalExpression:
|
def __invert__(self) -> AstLogicalExpression:
|
||||||
|
"""
|
||||||
|
Creates a logical negation of this expression.
|
||||||
|
|
||||||
|
This method allows for operator overloading of the ~ operator to create
|
||||||
|
negated expressions. If the expression is already a logical expression,
|
||||||
|
it toggles the negation flag. Otherwise, it wraps the expression in a
|
||||||
|
new AstLogicalExpression with negation set to True.
|
||||||
|
|
||||||
|
:return: An AstLogicalExpression representing the negated form of this expression.
|
||||||
|
"""
|
||||||
if isinstance(self, AstLogicalExpression):
|
if isinstance(self, AstLogicalExpression):
|
||||||
self.negated = not self.negated
|
self.negated = not self.negated
|
||||||
return self
|
return self
|
||||||
@@ -81,11 +128,25 @@ class AstTerm(AstExpression, ABC):
|
|||||||
class AstAtom(AstTerm):
|
class AstAtom(AstTerm):
|
||||||
"""
|
"""
|
||||||
Represents a grounded atom in AgentSpeak (e.g., lowercase constants).
|
Represents a grounded atom in AgentSpeak (e.g., lowercase constants).
|
||||||
|
|
||||||
|
Atoms are the simplest form of terms in AgentSpeak, representing concrete,
|
||||||
|
unchanging values. They are typically used as constants in logical expressions.
|
||||||
|
|
||||||
|
:ivar value: The string value of this atom, which will be converted to lowercase
|
||||||
|
in the AgentSpeak representation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
value: str
|
value: str
|
||||||
|
|
||||||
def _to_agentspeak(self) -> str:
|
def _to_agentspeak(self) -> str:
|
||||||
|
"""
|
||||||
|
Converts this atom to its AgentSpeak string representation.
|
||||||
|
|
||||||
|
Atoms are represented in lowercase in AgentSpeak to distinguish them
|
||||||
|
from variables (which are capitalized).
|
||||||
|
|
||||||
|
:return: The lowercase string representation of this atom.
|
||||||
|
"""
|
||||||
return self.value.lower()
|
return self.value.lower()
|
||||||
|
|
||||||
|
|
||||||
@@ -93,11 +154,25 @@ class AstAtom(AstTerm):
|
|||||||
class AstVar(AstTerm):
|
class AstVar(AstTerm):
|
||||||
"""
|
"""
|
||||||
Represents an ungrounded variable in AgentSpeak (e.g., capitalized names).
|
Represents an ungrounded variable in AgentSpeak (e.g., capitalized names).
|
||||||
|
|
||||||
|
Variables in AgentSpeak are placeholders that can be bound to specific values
|
||||||
|
during execution. They are distinguished from atoms by their capitalization.
|
||||||
|
|
||||||
|
:ivar name: The name of this variable, which will be capitalized in the
|
||||||
|
AgentSpeak representation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
def _to_agentspeak(self) -> str:
|
def _to_agentspeak(self) -> str:
|
||||||
|
"""
|
||||||
|
Converts this variable to its AgentSpeak string representation.
|
||||||
|
|
||||||
|
Variables are represented with capitalized names in AgentSpeak to distinguish
|
||||||
|
them from atoms (which are lowercase).
|
||||||
|
|
||||||
|
:return: The capitalized string representation of this variable.
|
||||||
|
"""
|
||||||
return self.name.capitalize()
|
return self.name.capitalize()
|
||||||
|
|
||||||
|
|
||||||
@@ -105,11 +180,21 @@ class AstVar(AstTerm):
|
|||||||
class AstNumber(AstTerm):
|
class AstNumber(AstTerm):
|
||||||
"""
|
"""
|
||||||
Represents a numeric constant in AgentSpeak.
|
Represents a numeric constant in AgentSpeak.
|
||||||
|
|
||||||
|
Numeric constants can be either integers or floating-point numbers and are
|
||||||
|
used in logical expressions and comparisons.
|
||||||
|
|
||||||
|
:ivar value: The numeric value of this constant (can be int or float).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
value: int | float
|
value: int | float
|
||||||
|
|
||||||
def _to_agentspeak(self) -> str:
|
def _to_agentspeak(self) -> str:
|
||||||
|
"""
|
||||||
|
Converts this numeric constant to its AgentSpeak string representation.
|
||||||
|
|
||||||
|
:return: The string representation of the numeric value.
|
||||||
|
"""
|
||||||
return str(self.value)
|
return str(self.value)
|
||||||
|
|
||||||
|
|
||||||
@@ -117,11 +202,23 @@ class AstNumber(AstTerm):
|
|||||||
class AstString(AstTerm):
|
class AstString(AstTerm):
|
||||||
"""
|
"""
|
||||||
Represents a string literal in AgentSpeak.
|
Represents a string literal in AgentSpeak.
|
||||||
|
|
||||||
|
String literals are used to represent textual data and are enclosed in
|
||||||
|
double quotes in the AgentSpeak representation.
|
||||||
|
|
||||||
|
:ivar value: The string content of this literal.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
value: str
|
value: str
|
||||||
|
|
||||||
def _to_agentspeak(self) -> str:
|
def _to_agentspeak(self) -> str:
|
||||||
|
"""
|
||||||
|
Converts this string literal to its AgentSpeak string representation.
|
||||||
|
|
||||||
|
String literals are enclosed in double quotes in AgentSpeak.
|
||||||
|
|
||||||
|
:return: The string literal enclosed in double quotes.
|
||||||
|
"""
|
||||||
return f'"{self.value}"'
|
return f'"{self.value}"'
|
||||||
|
|
||||||
|
|
||||||
@@ -129,12 +226,26 @@ class AstString(AstTerm):
|
|||||||
class AstLiteral(AstTerm):
|
class AstLiteral(AstTerm):
|
||||||
"""
|
"""
|
||||||
Represents a literal (functor and terms) in AgentSpeak.
|
Represents a literal (functor and terms) in AgentSpeak.
|
||||||
|
|
||||||
|
Literals are the fundamental building blocks of AgentSpeak programs, consisting
|
||||||
|
of a functor (predicate name) and an optional list of terms (arguments).
|
||||||
|
|
||||||
|
:ivar functor: The name of the predicate or function.
|
||||||
|
:ivar terms: A list of terms (arguments) for this literal. Defaults to an empty list.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
functor: str
|
functor: str
|
||||||
terms: list[AstTerm] = field(default_factory=list)
|
terms: list[AstTerm] = field(default_factory=list)
|
||||||
|
|
||||||
def _to_agentspeak(self) -> str:
|
def _to_agentspeak(self) -> str:
|
||||||
|
"""
|
||||||
|
Converts this literal to its AgentSpeak string representation.
|
||||||
|
|
||||||
|
If the literal has no terms, it returns just the functor name.
|
||||||
|
Otherwise, it returns the functor followed by the terms in parentheses.
|
||||||
|
|
||||||
|
:return: The AgentSpeak string representation of this literal.
|
||||||
|
"""
|
||||||
if not self.terms:
|
if not self.terms:
|
||||||
return self.functor
|
return self.functor
|
||||||
args = ", ".join(map(str, self.terms))
|
args = ", ".join(map(str, self.terms))
|
||||||
@@ -142,6 +253,13 @@ class AstLiteral(AstTerm):
|
|||||||
|
|
||||||
|
|
||||||
class BinaryOperatorType(StrEnum):
|
class BinaryOperatorType(StrEnum):
|
||||||
|
"""
|
||||||
|
Enumeration of binary operator types used in AgentSpeak expressions.
|
||||||
|
|
||||||
|
These operators are used to create binary operations between expressions,
|
||||||
|
including logical operations (AND, OR) and comparison operations.
|
||||||
|
"""
|
||||||
|
|
||||||
AND = "&"
|
AND = "&"
|
||||||
OR = "|"
|
OR = "|"
|
||||||
GREATER_THAN = ">"
|
GREATER_THAN = ">"
|
||||||
@@ -156,6 +274,13 @@ class BinaryOperatorType(StrEnum):
|
|||||||
class AstBinaryOp(AstExpression):
|
class AstBinaryOp(AstExpression):
|
||||||
"""
|
"""
|
||||||
Represents a binary logical or relational operation in AgentSpeak.
|
Represents a binary logical or relational operation in AgentSpeak.
|
||||||
|
|
||||||
|
Binary operations combine two expressions using a logical or comparison operator.
|
||||||
|
They are used to create complex logical conditions in AgentSpeak programs.
|
||||||
|
|
||||||
|
:ivar left: The left-hand side expression of the operation.
|
||||||
|
:ivar operator: The binary operator type (AND, OR, comparison operators, etc.).
|
||||||
|
:ivar right: The right-hand side expression of the operation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
left: AstExpression
|
left: AstExpression
|
||||||
@@ -163,10 +288,25 @@ class AstBinaryOp(AstExpression):
|
|||||||
right: AstExpression
|
right: AstExpression
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
|
"""
|
||||||
|
Post-initialization processing to ensure proper expression types.
|
||||||
|
|
||||||
|
This method wraps the left and right expressions in AstLogicalExpression
|
||||||
|
instances if they aren't already, ensuring consistent handling throughout
|
||||||
|
the AST.
|
||||||
|
"""
|
||||||
self.left = _as_logical(self.left)
|
self.left = _as_logical(self.left)
|
||||||
self.right = _as_logical(self.right)
|
self.right = _as_logical(self.right)
|
||||||
|
|
||||||
def _to_agentspeak(self) -> str:
|
def _to_agentspeak(self) -> str:
|
||||||
|
"""
|
||||||
|
Converts this binary operation to its AgentSpeak string representation.
|
||||||
|
|
||||||
|
The method handles proper parenthesization of sub-expressions to maintain
|
||||||
|
correct operator precedence and readability.
|
||||||
|
|
||||||
|
:return: The AgentSpeak string representation of this binary operation.
|
||||||
|
"""
|
||||||
l_str = str(self.left)
|
l_str = str(self.left)
|
||||||
r_str = str(self.right)
|
r_str = str(self.right)
|
||||||
|
|
||||||
@@ -185,12 +325,27 @@ class AstBinaryOp(AstExpression):
|
|||||||
class AstLogicalExpression(AstExpression):
|
class AstLogicalExpression(AstExpression):
|
||||||
"""
|
"""
|
||||||
Represents a logical expression, potentially negated, in AgentSpeak.
|
Represents a logical expression, potentially negated, in AgentSpeak.
|
||||||
|
|
||||||
|
Logical expressions can be either positive or negated and form the basis
|
||||||
|
of conditions and beliefs in AgentSpeak programs.
|
||||||
|
|
||||||
|
:ivar expression: The underlying expression being evaluated.
|
||||||
|
:ivar negated: Boolean flag indicating whether this expression is negated.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
expression: AstExpression
|
expression: AstExpression
|
||||||
negated: bool = False
|
negated: bool = False
|
||||||
|
|
||||||
def _to_agentspeak(self) -> str:
|
def _to_agentspeak(self) -> str:
|
||||||
|
"""
|
||||||
|
Converts this logical expression to its AgentSpeak string representation.
|
||||||
|
|
||||||
|
If the expression is negated, it prepends 'not ' to the expression string.
|
||||||
|
For complex expressions (binary operations), it adds parentheses when negated
|
||||||
|
to maintain correct logical interpretation.
|
||||||
|
|
||||||
|
:return: The AgentSpeak string representation of this logical expression.
|
||||||
|
"""
|
||||||
expr_str = str(self.expression)
|
expr_str = str(self.expression)
|
||||||
if isinstance(self.expression, AstBinaryOp) and self.negated:
|
if isinstance(self.expression, AstBinaryOp) and self.negated:
|
||||||
expr_str = f"({expr_str})"
|
expr_str = f"({expr_str})"
|
||||||
@@ -198,31 +353,76 @@ class AstLogicalExpression(AstExpression):
|
|||||||
|
|
||||||
|
|
||||||
def _as_logical(expr: AstExpression) -> AstLogicalExpression:
|
def _as_logical(expr: AstExpression) -> AstLogicalExpression:
|
||||||
|
"""
|
||||||
|
Converts an expression to a logical expression if it isn't already.
|
||||||
|
|
||||||
|
This helper function ensures that expressions are properly wrapped in
|
||||||
|
AstLogicalExpression instances, which is necessary for consistent handling
|
||||||
|
of logical operations in the AST.
|
||||||
|
|
||||||
|
:param expr: The expression to convert.
|
||||||
|
:return: The expression wrapped in an AstLogicalExpression if it wasn't already.
|
||||||
|
"""
|
||||||
if isinstance(expr, AstLogicalExpression):
|
if isinstance(expr, AstLogicalExpression):
|
||||||
return expr
|
return expr
|
||||||
return AstLogicalExpression(expr)
|
return AstLogicalExpression(expr)
|
||||||
|
|
||||||
|
|
||||||
class StatementType(StrEnum):
|
class StatementType(StrEnum):
|
||||||
|
"""
|
||||||
|
Enumeration of statement types that can appear in AgentSpeak plans.
|
||||||
|
|
||||||
|
These statement types define the different kinds of actions and operations
|
||||||
|
that can be performed within the body of an AgentSpeak plan.
|
||||||
|
"""
|
||||||
|
|
||||||
EMPTY = ""
|
EMPTY = ""
|
||||||
|
"""Empty statement (no operation, used when evaluating a plan to true)."""
|
||||||
|
|
||||||
DO_ACTION = "."
|
DO_ACTION = "."
|
||||||
|
"""Execute an action defined in Python."""
|
||||||
|
|
||||||
ACHIEVE_GOAL = "!"
|
ACHIEVE_GOAL = "!"
|
||||||
|
"""Achieve a goal (add a goal to be accomplished)."""
|
||||||
|
|
||||||
TEST_GOAL = "?"
|
TEST_GOAL = "?"
|
||||||
|
"""Test a goal (check if a goal can be achieved)."""
|
||||||
|
|
||||||
ADD_BELIEF = "+"
|
ADD_BELIEF = "+"
|
||||||
|
"""Add a belief to the belief base."""
|
||||||
|
|
||||||
REMOVE_BELIEF = "-"
|
REMOVE_BELIEF = "-"
|
||||||
|
"""Remove a belief from the belief base."""
|
||||||
|
|
||||||
REPLACE_BELIEF = "-+"
|
REPLACE_BELIEF = "-+"
|
||||||
|
"""Replace a belief in the belief base."""
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AstStatement(AstNode):
|
class AstStatement(AstNode):
|
||||||
"""
|
"""
|
||||||
A statement that can appear inside a plan.
|
A statement that can appear inside a plan.
|
||||||
|
|
||||||
|
Statements are the executable units within AgentSpeak plans. They consist
|
||||||
|
of a statement type (defining the operation) and an expression (defining
|
||||||
|
what to operate on).
|
||||||
|
|
||||||
|
:ivar type: The type of statement (action, goal, belief operation, etc.).
|
||||||
|
:ivar expression: The expression that this statement operates on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: StatementType
|
type: StatementType
|
||||||
expression: AstExpression
|
expression: AstExpression
|
||||||
|
|
||||||
def _to_agentspeak(self) -> str:
|
def _to_agentspeak(self) -> str:
|
||||||
|
"""
|
||||||
|
Converts this statement to its AgentSpeak string representation.
|
||||||
|
|
||||||
|
The representation consists of the statement type prefix followed by
|
||||||
|
the expression.
|
||||||
|
|
||||||
|
:return: The AgentSpeak string representation of this statement.
|
||||||
|
"""
|
||||||
return f"{self.type.value}{self.expression}"
|
return f"{self.type.value}{self.expression}"
|
||||||
|
|
||||||
|
|
||||||
@@ -230,26 +430,59 @@ class AstStatement(AstNode):
|
|||||||
class AstRule(AstNode):
|
class AstRule(AstNode):
|
||||||
"""
|
"""
|
||||||
Represents an inference rule in AgentSpeak. If there is no condition, it always holds.
|
Represents an inference rule in AgentSpeak. If there is no condition, it always holds.
|
||||||
|
|
||||||
|
Rules define logical implications in AgentSpeak programs. They consist of a
|
||||||
|
result (conclusion) and an optional condition (premise). When the condition
|
||||||
|
holds, the result is inferred to be true.
|
||||||
|
|
||||||
|
:ivar result: The conclusion or result of this rule.
|
||||||
|
:ivar condition: The premise or condition for this rule (optional).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result: AstExpression
|
result: AstExpression
|
||||||
condition: AstExpression | None = None
|
condition: AstExpression | None = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
|
"""
|
||||||
|
Post-initialization processing to ensure proper expression types.
|
||||||
|
|
||||||
|
If a condition is provided, this method wraps it in an AstLogicalExpression
|
||||||
|
to ensure consistent handling throughout the AST.
|
||||||
|
"""
|
||||||
if self.condition is not None:
|
if self.condition is not None:
|
||||||
self.condition = _as_logical(self.condition)
|
self.condition = _as_logical(self.condition)
|
||||||
|
|
||||||
def _to_agentspeak(self) -> str:
|
def _to_agentspeak(self) -> str:
|
||||||
|
"""
|
||||||
|
Converts this rule to its AgentSpeak string representation.
|
||||||
|
|
||||||
|
If no condition is specified, the rule is represented as a simple fact.
|
||||||
|
If a condition is specified, it's represented as an implication (result :- condition).
|
||||||
|
|
||||||
|
:return: The AgentSpeak string representation of this rule.
|
||||||
|
"""
|
||||||
if not self.condition:
|
if not self.condition:
|
||||||
return f"{self.result}."
|
return f"{self.result}."
|
||||||
return f"{self.result} :- {self.condition}."
|
return f"{self.result} :- {self.condition}."
|
||||||
|
|
||||||
|
|
||||||
class TriggerType(StrEnum):
|
class TriggerType(StrEnum):
|
||||||
|
"""
|
||||||
|
Enumeration of trigger types for AgentSpeak plans.
|
||||||
|
|
||||||
|
Trigger types define what kind of events can activate an AgentSpeak plan.
|
||||||
|
Currently, the system supports triggers for added beliefs and added goals.
|
||||||
|
"""
|
||||||
|
|
||||||
ADDED_BELIEF = "+"
|
ADDED_BELIEF = "+"
|
||||||
|
"""Trigger when a belief is added to the belief base."""
|
||||||
|
|
||||||
# REMOVED_BELIEF = "-" # TODO
|
# REMOVED_BELIEF = "-" # TODO
|
||||||
# MODIFIED_BELIEF = "^" # TODO
|
# MODIFIED_BELIEF = "^" # TODO
|
||||||
|
|
||||||
ADDED_GOAL = "+!"
|
ADDED_GOAL = "+!"
|
||||||
|
"""Trigger when a goal is added to be achieved."""
|
||||||
|
|
||||||
# REMOVED_GOAL = "-!" # TODO
|
# REMOVED_GOAL = "-!" # TODO
|
||||||
|
|
||||||
|
|
||||||
@@ -257,6 +490,14 @@ class TriggerType(StrEnum):
|
|||||||
class AstPlan(AstNode):
|
class AstPlan(AstNode):
|
||||||
"""
|
"""
|
||||||
Represents a plan in AgentSpeak, consisting of a trigger, context, and body.
|
Represents a plan in AgentSpeak, consisting of a trigger, context, and body.
|
||||||
|
|
||||||
|
Plans define the reactive behavior of agents in AgentSpeak. They specify what
|
||||||
|
actions to take when certain conditions are met (trigger and context).
|
||||||
|
|
||||||
|
:ivar type: The type of trigger that activates this plan.
|
||||||
|
:ivar trigger_literal: The specific event or condition that triggers this plan.
|
||||||
|
:ivar context: A list of conditions that must hold for this plan to be applicable.
|
||||||
|
:ivar body: A list of statements to execute when this plan is triggered.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: TriggerType
|
type: TriggerType
|
||||||
@@ -265,6 +506,16 @@ class AstPlan(AstNode):
|
|||||||
body: list[AstStatement]
|
body: list[AstStatement]
|
||||||
|
|
||||||
def _to_agentspeak(self) -> str:
|
def _to_agentspeak(self) -> str:
|
||||||
|
"""
|
||||||
|
Converts this plan to its AgentSpeak string representation.
|
||||||
|
|
||||||
|
The representation follows the standard AgentSpeak plan format:
|
||||||
|
trigger_type + trigger_literal
|
||||||
|
: context_conditions
|
||||||
|
<- body_statements.
|
||||||
|
|
||||||
|
:return: The AgentSpeak string representation of this plan.
|
||||||
|
"""
|
||||||
assert isinstance(self.trigger_literal, AstLiteral)
|
assert isinstance(self.trigger_literal, AstLiteral)
|
||||||
|
|
||||||
indent = " " * 6
|
indent = " " * 6
|
||||||
@@ -290,12 +541,26 @@ class AstPlan(AstNode):
|
|||||||
class AstProgram(AstNode):
|
class AstProgram(AstNode):
|
||||||
"""
|
"""
|
||||||
Represents a full AgentSpeak program, consisting of rules and plans.
|
Represents a full AgentSpeak program, consisting of rules and plans.
|
||||||
|
|
||||||
|
This is the root node of the AgentSpeak AST, containing all the rules
|
||||||
|
and plans that define the agent's behavior.
|
||||||
|
|
||||||
|
:ivar rules: A list of inference rules in this program.
|
||||||
|
:ivar plans: A list of reactive plans in this program.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rules: list[AstRule] = field(default_factory=list)
|
rules: list[AstRule] = field(default_factory=list)
|
||||||
plans: list[AstPlan] = field(default_factory=list)
|
plans: list[AstPlan] = field(default_factory=list)
|
||||||
|
|
||||||
def _to_agentspeak(self) -> str:
|
def _to_agentspeak(self) -> str:
|
||||||
|
"""
|
||||||
|
Converts this program to its AgentSpeak string representation.
|
||||||
|
|
||||||
|
The representation consists of all rules followed by all plans,
|
||||||
|
separated by blank lines for readability.
|
||||||
|
|
||||||
|
:return: The complete AgentSpeak source code for this program.
|
||||||
|
"""
|
||||||
lines = []
|
lines = []
|
||||||
lines.extend(map(str, self.rules))
|
lines.extend(map(str, self.rules))
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,15 @@ class AgentSpeakGenerator:
|
|||||||
|
|
||||||
It handles the conversion of phases, norms, goals, and triggers into AgentSpeak rules and plans,
|
It handles the conversion of phases, norms, goals, and triggers into AgentSpeak rules and plans,
|
||||||
ensuring the robot follows the defined behavioral logic.
|
ensuring the robot follows the defined behavioral logic.
|
||||||
|
|
||||||
|
The generator follows a systematic approach:
|
||||||
|
1. Sets up initial phase and cycle notification rules
|
||||||
|
2. Adds keyword inference capabilities for natural language processing
|
||||||
|
3. Creates default plans for common operations
|
||||||
|
4. Processes each phase with its norms, goals, and triggers
|
||||||
|
5. Adds fallback plans for robust execution
|
||||||
|
|
||||||
|
:ivar _asp: The internal AgentSpeak program representation being built.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_asp: AstProgram
|
_asp: AstProgram
|
||||||
@@ -54,6 +63,10 @@ class AgentSpeakGenerator:
|
|||||||
"""
|
"""
|
||||||
Translates a Program object into an AgentSpeak source string.
|
Translates a Program object into an AgentSpeak source string.
|
||||||
|
|
||||||
|
This is the main entry point for the code generation process. It initializes
|
||||||
|
the AgentSpeak program structure and orchestrates the conversion of all
|
||||||
|
program elements into their AgentSpeak representations.
|
||||||
|
|
||||||
:param program: The behavior program to translate.
|
:param program: The behavior program to translate.
|
||||||
:return: The generated AgentSpeak code as a string.
|
:return: The generated AgentSpeak code as a string.
|
||||||
"""
|
"""
|
||||||
@@ -76,6 +89,18 @@ class AgentSpeakGenerator:
|
|||||||
return str(self._asp)
|
return str(self._asp)
|
||||||
|
|
||||||
def _add_keyword_inference(self) -> None:
|
def _add_keyword_inference(self) -> None:
|
||||||
|
"""
|
||||||
|
Adds inference rules for keyword detection in user messages.
|
||||||
|
|
||||||
|
This method creates rules that allow the system to detect when specific
|
||||||
|
keywords are mentioned in user messages. It uses string operations to
|
||||||
|
check if a keyword is a substring of the user's message.
|
||||||
|
|
||||||
|
The generated rule has the form:
|
||||||
|
keyword_said(Keyword) :- user_said(Message) & .substring(Keyword, Message, Pos) & Pos >= 0
|
||||||
|
|
||||||
|
This enables the system to trigger behaviors based on keyword detection.
|
||||||
|
"""
|
||||||
keyword = AstVar("Keyword")
|
keyword = AstVar("Keyword")
|
||||||
message = AstVar("Message")
|
message = AstVar("Message")
|
||||||
position = AstVar("Pos")
|
position = AstVar("Pos")
|
||||||
@@ -90,12 +115,32 @@ class AgentSpeakGenerator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _add_default_plans(self):
|
def _add_default_plans(self):
|
||||||
|
"""
|
||||||
|
Adds default plans for common operations.
|
||||||
|
|
||||||
|
This method sets up the standard plans that handle fundamental operations
|
||||||
|
like replying with goals, simple speech actions, general replies, and
|
||||||
|
cycle notifications. These plans provide the basic infrastructure for
|
||||||
|
the agent's reactive behavior.
|
||||||
|
"""
|
||||||
self._add_reply_with_goal_plan()
|
self._add_reply_with_goal_plan()
|
||||||
self._add_say_plan()
|
self._add_say_plan()
|
||||||
self._add_reply_plan()
|
self._add_reply_plan()
|
||||||
self._add_notify_cycle_plan()
|
self._add_notify_cycle_plan()
|
||||||
|
|
||||||
def _add_reply_with_goal_plan(self):
|
def _add_reply_with_goal_plan(self):
|
||||||
|
"""
|
||||||
|
Adds a plan for replying with a specific conversational goal.
|
||||||
|
|
||||||
|
This plan handles the case where the agent needs to respond to user input
|
||||||
|
while pursuing a specific conversational goal. It:
|
||||||
|
1. Marks that the agent has responded this turn
|
||||||
|
2. Gathers all active norms
|
||||||
|
3. Generates a reply that considers both the user message and the goal
|
||||||
|
|
||||||
|
Trigger: +!reply_with_goal(Goal)
|
||||||
|
Context: user_said(Message)
|
||||||
|
"""
|
||||||
self._asp.plans.append(
|
self._asp.plans.append(
|
||||||
AstPlan(
|
AstPlan(
|
||||||
TriggerType.ADDED_GOAL,
|
TriggerType.ADDED_GOAL,
|
||||||
@@ -121,6 +166,17 @@ class AgentSpeakGenerator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _add_say_plan(self):
|
def _add_say_plan(self):
|
||||||
|
"""
|
||||||
|
Adds a plan for simple speech actions.
|
||||||
|
|
||||||
|
This plan handles direct speech actions where the agent needs to say
|
||||||
|
a specific text. It:
|
||||||
|
1. Marks that the agent has responded this turn
|
||||||
|
2. Executes the speech action
|
||||||
|
|
||||||
|
Trigger: +!say(Text)
|
||||||
|
Context: None (can be executed anytime)
|
||||||
|
"""
|
||||||
self._asp.plans.append(
|
self._asp.plans.append(
|
||||||
AstPlan(
|
AstPlan(
|
||||||
TriggerType.ADDED_GOAL,
|
TriggerType.ADDED_GOAL,
|
||||||
@@ -134,6 +190,18 @@ class AgentSpeakGenerator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _add_reply_plan(self):
|
def _add_reply_plan(self):
|
||||||
|
"""
|
||||||
|
Adds a plan for general reply actions.
|
||||||
|
|
||||||
|
This plan handles general reply actions where the agent needs to respond
|
||||||
|
to user input without a specific conversational goal. It:
|
||||||
|
1. Marks that the agent has responded this turn
|
||||||
|
2. Gathers all active norms
|
||||||
|
3. Generates a reply based on the user message and norms
|
||||||
|
|
||||||
|
Trigger: +!reply
|
||||||
|
Context: user_said(Message)
|
||||||
|
"""
|
||||||
self._asp.plans.append(
|
self._asp.plans.append(
|
||||||
AstPlan(
|
AstPlan(
|
||||||
TriggerType.ADDED_GOAL,
|
TriggerType.ADDED_GOAL,
|
||||||
@@ -157,6 +225,19 @@ class AgentSpeakGenerator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _add_notify_cycle_plan(self):
|
def _add_notify_cycle_plan(self):
|
||||||
|
"""
|
||||||
|
Adds a plan for cycle notification.
|
||||||
|
|
||||||
|
This plan handles the periodic notification cycle that allows the system
|
||||||
|
to monitor and report on the current state. It:
|
||||||
|
1. Gathers all active norms
|
||||||
|
2. Notifies the system about the current norms
|
||||||
|
3. Waits briefly to allow processing
|
||||||
|
4. Recursively triggers the next cycle
|
||||||
|
|
||||||
|
Trigger: +!notify_cycle
|
||||||
|
Context: None (can be executed anytime)
|
||||||
|
"""
|
||||||
self._asp.plans.append(
|
self._asp.plans.append(
|
||||||
AstPlan(
|
AstPlan(
|
||||||
TriggerType.ADDED_GOAL,
|
TriggerType.ADDED_GOAL,
|
||||||
@@ -180,6 +261,16 @@ class AgentSpeakGenerator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _process_phases(self, phases: list[Phase]) -> None:
|
def _process_phases(self, phases: list[Phase]) -> None:
|
||||||
|
"""
|
||||||
|
Processes all phases in the program and their transitions.
|
||||||
|
|
||||||
|
This method iterates through each phase and:
|
||||||
|
1. Processes the current phase (norms, goals, triggers)
|
||||||
|
2. Sets up transitions between phases
|
||||||
|
3. Adds special handling for the end phase
|
||||||
|
|
||||||
|
:param phases: The list of phases to process.
|
||||||
|
"""
|
||||||
for curr_phase, next_phase in zip([None] + phases, phases + [None], strict=True):
|
for curr_phase, next_phase in zip([None] + phases, phases + [None], strict=True):
|
||||||
if curr_phase:
|
if curr_phase:
|
||||||
self._process_phase(curr_phase)
|
self._process_phase(curr_phase)
|
||||||
@@ -202,6 +293,17 @@ class AgentSpeakGenerator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _process_phase(self, phase: Phase) -> None:
|
def _process_phase(self, phase: Phase) -> None:
|
||||||
|
"""
|
||||||
|
Processes a single phase, including its norms, goals, and triggers.
|
||||||
|
|
||||||
|
This method handles the complete processing of a phase by:
|
||||||
|
1. Processing all norms in the phase
|
||||||
|
2. Setting up the default execution loop for the phase
|
||||||
|
3. Processing all goals in sequence
|
||||||
|
4. Processing all triggers for reactive behavior
|
||||||
|
|
||||||
|
:param phase: The phase to process.
|
||||||
|
"""
|
||||||
for norm in phase.norms:
|
for norm in phase.norms:
|
||||||
self._process_norm(norm, phase)
|
self._process_norm(norm, phase)
|
||||||
|
|
||||||
@@ -216,6 +318,21 @@ class AgentSpeakGenerator:
|
|||||||
self._process_trigger(trigger, phase)
|
self._process_trigger(trigger, phase)
|
||||||
|
|
||||||
def _add_phase_transition(self, from_phase: Phase | None, to_phase: Phase | None) -> None:
|
def _add_phase_transition(self, from_phase: Phase | None, to_phase: Phase | None) -> None:
|
||||||
|
"""
|
||||||
|
Adds plans for transitioning between phases.
|
||||||
|
|
||||||
|
This method creates two plans for each phase transition:
|
||||||
|
1. A check plan that verifies if transition conditions are met
|
||||||
|
2. A force plan that actually performs the transition (can be forced externally)
|
||||||
|
|
||||||
|
The transition involves:
|
||||||
|
- Notifying the system about the phase change
|
||||||
|
- Removing the current phase belief
|
||||||
|
- Adding the next phase belief
|
||||||
|
|
||||||
|
:param from_phase: The phase being transitioned from (or None for initial setup).
|
||||||
|
:param to_phase: The phase being transitioned to (or None for end phase).
|
||||||
|
"""
|
||||||
if from_phase is None:
|
if from_phase is None:
|
||||||
return
|
return
|
||||||
from_phase_ast = self._astify(from_phase)
|
from_phase_ast = self._astify(from_phase)
|
||||||
@@ -245,18 +362,6 @@ class AgentSpeakGenerator:
|
|||||||
AstStatement(StatementType.ADD_BELIEF, to_phase_ast),
|
AstStatement(StatementType.ADD_BELIEF, to_phase_ast),
|
||||||
]
|
]
|
||||||
|
|
||||||
# if from_phase:
|
|
||||||
# body.extend(
|
|
||||||
# [
|
|
||||||
# AstStatement(
|
|
||||||
# StatementType.TEST_GOAL, AstLiteral("user_said", [AstVar("Message")])
|
|
||||||
# ),
|
|
||||||
# AstStatement(
|
|
||||||
# StatementType.REPLACE_BELIEF, AstLiteral("user_said", [AstVar("Message")])
|
|
||||||
# ),
|
|
||||||
# ]
|
|
||||||
# )
|
|
||||||
|
|
||||||
# Check
|
# Check
|
||||||
self._asp.plans.append(
|
self._asp.plans.append(
|
||||||
AstPlan(
|
AstPlan(
|
||||||
@@ -277,6 +382,17 @@ class AgentSpeakGenerator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _process_norm(self, norm: Norm, phase: Phase) -> None:
|
def _process_norm(self, norm: Norm, phase: Phase) -> None:
|
||||||
|
"""
|
||||||
|
Processes a norm and adds it as an inference rule.
|
||||||
|
|
||||||
|
This method converts norms into AgentSpeak rules that define when
|
||||||
|
the norm should be active. It handles both basic norms (always active
|
||||||
|
in their phase) and conditional norms (active only when their condition
|
||||||
|
is met).
|
||||||
|
|
||||||
|
:param norm: The norm to process.
|
||||||
|
:param phase: The phase this norm belongs to.
|
||||||
|
"""
|
||||||
rule: AstRule | None = None
|
rule: AstRule | None = None
|
||||||
|
|
||||||
match norm:
|
match norm:
|
||||||
@@ -295,6 +411,18 @@ class AgentSpeakGenerator:
|
|||||||
self._asp.rules.append(rule)
|
self._asp.rules.append(rule)
|
||||||
|
|
||||||
def _add_default_loop(self, phase: Phase) -> None:
|
def _add_default_loop(self, phase: Phase) -> None:
|
||||||
|
"""
|
||||||
|
Adds the default execution loop for a phase.
|
||||||
|
|
||||||
|
This method creates the main reactive loop that runs when the agent
|
||||||
|
receives user input during a phase. The loop:
|
||||||
|
1. Notifies the system about the user input
|
||||||
|
2. Resets the response tracking
|
||||||
|
3. Executes all phase goals
|
||||||
|
4. Attempts phase transition
|
||||||
|
|
||||||
|
:param phase: The phase to create the loop for.
|
||||||
|
"""
|
||||||
actions = []
|
actions = []
|
||||||
|
|
||||||
actions.append(
|
actions.append(
|
||||||
@@ -303,7 +431,6 @@ class AgentSpeakGenerator:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
actions.append(AstStatement(StatementType.REMOVE_BELIEF, AstLiteral("responded_this_turn")))
|
actions.append(AstStatement(StatementType.REMOVE_BELIEF, AstLiteral("responded_this_turn")))
|
||||||
actions.append(AstStatement(StatementType.ACHIEVE_GOAL, AstLiteral("check_triggers")))
|
|
||||||
|
|
||||||
for goal in phase.goals:
|
for goal in phase.goals:
|
||||||
actions.append(AstStatement(StatementType.ACHIEVE_GOAL, self._astify(goal)))
|
actions.append(AstStatement(StatementType.ACHIEVE_GOAL, self._astify(goal)))
|
||||||
@@ -327,6 +454,22 @@ class AgentSpeakGenerator:
|
|||||||
continues_response: bool = False,
|
continues_response: bool = False,
|
||||||
main_goal: bool = False,
|
main_goal: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Processes a goal and creates plans for achieving it.
|
||||||
|
|
||||||
|
This method creates two plans for each goal:
|
||||||
|
1. A main plan that executes the goal's steps when conditions are met
|
||||||
|
2. A fallback plan that provides a default empty implementation (prevents crashes)
|
||||||
|
|
||||||
|
The method also recursively processes any subgoals contained within
|
||||||
|
the goal's plan.
|
||||||
|
|
||||||
|
:param goal: The goal to process.
|
||||||
|
:param phase: The phase this goal belongs to.
|
||||||
|
:param previous_goal: The previous goal in sequence (for dependency tracking).
|
||||||
|
:param continues_response: Whether this goal continues an existing response.
|
||||||
|
:param main_goal: Whether this is a main goal (for UI notification purposes).
|
||||||
|
"""
|
||||||
context: list[AstExpression] = [self._astify(phase)]
|
context: list[AstExpression] = [self._astify(phase)]
|
||||||
context.append(~self._astify(goal, achieved=True))
|
context.append(~self._astify(goal, achieved=True))
|
||||||
if previous_goal and previous_goal.can_fail:
|
if previous_goal and previous_goal.can_fail:
|
||||||
@@ -369,14 +512,38 @@ class AgentSpeakGenerator:
|
|||||||
prev_goal = subgoal
|
prev_goal = subgoal
|
||||||
|
|
||||||
def _step_to_statement(self, step: PlanElement) -> AstStatement:
|
def _step_to_statement(self, step: PlanElement) -> AstStatement:
|
||||||
|
"""
|
||||||
|
Converts a plan step to an AgentSpeak statement.
|
||||||
|
|
||||||
|
This method transforms different types of plan elements into their
|
||||||
|
corresponding AgentSpeak statements. Goals and speech-related actions
|
||||||
|
become achieve-goal statements, while gesture actions become do-action
|
||||||
|
statements.
|
||||||
|
|
||||||
|
:param step: The plan element to convert.
|
||||||
|
:return: The corresponding AgentSpeak statement.
|
||||||
|
"""
|
||||||
match step:
|
match step:
|
||||||
case Goal() | SpeechAction() | LLMAction() as a:
|
case Goal() | SpeechAction() | LLMAction() as a:
|
||||||
return AstStatement(StatementType.ACHIEVE_GOAL, self._astify(a))
|
return AstStatement(StatementType.ACHIEVE_GOAL, self._astify(a))
|
||||||
case GestureAction() as a:
|
case GestureAction() as a:
|
||||||
return AstStatement(StatementType.DO_ACTION, self._astify(a))
|
return AstStatement(StatementType.DO_ACTION, self._astify(a))
|
||||||
|
|
||||||
# TODO: separate handling of keyword and others
|
|
||||||
def _process_trigger(self, trigger: Trigger, phase: Phase) -> None:
|
def _process_trigger(self, trigger: Trigger, phase: Phase) -> None:
|
||||||
|
"""
|
||||||
|
Processes a trigger and creates plans for its execution.
|
||||||
|
|
||||||
|
This method creates plans that execute when trigger conditions are met.
|
||||||
|
It handles both automatic triggering (when conditions are detected) and
|
||||||
|
manual forcing (from UI). The trigger execution includes:
|
||||||
|
1. Notifying the system about trigger start
|
||||||
|
2. Executing all trigger steps
|
||||||
|
3. Waiting briefly for UI display
|
||||||
|
4. Notifying the system about trigger end
|
||||||
|
|
||||||
|
:param trigger: The trigger to process.
|
||||||
|
:param phase: The phase this trigger belongs to.
|
||||||
|
"""
|
||||||
body = []
|
body = []
|
||||||
subgoals = []
|
subgoals = []
|
||||||
|
|
||||||
@@ -418,6 +585,18 @@ class AgentSpeakGenerator:
|
|||||||
self._process_goal(subgoal, phase, continues_response=True)
|
self._process_goal(subgoal, phase, continues_response=True)
|
||||||
|
|
||||||
def _add_fallbacks(self):
|
def _add_fallbacks(self):
|
||||||
|
"""
|
||||||
|
Adds fallback plans for robust execution, preventing crashes.
|
||||||
|
|
||||||
|
This method creates fallback plans that provide default empty implementations
|
||||||
|
for key goals. These fallbacks ensure that the system can continue execution
|
||||||
|
even when no specific plans are applicable, preventing crashes.
|
||||||
|
|
||||||
|
The fallbacks are created for:
|
||||||
|
- check_triggers: When no triggers are applicable
|
||||||
|
- transition_phase: When phase transition conditions aren't met
|
||||||
|
- force_transition_phase: When forced transitions aren't possible
|
||||||
|
"""
|
||||||
# Trigger fallback
|
# Trigger fallback
|
||||||
self._asp.plans.append(
|
self._asp.plans.append(
|
||||||
AstPlan(
|
AstPlan(
|
||||||
@@ -450,18 +629,57 @@ class AgentSpeakGenerator:
|
|||||||
|
|
||||||
@singledispatchmethod
|
@singledispatchmethod
|
||||||
def _astify(self, element: ProgramElement) -> AstExpression:
|
def _astify(self, element: ProgramElement) -> AstExpression:
|
||||||
|
"""
|
||||||
|
Converts program elements to AgentSpeak expressions (base method).
|
||||||
|
|
||||||
|
This is the base method for the singledispatch mechanism that handles
|
||||||
|
conversion of different program element types to their AgentSpeak
|
||||||
|
representations. Specific implementations are provided for each
|
||||||
|
element type through registered methods.
|
||||||
|
|
||||||
|
:param element: The program element to convert.
|
||||||
|
:return: The corresponding AgentSpeak expression.
|
||||||
|
:raises NotImplementedError: If no specific implementation exists for the element type.
|
||||||
|
"""
|
||||||
raise NotImplementedError(f"Cannot convert element {element} to an AgentSpeak expression.")
|
raise NotImplementedError(f"Cannot convert element {element} to an AgentSpeak expression.")
|
||||||
|
|
||||||
@_astify.register
|
@_astify.register
|
||||||
def _(self, kwb: KeywordBelief) -> AstExpression:
|
def _(self, kwb: KeywordBelief) -> AstExpression:
|
||||||
|
"""
|
||||||
|
Converts a KeywordBelief to an AgentSpeak expression.
|
||||||
|
|
||||||
|
Keyword beliefs are converted to keyword_said literals that check
|
||||||
|
if the keyword was mentioned in user input.
|
||||||
|
|
||||||
|
:param kwb: The KeywordBelief to convert.
|
||||||
|
:return: An AstLiteral representing the keyword detection.
|
||||||
|
"""
|
||||||
return AstLiteral("keyword_said", [AstString(kwb.keyword)])
|
return AstLiteral("keyword_said", [AstString(kwb.keyword)])
|
||||||
|
|
||||||
@_astify.register
|
@_astify.register
|
||||||
def _(self, sb: SemanticBelief) -> AstExpression:
|
def _(self, sb: SemanticBelief) -> AstExpression:
|
||||||
|
"""
|
||||||
|
Converts a SemanticBelief to an AgentSpeak expression.
|
||||||
|
|
||||||
|
Semantic beliefs are converted to literals using their slugified names,
|
||||||
|
which are used for LLM-based belief evaluation.
|
||||||
|
|
||||||
|
:param sb: The SemanticBelief to convert.
|
||||||
|
:return: An AstLiteral representing the semantic belief.
|
||||||
|
"""
|
||||||
return AstLiteral(self.slugify(sb))
|
return AstLiteral(self.slugify(sb))
|
||||||
|
|
||||||
@_astify.register
|
@_astify.register
|
||||||
def _(self, ib: InferredBelief) -> AstExpression:
|
def _(self, ib: InferredBelief) -> AstExpression:
|
||||||
|
"""
|
||||||
|
Converts an InferredBelief to an AgentSpeak expression.
|
||||||
|
|
||||||
|
Inferred beliefs are converted to binary operations that combine
|
||||||
|
their left and right operands using the appropriate logical operator.
|
||||||
|
|
||||||
|
:param ib: The InferredBelief to convert.
|
||||||
|
:return: An AstBinaryOp representing the logical combination.
|
||||||
|
"""
|
||||||
return AstBinaryOp(
|
return AstBinaryOp(
|
||||||
self._astify(ib.left),
|
self._astify(ib.left),
|
||||||
BinaryOperatorType.AND if ib.operator == LogicalOperator.AND else BinaryOperatorType.OR,
|
BinaryOperatorType.AND if ib.operator == LogicalOperator.AND else BinaryOperatorType.OR,
|
||||||
@@ -470,59 +688,187 @@ class AgentSpeakGenerator:
|
|||||||
|
|
||||||
@_astify.register
|
@_astify.register
|
||||||
def _(self, norm: Norm) -> AstExpression:
|
def _(self, norm: Norm) -> AstExpression:
|
||||||
|
"""
|
||||||
|
Converts a Norm to an AgentSpeak expression.
|
||||||
|
|
||||||
|
Norms are converted to literals with either 'norm' or 'critical_norm'
|
||||||
|
functors depending on their critical flag, with the norm text as an argument.
|
||||||
|
|
||||||
|
Note that currently, critical norms are not yet functionally supported. They are possible
|
||||||
|
to astify for future use.
|
||||||
|
|
||||||
|
:param norm: The Norm to convert.
|
||||||
|
:return: An AstLiteral representing the norm.
|
||||||
|
"""
|
||||||
functor = "critical_norm" if norm.critical else "norm"
|
functor = "critical_norm" if norm.critical else "norm"
|
||||||
return AstLiteral(functor, [AstString(norm.norm)])
|
return AstLiteral(functor, [AstString(norm.norm)])
|
||||||
|
|
||||||
@_astify.register
|
@_astify.register
|
||||||
def _(self, phase: Phase) -> AstExpression:
|
def _(self, phase: Phase) -> AstExpression:
|
||||||
|
"""
|
||||||
|
Converts a Phase to an AgentSpeak expression.
|
||||||
|
|
||||||
|
Phases are converted to phase literals with their unique identifier
|
||||||
|
as an argument, which is used for phase tracking and transitions.
|
||||||
|
|
||||||
|
:param phase: The Phase to convert.
|
||||||
|
:return: An AstLiteral representing the phase.
|
||||||
|
"""
|
||||||
return AstLiteral("phase", [AstString(str(phase.id))])
|
return AstLiteral("phase", [AstString(str(phase.id))])
|
||||||
|
|
||||||
@_astify.register
|
@_astify.register
|
||||||
def _(self, goal: Goal, achieved: bool = False) -> AstExpression:
|
def _(self, goal: Goal, achieved: bool = False) -> AstExpression:
|
||||||
|
"""
|
||||||
|
Converts a Goal to an AgentSpeak expression.
|
||||||
|
|
||||||
|
Goals are converted to literals using their slugified names. If the
|
||||||
|
achieved parameter is True, the literal is prefixed with 'achieved_'.
|
||||||
|
|
||||||
|
:param goal: The Goal to convert.
|
||||||
|
:param achieved: Whether to represent this as an achieved goal.
|
||||||
|
:return: An AstLiteral representing the goal.
|
||||||
|
"""
|
||||||
return AstLiteral(f"{'achieved_' if achieved else ''}{self._slugify_str(goal.name)}")
|
return AstLiteral(f"{'achieved_' if achieved else ''}{self._slugify_str(goal.name)}")
|
||||||
|
|
||||||
@_astify.register
|
@_astify.register
|
||||||
def _(self, trigger: Trigger) -> AstExpression:
|
def _(self, trigger: Trigger) -> AstExpression:
|
||||||
|
"""
|
||||||
|
Converts a Trigger to an AgentSpeak expression.
|
||||||
|
|
||||||
|
Triggers are converted to literals using their slugified names,
|
||||||
|
which are used to identify and execute trigger plans.
|
||||||
|
|
||||||
|
:param trigger: The Trigger to convert.
|
||||||
|
:return: An AstLiteral representing the trigger.
|
||||||
|
"""
|
||||||
return AstLiteral(self.slugify(trigger))
|
return AstLiteral(self.slugify(trigger))
|
||||||
|
|
||||||
@_astify.register
|
@_astify.register
|
||||||
def _(self, sa: SpeechAction) -> AstExpression:
|
def _(self, sa: SpeechAction) -> AstExpression:
|
||||||
|
"""
|
||||||
|
Converts a SpeechAction to an AgentSpeak expression.
|
||||||
|
|
||||||
|
Speech actions are converted to say literals with the text content
|
||||||
|
as an argument, which are used for direct speech output.
|
||||||
|
|
||||||
|
:param sa: The SpeechAction to convert.
|
||||||
|
:return: An AstLiteral representing the speech action.
|
||||||
|
"""
|
||||||
return AstLiteral("say", [AstString(sa.text)])
|
return AstLiteral("say", [AstString(sa.text)])
|
||||||
|
|
||||||
@_astify.register
|
@_astify.register
|
||||||
def _(self, ga: GestureAction) -> AstExpression:
|
def _(self, ga: GestureAction) -> AstExpression:
|
||||||
|
"""
|
||||||
|
Converts a GestureAction to an AgentSpeak expression.
|
||||||
|
|
||||||
|
Gesture actions are converted to gesture literals with the gesture
|
||||||
|
type and name as arguments, which are used for physical robot gestures.
|
||||||
|
|
||||||
|
:param ga: The GestureAction to convert.
|
||||||
|
:return: An AstLiteral representing the gesture action.
|
||||||
|
"""
|
||||||
gesture = ga.gesture
|
gesture = ga.gesture
|
||||||
return AstLiteral("gesture", [AstString(gesture.type), AstString(gesture.name)])
|
return AstLiteral("gesture", [AstString(gesture.type), AstString(gesture.name)])
|
||||||
|
|
||||||
@_astify.register
|
@_astify.register
|
||||||
def _(self, la: LLMAction) -> AstExpression:
|
def _(self, la: LLMAction) -> AstExpression:
|
||||||
|
"""
|
||||||
|
Converts an LLMAction to an AgentSpeak expression.
|
||||||
|
|
||||||
|
LLM actions are converted to reply_with_goal literals with the
|
||||||
|
conversational goal as an argument, which are used for LLM-generated
|
||||||
|
responses guided by specific goals.
|
||||||
|
|
||||||
|
:param la: The LLMAction to convert.
|
||||||
|
:return: An AstLiteral representing the LLM action.
|
||||||
|
"""
|
||||||
return AstLiteral("reply_with_goal", [AstString(la.goal)])
|
return AstLiteral("reply_with_goal", [AstString(la.goal)])
|
||||||
|
|
||||||
@singledispatchmethod
|
@singledispatchmethod
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def slugify(element: ProgramElement) -> str:
|
def slugify(element: ProgramElement) -> str:
|
||||||
|
"""
|
||||||
|
Converts program elements to slugs (base method).
|
||||||
|
|
||||||
|
This is the base method for the singledispatch mechanism that handles
|
||||||
|
conversion of different program element types to their slug representations.
|
||||||
|
Specific implementations are provided for each element type through
|
||||||
|
registered methods.
|
||||||
|
|
||||||
|
Slugs are used outside of AgentSpeak, mostly for identifying what to send to the AgentSpeak
|
||||||
|
program as beliefs.
|
||||||
|
|
||||||
|
:param element: The program element to convert to a slug.
|
||||||
|
:return: The slug string representation.
|
||||||
|
:raises NotImplementedError: If no specific implementation exists for the element type.
|
||||||
|
"""
|
||||||
raise NotImplementedError(f"Cannot convert element {element} to a slug.")
|
raise NotImplementedError(f"Cannot convert element {element} to a slug.")
|
||||||
|
|
||||||
@slugify.register
|
@slugify.register
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _(n: Norm) -> str:
|
def _(n: Norm) -> str:
|
||||||
|
"""
|
||||||
|
Converts a Norm to a slug.
|
||||||
|
|
||||||
|
Norms are converted to slugs with the 'norm_' prefix followed by
|
||||||
|
the slugified norm text.
|
||||||
|
|
||||||
|
:param n: The Norm to convert.
|
||||||
|
:return: The slug string representation.
|
||||||
|
"""
|
||||||
return f"norm_{AgentSpeakGenerator._slugify_str(n.norm)}"
|
return f"norm_{AgentSpeakGenerator._slugify_str(n.norm)}"
|
||||||
|
|
||||||
@slugify.register
|
@slugify.register
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _(sb: SemanticBelief) -> str:
|
def _(sb: SemanticBelief) -> str:
|
||||||
|
"""
|
||||||
|
Converts a SemanticBelief to a slug.
|
||||||
|
|
||||||
|
Semantic beliefs are converted to slugs with the 'semantic_' prefix
|
||||||
|
followed by the slugified belief name.
|
||||||
|
|
||||||
|
:param sb: The SemanticBelief to convert.
|
||||||
|
:return: The slug string representation.
|
||||||
|
"""
|
||||||
return f"semantic_{AgentSpeakGenerator._slugify_str(sb.name)}"
|
return f"semantic_{AgentSpeakGenerator._slugify_str(sb.name)}"
|
||||||
|
|
||||||
@slugify.register
|
@slugify.register
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _(g: BaseGoal) -> str:
|
def _(g: BaseGoal) -> str:
|
||||||
|
"""
|
||||||
|
Converts a BaseGoal to a slug.
|
||||||
|
|
||||||
|
Goals are converted to slugs using their slugified names directly.
|
||||||
|
|
||||||
|
:param g: The BaseGoal to convert.
|
||||||
|
:return: The slug string representation.
|
||||||
|
"""
|
||||||
return AgentSpeakGenerator._slugify_str(g.name)
|
return AgentSpeakGenerator._slugify_str(g.name)
|
||||||
|
|
||||||
@slugify.register
|
@slugify.register
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _(t: Trigger):
|
def _(t: Trigger) -> str:
|
||||||
|
"""
|
||||||
|
Converts a Trigger to a slug.
|
||||||
|
|
||||||
|
Triggers are converted to slugs with the 'trigger_' prefix followed by
|
||||||
|
the slugified trigger name.
|
||||||
|
|
||||||
|
:param t: The Trigger to convert.
|
||||||
|
:return: The slug string representation.
|
||||||
|
"""
|
||||||
return f"trigger_{AgentSpeakGenerator._slugify_str(t.name)}"
|
return f"trigger_{AgentSpeakGenerator._slugify_str(t.name)}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _slugify_str(text: str) -> str:
|
def _slugify_str(text: str) -> str:
|
||||||
|
"""
|
||||||
|
Converts a text string to a slug.
|
||||||
|
|
||||||
|
This helper method converts arbitrary text to a URL-friendly slug format
|
||||||
|
by converting to lowercase, removing special characters, and replacing
|
||||||
|
spaces with underscores. It also removes common stopwords.
|
||||||
|
|
||||||
|
:param text: The text string to convert.
|
||||||
|
:return: The slugified string.
|
||||||
|
"""
|
||||||
return slugify(text, separator="_", stopwords=["a", "an", "the", "we", "you", "I"])
|
return slugify(text, separator="_", stopwords=["a", "an", "the", "we", "you", "I"])
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ class ProgramElement(BaseModel):
|
|||||||
class LogicalOperator(Enum):
|
class LogicalOperator(Enum):
|
||||||
"""
|
"""
|
||||||
Logical operators for combining beliefs.
|
Logical operators for combining beliefs.
|
||||||
|
|
||||||
|
These operators define how beliefs can be combined to form more complex
|
||||||
|
logical conditions. They are used in inferred beliefs to create compound
|
||||||
|
beliefs from simpler ones.
|
||||||
|
|
||||||
|
AND: Both operands must be true for the result to be true.
|
||||||
|
OR: At least one operand must be true for the result to be true.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
AND = "AND"
|
AND = "AND"
|
||||||
@@ -36,7 +43,15 @@ class KeywordBelief(ProgramElement):
|
|||||||
"""
|
"""
|
||||||
Represents a belief that is activated when a specific keyword is detected in the user's speech.
|
Represents a belief that is activated when a specific keyword is detected in the user's speech.
|
||||||
|
|
||||||
|
Keyword beliefs provide a simple but effective way to detect specific topics
|
||||||
|
or intentions in user speech. They are triggered when the exact keyword
|
||||||
|
string appears in the transcribed user input.
|
||||||
|
|
||||||
:ivar keyword: The string to look for in the transcription.
|
:ivar keyword: The string to look for in the transcription.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
A keyword belief with keyword="robot" would be activated when the user
|
||||||
|
says "I like the robot" or "Tell me about robots".
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = ""
|
name: str = ""
|
||||||
@@ -48,8 +63,18 @@ class SemanticBelief(ProgramElement):
|
|||||||
Represents a belief whose truth value is determined by an LLM analyzing the conversation
|
Represents a belief whose truth value is determined by an LLM analyzing the conversation
|
||||||
context.
|
context.
|
||||||
|
|
||||||
|
Semantic beliefs provide more sophisticated belief detection by using
|
||||||
|
an LLM to analyze the conversation context and determine
|
||||||
|
if the belief should be considered true. This allows for more nuanced
|
||||||
|
and context-aware belief evaluation.
|
||||||
|
|
||||||
:ivar description: A natural language description of what this belief represents,
|
:ivar description: A natural language description of what this belief represents,
|
||||||
used as a prompt for the LLM.
|
used as a prompt for the LLM.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
A semantic belief with description="The user is expressing frustration"
|
||||||
|
would be activated when the LLM determines that the user's statements
|
||||||
|
indicate frustration, even if no specific keywords are used.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
description: str
|
description: str
|
||||||
@@ -59,6 +84,11 @@ class InferredBelief(ProgramElement):
|
|||||||
"""
|
"""
|
||||||
Represents a belief derived from other beliefs using logical operators.
|
Represents a belief derived from other beliefs using logical operators.
|
||||||
|
|
||||||
|
Inferred beliefs allow for the creation of complex belief structures by
|
||||||
|
combining simpler beliefs using logical operators. This enables the
|
||||||
|
representation of sophisticated conditions and relationships between
|
||||||
|
different aspects of the conversation or context.
|
||||||
|
|
||||||
:ivar operator: The :class:`LogicalOperator` (AND/OR) to apply.
|
:ivar operator: The :class:`LogicalOperator` (AND/OR) to apply.
|
||||||
:ivar left: The left operand (another belief).
|
:ivar left: The left operand (another belief).
|
||||||
:ivar right: The right operand (another belief).
|
:ivar right: The right operand (another belief).
|
||||||
@@ -74,8 +104,16 @@ class Norm(ProgramElement):
|
|||||||
"""
|
"""
|
||||||
Base class for behavioral norms that guide the robot's interactions.
|
Base class for behavioral norms that guide the robot's interactions.
|
||||||
|
|
||||||
|
Norms represent guidelines, principles, or rules that should govern the
|
||||||
|
robot's behavior during interactions. They can be either basic (always
|
||||||
|
active in their phase) or conditional (active only when specific beliefs
|
||||||
|
are true).
|
||||||
|
|
||||||
:ivar norm: The textual description of the norm.
|
:ivar norm: The textual description of the norm.
|
||||||
:ivar critical: Whether this norm is considered critical and should be strictly enforced.
|
:ivar critical: Whether this norm is considered critical and should be strictly enforced.
|
||||||
|
|
||||||
|
Critical norms are currently not supported yet, but are intended for norms that should
|
||||||
|
ABSOLUTELY NOT be violated, possible cheched by additional validator agents.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = ""
|
name: str = ""
|
||||||
@@ -86,6 +124,13 @@ class Norm(ProgramElement):
|
|||||||
class BasicNorm(Norm):
|
class BasicNorm(Norm):
|
||||||
"""
|
"""
|
||||||
A simple behavioral norm that is always considered for activation when its phase is active.
|
A simple behavioral norm that is always considered for activation when its phase is active.
|
||||||
|
|
||||||
|
Basic norms are the most straightforward type of norms. They are active
|
||||||
|
throughout their assigned phase and provide consistent behavioral guidance
|
||||||
|
without any additional conditions.
|
||||||
|
|
||||||
|
These norms are suitable for general principles that should always apply
|
||||||
|
during a particular interaction phase.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
@@ -95,7 +140,20 @@ class ConditionalNorm(Norm):
|
|||||||
"""
|
"""
|
||||||
A behavioral norm that is only active when a specific condition (belief) is met.
|
A behavioral norm that is only active when a specific condition (belief) is met.
|
||||||
|
|
||||||
|
Conditional norms provide context-sensitive behavioral guidance. They are
|
||||||
|
only active and considered for activation when their associated condition
|
||||||
|
(belief) is true. This allows for more nuanced and adaptive behavior that
|
||||||
|
responds to the specific context of the interaction.
|
||||||
|
|
||||||
|
An important note, is that the current implementation of these norms for keyword-based beliefs
|
||||||
|
is that they only hold for 1 turn, as keyword-based conditions often express temporary
|
||||||
|
conditions.
|
||||||
|
|
||||||
:ivar condition: The :class:`Belief` that must hold for this norm to be active.
|
:ivar condition: The :class:`Belief` that must hold for this norm to be active.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
A conditional norm with the condition "user is frustrated" might specify
|
||||||
|
that the robot should use more empathetic language and avoid complex topics.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
condition: Belief
|
condition: Belief
|
||||||
@@ -109,6 +167,11 @@ class Plan(ProgramElement):
|
|||||||
Represents a list of steps to execute. Each of these steps can be a goal (with its own plan)
|
Represents a list of steps to execute. Each of these steps can be a goal (with its own plan)
|
||||||
or a simple action.
|
or a simple action.
|
||||||
|
|
||||||
|
Plans define sequences of actions and subgoals that the robot should execute
|
||||||
|
to achieve a particular objective. They form the procedural knowledge of
|
||||||
|
the behavior program, specifying what the robot should do in different
|
||||||
|
situations.
|
||||||
|
|
||||||
:ivar steps: The actions or subgoals to execute, in order.
|
:ivar steps: The actions or subgoals to execute, in order.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -123,6 +186,10 @@ class BaseGoal(ProgramElement):
|
|||||||
|
|
||||||
:ivar description: A description of the goal, used to determine if it has been achieved.
|
:ivar description: A description of the goal, used to determine if it has been achieved.
|
||||||
:ivar can_fail: Whether we can fail to achieve the goal after executing the plan.
|
:ivar can_fail: Whether we can fail to achieve the goal after executing the plan.
|
||||||
|
|
||||||
|
The can_fail attribute determines whether goal achievement is binary
|
||||||
|
(success/failure) or whether it can be determined through conversation
|
||||||
|
analysis.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
description: str = ""
|
description: str = ""
|
||||||
@@ -132,9 +199,13 @@ class BaseGoal(ProgramElement):
|
|||||||
class Goal(BaseGoal):
|
class Goal(BaseGoal):
|
||||||
"""
|
"""
|
||||||
Represents an objective to be achieved. To reach the goal, we should execute the corresponding
|
Represents an objective to be achieved. To reach the goal, we should execute the corresponding
|
||||||
plan. It inherits from the BaseGoal a variable `can_fail`, which if true will cause the
|
plan. It inherits from the BaseGoal a variable `can_fail`, which, if true, will cause the
|
||||||
completion to be determined based on the conversation.
|
completion to be determined based on the conversation.
|
||||||
|
|
||||||
|
Goals extend base goals by including a specific plan to achieve the objective.
|
||||||
|
They form the core of the robot's proactive behavior, defining both what
|
||||||
|
should be accomplished and how to accomplish it.
|
||||||
|
|
||||||
Instances of this goal are not hashable because a plan is not hashable.
|
Instances of this goal are not hashable because a plan is not hashable.
|
||||||
|
|
||||||
:ivar plan: The plan to execute.
|
:ivar plan: The plan to execute.
|
||||||
@@ -163,6 +234,10 @@ class Gesture(BaseModel):
|
|||||||
|
|
||||||
:ivar type: Whether to use a specific "single" gesture or a random one from a "tag" category.
|
: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.
|
:ivar name: The identifier for the gesture or tag.
|
||||||
|
|
||||||
|
The type field determines how the gesture is selected:
|
||||||
|
- "single": Use the specific gesture identified by name
|
||||||
|
- "tag": Select a random gesture from the category identified by name
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: Literal["tag", "single"]
|
type: Literal["tag", "single"]
|
||||||
@@ -185,6 +260,10 @@ class LLMAction(ProgramElement):
|
|||||||
An action that triggers an LLM-generated conversational response.
|
An action that triggers an LLM-generated conversational response.
|
||||||
|
|
||||||
:ivar goal: A temporary conversational goal to guide the LLM's response generation.
|
:ivar goal: A temporary conversational goal to guide the LLM's response generation.
|
||||||
|
|
||||||
|
The goal parameter provides high-level guidance to the LLM about what
|
||||||
|
the response should aim to achieve, while allowing the LLM flexibility
|
||||||
|
in how to express it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = ""
|
name: str = ""
|
||||||
@@ -222,6 +301,10 @@ class Program(BaseModel):
|
|||||||
"""
|
"""
|
||||||
The top-level container for a complete robot behavior definition.
|
The top-level container for a complete robot behavior definition.
|
||||||
|
|
||||||
|
The Program class represents the complete specification of a robot's
|
||||||
|
behavioral logic. It contains all the phases, norms, goals, triggers,
|
||||||
|
and actions that define how the robot should behave during interactions.
|
||||||
|
|
||||||
:ivar phases: An ordered list of :class:`Phase` objects defining the interaction flow.
|
:ivar phases: An ordered list of :class:`Phase` objects defining the interaction flow.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user