Merge branch 'fix/trigger-subgoal-error' into 'main'

Allow subgoals in triggers and empty plan

See merge request ics/sp/2025/n25b/pepperplus-cb!50
This commit was merged in pull request #50.
This commit is contained in:
2026-01-30 19:53:51 +00:00
2 changed files with 23 additions and 7 deletions

View File

@@ -4,6 +4,7 @@ University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences)
"""
import logging
from functools import singledispatchmethod
from slugify import slugify
@@ -66,6 +67,7 @@ class AgentSpeakGenerator:
"""
_asp: AstProgram
logger = logging.getLogger(__name__)
def generate(self, program: Program) -> str:
"""
@@ -479,7 +481,8 @@ class AgentSpeakGenerator:
:param main_goal: Whether this is a main goal (for UI notification purposes).
"""
context: list[AstExpression] = [self._astify(phase)]
context.append(~self._astify(goal, achieved=True))
if goal.can_fail:
context.append(~self._astify(goal, achieved=True))
if previous_goal and previous_goal.can_fail:
context.append(self._astify(previous_goal, achieved=True))
if not continues_response:
@@ -503,6 +506,10 @@ class AgentSpeakGenerator:
if not goal.can_fail and not continues_response:
body.append(AstStatement(StatementType.ADD_BELIEF, self._astify(goal, achieved=True)))
if len(body) == 0:
self.logger.warning("Goal with no plan detected: %s", goal.name)
body.append(AstStatement(StatementType.EMPTY, AstLiteral("true")))
self._asp.plans.append(AstPlan(TriggerType.ADDED_GOAL, self._astify(goal), context, body))
self._asp.plans.append(
@@ -563,10 +570,10 @@ class AgentSpeakGenerator:
)
)
for step in trigger.plan.steps:
body.append(self._step_to_statement(step))
if isinstance(step, Goal):
step.can_fail = False # triggers are continuous sequence
subgoals.append(step)
new_step = step.model_copy(update={"can_fail": False}) # triggers are sequence
subgoals.append(new_step)
body.append(self._step_to_statement(step))
# Arbitrary wait for UI to display nicely
body.append(
@@ -682,7 +689,7 @@ class AgentSpeakGenerator:
:return: An AstLiteral representing the semantic belief.
"""
return AstLiteral(self.slugify(sb))
@_astify.register
def _(self, eb: EmotionBelief) -> AstExpression:
return AstLiteral("emotion_detected", [AstAtom(eb.emotion)])

View File

@@ -7,7 +7,7 @@ University within the Software Project course.
from enum import Enum
from typing import Literal
from pydantic import UUID4, BaseModel
from pydantic import UUID4, BaseModel, field_validator
class ProgramElement(BaseModel):
@@ -24,6 +24,13 @@ class ProgramElement(BaseModel):
# To make program elements hashable
model_config = {"frozen": True}
@field_validator("name")
@classmethod
def name_must_not_start_with_number(cls, v: str) -> str:
if v and v[0].isdigit():
raise ValueError('Field "name" must not start with a number.')
return v
class LogicalOperator(Enum):
"""
@@ -105,6 +112,7 @@ class InferredBelief(ProgramElement):
left: Belief
right: Belief
class EmotionBelief(ProgramElement):
"""
Represents a belief that is set when a certain emotion is detected.
@@ -115,6 +123,7 @@ class EmotionBelief(ProgramElement):
name: str = ""
emotion: str
class Norm(ProgramElement):
"""
Base class for behavioral norms that guide the robot's interactions.
@@ -329,4 +338,4 @@ class Program(BaseModel):
if __name__ == "__main__":
input = input("Enter program JSON: ")
program = Program.model_validate_json(input)
print(program)
print(program)