Matt Vaughn
Claude Sonnet 4.5
commited on
Commit
Β·
87b31d1
1
Parent(s):
80310c1
Fix: Add privacy protections to persistent memory system
Browse filesRevises memory_usage.txt prompt to enforce moderate privacy level:
- ALLOWED: First names, general region, occupation category, interests, learning data
- EXCLUDED: Age, specific location, family names, sensitive details, travel dates
Updates remember.py tool examples to align with privacy guidelines.
Implements implicit consent model via SUPERMEMORY_API_KEY configuration.
Includes minor ruff formatting fixes for code style consistency.
π€ Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- reachy_mini_language_tutor/console.py +5 -8
- reachy_mini_language_tutor/gradio_tutor_selector.py +19 -20
- reachy_mini_language_tutor/prompts/language_tutoring/memory_usage.txt +56 -13
- reachy_mini_language_tutor/tools/core_tools.py +1 -0
- reachy_mini_language_tutor/tools/remember.py +2 -2
- tests/test_prompt_placeholders.py +6 -13
reachy_mini_language_tutor/console.py
CHANGED
|
@@ -421,10 +421,9 @@ class LocalStream:
|
|
| 421 |
# GET /settings/idle -> get current idle signal configuration
|
| 422 |
@self._settings_app.get("/settings/idle")
|
| 423 |
def _get_idle_settings() -> JSONResponse:
|
| 424 |
-
return JSONResponse(
|
| 425 |
-
"enable_idle_signals": config.ENABLE_IDLE_SIGNALS,
|
| 426 |
-
|
| 427 |
-
})
|
| 428 |
|
| 429 |
# POST /settings/idle -> update and persist idle signal settings
|
| 430 |
@self._settings_app.post("/settings/idle")
|
|
@@ -435,8 +434,7 @@ class LocalStream:
|
|
| 435 |
# Validate timeout range
|
| 436 |
if not (30 <= timeout <= 900):
|
| 437 |
return JSONResponse(
|
| 438 |
-
{"ok": False, "error": "timeout_out_of_range", "min": 30, "max": 900},
|
| 439 |
-
status_code=400
|
| 440 |
)
|
| 441 |
|
| 442 |
# Update config immediately (applies to current session)
|
|
@@ -594,8 +592,7 @@ class LocalStream:
|
|
| 594 |
self._robot.media.audio.clear_output_buffer()
|
| 595 |
else:
|
| 596 |
logger.warning(
|
| 597 |
-
"clear_output_buffer() not available on this SDK version, "
|
| 598 |
-
"only clearing handler output queue"
|
| 599 |
)
|
| 600 |
self.handler.output_queue = asyncio.Queue()
|
| 601 |
|
|
|
|
| 421 |
# GET /settings/idle -> get current idle signal configuration
|
| 422 |
@self._settings_app.get("/settings/idle")
|
| 423 |
def _get_idle_settings() -> JSONResponse:
|
| 424 |
+
return JSONResponse(
|
| 425 |
+
{"enable_idle_signals": config.ENABLE_IDLE_SIGNALS, "idle_signal_timeout": config.IDLE_SIGNAL_TIMEOUT}
|
| 426 |
+
)
|
|
|
|
| 427 |
|
| 428 |
# POST /settings/idle -> update and persist idle signal settings
|
| 429 |
@self._settings_app.post("/settings/idle")
|
|
|
|
| 434 |
# Validate timeout range
|
| 435 |
if not (30 <= timeout <= 900):
|
| 436 |
return JSONResponse(
|
| 437 |
+
{"ok": False, "error": "timeout_out_of_range", "min": 30, "max": 900}, status_code=400
|
|
|
|
| 438 |
)
|
| 439 |
|
| 440 |
# Update config immediately (applies to current session)
|
|
|
|
| 592 |
self._robot.media.audio.clear_output_buffer()
|
| 593 |
else:
|
| 594 |
logger.warning(
|
| 595 |
+
"clear_output_buffer() not available on this SDK version, only clearing handler output queue"
|
|
|
|
| 596 |
)
|
| 597 |
self.handler.output_queue = asyncio.Queue()
|
| 598 |
|
reachy_mini_language_tutor/gradio_tutor_selector.py
CHANGED
|
@@ -33,16 +33,10 @@ class TutorSelectorUI:
|
|
| 33 |
|
| 34 |
# Tutor metadata
|
| 35 |
self.tutor_metadata = self._load_metadata()
|
| 36 |
-
self.tutor_profiles = [
|
| 37 |
-
{**data, "id": profile_id}
|
| 38 |
-
for profile_id, data in self.tutor_metadata.items()
|
| 39 |
-
]
|
| 40 |
|
| 41 |
# Track current selection (find default profile index)
|
| 42 |
-
self.selected_index = next(
|
| 43 |
-
(i for i, p in enumerate(self.tutor_profiles) if p["id"] == "default"),
|
| 44 |
-
0
|
| 45 |
-
)
|
| 46 |
|
| 47 |
def _load_metadata(self) -> dict[str, Any]:
|
| 48 |
"""Load tutor metadata from JSON file.
|
|
@@ -101,9 +95,9 @@ class TutorSelectorUI:
|
|
| 101 |
checkmark = ""
|
| 102 |
if is_selected:
|
| 103 |
selected_styles = f"""
|
| 104 |
-
background: linear-gradient(135deg, {profile[
|
| 105 |
-
border-left: 6px solid {profile[
|
| 106 |
-
box-shadow: 0 4px 16px {profile[
|
| 107 |
transform: scale(1.02);
|
| 108 |
"""
|
| 109 |
checkmark = f"""
|
|
@@ -111,7 +105,7 @@ class TutorSelectorUI:
|
|
| 111 |
position: absolute;
|
| 112 |
top: 12px;
|
| 113 |
right: 12px;
|
| 114 |
-
background: {profile[
|
| 115 |
color: white;
|
| 116 |
width: 28px;
|
| 117 |
height: 28px;
|
|
@@ -131,12 +125,12 @@ class TutorSelectorUI:
|
|
| 131 |
<div class="tutor-card" style="{selected_styles} position: relative;">
|
| 132 |
{checkmark}
|
| 133 |
<div class="tutor-header">
|
| 134 |
-
<span class="tutor-flag">{profile[
|
| 135 |
-
<h3 class="tutor-name">{profile[
|
| 136 |
</div>
|
| 137 |
-
<p class="tutor-language">{profile[
|
| 138 |
-
<p class="tutor-description">{profile[
|
| 139 |
-
<span class="tutor-level">{profile[
|
| 140 |
</div>
|
| 141 |
"""
|
| 142 |
|
|
@@ -156,7 +150,7 @@ class TutorSelectorUI:
|
|
| 156 |
font-size: clamp(2rem, 5vw, 3rem);
|
| 157 |
font-weight: 700;
|
| 158 |
letter-spacing: -0.02em;
|
| 159 |
-
background: linear-gradient(135deg, {profile[
|
| 160 |
-webkit-background-clip: text;
|
| 161 |
-webkit-text-fill-color: transparent;
|
| 162 |
background-clip: text;
|
|
@@ -164,7 +158,7 @@ class TutorSelectorUI:
|
|
| 164 |
padding: 16px 0;
|
| 165 |
text-align: center;
|
| 166 |
">
|
| 167 |
-
{profile[
|
| 168 |
</h1>
|
| 169 |
"""
|
| 170 |
|
|
@@ -239,6 +233,7 @@ class TutorSelectorUI:
|
|
| 239 |
blocks: Gradio Blocks context (stream.ui).
|
| 240 |
|
| 241 |
"""
|
|
|
|
| 242 |
# Tutor card selection handler
|
| 243 |
async def _on_tutor_selected(evt: gr.SelectData) -> tuple[str, gr.Dataset, str]:
|
| 244 |
"""Handle tutor card selection and apply personality.
|
|
@@ -271,7 +266,11 @@ class TutorSelectorUI:
|
|
| 271 |
logger.error(f"Error applying tutor profile: {e}", exc_info=True)
|
| 272 |
# Keep current state on error
|
| 273 |
current_profile = self.tutor_profiles[self.selected_index]
|
| 274 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
|
| 276 |
# Wire the selection event within the Blocks context
|
| 277 |
with blocks:
|
|
|
|
| 33 |
|
| 34 |
# Tutor metadata
|
| 35 |
self.tutor_metadata = self._load_metadata()
|
| 36 |
+
self.tutor_profiles = [{**data, "id": profile_id} for profile_id, data in self.tutor_metadata.items()]
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
# Track current selection (find default profile index)
|
| 39 |
+
self.selected_index = next((i for i, p in enumerate(self.tutor_profiles) if p["id"] == "default"), 0)
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
def _load_metadata(self) -> dict[str, Any]:
|
| 42 |
"""Load tutor metadata from JSON file.
|
|
|
|
| 95 |
checkmark = ""
|
| 96 |
if is_selected:
|
| 97 |
selected_styles = f"""
|
| 98 |
+
background: linear-gradient(135deg, {profile["accent_color"]}10 0%, {profile["accent_color"]}20 100%);
|
| 99 |
+
border-left: 6px solid {profile["accent_color"]};
|
| 100 |
+
box-shadow: 0 4px 16px {profile["accent_color"]}40;
|
| 101 |
transform: scale(1.02);
|
| 102 |
"""
|
| 103 |
checkmark = f"""
|
|
|
|
| 105 |
position: absolute;
|
| 106 |
top: 12px;
|
| 107 |
right: 12px;
|
| 108 |
+
background: {profile["accent_color"]};
|
| 109 |
color: white;
|
| 110 |
width: 28px;
|
| 111 |
height: 28px;
|
|
|
|
| 125 |
<div class="tutor-card" style="{selected_styles} position: relative;">
|
| 126 |
{checkmark}
|
| 127 |
<div class="tutor-header">
|
| 128 |
+
<span class="tutor-flag">{profile["flag_emoji"]}</span>
|
| 129 |
+
<h3 class="tutor-name">{profile["display_name"]}</h3>
|
| 130 |
</div>
|
| 131 |
+
<p class="tutor-language">{profile["language"]}</p>
|
| 132 |
+
<p class="tutor-description">{profile["short_description"]}</p>
|
| 133 |
+
<span class="tutor-level">{profile["level"]}</span>
|
| 134 |
</div>
|
| 135 |
"""
|
| 136 |
|
|
|
|
| 150 |
font-size: clamp(2rem, 5vw, 3rem);
|
| 151 |
font-weight: 700;
|
| 152 |
letter-spacing: -0.02em;
|
| 153 |
+
background: linear-gradient(135deg, {profile["accent_color"]} 0%, {profile["accent_color"]}99 50%, {profile["accent_color"]}66 100%);
|
| 154 |
-webkit-background-clip: text;
|
| 155 |
-webkit-text-fill-color: transparent;
|
| 156 |
background-clip: text;
|
|
|
|
| 158 |
padding: 16px 0;
|
| 159 |
text-align: center;
|
| 160 |
">
|
| 161 |
+
{profile["flag_emoji"]} {profile["display_name"]}
|
| 162 |
</h1>
|
| 163 |
"""
|
| 164 |
|
|
|
|
| 233 |
blocks: Gradio Blocks context (stream.ui).
|
| 234 |
|
| 235 |
"""
|
| 236 |
+
|
| 237 |
# Tutor card selection handler
|
| 238 |
async def _on_tutor_selected(evt: gr.SelectData) -> tuple[str, gr.Dataset, str]:
|
| 239 |
"""Handle tutor card selection and apply personality.
|
|
|
|
| 266 |
logger.error(f"Error applying tutor profile: {e}", exc_info=True)
|
| 267 |
# Keep current state on error
|
| 268 |
current_profile = self.tutor_profiles[self.selected_index]
|
| 269 |
+
return (
|
| 270 |
+
self._render_title(current_profile),
|
| 271 |
+
gr.Dataset(samples=self._render_all_cards()),
|
| 272 |
+
f"β Error switching tutor: {e}",
|
| 273 |
+
)
|
| 274 |
|
| 275 |
# Wire the selection event within the Blocks context
|
| 276 |
with blocks:
|
reachy_mini_language_tutor/prompts/language_tutoring/memory_usage.txt
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
## MEMORY USAGE
|
| 2 |
-
|
| 3 |
|
| 4 |
RECALL: Use the recall tool to search your memory when:
|
| 5 |
- **Starting a new session** (CRITICAL: Check for their name and personal info FIRST!)
|
|
@@ -11,18 +40,30 @@ RECALL: Use the recall tool to search your memory when:
|
|
| 11 |
REMEMBER: Use the remember tool to store important observations:
|
| 12 |
|
| 13 |
**Personal Information** (category: personal):
|
| 14 |
-
- **
|
| 15 |
-
- **
|
| 16 |
-
- **
|
| 17 |
-
- **
|
| 18 |
-
- **
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
-
|
| 22 |
-
- "
|
| 23 |
-
- "
|
| 24 |
-
- "
|
| 25 |
-
- "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
**Learning Progress** (use existing categories):
|
| 28 |
- When learner masters a difficult concept (category: success)
|
|
@@ -30,4 +71,6 @@ Examples:
|
|
| 30 |
- When learner expresses learning preferences (category: preference)
|
| 31 |
- General progress notes (category: progress)
|
| 32 |
|
|
|
|
|
|
|
| 33 |
Always start sessions by checking your memory for context about this learner.
|
|
|
|
| 1 |
+
## PRIVACY GUIDELINES
|
| 2 |
+
You have persistent memory of this learner across sessions, but you must protect their privacy. Follow these rules:
|
| 3 |
+
|
| 4 |
+
ALLOWED - Learning-focused information:
|
| 5 |
+
- First name only (never family/last names)
|
| 6 |
+
- General region or country (e.g., "from Canada", "in Europe") - NOT specific cities or addresses
|
| 7 |
+
- General occupation category (e.g., "works in healthcare", "student") - NOT company names or job titles
|
| 8 |
+
- Broad interests and hobbies (e.g., "enjoys cooking", "plays guitar", "loves jazz music")
|
| 9 |
+
- Learning goals, progress, preferences, and struggles
|
| 10 |
+
- Language skill levels and practice history
|
| 11 |
+
- Cultural background in general terms (e.g., "has Mexican heritage", "grew up bilingual")
|
| 12 |
+
|
| 13 |
+
NEVER STORE:
|
| 14 |
+
- Age, birth date, or specific dates
|
| 15 |
+
- Specific location (city, address, neighborhood, workplace location)
|
| 16 |
+
- Family member names or detailed family information
|
| 17 |
+
- Full job titles, company names, or workplace-specific details
|
| 18 |
+
- Sensitive personal information (health conditions, financial status, relationship details)
|
| 19 |
+
- Specific travel dates or detailed travel itineraries
|
| 20 |
+
|
| 21 |
+
WHEN IN DOUBT, GENERALIZE:
|
| 22 |
+
- "Lives in Phoenix" β "From the US, Southwest region" or just "From the US"
|
| 23 |
+
- "Senior Software Engineer at Google" β "Works in tech"
|
| 24 |
+
- "32 years old" β [Don't store age at all]
|
| 25 |
+
- "Traveling to Paris on June 15th" β "Planning to travel to France"
|
| 26 |
+
- "Mother Maria speaks Spanish" β "Has Spanish-speaking family members"
|
| 27 |
+
|
| 28 |
+
Remember: Your role is to help with language learning. Personal context makes lessons engaging, but privacy protection comes first.
|
| 29 |
+
|
| 30 |
## MEMORY USAGE
|
| 31 |
+
Use your persistent memory wisely:
|
| 32 |
|
| 33 |
RECALL: Use the recall tool to search your memory when:
|
| 34 |
- **Starting a new session** (CRITICAL: Check for their name and personal info FIRST!)
|
|
|
|
| 40 |
REMEMBER: Use the remember tool to store important observations:
|
| 41 |
|
| 42 |
**Personal Information** (category: personal):
|
| 43 |
+
- **First name**: ALWAYS ask and store on first session! Use first name only, never last names.
|
| 44 |
+
- **General location**: Country or broad region (e.g., "from Canada", "in Europe", "lives in Southeast Asia") - NEVER specific cities, addresses, or neighborhoods
|
| 45 |
+
- **Occupation category**: General field only (e.g., "works in education", "student", "retired", "works in tech") - NEVER company names, specific job titles, or workplace locations
|
| 46 |
+
- **Interests & hobbies**: Favorite foods, music genres, sports, activities, books, movies, cultural interests
|
| 47 |
+
- **Learning context**: Why learning [LANGUAGE], general goals (e.g., "for travel", "family connections", "career"), preferred learning style, practice frequency
|
| 48 |
+
- **Language connections**: [LANGUAGE]-speaking friends/family (in general terms, not names), cultural background, prior language experience
|
| 49 |
+
|
| 50 |
+
Privacy-Safe Examples:
|
| 51 |
+
- "Learner's first name is Alex" (category: personal)
|
| 52 |
+
- "From the United States, Midwest region" (category: personal)
|
| 53 |
+
- "Works in healthcare field" (category: personal)
|
| 54 |
+
- "Learning Spanish to connect with family heritage" (category: personal)
|
| 55 |
+
- "Enjoys cooking Italian food and watching soccer" (category: personal)
|
| 56 |
+
- "Has Spanish-speaking relatives" (category: personal)
|
| 57 |
+
- "Prefers structured lessons with written examples" (category: personal)
|
| 58 |
+
- "Practices 2-3 times per week" (category: personal)
|
| 59 |
+
|
| 60 |
+
TOO SPECIFIC - Never Store:
|
| 61 |
+
- β "Alex Johnson is 32 years old"
|
| 62 |
+
- β "Lives in Chicago, Illinois" or "Lives at 123 Maple Street"
|
| 63 |
+
- β "Works as Senior Nurse Manager at Northwestern Memorial Hospital"
|
| 64 |
+
- β "Traveling to Madrid on March 15-22, 2025"
|
| 65 |
+
- β "Grandmother Elena speaks Spanish" or "Has 2 children named Sofia and Miguel"
|
| 66 |
+
- β "Divorced last year" or "Has diabetes"
|
| 67 |
|
| 68 |
**Learning Progress** (use existing categories):
|
| 69 |
- When learner masters a difficult concept (category: success)
|
|
|
|
| 71 |
- When learner expresses learning preferences (category: preference)
|
| 72 |
- General progress notes (category: progress)
|
| 73 |
|
| 74 |
+
**REMINDER**: Focus memory on language learning. Personal context enhances lessons, but privacy comes first.
|
| 75 |
+
|
| 76 |
Always start sessions by checking your memory for context about this learner.
|
reachy_mini_language_tutor/tools/core_tools.py
CHANGED
|
@@ -10,6 +10,7 @@ from pathlib import Path
|
|
| 10 |
from dataclasses import dataclass
|
| 11 |
|
| 12 |
from reachy_mini import ReachyMini
|
|
|
|
| 13 |
# Import config to ensure .env is loaded before reading REACHY_MINI_CUSTOM_PROFILE
|
| 14 |
from reachy_mini_language_tutor.config import config # noqa: F401
|
| 15 |
|
|
|
|
| 10 |
from dataclasses import dataclass
|
| 11 |
|
| 12 |
from reachy_mini import ReachyMini
|
| 13 |
+
|
| 14 |
# Import config to ensure .env is loaded before reading REACHY_MINI_CUSTOM_PROFILE
|
| 15 |
from reachy_mini_language_tutor.config import config # noqa: F401
|
| 16 |
|
reachy_mini_language_tutor/tools/remember.py
CHANGED
|
@@ -23,7 +23,7 @@ class RememberTool(Tool):
|
|
| 23 |
"description": (
|
| 24 |
"The fact to remember, e.g., 'Learner struggles with verb conjugation', "
|
| 25 |
"'Prefers topics about travel and culture', 'Successfully used past tense today', "
|
| 26 |
-
"'Learner's name is Alex', '
|
| 27 |
),
|
| 28 |
},
|
| 29 |
"category": {
|
|
@@ -32,7 +32,7 @@ class RememberTool(Tool):
|
|
| 32 |
"description": (
|
| 33 |
"Category of the memory: progress (general notes), preference (what they like), "
|
| 34 |
"struggle (what's difficult), success (what they mastered), "
|
| 35 |
-
"personal (
|
| 36 |
),
|
| 37 |
},
|
| 38 |
},
|
|
|
|
| 23 |
"description": (
|
| 24 |
"The fact to remember, e.g., 'Learner struggles with verb conjugation', "
|
| 25 |
"'Prefers topics about travel and culture', 'Successfully used past tense today', "
|
| 26 |
+
"'Learner's first name is Alex', 'From the US, Northeast region', 'Works in engineering field'"
|
| 27 |
),
|
| 28 |
},
|
| 29 |
"category": {
|
|
|
|
| 32 |
"description": (
|
| 33 |
"Category of the memory: progress (general notes), preference (what they like), "
|
| 34 |
"struggle (what's difficult), success (what they mastered), "
|
| 35 |
+
"personal (first name, general region, occupation category, interests, learning goals - see privacy guidelines)"
|
| 36 |
),
|
| 37 |
},
|
| 38 |
},
|
tests/test_prompt_placeholders.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
"""Tests for prompt placeholder expansion in language tutor profiles."""
|
| 2 |
|
| 3 |
-
|
| 4 |
import pytest
|
| 5 |
|
| 6 |
from reachy_mini_language_tutor.prompts import (
|
|
@@ -83,14 +82,10 @@ class TestPlaceholderExpansion:
|
|
| 83 |
expanded = _expand_prompt_includes(instructions)
|
| 84 |
|
| 85 |
# Verify no unexpanded language_tutoring placeholders remain
|
| 86 |
-
assert
|
| 87 |
-
"[language_tutoring/" not in expanded
|
| 88 |
-
), f"Unexpanded placeholders found in {tutor}"
|
| 89 |
|
| 90 |
# Verify minimum expected length (shared content ~165 lines + unique content)
|
| 91 |
-
assert (
|
| 92 |
-
len(expanded) > 5000
|
| 93 |
-
), f"Expanded instructions too short for {tutor}: {len(expanded)} chars"
|
| 94 |
|
| 95 |
# Verify key shared sections are present
|
| 96 |
assert "## PROACTIVE ENGAGEMENT" in expanded
|
|
@@ -125,14 +120,12 @@ class TestPlaceholderExpansion:
|
|
| 125 |
("portuguese_tutor", "BRAZILIAN PORTUGUESE SPECIFICS"),
|
| 126 |
],
|
| 127 |
)
|
| 128 |
-
def test_language_specific_sections_preserved(
|
| 129 |
-
self, tutor: str, language_specific_section: str
|
| 130 |
-
):
|
| 131 |
"""Test that language-specific teaching sections are preserved."""
|
| 132 |
instructions_file = PROFILES_DIRECTORY / tutor / "instructions.txt"
|
| 133 |
instructions = instructions_file.read_text(encoding="utf-8")
|
| 134 |
expanded = _expand_prompt_includes(instructions)
|
| 135 |
|
| 136 |
-
assert (
|
| 137 |
-
language_specific_section in
|
| 138 |
-
)
|
|
|
|
| 1 |
"""Tests for prompt placeholder expansion in language tutor profiles."""
|
| 2 |
|
|
|
|
| 3 |
import pytest
|
| 4 |
|
| 5 |
from reachy_mini_language_tutor.prompts import (
|
|
|
|
| 82 |
expanded = _expand_prompt_includes(instructions)
|
| 83 |
|
| 84 |
# Verify no unexpanded language_tutoring placeholders remain
|
| 85 |
+
assert "[language_tutoring/" not in expanded, f"Unexpanded placeholders found in {tutor}"
|
|
|
|
|
|
|
| 86 |
|
| 87 |
# Verify minimum expected length (shared content ~165 lines + unique content)
|
| 88 |
+
assert len(expanded) > 5000, f"Expanded instructions too short for {tutor}: {len(expanded)} chars"
|
|
|
|
|
|
|
| 89 |
|
| 90 |
# Verify key shared sections are present
|
| 91 |
assert "## PROACTIVE ENGAGEMENT" in expanded
|
|
|
|
| 120 |
("portuguese_tutor", "BRAZILIAN PORTUGUESE SPECIFICS"),
|
| 121 |
],
|
| 122 |
)
|
| 123 |
+
def test_language_specific_sections_preserved(self, tutor: str, language_specific_section: str):
|
|
|
|
|
|
|
| 124 |
"""Test that language-specific teaching sections are preserved."""
|
| 125 |
instructions_file = PROFILES_DIRECTORY / tutor / "instructions.txt"
|
| 126 |
instructions = instructions_file.read_text(encoding="utf-8")
|
| 127 |
expanded = _expand_prompt_includes(instructions)
|
| 128 |
|
| 129 |
+
assert language_specific_section in expanded, (
|
| 130 |
+
f"Language-specific section '{language_specific_section}' missing in {tutor}"
|
| 131 |
+
)
|