Skip to main content

Week 10: LLM Cognitive Planning

This week focuses on integrating Large Language Models (LLMs) for cognitive planning in humanoid robots. You'll learn to implement LLM-based reasoning, create task decomposition systems, and develop cognitive architectures that enable robots to understand complex instructions and plan multi-step actions.

Learning Objectives

By the end of this week, you will be able to:

  • Integrate LLMs (like OpenAI GPT, Claude, or open-source alternatives) with robotic systems
  • Implement cognitive planning architectures for complex task execution
  • Create task decomposition and reasoning systems
  • Design natural language understanding for robot instructions
  • Develop memory and context management for long-term interactions

10.1 Introduction to LLM Integration in Robotics

Cognitive Planning for Humanoid Robots

Cognitive planning in robotics involves high-level reasoning about tasks, goals, and actions. LLMs provide powerful capabilities for:

  • Natural language understanding: Interpreting complex human instructions
  • Task decomposition: Breaking down complex tasks into executable steps
  • Context reasoning: Understanding the environment and situational context
  • Plan generation: Creating sequences of actions to achieve goals
  • Learning from interaction: Adapting behavior based on experience

LLM Integration Architecture

import rclpy
from rclpy.node import Node
from std_msgs.msg import String
from humanoid_msgs.msg import ParsedVoiceCommand, CognitivePlan, TaskStep
from geometry_msgs.msg import Pose
from action_msgs.msg import GoalStatus
import openai
import json
import asyncio
from typing import List, Dict, Any, Optional
import time

class LLMCognitivePlannerNode(Node):
def __init__(self):
super().__init__('llm_cognitive_planner')

# Initialize LLM client (using OpenAI as example)
# In practice, you might use local models like Llama, Mistral, etc.
openai.api_key = self.declare_parameter('openai_api_key', '').value
self.model_name = self.declare_parameter('llm_model', 'gpt-3.5-turbo').value

# Publishers and subscribers
self.plan_pub = self.create_publisher(CognitivePlan, '/cognitive_plan', 10)
self.status_pub = self.create_publisher(String, '/cognitive_status', 10)
self.voice_cmd_sub = self.create_subscription(
ParsedVoiceCommand, '/parsed_voice_command', self.voice_command_callback, 10
)
self.task_sub = self.create_subscription(
String, '/high_level_task', self.task_callback, 10
)

# Memory and context management
self.conversation_history = []
self.robot_capabilities = self.get_robot_capabilities()
self.environment_context = {}

# Planning state
self.current_plan = None
self.plan_execution_status = "idle"

self.get_logger().info('LLM Cognitive Planner initialized')

def get_robot_capabilities(self) -> Dict[str, Any]:
"""Define robot capabilities for LLM context"""
return {
"locomotion": {
"movement": ["forward", "backward", "left", "right", "turn"],
"speed_range": [0.1, 0.5],
"turn_speed": 0.5
},
"manipulation": {
"actions": ["pick", "place", "grasp", "release"],
"reachable_area": {"x": [-1.0, 1.0], "y": [-0.5, 0.5], "z": [0.2, 1.5]}
},
"navigation": {
"locations": ["kitchen", "living_room", "bedroom", "office", "hallway"],
"navigation_modes": ["move_to", "follow_path", "explore"]
},
"sensors": {
"types": ["camera", "lidar", "imu", "microphone"],
"capabilities": ["object_detection", "person_detection", "obstacle_detection"]
}
}

def voice_command_callback(self, msg: ParsedVoiceCommand):
"""Handle parsed voice commands for cognitive planning"""
self.get_logger().info(f'Processing voice command: {msg.raw_command}')

# Add to conversation history
self.conversation_history.append({
"role": "user",
"content": msg.raw_command,
"timestamp": time.time()
})

# Generate cognitive plan
plan = self.generate_cognitive_plan(msg.raw_command, msg.intent, msg.entities)

if plan:
self.publish_plan(plan)
self.execute_plan(plan)

def task_callback(self, msg: String):
"""Handle high-level task requests"""
self.get_logger().info(f'Processing high-level task: {msg.data}')

# Add to conversation history
self.conversation_history.append({
"role": "user",
"content": msg.data,
"timestamp": time.time()
})

# Generate cognitive plan for complex task
plan = self.generate_complex_plan(msg.data)

if plan:
self.publish_plan(plan)
self.execute_plan(plan)

async def query_llm(self, messages: List[Dict[str, str]], max_tokens: int = 500) -> Optional[str]:
"""Query LLM with conversation history and context"""
try:
response = await openai.ChatCompletion.acreate(
model=self.model_name,
messages=messages,
max_tokens=max_tokens,
temperature=0.3,
timeout=30
)
return response.choices[0].message.content.strip()
except Exception as e:
self.get_logger().error(f'LLM query failed: {e}')
return None

def generate_cognitive_plan(self, command: str, intent: str, entities: List[str]) -> Optional[CognitivePlan]:
"""Generate cognitive plan from voice command using LLM"""
# Prepare context for LLM
context_messages = [
{
"role": "system",
"content": f"""You are a cognitive planner for a humanoid robot. The robot has these capabilities:
{json.dumps(self.robot_capabilities, indent=2)}

Your task is to:
1. Understand the user command
2. Break it down into executable steps
3. Generate a plan in JSON format with steps, dependencies, and conditions

Return ONLY valid JSON with this structure:
{{
"task_description": "Brief description of the task",
"steps": [
{{
"id": "step_id",
"description": "What to do in this step",
"action": "robot_action_type",
"parameters": {{"param": "value"}},
"preconditions": ["condition1", "condition2"],
"postconditions": ["condition1", "condition2"],
"dependencies": ["step_id1", "step_id2"]
}}
],
"success_criteria": ["criteria1", "criteria2"]
}}"""
}
]

# Add conversation history
context_messages.extend(self.conversation_history[-5:]) # Last 5 interactions

# Add current command
user_message = f"Command: {command}\nIntent: {intent}\nEntities: {entities}\n\nGenerate a cognitive plan for this command."
context_messages.append({"role": "user", "content": user_message})

# Query LLM
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
response = loop.run_until_complete(self.query_llm(context_messages))
finally:
loop.close()

if response:
try:
# Parse LLM response
plan_data = json.loads(response)
return self.create_plan_message(plan_data)
except json.JSONDecodeError as e:
self.get_logger().error(f'Failed to parse LLM response: {e}')
self.get_logger().debug(f'LLM response: {response}')
return None
else:
return None

def generate_complex_plan(self, task: str) -> Optional[CognitivePlan]:
"""Generate complex plan for high-level tasks using LLM"""
context_messages = [
{
"role": "system",
"content": f"""You are a cognitive planner for a humanoid robot. The robot has these capabilities:
{json.dumps(self.robot_capabilities, indent=2)}

Your task is to create a detailed plan for complex multi-step tasks.
Consider:
- Task decomposition into subtasks
- Resource requirements
- Potential obstacles and fallback plans
- Success criteria for each step

Return ONLY valid JSON with this structure:
{{
"task_name": "Name of the overall task",
"task_description": "Detailed description",
"priority": "high/medium/low",
"estimated_duration": "Estimated time in seconds",
"steps": [
{{
"id": "step_id",
"name": "Step name",
"description": "What to do",
"action": "action_type",
"parameters": {{"param": "value"}},
"preconditions": ["condition1"],
"postconditions": ["condition1"],
"dependencies": ["step_id"],
"timeout": 30,
"fallback_action": "alternative_action"
}}
],
"success_criteria": ["criteria1"],
"failure_criteria": ["criteria1"],
"monitoring_points": ["step_id1", "step_id2"]
}}"""
},
{
"role": "user",
"content": f"Create a cognitive plan for: {task}"
}
]

# Query LLM
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
response = loop.run_until_complete(self.query_llm(context_messages))
finally:
loop.close()

if response:
try:
plan_data = json.loads(response)
return self.create_plan_message(plan_data)
except json.JSONDecodeError as e:
self.get_logger().error(f'Failed to parse complex plan: {e}')
return None
else:
return None

def create_plan_message(self, plan_data: Dict[str, Any]) -> CognitivePlan:
"""Convert plan data to ROS message"""
plan_msg = CognitivePlan()
plan_msg.task_name = plan_data.get('task_name', 'unnamed_task')
plan_msg.task_description = plan_data.get('task_description', '')
plan_msg.priority = plan_data.get('priority', 'medium')
plan_msg.estimated_duration = float(plan_data.get('estimated_duration', 0))

# Convert steps
for step_data in plan_data.get('steps', []):
step_msg = TaskStep()
step_msg.id = step_data.get('id', '')
step_msg.name = step_data.get('name', step_data.get('description', ''))
step_msg.description = step_data.get('description', '')
step_msg.action = step_data.get('action', '')
step_msg.parameters = json.dumps(step_data.get('parameters', {}))
step_msg.preconditions = step_data.get('preconditions', [])
step_msg.postconditions = step_data.get('postconditions', [])
step_msg.dependencies = step_data.get('dependencies', [])
step_msg.timeout = float(step_data.get('timeout', 30.0))
step_msg.fallback_action = step_data.get('fallback_action', '')

plan_msg.steps.append(step_msg)

plan_msg.success_criteria = plan_data.get('success_criteria', [])
plan_msg.failure_criteria = plan_data.get('failure_criteria', [])
plan_msg.monitoring_points = plan_data.get('monitoring_points', [])

return plan_msg

def publish_plan(self, plan: CognitivePlan):
"""Publish cognitive plan"""
self.plan_pub.publish(plan)
self.current_plan = plan
self.plan_execution_status = "published"

self.get_logger().info(f'Published cognitive plan: {plan.task_name}')

def execute_plan(self, plan: CognitivePlan):
"""Execute the cognitive plan"""
self.get_logger().info(f'Executing plan: {plan.task_name}')

# This would integrate with action execution system
# For now, just log the plan execution
for step in plan.steps:
self.get_logger().info(f' - {step.id}: {step.description}')

class LLMPlanningManager:
def __init__(self, node: LLMCognitivePlannerNode):
self.node = node
self.active_plans = {}
self.plan_execution_threads = {}

def start_plan_execution(self, plan: CognitivePlan):
"""Start executing a cognitive plan"""
plan_id = plan.task_name + f"_{time.time()}"
self.active_plans[plan_id] = plan

# Execute plan in separate thread
import threading
execution_thread = threading.Thread(
target=self.execute_plan_steps,
args=(plan_id, plan)
)
execution_thread.daemon = True
execution_thread.start()

self.plan_execution_threads[plan_id] = execution_thread

def execute_plan_steps(self, plan_id: str, plan: CognitivePlan):
"""Execute individual steps of a plan"""
for step in plan.steps:
self.node.get_logger().info(f'Executing step: {step.id}')

# Check preconditions
if not self.check_preconditions(step.preconditions):
self.node.get_logger().error(f'Preconditions not met for step {step.id}')
# Execute fallback if available
if step.fallback_action:
self.execute_fallback(step.fallback_action)
continue

# Execute the action
success = self.execute_action(step.action, json.loads(step.parameters))

if success:
# Check postconditions
if self.check_postconditions(step.postconditions):
self.node.get_logger().info(f'Step {step.id} completed successfully')
else:
self.node.get_logger().warn(f'Postconditions not met for step {step.id}')
else:
self.node.get_logger().error(f'Step {step.id} failed')
if step.fallback_action:
self.execute_fallback(step.fallback_action)

def check_preconditions(self, preconditions: List[str]) -> bool:
"""Check if preconditions are satisfied"""
# This would check robot state, environment, etc.
# For now, return True
return True

def check_postconditions(self, postconditions: List[str]) -> bool:
"""Check if postconditions are satisfied"""
# This would verify action results
# For now, return True
return True

def execute_action(self, action_type: str, parameters: Dict[str, Any]) -> bool:
"""Execute a specific robot action"""
# This would interface with robot action servers
# For now, simulate action execution
self.node.get_logger().info(f'Executing action: {action_type} with params: {parameters}')
time.sleep(1) # Simulate action time
return True

def execute_fallback(self, fallback_action: str):
"""Execute fallback action"""
self.node.get_logger().info(f'Executing fallback: {fallback_action}')

10.2 Task Decomposition and Reasoning Systems

Hierarchical Task Network (HTN) Planner

from dataclasses import dataclass, field
from typing import List, Dict, Any, Callable, Optional
import json

@dataclass
class Task:
"""Represents a task in the hierarchical task network"""
id: str
name: str
description: str
action: str
parameters: Dict[str, Any] = field(default_factory=dict)
subtasks: List['Task'] = field(default_factory=list)
preconditions: List[str] = field(default_factory=list)
postconditions: List[str] = field(default_factory=list)
dependencies: List[str] = field(default_factory=list)
priority: int = 0

class HTNPlanner:
"""Hierarchical Task Network planner using LLM reasoning"""

def __init__(self, llm_interface):
self.llm_interface = llm_interface
self.task_library = self.initialize_task_library()

def initialize_task_library(self) -> Dict[str, Task]:
"""Initialize library of primitive and compound tasks"""
return {
# Primitive tasks (direct robot actions)
"move_to": Task(
id="move_to",
name="Move To Location",
description="Move robot to specified location",
action="navigation",
parameters={"target_location": "", "speed": 0.2}
),
"pick_object": Task(
id="pick_object",
name="Pick Object",
description="Pick up an object",
action="manipulation",
parameters={"object_name": "", "position": {}}
),
"place_object": Task(
id="place_object",
name="Place Object",
description="Place object at location",
action="manipulation",
parameters={"target_location": ""}
),
"detect_object": Task(
id="detect_object",
name="Detect Object",
description="Detect specific object in environment",
action="perception",
parameters={"object_type": ""}
),

# Compound tasks (higher-level tasks decomposed into primitives)
"fetch_object": Task(
id="fetch_object",
name="Fetch Object",
description="Go to object, pick it, and bring it back",
action="compound",
subtasks=[
Task("detect_obj", "Detect Object", "", "detect_object"),
Task("move_to_obj", "Move to Object", "", "move_to"),
Task("pick_obj", "Pick Object", "", "pick_object"),
Task("return_with_obj", "Return with Object", "", "move_to")
]
),
"set_table": Task(
id="set_table",
name="Set Table",
description="Place multiple items on table",
action="compound",
subtasks=[
Task("get_plate", "Get Plate", "", "fetch_object"),
Task("place_plate", "Place Plate", "", "place_object"),
Task("get_cup", "Get Cup", "", "fetch_object"),
Task("place_cup", "Place Cup", "", "place_object")
]
)
}

def decompose_task(self, high_level_task: str) -> Optional[Task]:
"""Decompose high-level task using LLM reasoning"""
# Query LLM to decompose task
prompt = f"""
Decompose the following high-level task into a hierarchical structure of subtasks:
Task: {high_level_task}

Use the following task library:
{json.dumps([t.name for t in self.task_library.values()], indent=2)}

Return the decomposition as a JSON object with this structure:
{{
"id": "task_id",
"name": "Task Name",
"description": "Task description",
"action": "compound",
"subtasks": [
{{
"id": "subtask_id",
"name": "Subtask Name",
"description": "Subtask description",
"action": "primitive_or_compound",
"parameters": {{"param": "value"}},
"preconditions": ["condition1"],
"postconditions": ["condition1"]
}}
]
}}
"""

response = self.llm_interface.query_llm([{"role": "user", "content": prompt}])

if response:
try:
task_data = json.loads(response)
return self.create_task_from_data(task_data)
except json.JSONDecodeError:
return None

return None

def create_task_from_data(self, task_data: Dict[str, Any]) -> Task:
"""Create Task object from LLM response data"""
subtasks = []
for subtask_data in task_data.get('subtasks', []):
subtask = Task(
id=subtask_data['id'],
name=subtask_data['name'],
description=subtask_data['description'],
action=subtask_data['action'],
parameters=subtask_data.get('parameters', {}),
preconditions=subtask_data.get('preconditions', []),
postconditions=subtask_data.get('postconditions', [])
)
subtasks.append(subtask)

return Task(
id=task_data['id'],
name=task_data['name'],
description=task_data['description'],
action=task_data['action'],
subtasks=subtasks,
preconditions=task_data.get('preconditions', []),
postconditions=task_data.get('postconditions', [])
)

def generate_execution_plan(self, task: Task) -> List[Dict[str, Any]]:
"""Generate linear execution plan from hierarchical task"""
execution_plan = []

def traverse_task(t: Task, plan: List[Dict[str, Any]]):
if not t.subtasks: # Primitive task
plan.append({
'id': t.id,
'name': t.name,
'action': t.action,
'parameters': t.parameters,
'preconditions': t.preconditions,
'postconditions': t.postconditions
})
else: # Compound task
for subtask in t.subtasks:
traverse_task(subtask, plan)

traverse_task(task, execution_plan)
return execution_plan

class EnhancedLLMCognitivePlannerNode(LLMCognitivePlannerNode):
def __init__(self):
super().__init__()

# Initialize HTN planner
self.htn_planner = HTNPlanner(self)

# Add HTN-specific publishers
self.decomposition_pub = self.create_publisher(
String, '/task_decomposition', 10
)

def generate_complex_plan(self, task: str) -> Optional[CognitivePlan]:
"""Enhanced plan generation using HTN decomposition"""
# First, try to decompose task using HTN
decomposed_task = self.htn_planner.decompose_task(task)

if decomposed_task:
# Generate execution plan from decomposition
execution_plan = self.htn_planner.generate_execution_plan(decomposed_task)

# Create CognitivePlan from execution plan
plan_msg = CognitivePlan()
plan_msg.task_name = decomposed_task.name
plan_msg.task_description = decomposed_task.description
plan_msg.priority = "medium"

# Convert execution plan to TaskStep messages
for i, step_data in enumerate(execution_plan):
step_msg = TaskStep()
step_msg.id = f"step_{i}"
step_msg.name = step_data['name']
step_msg.description = step_data['name']
step_msg.action = step_data['action']
step_msg.parameters = json.dumps(step_data['parameters'])
step_msg.preconditions = step_data['preconditions']
step_msg.postconditions = step_data['postconditions']

plan_msg.steps.append(step_msg)

# Publish decomposition for debugging
decomposition_msg = String()
decomposition_msg.data = json.dumps({
'task': task,
'decomposition': [s['name'] for s in execution_plan]
}, indent=2)
self.decomposition_pub.publish(decomposition_msg)

return plan_msg

# Fallback to LLM-based planning
return super().generate_complex_plan(task)

10.3 Natural Language Understanding for Robot Instructions

Semantic Parser for Robot Instructions

import spacy
import re
from typing import Union, List, Dict, Any
from dataclasses import dataclass

@dataclass
class SemanticFrame:
"""Represents the semantic structure of an instruction"""
action: str
objects: List[Dict[str, str]]
locations: List[Dict[str, str]]
attributes: Dict[str, Any]
temporal: Dict[str, Any]
spatial: Dict[str, Any]

class SemanticParser:
def __init__(self):
# Load spaCy model
try:
self.nlp = spacy.load("en_core_web_sm")
except OSError:
self.nlp = None
print("spaCy model not found. Install with: python -m spacy download en_core_web_sm")

# Define action patterns
self.action_patterns = {
'navigation': [
r'go\s+to|move\s+to|navigate\s+to|walk\s+to|head\s+to|go\s+over\s+to',
r'bring\s+me|fetch|get|retrieve|carry',
r'follow|chase|pursue'
],
'manipulation': [
r'pick\s+up|grasp|grab|take|hold|lift',
r'put|place|set|drop|release|set\s+down',
r'give|hand|deliver'
],
'interaction': [
r'greet|say\s+hello|wave|introduce',
r'wait|stop|pause|hold|freeze',
r'help|assist|aid'
],
'perception': [
r'find|look\s+for|search|locate|spot|see',
r'identify|recognize|detect|notice',
r'show|display|present'
]
}

# Define location patterns
self.location_patterns = [
r'(kitchen|living\s+room|bedroom|office|bathroom|hallway|dining\s+room)',
r'(table|counter|shelf|cabinet|fridge|couch|chair)',
r'(here|there|nearby|in\s+front|behind|left|right)'
]

def parse_instruction(self, instruction: str) -> Optional[SemanticFrame]:
"""Parse natural language instruction into semantic frame"""
if not self.nlp:
return None

doc = self.nlp(instruction.lower())

# Extract action
action = self.extract_action(doc, instruction)

# Extract objects
objects = self.extract_objects(doc)

# Extract locations
locations = self.extract_locations(doc, instruction)

# Extract attributes and modifiers
attributes = self.extract_attributes(doc)

# Extract temporal information
temporal = self.extract_temporal(doc)

# Extract spatial relationships
spatial = self.extract_spatial(doc)

return SemanticFrame(
action=action,
objects=objects,
locations=locations,
attributes=attributes,
temporal=temporal,
spatial=spatial
)

def extract_action(self, doc, original_text: str) -> str:
"""Extract the main action from the instruction"""
# Check for action patterns
for action_type, patterns in self.action_patterns.items():
for pattern in patterns:
if re.search(pattern, original_text.lower()):
return action_type

# If no pattern matches, use NLP to identify main verb
for token in doc:
if token.pos_ == "VERB" and token.dep_ == "ROOT":
return token.lemma_

return "unknown"

def extract_objects(self, doc) -> List[Dict[str, str]]:
"""Extract objects mentioned in the instruction"""
objects = []

for token in doc:
if token.pos_ in ["NOUN", "PROPN"] and token.dep_ in ["dobj", "pobj", "attr"]:
# Get the full noun phrase
obj_phrase = self.get_noun_phrase(token)
objects.append({
"name": obj_phrase.text,
"type": "object",
"modifier": self.get_modifiers(token)
})

return objects

def extract_locations(self, doc, original_text: str) -> List[Dict[str, str]]:
"""Extract locations from the instruction"""
locations = []

# Check for location patterns
for pattern_list in self.location_patterns:
matches = re.findall(pattern_list, original_text.lower())
for match in matches:
locations.append({
"name": match,
"type": "location"
})

# Also extract from NLP
for ent in doc.ents:
if ent.label_ in ["GPE", "LOC", "FAC"]:
locations.append({
"name": ent.text,
"type": ent.label_.lower()
})

return locations

def extract_attributes(self, doc) -> Dict[str, Any]:
"""Extract attributes and modifiers"""
attributes = {}

for token in doc:
if token.pos_ == "ADJ":
attributes[token.lemma_] = True
elif token.pos_ == "ADV":
attributes[token.lemma_] = True
elif token.pos_ == "NUM":
attributes["quantity"] = token.text

return attributes

def extract_temporal(self, doc) -> Dict[str, Any]:
"""Extract temporal information"""
temporal = {}

for ent in doc.ents:
if ent.label_ == "TIME":
temporal["time"] = ent.text
elif ent.label_ == "DATE":
temporal["date"] = ent.text

return temporal

def extract_spatial(self, doc) -> Dict[str, Any]:
"""Extract spatial relationships"""
spatial = {}

for token in doc:
if token.pos_ == "ADP": # Preposition
spatial[token.text] = True
elif token.text in ["near", "far", "close", "next", "beside", "behind", "in", "on", "under", "over"]:
spatial[token.text] = True

return spatial

def get_noun_phrase(self, token) -> str:
"""Get the full noun phrase containing a token"""
span = token.subtree
return " ".join([t.text for t in span if t.pos_ != "DET"]) # Exclude determiners

def get_modifiers(self, token) -> List[str]:
"""Get modifiers for a noun"""
modifiers = []
for child in token.children:
if child.pos_ in ["ADJ", "DET", "NUM"]:
modifiers.append(child.text)
return modifiers

class NaturalLanguageInterface:
def __init__(self, semantic_parser: SemanticParser):
self.parser = semantic_parser

def interpret_command(self, command: str) -> Dict[str, Any]:
"""Interpret natural language command and convert to robot action"""
# Parse the command
frame = self.parser.parse_instruction(command)

if not frame:
return {"action": "unknown", "parameters": {}}

# Convert semantic frame to robot command
robot_command = self.convert_to_robot_command(frame)

return robot_command

def convert_to_robot_command(self, frame: SemanticFrame) -> Dict[str, Any]:
"""Convert semantic frame to robot command structure"""
command = {
"action": frame.action,
"parameters": {}
}

# Add object parameters
if frame.objects:
command["parameters"]["objects"] = [obj["name"] for obj in frame.objects]

# Add location parameters
if frame.locations:
command["parameters"]["locations"] = [loc["name"] for loc in frame.locations]

# Add attributes
if frame.attributes:
command["parameters"]["attributes"] = frame.attributes

# Add spatial relationships
if frame.spatial:
command["parameters"]["spatial"] = frame.spatial

return command

# Integration with the main node
class NLUCognitivePlannerNode(EnhancedLLMCognitivePlannerNode):
def __init__(self):
super().__init__()

# Initialize NLU components
self.semantic_parser = SemanticParser()
self.nlu_interface = NaturalLanguageInterface(self.semantic_parser)

# Add NLU publisher
self.nlu_pub = self.create_publisher(
String, '/nlu_interpretation', 10
)

def parse_voice_command(self, command):
"""Enhanced command parsing with NLU"""
# Use semantic parser
interpretation = self.nlu_interface.interpret_command(command)

# Publish interpretation for debugging
interpretation_msg = String()
interpretation_msg.data = json.dumps(interpretation, indent=2)
self.nlu_pub.publish(interpretation_msg)

# Generate plan based on interpretation
plan = self.generate_plan_from_interpretation(interpretation, command)

if plan:
self.publish_plan(plan)
self.execute_plan(plan)

def generate_plan_from_interpretation(self, interpretation: Dict[str, Any], original_command: str) -> Optional[CognitivePlan]:
"""Generate cognitive plan from NLU interpretation"""
# Create a simple plan based on interpretation
plan_msg = CognitivePlan()
plan_msg.task_name = f"interpreted_task_{time.time()}"
plan_msg.task_description = original_command
plan_msg.priority = "medium"

# Create steps based on interpretation
action = interpretation.get("action", "unknown")
params = interpretation.get("parameters", {})

step_msg = TaskStep()
step_msg.id = "main_step"
step_msg.name = f"Execute {action}"
step_msg.description = original_command
step_msg.action = action
step_msg.parameters = json.dumps(params)

plan_msg.steps.append(step_msg)

return plan_msg

10.4 Memory and Context Management

Context Manager for Long-term Interactions

import pickle
import os
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional

class InteractionContext:
"""Represents the context of an interaction session"""
def __init__(self):
self.session_id = None
self.start_time = datetime.now()
self.user_profile = {}
self.task_history = []
self.object_locations = {}
self.environment_state = {}
self.conversation_context = []
self.goals = []

class ContextManager:
def __init__(self, node):
self.node = node
self.current_context = InteractionContext()
self.context_history = []
self.max_contexts = 100 # Keep last 100 contexts
self.context_file = "/tmp/robot_context.pkl"

def update_context(self, **kwargs):
"""Update the current interaction context"""
for key, value in kwargs.items():
if hasattr(self.current_context, key):
setattr(self.current_context, key, value)
else:
self.node.get_logger().warn(f"Unknown context attribute: {key}")

def add_task_to_history(self, task_description: str, result: str):
"""Add a completed task to history"""
task_entry = {
"timestamp": datetime.now(),
"task": task_description,
"result": result
}
self.current_context.task_history.append(task_entry)

# Keep only recent history
if len(self.current_context.task_history) > 50:
self.current_context.task_history = self.current_context.task_history[-50:]

def remember_object_location(self, object_name: str, location: str):
"""Remember where an object was seen or placed"""
self.current_context.object_locations[object_name.lower()] = {
"location": location,
"timestamp": datetime.now(),
"confidence": 1.0
}

def find_object_location(self, object_name: str) -> Optional[str]:
"""Find remembered location of an object"""
obj_key = object_name.lower()
if obj_key in self.current_context.object_locations:
location_data = self.current_context.object_locations[obj_key]

# Check if location is still valid (less than 1 hour old)
age = datetime.now() - location_data["timestamp"]
if age < timedelta(hours=1):
return location_data["location"]
else:
# Remove expired location
del self.current_context.object_locations[obj_key]

return None

def add_conversation_turn(self, user_input: str, robot_response: str):
"""Add a turn to the conversation context"""
turn = {
"timestamp": datetime.now(),
"user": user_input,
"robot": robot_response
}
self.current_context.conversation_context.append(turn)

# Keep only recent conversation (last 10 turns)
if len(self.current_context.conversation_context) > 10:
self.current_context.conversation_context = self.current_context.conversation_context[-10:]

def get_relevant_context(self, current_request: str) -> str:
"""Get relevant context for current request"""
context_parts = []

# Add recent conversation context
if self.current_context.conversation_context:
recent_turns = self.current_context.conversation_context[-3:] # Last 3 turns
context = "Recent conversation:\n"
for turn in recent_turns:
context += f"User: {turn['user']}\n"
context += f"Robot: {turn['robot']}\n"
context_parts.append(context)

# Add object location context
if self.current_context.object_locations:
locations = []
for obj, data in self.current_context.object_locations.items():
age = datetime.now() - data["timestamp"]
if age < timedelta(minutes=30): # Less than 30 minutes old
locations.append(f"{obj} is at {data['location']}")

if locations:
context_parts.append("Known object locations:\n" + "\n".join(locations))

# Add recent task context
if self.current_context.task_history:
recent_tasks = self.current_context.task_history[-3:] # Last 3 tasks
tasks = []
for task in recent_tasks:
tasks.append(f"Task: {task['task']}, Result: {task['result']}")

if tasks:
context_parts.append("Recent tasks:\n" + "\n".join(tasks))

return "\n\n".join(context_parts)

def save_context(self):
"""Save current context to file"""
try:
with open(self.context_file, 'wb') as f:
pickle.dump({
'current_context': self.current_context,
'context_history': self.context_history[-self.max_contexts:]
}, f)
except Exception as e:
self.node.get_logger().error(f'Failed to save context: {e}')

def load_context(self):
"""Load context from file"""
try:
if os.path.exists(self.context_file):
with open(self.context_file, 'rb') as f:
data = pickle.load(f)
self.current_context = data.get('current_context', InteractionContext())
self.context_history = data.get('context_history', [])
except Exception as e:
self.node.get_logger().error(f'Failed to load context: {e}')

class ContextAwareCognitivePlannerNode(NLUCognitivePlannerNode):
def __init__(self):
super().__init__()

# Initialize context manager
self.context_manager = ContextManager(self)
self.context_manager.load_context()

# Add context publisher
self.context_pub = self.create_publisher(
String, '/interaction_context', 10
)

def voice_command_callback(self, msg: ParsedVoiceCommand):
"""Handle voice command with context awareness"""
self.get_logger().info(f'Processing voice command with context: {msg.raw_command}')

# Get relevant context for this request
relevant_context = self.context_manager.get_relevant_context(msg.raw_command)

# Add to conversation history
self.context_manager.add_conversation_turn(msg.raw_command, "Processing...")

# Generate cognitive plan with context
plan = self.generate_context_aware_plan(msg.raw_command, msg.intent, msg.entities, relevant_context)

if plan:
self.publish_plan(plan)
self.execute_plan_with_context(plan, msg.raw_command)

def generate_context_aware_plan(self, command: str, intent: str, entities: List[str], context: str) -> Optional[CognitivePlan]:
"""Generate plan considering current context"""
# Prepare context for LLM including interaction history
context_messages = [
{
"role": "system",
"content": f"""You are a cognitive planner for a humanoid robot. The robot has these capabilities:
{json.dumps(self.robot_capabilities, indent=2)}

Current context:
{context}

Your task is to generate a plan considering the current context and conversation history.
Be aware of:
- Previously mentioned objects and their locations
- Recent tasks and their outcomes
- Current conversation flow
- User preferences if known"""
},
{
"role": "user",
"content": f"Command: {command}\nIntent: {intent}\nEntities: {entities}\n\nGenerate a context-aware cognitive plan."
}
]

# Query LLM
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
response = loop.run_until_complete(self.query_llm(context_messages))
finally:
loop.close()

if response:
try:
plan_data = json.loads(response)
plan_msg = self.create_plan_message(plan_data)

# Update context with new plan
self.context_manager.update_context(current_plan=plan_msg.task_name)

return plan_msg
except json.JSONDecodeError as e:
self.get_logger().error(f'Failed to parse context-aware plan: {e}')
return None
else:
return None

def execute_plan_with_context(self, plan: CognitivePlan, original_command: str):
"""Execute plan while updating context"""
self.get_logger().info(f'Executing context-aware plan: {plan.task_name}')

# Execute the plan (simplified)
success = True # In real implementation, this would track actual execution

# Update context based on execution result
result = "success" if success else "failed"
self.context_manager.add_task_to_history(original_command, result)

# Publish updated context
context_msg = String()
context_msg.data = json.dumps({
"session_id": self.context_manager.current_context.session_id,
"active_plan": plan.task_name,
"object_locations": self.context_manager.current_context.object_locations,
"task_history_count": len(self.context_manager.current_context.task_history)
})
self.context_pub.publish(context_msg)

# Save context
self.context_manager.save_context()

def main(args=None):
rclpy.init(args=args)
node = ContextAwareCognitivePlannerNode()
rclpy.spin(node)
node.context_manager.save_context() # Save on shutdown
node.destroy_node()
rclpy.shutdown()

if __name__ == '__main__':
main()

10.5 Practical Exercise: Cognitive Planning System

Complete LLM Cognitive Planning System

Let's create a launch file for the cognitive planning system:

<!-- llm_cognitive_planning.launch.py -->
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from ament_index_python.packages import get_package_share_directory
import os

def generate_launch_description():
# Launch arguments
namespace = LaunchConfiguration('namespace')
use_sim_time = LaunchConfiguration('use_sim_time')
openai_api_key = LaunchConfiguration('openai_api_key')
llm_model = LaunchConfiguration('llm_model')

# Declare launch arguments
declare_namespace_cmd = DeclareLaunchArgument(
'namespace',
default_value='',
description='Top-level namespace'
)

declare_use_sim_time_cmd = DeclareLaunchArgument(
'use_sim_time',
default_value='false',
description='Use simulation (Gazebo) clock if true'
)

declare_openai_api_key_cmd = DeclareLaunchArgument(
'openai_api_key',
default_value='',
description='OpenAI API key for LLM access'
)

declare_llm_model_cmd = DeclareLaunchArgument(
'llm_model',
default_value='gpt-3.5-turbo',
description='LLM model to use for cognitive planning'
)

# LLM cognitive planner node
cognitive_planner_node = Node(
package='humanoid_cognitive_planning',
executable='llm_cognitive_planner',
name='llm_cognitive_planner',
parameters=[{
'use_sim_time': use_sim_time,
'openai_api_key': openai_api_key,
'llm_model': llm_model,
}],
remappings=[
('/parsed_voice_command', '/voice_commands/parsed'),
('/cognitive_plan', '/planning/cognitive_plan'),
],
output='screen'
)

# Task executor node (to execute the plans)
task_executor_node = Node(
package='humanoid_task_execution',
executable='task_executor',
name='task_executor',
parameters=[{
'use_sim_time': use_sim_time,
}],
remappings=[
('/cognitive_plan', '/planning/cognitive_plan'),
],
output='screen'
)

# Context manager node (optional, for advanced context management)
context_manager_node = Node(
package='humanoid_context',
executable='context_manager',
name='context_manager',
parameters=[{
'use_sim_time': use_sim_time,
}],
output='screen'
)

# Create the launch description
ld = LaunchDescription()

# Declare launch options
ld.add_action(declare_namespace_cmd)
ld.add_action(declare_use_sim_time_cmd)
ld.add_action(declare_openai_api_key_cmd)
ld.add_action(declare_llm_model_cmd)

# Add nodes
ld.add_action(cognitive_planner_node)
ld.add_action(task_executor_node)
ld.add_action(context_manager_node)

return ld

10.6 Mind Map: Showing VLA System Components and Interactions

mindmap
root((LLM Cognitive Planning System))
Natural Language Understanding
Semantic Parsing
Action Recognition
Object Detection
Location Extraction
Attribute Identification
Context Integration
Conversation History
Object Locations
Task Memory
User Preferences
Intent Classification
Navigation Tasks
Manipulation Tasks
Interaction Tasks
Perception Tasks
LLM Integration
Model Selection
GPT Models
Claude Models
Open Source Models
Local Deployment
Prompt Engineering
System Context
Capability Definitions
Safety Constraints
Response Formatting
Query Processing
Context Injection
Multi-turn Handling
Error Recovery
Rate Limiting
Cognitive Architecture
Task Decomposition
HTN Planning
Subtask Generation
Dependency Analysis
Resource Allocation
Plan Generation
Step Sequencing
Condition Checking
Fallback Planning
Success Criteria
Memory Systems
Short-term Context
Long-term Memory
Episodic Memory
Semantic Memory
Execution Interface
Action Mapping
Primitive Actions
Compound Tasks
Parameter Binding
Precondition Checking
Monitoring
Execution Tracking
Failure Detection
Plan Adaptation
Human Feedback
Safety Systems
Constraint Checking
Emergency Stops
Human Override
Validation Layers

Summary

This week covered LLM cognitive planning for humanoid robots, including LLM integration, task decomposition systems, natural language understanding, and memory/context management. The combination of large language models with robotic planning enables humanoid robots to understand complex instructions, decompose tasks hierarchically, and maintain context for long-term interactions.

Next Week Preview

Next week, we'll explore vision integration for the Vision-Language-Action system, learning how to combine computer vision with cognitive planning for enhanced robot capabilities.