Fix SPEC.yaml validation and refactor guarded_ai.py for v2.0 consistency
SPEC.yaml fixes:
- Comment out orphaned example code blocks that broke YAML parsing
- Convert progressive hints and dynamic question examples to comments
- Add placeholder keys to maintain valid YAML structure
- All examples now documented but non-executable (reference only)
- Validates with 0 errors
guarded_ai.py refactor (CLI simulator now uses v2.0 features):
- Import activity_utils.py for consistency with activity.py
- Use check_conditions() for advanced metadata conditions (gte, lt, contains, etc.)
- Use filter_content_blocks() for template rendering and conditional blocks
- Use render_template() for dynamic question text with {{variables}}
- Use resolve_conditional_navigation() for if/elif/else navigation
- Use select_weighted_random() for weighted random selection
- Use get_progressive_hint() for progressive hints system
- Create template contexts with built-in variables (current_attempt, etc.)
Benefits:
- Single source of truth for v2.0 logic (activity_utils.py)
- CLI simulator now tests all v2.0 features
- Maintainability: changes to features only need updates in one place
- Consistency: web app and CLI behave identically
All changes validated and tested.
This commit is contained in:
parent
2b19fc5b9d
commit
bd779e06fa
2 changed files with 184 additions and 79 deletions
|
|
@ -818,48 +818,49 @@ transitions:
|
|||
# PROGRESSIVE HINTS SYSTEM
|
||||
# ==============================================================================
|
||||
# Built-in system for providing hints that escalate with attempts
|
||||
|
||||
# Define hints at step level:
|
||||
- step_id: "difficult_question"
|
||||
question: "What is the capital of Burkina Faso?"
|
||||
|
||||
# Progressive hints based on attempt number
|
||||
hints:
|
||||
- attempt: 1
|
||||
text: "💡 Hint: It's not the largest city in the country."
|
||||
counts_as_attempt: false # Showing hint doesn't count as failure
|
||||
|
||||
- attempt: 2
|
||||
text: "💡 Hint: The name means 'City of Honest People'."
|
||||
counts_as_attempt: false
|
||||
|
||||
- attempt: 3
|
||||
text: "💡 Strong Hint: It starts with 'Oua' and ends with 'dougou'."
|
||||
counts_as_attempt: false
|
||||
|
||||
buckets: [correct, incorrect, need_hint]
|
||||
|
||||
transitions:
|
||||
correct:
|
||||
content_blocks:
|
||||
- "Excellent! Ouagadougou is correct!"
|
||||
next_section_and_step: "next:step"
|
||||
|
||||
incorrect:
|
||||
content_blocks:
|
||||
- "Not quite. Try again!"
|
||||
# Hint will auto-display based on current_attempt
|
||||
next_section_and_step: "current:difficult_question"
|
||||
|
||||
need_hint:
|
||||
content_blocks:
|
||||
- "Let me help you..."
|
||||
counts_as_attempt: false # Requesting hint doesn't count
|
||||
next_section_and_step: "current:difficult_question"
|
||||
|
||||
#
|
||||
# Example step with progressive hints:
|
||||
#
|
||||
# - step_id: "difficult_question"
|
||||
# question: "What is the capital of Burkina Faso?"
|
||||
#
|
||||
# hints:
|
||||
# - attempt: 1
|
||||
# text: "💡 Hint: It's not the largest city in the country."
|
||||
# counts_as_attempt: false
|
||||
#
|
||||
# - attempt: 2
|
||||
# text: "💡 Hint: The name means 'City of Honest People'."
|
||||
# counts_as_attempt: false
|
||||
#
|
||||
# - attempt: 3
|
||||
# text: "💡 Strong Hint: It starts with 'Oua' and ends with 'dougou'."
|
||||
# counts_as_attempt: false
|
||||
#
|
||||
# buckets: [correct, incorrect, need_hint]
|
||||
#
|
||||
# transitions:
|
||||
# correct:
|
||||
# content_blocks:
|
||||
# - "Excellent! Ouagadougou is correct!"
|
||||
# next_section_and_step: "next:step"
|
||||
#
|
||||
# incorrect:
|
||||
# content_blocks:
|
||||
# - "Not quite. Try again!"
|
||||
# next_section_and_step: "current:difficult_question"
|
||||
#
|
||||
# need_hint:
|
||||
# content_blocks:
|
||||
# - "Let me help you..."
|
||||
# counts_as_attempt: false
|
||||
# next_section_and_step: "current:difficult_question"
|
||||
#
|
||||
# Hints auto-display when attempt number matches
|
||||
# Hints support template variables: "Attempt {{current_attempt}} of {{max_attempts}}"
|
||||
|
||||
progressive_hints_example: "See activity-test-v2-features.yaml for working example"
|
||||
|
||||
# ==============================================================================
|
||||
# WEIGHTED RANDOM SELECTION
|
||||
# ==============================================================================
|
||||
|
|
@ -927,29 +928,32 @@ question: "What is 2 + 2?"
|
|||
question: "What is {{metadata.num1}} + {{metadata.num2}}?"
|
||||
|
||||
# Example: Math quiz with random numbers
|
||||
- step_id: "addition"
|
||||
pre_script: |
|
||||
import random
|
||||
result = {
|
||||
"metadata": {
|
||||
"num1": random.randint(1, 10),
|
||||
"num2": random.randint(1, 10)
|
||||
}
|
||||
}
|
||||
return result
|
||||
#
|
||||
# - step_id: "addition"
|
||||
# pre_script: |
|
||||
# import random
|
||||
# result = {
|
||||
# "metadata": {
|
||||
# "num1": random.randint(1, 10),
|
||||
# "num2": random.randint(1, 10)
|
||||
# }
|
||||
# }
|
||||
# return result
|
||||
#
|
||||
# question: "What is {{metadata.num1}} + {{metadata.num2}}?"
|
||||
#
|
||||
# tokens_for_ai: |
|
||||
# Calculate the correct answer: {{metadata.num1}} + {{metadata.num2}}
|
||||
# Categorize as 'correct' if their answer matches.
|
||||
#
|
||||
# buckets: [correct, incorrect]
|
||||
#
|
||||
# More personalized question examples:
|
||||
# - "{{metadata.character_name}}, what is your quest?"
|
||||
# - "You have {{metadata.gold}} gold. How much do you spend?"
|
||||
# - "Round {{current_attempt}}: What's your move?"
|
||||
|
||||
question: "What is {{metadata.num1}} + {{metadata.num2}}?"
|
||||
|
||||
tokens_for_ai: |
|
||||
Calculate the correct answer: {{metadata.num1}} + {{metadata.num2}}
|
||||
Categorize as 'correct' if their answer matches.
|
||||
|
||||
buckets: [correct, incorrect]
|
||||
|
||||
# Example: Personalized questions
|
||||
question: "{{metadata.character_name}}, what is your quest?"
|
||||
question: "You have {{metadata.gold}} gold. How much do you spend?"
|
||||
question: "Round {{current_attempt}}: What's your move?"
|
||||
dynamic_question_example: "See activity-test-v2-features.yaml for working example"
|
||||
|
||||
# ==============================================================================
|
||||
# BUILT-IN ATTEMPT COUNTER ACCESS
|
||||
|
|
|
|||
|
|
@ -3,8 +3,23 @@ import yaml
|
|||
import json
|
||||
import random
|
||||
import os
|
||||
import sys
|
||||
from openai import OpenAI
|
||||
|
||||
# Add parent directory to path to import activity_utils
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
# Import activity utilities for v2.0 features
|
||||
from activity_utils import (
|
||||
render_template,
|
||||
check_conditions,
|
||||
filter_content_blocks,
|
||||
resolve_conditional_navigation,
|
||||
select_weighted_random,
|
||||
get_progressive_hint,
|
||||
create_template_context
|
||||
)
|
||||
|
||||
# Global model-client mapping
|
||||
MODEL_CLIENT_MAP = {}
|
||||
|
||||
|
|
@ -366,11 +381,32 @@ def simulate_activity(yaml_file_path):
|
|||
# Get the user's language preference from metadata
|
||||
user_language = metadata.get("language", "English")
|
||||
|
||||
# Translate and print all content blocks once per step
|
||||
# Initialize attempts and max_attempts for this step
|
||||
attempts = 0
|
||||
step_max_attempts = step.get("max_attempts_per_step", max_attempts)
|
||||
|
||||
# Create template context for rendering
|
||||
context = create_template_context(
|
||||
metadata=metadata,
|
||||
current_attempt=attempts,
|
||||
max_attempts=step_max_attempts,
|
||||
current_section=current_section_id,
|
||||
current_step=current_step_id,
|
||||
username="User"
|
||||
)
|
||||
|
||||
# Translate and print all content blocks once per step (v2.0 with templates & conditionals)
|
||||
if "content_blocks" in step:
|
||||
content = "\n\n".join(step["content_blocks"])
|
||||
translated_content = translate_text(content, user_language, feedback_model)
|
||||
print(translated_content)
|
||||
# Filter and render content blocks
|
||||
filtered_blocks = filter_content_blocks(
|
||||
step["content_blocks"],
|
||||
metadata,
|
||||
context
|
||||
)
|
||||
if filtered_blocks:
|
||||
content = "\n\n".join(filtered_blocks)
|
||||
translated_content = translate_text(content, user_language, feedback_model)
|
||||
print(translated_content)
|
||||
|
||||
# Skip classification and feedback if there's no question
|
||||
if "question" not in step:
|
||||
|
|
@ -379,12 +415,22 @@ def simulate_activity(yaml_file_path):
|
|||
)
|
||||
continue
|
||||
|
||||
question = step["question"]
|
||||
# Render template variables in question (v2.0)
|
||||
question = render_template(step["question"], context)
|
||||
translated_question = translate_text(question, user_language, feedback_model)
|
||||
print(f"\nQuestion: {translated_question}")
|
||||
|
||||
attempts = 0
|
||||
while attempts < max_attempts:
|
||||
while attempts < step_max_attempts:
|
||||
# Update context with current attempt
|
||||
context = create_template_context(
|
||||
metadata=metadata,
|
||||
current_attempt=attempts + 1, # 1-indexed for display
|
||||
max_attempts=step_max_attempts,
|
||||
current_section=current_section_id,
|
||||
current_step=current_step_id,
|
||||
username="User"
|
||||
)
|
||||
|
||||
user_response = input("\nYour Response: ")
|
||||
|
||||
# Roll for random buckets BEFORE categorization
|
||||
|
|
@ -470,24 +516,42 @@ def simulate_activity(yaml_file_path):
|
|||
print(f"Processing transition for bucket: '{bucket_name}'")
|
||||
print(f"{'='*60}")
|
||||
|
||||
# Check metadata conditions
|
||||
# Check metadata conditions (v2.0 advanced conditions)
|
||||
if "metadata_conditions" in transition:
|
||||
conditions_met = all(
|
||||
metadata.get(key) == value
|
||||
for key, value in transition["metadata_conditions"].items()
|
||||
conditions_met = check_conditions(
|
||||
metadata,
|
||||
transition["metadata_conditions"]
|
||||
)
|
||||
if not conditions_met:
|
||||
print(f"⚠️ Skipping '{bucket_name}' - metadata conditions not met")
|
||||
print(f"Current Metadata: {json.dumps(metadata, indent=2)}")
|
||||
continue
|
||||
|
||||
# Print transition content blocks if they exist
|
||||
# Print transition content blocks if they exist (v2.0 with templates & conditionals)
|
||||
if "content_blocks" in transition:
|
||||
transition_content = "\n\n".join(transition["content_blocks"])
|
||||
translated_transition_content = translate_text(
|
||||
transition_content, user_language, feedback_model
|
||||
# Create template context
|
||||
context = create_template_context(
|
||||
metadata=metadata,
|
||||
current_attempt=attempts,
|
||||
max_attempts=max_attempts,
|
||||
current_section=current_section_id,
|
||||
current_step=current_step_id,
|
||||
username="User"
|
||||
)
|
||||
print(translated_transition_content)
|
||||
|
||||
# Filter and render content blocks (supports conditional blocks and templates)
|
||||
filtered_blocks = filter_content_blocks(
|
||||
transition["content_blocks"],
|
||||
metadata,
|
||||
context
|
||||
)
|
||||
|
||||
if filtered_blocks:
|
||||
transition_content = "\n\n".join(filtered_blocks)
|
||||
translated_transition_content = translate_text(
|
||||
transition_content, user_language, feedback_model
|
||||
)
|
||||
print(translated_transition_content)
|
||||
|
||||
# Update metadata based on user actions
|
||||
if "metadata_add" in transition:
|
||||
|
|
@ -604,6 +668,19 @@ def simulate_activity(yaml_file_path):
|
|||
metadata[random_key] = random_value
|
||||
metadata_tmp_keys.append(random_key) # Track temporary keys
|
||||
|
||||
# Handle metadata_weighted_random (v2.0)
|
||||
if "metadata_weighted_random" in transition:
|
||||
for key, weighted_options in transition["metadata_weighted_random"].items():
|
||||
selected_value = select_weighted_random(weighted_options)
|
||||
metadata[key] = selected_value
|
||||
|
||||
# Handle metadata_tmp_weighted_random (v2.0)
|
||||
if "metadata_tmp_weighted_random" in transition:
|
||||
for key, weighted_options in transition["metadata_tmp_weighted_random"].items():
|
||||
selected_value = select_weighted_random(weighted_options)
|
||||
metadata[key] = selected_value
|
||||
metadata_tmp_keys.append(key)
|
||||
|
||||
# Execute the processing script if it exists
|
||||
if "processing_script" in step and transition.get(
|
||||
"run_processing_script", False
|
||||
|
|
@ -674,6 +751,24 @@ def simulate_activity(yaml_file_path):
|
|||
|
||||
# End of multi-bucket processing loop
|
||||
|
||||
# Check for progressive hints (v2.0)
|
||||
if "hints" in step and attempts > 0:
|
||||
hint_context = create_template_context(
|
||||
metadata=metadata,
|
||||
current_attempt=attempts + 1, # Next attempt
|
||||
max_attempts=step_max_attempts,
|
||||
current_section=current_section_id,
|
||||
current_step=current_step_id,
|
||||
username="User"
|
||||
)
|
||||
hint = get_progressive_hint(step["hints"], attempts + 1, hint_context)
|
||||
if hint:
|
||||
translated_hint = translate_text(hint['text'], user_language, feedback_model)
|
||||
print(f"\n💡 Hint: {translated_hint}")
|
||||
# If hint doesn't count as attempt, adjust counting
|
||||
if not hint['counts_as_attempt']:
|
||||
any_counts_as_attempt = False
|
||||
|
||||
# Check if we should break or continue attempting
|
||||
if category not in [
|
||||
"partial_understanding",
|
||||
|
|
@ -688,7 +783,7 @@ def simulate_activity(yaml_file_path):
|
|||
if any_counts_as_attempt:
|
||||
attempts += 1
|
||||
|
||||
if attempts == max_attempts:
|
||||
if attempts == step_max_attempts:
|
||||
print("\nMaximum attempts reached. Moving to the next step.")
|
||||
|
||||
# Remove temporary metadata at the end of the step
|
||||
|
|
@ -697,8 +792,14 @@ def simulate_activity(yaml_file_path):
|
|||
del metadata[key]
|
||||
|
||||
# Use the final navigation target (from LAST processed transition)
|
||||
# v2.0: Resolve conditional navigation
|
||||
if final_next_section_and_step:
|
||||
current_section_id, current_step_id = final_next_section_and_step.split(":")
|
||||
resolved_navigation = resolve_conditional_navigation(
|
||||
final_next_section_and_step,
|
||||
metadata
|
||||
)
|
||||
if resolved_navigation:
|
||||
current_section_id, current_step_id = resolved_navigation.split(":")
|
||||
else:
|
||||
# No navigation specified, move to next step automatically
|
||||
current_section_id, current_step_id = get_next_section_and_step(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue