feat: support force completed goals in semantic belief agent
ref: N25B-427
This commit is contained in:
@@ -18,6 +18,7 @@ from control_backend.agents.bdi.agentspeak_ast import (
|
||||
TriggerType,
|
||||
)
|
||||
from control_backend.schemas.program import (
|
||||
BaseGoal,
|
||||
BasicNorm,
|
||||
ConditionalNorm,
|
||||
GestureAction,
|
||||
@@ -436,7 +437,7 @@ class AgentSpeakGenerator:
|
||||
|
||||
@slugify.register
|
||||
@staticmethod
|
||||
def _(g: Goal) -> str:
|
||||
def _(g: BaseGoal) -> str:
|
||||
return AgentSpeakGenerator._slugify_str(g.name)
|
||||
|
||||
@slugify.register
|
||||
|
||||
@@ -12,7 +12,7 @@ from control_backend.schemas.belief_list import BeliefList, GoalList
|
||||
from control_backend.schemas.belief_message import Belief as InternalBelief
|
||||
from control_backend.schemas.belief_message import BeliefMessage
|
||||
from control_backend.schemas.chat_history import ChatHistory, ChatMessage
|
||||
from control_backend.schemas.program import Goal, SemanticBelief
|
||||
from control_backend.schemas.program import BaseGoal, SemanticBelief
|
||||
|
||||
type JSONLike = None | bool | int | float | str | list["JSONLike"] | dict[str, "JSONLike"]
|
||||
|
||||
@@ -62,6 +62,7 @@ class TextBeliefExtractorAgent(BaseAgent):
|
||||
self.goal_inferrer = GoalAchievementInferrer(self._llm)
|
||||
self._current_beliefs = BeliefState()
|
||||
self._current_goal_completions: dict[str, bool] = {}
|
||||
self._force_completed_goals: set[BaseGoal] = set()
|
||||
self.conversation = ChatHistory(messages=[])
|
||||
|
||||
async def setup(self):
|
||||
@@ -118,13 +119,15 @@ class TextBeliefExtractorAgent(BaseAgent):
|
||||
case "goals":
|
||||
self._handle_goals_message(msg)
|
||||
await self._infer_goal_completions()
|
||||
case "achieved_goals":
|
||||
self._handle_goal_achieved_message(msg)
|
||||
case "conversation_history":
|
||||
if msg.body == "reset":
|
||||
self._reset()
|
||||
self._reset_phase()
|
||||
case _:
|
||||
self.logger.warning("Received unexpected message from %s", msg.sender)
|
||||
|
||||
def _reset(self):
|
||||
def _reset_phase(self):
|
||||
self.conversation = ChatHistory(messages=[])
|
||||
self.belief_inferrer.available_beliefs.clear()
|
||||
self._current_beliefs = BeliefState()
|
||||
@@ -158,7 +161,8 @@ class TextBeliefExtractorAgent(BaseAgent):
|
||||
return
|
||||
|
||||
# Use only goals that can fail, as the others are always assumed to be completed
|
||||
available_goals = [g for g in goals_list.goals if g.can_fail]
|
||||
available_goals = {g for g in goals_list.goals if g.can_fail}
|
||||
available_goals -= self._force_completed_goals
|
||||
self.goal_inferrer.goals = available_goals
|
||||
self.logger.debug(
|
||||
"Received %d failable goals from the program manager: %s",
|
||||
@@ -166,6 +170,23 @@ class TextBeliefExtractorAgent(BaseAgent):
|
||||
", ".join(g.name for g in available_goals),
|
||||
)
|
||||
|
||||
def _handle_goal_achieved_message(self, msg: InternalMessage):
|
||||
# NOTE: When goals can be marked unachieved, remember to re-add them to the goal_inferrer
|
||||
try:
|
||||
goals_list = GoalList.model_validate_json(msg.body)
|
||||
except ValidationError:
|
||||
self.logger.warning(
|
||||
"Received goal achieved message from the program manager, "
|
||||
"but it is not a valid list of goals."
|
||||
)
|
||||
return
|
||||
|
||||
for goal in goals_list.goals:
|
||||
self._force_completed_goals.add(goal)
|
||||
self._current_goal_completions[f"achieved_{AgentSpeakGenerator.slugify(goal)}"] = True
|
||||
|
||||
self.goal_inferrer.goals -= self._force_completed_goals
|
||||
|
||||
async def _user_said(self, text: str):
|
||||
"""
|
||||
Create a belief for the user's full speech.
|
||||
@@ -445,7 +466,7 @@ Respond with a JSON similar to the following, but with the property names as giv
|
||||
class GoalAchievementInferrer(SemanticBeliefInferrer):
|
||||
def __init__(self, llm: TextBeliefExtractorAgent.LLM):
|
||||
super().__init__(llm)
|
||||
self.goals = []
|
||||
self.goals: set[BaseGoal] = set()
|
||||
|
||||
async def infer_from_conversation(self, conversation: ChatHistory) -> dict[str, bool]:
|
||||
"""
|
||||
@@ -465,7 +486,7 @@ class GoalAchievementInferrer(SemanticBeliefInferrer):
|
||||
for goal, achieved in zip(self.goals, goals_achieved, strict=True)
|
||||
}
|
||||
|
||||
async def _infer_goal(self, conversation: ChatHistory, goal: Goal) -> bool:
|
||||
async def _infer_goal(self, conversation: ChatHistory, goal: BaseGoal) -> bool:
|
||||
prompt = f"""{self._format_conversation(conversation)}
|
||||
|
||||
Given the above conversation, what has the following goal been achieved?
|
||||
|
||||
@@ -310,7 +310,7 @@ class UserInterruptAgent(BaseAgent):
|
||||
async def _send_to_bdi_belief(self, asl_goal: str):
|
||||
"""Send belief to BDI Core"""
|
||||
belief_name = f"achieved_{asl_goal}"
|
||||
belief = Belief(name=belief_name)
|
||||
belief = Belief(name=belief_name, arguments=None)
|
||||
self.logger.debug(f"Sending belief to BDI Core: {belief_name}")
|
||||
belief_message = BeliefMessage(create=[belief])
|
||||
msg = InternalMessage(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from control_backend.schemas.program import BaseGoal
|
||||
from control_backend.schemas.program import Belief as ProgramBelief
|
||||
from control_backend.schemas.program import Goal
|
||||
|
||||
|
||||
class BeliefList(BaseModel):
|
||||
@@ -16,4 +16,4 @@ class BeliefList(BaseModel):
|
||||
|
||||
|
||||
class GoalList(BaseModel):
|
||||
goals: list[Goal]
|
||||
goals: list[BaseGoal]
|
||||
|
||||
@@ -15,6 +15,9 @@ class ProgramElement(BaseModel):
|
||||
name: str
|
||||
id: UUID4
|
||||
|
||||
# To make program elements hashable
|
||||
model_config = {"frozen": True}
|
||||
|
||||
|
||||
class LogicalOperator(Enum):
|
||||
AND = "AND"
|
||||
@@ -105,23 +108,33 @@ class Plan(ProgramElement):
|
||||
steps: list[PlanElement]
|
||||
|
||||
|
||||
class Goal(ProgramElement):
|
||||
class BaseGoal(ProgramElement):
|
||||
"""
|
||||
Represents an objective to be achieved. To reach the goal, we should execute
|
||||
the corresponding plan. If we can fail to achieve a goal after executing the plan,
|
||||
for example when the achieving of the goal is dependent on the user's reply, this means
|
||||
that the achieved status will be set from somewhere else in the program.
|
||||
Represents an objective to be achieved. This base version does not include a plan to achieve
|
||||
this goal, and is used in semantic belief extraction.
|
||||
|
||||
:ivar description: A description of the goal, used to determine if it has been achieved.
|
||||
:ivar plan: The plan to execute.
|
||||
:ivar can_fail: Whether we can fail to achieve the goal after executing the plan.
|
||||
"""
|
||||
|
||||
description: str = ""
|
||||
plan: Plan
|
||||
can_fail: bool = True
|
||||
|
||||
|
||||
class Goal(BaseGoal):
|
||||
"""
|
||||
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
|
||||
completion to be determined based on the conversation.
|
||||
|
||||
Instances of this goal are not hashable because a plan is not hashable.
|
||||
|
||||
:ivar plan: The plan to execute.
|
||||
"""
|
||||
|
||||
plan: Plan
|
||||
|
||||
|
||||
type Action = SpeechAction | GestureAction | LLMAction
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user