feat: basic flow and phase transitions
ref: N25B-376
This commit is contained in:
@@ -173,12 +173,20 @@ class AgentSpeakGenerator:
|
|||||||
Converts Pydantic representation of behavior programs into AgentSpeak(L) code string.
|
Converts Pydantic representation of behavior programs into AgentSpeak(L) code string.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
arrow_prefix = f"{' ' * 2}<-{' ' * 2}"
|
||||||
|
colon_prefix = f"{' ' * 2}:{' ' * 3}"
|
||||||
|
indent_prefix = " " * 6
|
||||||
|
|
||||||
def generate(self, program: Program) -> str:
|
def generate(self, program: Program) -> str:
|
||||||
lines = []
|
lines = []
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
lines += self._generate_initial_beliefs(program)
|
lines += self._generate_initial_beliefs(program)
|
||||||
|
|
||||||
|
lines += self._generate_basic_flow(program)
|
||||||
|
|
||||||
|
lines += self._generate_phase_transitions(program)
|
||||||
|
|
||||||
lines += self._generate_norms(program)
|
lines += self._generate_norms(program)
|
||||||
|
|
||||||
lines += self._generate_belief_inference(program)
|
lines += self._generate_belief_inference(program)
|
||||||
@@ -192,10 +200,60 @@ class AgentSpeakGenerator:
|
|||||||
def _generate_initial_beliefs(self, program: Program) -> Iterable[str]:
|
def _generate_initial_beliefs(self, program: Program) -> Iterable[str]:
|
||||||
yield "// --- Initial beliefs ---"
|
yield "// --- Initial beliefs ---"
|
||||||
|
|
||||||
yield f"phase({program.phases[0].id})."
|
yield "phase(start)."
|
||||||
|
|
||||||
yield from ["", ""]
|
yield from ["", ""]
|
||||||
|
|
||||||
|
def _generate_basic_flow(self, program: Program) -> Iterable[str]:
|
||||||
|
yield "// --- Basic flow ---"
|
||||||
|
|
||||||
|
for phase in program.phases:
|
||||||
|
yield from self._generate_basic_flow_per_phase(phase)
|
||||||
|
|
||||||
|
yield from ["", ""]
|
||||||
|
|
||||||
|
def _generate_basic_flow_per_phase(self, phase: Phase) -> Iterable[str]:
|
||||||
|
yield "+user_said(Message)"
|
||||||
|
yield f"{self.colon_prefix}phase({phase.id})"
|
||||||
|
|
||||||
|
goals = phase.goals
|
||||||
|
if goals:
|
||||||
|
yield f"{self.arrow_prefix}{self._slugify(goals[0], include_prefix=True)}"
|
||||||
|
for goal in goals[1:]:
|
||||||
|
yield f"{self.indent_prefix}{self._slugify(goal, include_prefix=True)}"
|
||||||
|
|
||||||
|
yield f"{self.indent_prefix if goals else self.arrow_prefix}!transition_phase."
|
||||||
|
|
||||||
|
def _generate_phase_transitions(self, program: Program) -> Iterable[str]:
|
||||||
|
yield "// --- Phase transitions ---"
|
||||||
|
|
||||||
|
if len(program.phases) == 0:
|
||||||
|
yield from ["", ""]
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: remove outdated things
|
||||||
|
|
||||||
|
for i in range(-1, len(program.phases)):
|
||||||
|
predecessor = program.phases[i] if i >= 0 else None
|
||||||
|
successor = program.phases[i + 1] if i < len(program.phases) - 1 else None
|
||||||
|
yield from self._generate_phase_transition(predecessor, successor)
|
||||||
|
|
||||||
|
yield from self._generate_phase_transition(None, None) # to avoid failing plan
|
||||||
|
|
||||||
|
yield from ["", ""]
|
||||||
|
|
||||||
|
def _generate_phase_transition(
|
||||||
|
self, phase: Phase | None = None, next_phase: Phase | None = None
|
||||||
|
) -> Iterable[str]:
|
||||||
|
yield "+!transition_phase"
|
||||||
|
|
||||||
|
if phase is None and next_phase is None: # base case true to avoid failing plan
|
||||||
|
yield f"{self.arrow_prefix}true."
|
||||||
|
return
|
||||||
|
|
||||||
|
yield f"{self.colon_prefix}phase({phase.id if phase else 'start'})"
|
||||||
|
yield f"{self.arrow_prefix}-+phase({next_phase.id if next_phase else 'end'})."
|
||||||
|
|
||||||
def _generate_norms(self, program: Program) -> Iterable[str]:
|
def _generate_norms(self, program: Program) -> Iterable[str]:
|
||||||
yield "// --- Norms ---"
|
yield "// --- Norms ---"
|
||||||
|
|
||||||
@@ -259,46 +317,49 @@ class AgentSpeakGenerator:
|
|||||||
yield f"+{self._slugify(goal, include_prefix=True)}"
|
yield f"+{self._slugify(goal, include_prefix=True)}"
|
||||||
|
|
||||||
# Context
|
# Context
|
||||||
yield f"{' ' * 2}:{' ' * 3}phase({phase.id}) &"
|
yield f"{self.colon_prefix}phase({phase.id}) &"
|
||||||
yield f"{' ' * 6}not responded_this_turn &"
|
yield f"{self.indent_prefix}not responded_this_turn &"
|
||||||
yield f"{' ' * 6}not achieved_{self._slugify(goal)} &"
|
yield f"{self.indent_prefix}not achieved_{self._slugify(goal)} &"
|
||||||
if previous_goal:
|
if previous_goal:
|
||||||
yield f"{' ' * 6}achieved_{self._slugify(previous_goal)}"
|
yield f"{self.indent_prefix}achieved_{self._slugify(previous_goal)}"
|
||||||
else:
|
else:
|
||||||
yield f"{' ' * 6}true"
|
yield f"{self.indent_prefix}true"
|
||||||
|
|
||||||
extra_goals_to_generate = []
|
extra_goals_to_generate = []
|
||||||
|
|
||||||
steps = goal.plan.steps
|
steps = goal.plan.steps
|
||||||
|
|
||||||
if len(steps) == 0:
|
if len(steps) == 0:
|
||||||
yield f"{' ' * 2}<-{' ' * 2}true."
|
yield f"{self.arrow_prefix}true."
|
||||||
return
|
return
|
||||||
|
|
||||||
first_step = steps[0]
|
first_step = steps[0]
|
||||||
yield (
|
yield (
|
||||||
f"{' ' * 2}<-{' ' * 2}{self._slugify(first_step, include_prefix=True)}"
|
f"{self.arrow_prefix}{self._slugify(first_step, include_prefix=True)}"
|
||||||
f"{'.' if len(steps) == 1 and goal.can_fail else ';'}"
|
f"{'.' if len(steps) == 1 and goal.can_fail else ';'}"
|
||||||
)
|
)
|
||||||
if isinstance(first_step, Goal):
|
if isinstance(first_step, Goal):
|
||||||
extra_goals_to_generate.append(first_step)
|
extra_goals_to_generate.append(first_step)
|
||||||
|
|
||||||
for step in steps[1:-1]:
|
for step in steps[1:-1]:
|
||||||
yield f"{' ' * 6}{self._slugify(step, include_prefix=True)};"
|
yield f"{self.indent_prefix}{self._slugify(step, include_prefix=True)};"
|
||||||
if isinstance(step, Goal):
|
if isinstance(step, Goal):
|
||||||
extra_goals_to_generate.append(step)
|
extra_goals_to_generate.append(step)
|
||||||
|
|
||||||
if len(steps) > 1:
|
if len(steps) > 1:
|
||||||
last_step = steps[-1]
|
last_step = steps[-1]
|
||||||
yield (
|
yield (
|
||||||
f"{' ' * 6}{self._slugify(last_step, include_prefix=True)}"
|
f"{self.indent_prefix}{self._slugify(last_step, include_prefix=True)}"
|
||||||
f"{'.' if goal.can_fail else ';'}"
|
f"{'.' if goal.can_fail else ';'}"
|
||||||
)
|
)
|
||||||
if isinstance(last_step, Goal):
|
if isinstance(last_step, Goal):
|
||||||
extra_goals_to_generate.append(last_step)
|
extra_goals_to_generate.append(last_step)
|
||||||
|
|
||||||
if not goal.can_fail:
|
if not goal.can_fail:
|
||||||
yield f"{' ' * 6}+achieved_{self._slugify(goal)}."
|
yield f"{self.indent_prefix}+achieved_{self._slugify(goal)}."
|
||||||
|
|
||||||
|
yield f"+{self._slugify(goal, include_prefix=True)}"
|
||||||
|
yield f"{self.arrow_prefix}true."
|
||||||
|
|
||||||
yield ""
|
yield ""
|
||||||
|
|
||||||
@@ -320,32 +381,32 @@ class AgentSpeakGenerator:
|
|||||||
belief_name = self._slugify(trigger.condition)
|
belief_name = self._slugify(trigger.condition)
|
||||||
|
|
||||||
yield f"+{belief_name}"
|
yield f"+{belief_name}"
|
||||||
yield f"{' ' * 2}:{' ' * 3}phase({phase.id})"
|
yield f"{self.colon_prefix}phase({phase.id})"
|
||||||
|
|
||||||
extra_goals_to_generate = []
|
extra_goals_to_generate = []
|
||||||
|
|
||||||
steps = trigger.plan.steps
|
steps = trigger.plan.steps
|
||||||
|
|
||||||
if len(steps) == 0:
|
if len(steps) == 0:
|
||||||
yield f"{' ' * 2}<-{' ' * 2}true."
|
yield f"{self.arrow_prefix}true."
|
||||||
return
|
return
|
||||||
|
|
||||||
first_step = steps[0]
|
first_step = steps[0]
|
||||||
yield (
|
yield (
|
||||||
f"{' ' * 2}<-{' ' * 2}{self._slugify(first_step, include_prefix=True)}"
|
f"{self.arrow_prefix}{self._slugify(first_step, include_prefix=True)}"
|
||||||
f"{'.' if len(steps) == 1 else ';'}"
|
f"{'.' if len(steps) == 1 else ';'}"
|
||||||
)
|
)
|
||||||
if isinstance(first_step, Goal):
|
if isinstance(first_step, Goal):
|
||||||
extra_goals_to_generate.append(first_step)
|
extra_goals_to_generate.append(first_step)
|
||||||
|
|
||||||
for step in steps[1:-1]:
|
for step in steps[1:-1]:
|
||||||
yield f"{' ' * 6}{self._slugify(step, include_prefix=True)};"
|
yield f"{self.indent_prefix}{self._slugify(step, include_prefix=True)};"
|
||||||
if isinstance(step, Goal):
|
if isinstance(step, Goal):
|
||||||
extra_goals_to_generate.append(step)
|
extra_goals_to_generate.append(step)
|
||||||
|
|
||||||
if len(steps) > 1:
|
if len(steps) > 1:
|
||||||
last_step = steps[-1]
|
last_step = steps[-1]
|
||||||
yield f"{' ' * 6}{self._slugify(last_step, include_prefix=True)}."
|
yield f"{self.indent_prefix}{self._slugify(last_step, include_prefix=True)}."
|
||||||
if isinstance(last_step, Goal):
|
if isinstance(last_step, Goal):
|
||||||
extra_goals_to_generate.append(last_step)
|
extra_goals_to_generate.append(last_step)
|
||||||
|
|
||||||
@@ -366,33 +427,36 @@ class AgentSpeakGenerator:
|
|||||||
steps = goal.plan.steps
|
steps = goal.plan.steps
|
||||||
|
|
||||||
if len(steps) == 0:
|
if len(steps) == 0:
|
||||||
yield f"{' ' * 2}<-{' ' * 2}true."
|
yield f"{self.arrow_prefix}true."
|
||||||
return
|
return
|
||||||
|
|
||||||
first_step = steps[0]
|
first_step = steps[0]
|
||||||
yield (
|
yield (
|
||||||
f"{' ' * 2}<-{' ' * 2}{self._slugify(first_step, include_prefix=True)}"
|
f"{self.arrow_prefix}{self._slugify(first_step, include_prefix=True)}"
|
||||||
f"{'.' if len(steps) == 1 and goal.can_fail else ';'}"
|
f"{'.' if len(steps) == 1 and goal.can_fail else ';'}"
|
||||||
)
|
)
|
||||||
if isinstance(first_step, Goal):
|
if isinstance(first_step, Goal):
|
||||||
extra_goals_to_generate.append(first_step)
|
extra_goals_to_generate.append(first_step)
|
||||||
|
|
||||||
for step in steps[1:-1]:
|
for step in steps[1:-1]:
|
||||||
yield f"{' ' * 6}{self._slugify(step, include_prefix=True)};"
|
yield f"{self.indent_prefix}{self._slugify(step, include_prefix=True)};"
|
||||||
if isinstance(step, Goal):
|
if isinstance(step, Goal):
|
||||||
extra_goals_to_generate.append(step)
|
extra_goals_to_generate.append(step)
|
||||||
|
|
||||||
if len(steps) > 1:
|
if len(steps) > 1:
|
||||||
last_step = steps[-1]
|
last_step = steps[-1]
|
||||||
yield (
|
yield (
|
||||||
f"{' ' * 6}{self._slugify(last_step, include_prefix=True)}"
|
f"{self.indent_prefix}{self._slugify(last_step, include_prefix=True)}"
|
||||||
f"{'.' if goal.can_fail else ';'}"
|
f"{'.' if goal.can_fail else ';'}"
|
||||||
)
|
)
|
||||||
if isinstance(last_step, Goal):
|
if isinstance(last_step, Goal):
|
||||||
extra_goals_to_generate.append(last_step)
|
extra_goals_to_generate.append(last_step)
|
||||||
|
|
||||||
if not goal.can_fail:
|
if not goal.can_fail:
|
||||||
yield f"{' ' * 6}+achieved_{self._slugify(goal)}."
|
yield f"{self.indent_prefix}+achieved_{self._slugify(goal)}."
|
||||||
|
|
||||||
|
yield f"+{self._slugify(goal, include_prefix=True)}"
|
||||||
|
yield f"{self.arrow_prefix}true."
|
||||||
|
|
||||||
yield ""
|
yield ""
|
||||||
|
|
||||||
|
|||||||
@@ -90,8 +90,6 @@ class ConditionalNorm(BasicNorm):
|
|||||||
:ivar condition: When to activate this norm.
|
:ivar condition: When to activate this norm.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = ""
|
|
||||||
id: int = -1
|
|
||||||
condition: Belief
|
condition: Belief
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user