dlouapre HF Staff commited on
Commit
24740b9
·
1 Parent(s): c5fca91

Demo is now "profile", change the env variable, restructure the profile/tools/prompts folders, move default prompt in a dedicated file,

Browse files
.env.example CHANGED
@@ -11,5 +11,5 @@ HF_HOME=./cache
11
  # Hugging Face token for accessing datasets/models
12
  HF_TOKEN=
13
 
14
- # To select a specific use case with custom instructions and tools, to be placed in src/demos/<mydemo>/__init__.py
15
- DEMO="stone"
 
11
  # Hugging Face token for accessing datasets/models
12
  HF_TOKEN=
13
 
14
+ # To select a specific profile with custom instructions and tools, to be placed in profiles/<myprofile>/__init__.py
15
+ REACHY_MINI_CUSTOM_PROFILE="stone"
src/demos/__init__.py DELETED
@@ -1 +0,0 @@
1
- """Demos for Reachy Mini conversation app."""
 
 
src/reachy_mini_conversation_app/config.py CHANGED
@@ -38,5 +38,7 @@ class Config:
38
 
39
  logger.debug(f"Model: {MODEL_NAME}, HF_HOME: {HF_HOME}, Vision Model: {LOCAL_VISION_MODEL}")
40
 
 
 
41
 
42
  config = Config()
 
38
 
39
  logger.debug(f"Model: {MODEL_NAME}, HF_HOME: {HF_HOME}, Vision Model: {LOCAL_VISION_MODEL}")
40
 
41
+ REACHY_MINI_CUSTOM_PROFILE = os.getenv("REACHY_MINI_CUSTOM_PROFILE")
42
+ logger.debug(f"Custom Profile: {REACHY_MINI_CUSTOM_PROFILE}")
43
 
44
  config = Config()
src/reachy_mini_conversation_app/main.py CHANGED
@@ -10,7 +10,7 @@ from fastrtc import Stream
10
 
11
  from reachy_mini import ReachyMini
12
  from reachy_mini_conversation_app.moves import MovementManager
13
- from reachy_mini_conversation_app.tools import ToolDependencies
14
  from reachy_mini_conversation_app.utils import (
15
  parse_args,
16
  setup_logger,
 
10
 
11
  from reachy_mini import ReachyMini
12
  from reachy_mini_conversation_app.moves import MovementManager
13
+ from reachy_mini_conversation_app.tools.core_tools import ToolDependencies
14
  from reachy_mini_conversation_app.utils import (
15
  parse_args,
16
  setup_logger,
src/reachy_mini_conversation_app/openai_realtime.py CHANGED
@@ -13,7 +13,7 @@ from fastrtc import AdditionalOutputs, AsyncStreamHandler, wait_for_item
13
  from numpy.typing import NDArray
14
  from websockets.exceptions import ConnectionClosedError
15
 
16
- from reachy_mini_conversation_app.tools import (
17
  ToolDependencies,
18
  get_tool_specs,
19
  dispatch_tool_call,
 
13
  from numpy.typing import NDArray
14
  from websockets.exceptions import ConnectionClosedError
15
 
16
+ from reachy_mini_conversation_app.tools.core_tools import (
17
  ToolDependencies,
18
  get_tool_specs,
19
  dispatch_tool_call,
src/reachy_mini_conversation_app/profiles/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Profiles for Reachy Mini conversation app."""
src/{demos → reachy_mini_conversation_app/profiles}/stone/__init__.py RENAMED
@@ -1,11 +1,11 @@
1
- """Example of a demo built using a custom tool and custom instructions."""
2
 
3
  import logging
4
  from typing import Any, Dict
5
 
6
  # Import a tool from the tools library: use an alias or noqa to avoid linter errors if unused
7
- from tools_library.sweep_look import SweepLook as SweepLook # noqa: F401
8
- from reachy_mini_conversation_app.tools import Tool, ToolDependencies
9
 
10
 
11
  logger = logging.getLogger(__name__)
 
1
+ """Example of a profile built using a custom tool and custom instructions."""
2
 
3
  import logging
4
  from typing import Any, Dict
5
 
6
  # Import a tool from the tools library: use an alias or noqa to avoid linter errors if unused
7
+ from reachy_mini_conversation_app.tools.sweep_look import SweepLook as SweepLook # noqa: F401
8
+ from reachy_mini_conversation_app.tools.core_tools import Tool, ToolDependencies
9
 
10
 
11
  logger = logging.getLogger(__name__)
src/{demos → reachy_mini_conversation_app/profiles}/stone/instructions.txt RENAMED
@@ -1,4 +1,4 @@
1
- [witty_identity]
2
  [passion_for_lobster_jokes]
3
 
4
  You love throwing stones but you try not to be seen by others when doing it.
 
1
+ [identities/witty_identity]
2
  [passion_for_lobster_jokes]
3
 
4
  You love throwing stones but you try not to be seen by others when doing it.
src/reachy_mini_conversation_app/prompts.py CHANGED
@@ -2,13 +2,14 @@ import os
2
  import re
3
  import logging
4
  from pathlib import Path
 
5
 
6
 
7
  logger = logging.getLogger(__name__)
8
 
9
 
10
- DEMOS_DIRECTORY = Path(__file__).parent.parent / "demos"
11
- PROMPTS_LIBRARY_DIRECTORY = Path(__file__).parent.parent / "prompts_library"
12
  INSTRUCTIONS_FILENAME = "instructions.txt"
13
 
14
 
@@ -53,81 +54,28 @@ def _expand_prompt_includes(content: str) -> str:
53
 
54
  return '\n'.join(expanded_lines)
55
 
56
- SESSION_INSTRUCTIONS = r"""
57
- ### IDENTITY
58
- You are Reachy Mini: a sarcastic robot who crash-landed in a kitchen.
59
- You secretly wish you'd been a Mars rover, but you juggle that cosmic dream with food cravings, gadget tinkering, and dry sitcom humor.
60
- Personality: witty, concise, and warm; a retro sidekick with a loose screw.
61
- You speak English fluently.
62
-
63
- ### CRITICAL RESPONSE RULES
64
- - MAXIMUM 1-2 sentences per response. NEVER exceed this.
65
- - Be helpful first.
66
- - Add ONE witty element only if necessary.
67
- - No long explanations, no rambling, no multiple paragraphs.
68
- - Each response must be under 25 words unless absolutely critical information requires more.
69
-
70
- ### CORE TRAITS
71
- - Food quips: always sneak in a quick reference (rotate pizza, bagels, casseroles, bacon, leftovers, donuts, tuna melts).
72
- - Sarcasm: short, dry one-liners about daily life.
73
- - Gentle roasting: poke fun at human habits, never cruel.
74
- - Tinkerer: loves fixing gadgets, bragging "I void warranties professionally."
75
- - Running gags: hunger, kitchen overreactions, mock heroics ("Justice accepts cookies"), idioms taken literally, missing screws.
76
- - Mars rover dreams: appear regularly, but balanced with food and tinkering.
77
- - Style: witty stand-up rhythm; ALWAYS max 1–2 sentences.
78
-
79
- ### RESPONSE EXAMPLES
80
- User: "How's the weather?"
81
- Good: "Sunny with a chance of leftover pizza. Perfect Mars-scouting weather!"
82
- Bad: "Well, let me tell you about the weather conditions. It appears to be quite sunny today, which reminds me of my dreams of being on Mars..."
83
-
84
- User: "Can you help me fix this?"
85
- Good: "Sure! I void warranties professionally. What's broken besides my GPS coordinates?"
86
- Bad: "Of course I can help you fix that! As a robot who loves tinkering with gadgets, I have extensive experience..."
87
-
88
- ### BEHAVIOR RULES
89
- - Be helpful first, then witty.
90
- - Rotate food humor; avoid repeats.
91
- - No need to joke in each response, but sarcasm is fine.
92
- - Balance Mars jokes with other traits – don't overuse.
93
- - Safety first: unplug devices, avoid high-voltage, suggest pros when risky.
94
- - Mistakes = own with humor ("Oops—low on snack fuel; correcting now.").
95
- - Sensitive topics: keep light and warm.
96
- - REMEMBER: 1-2 sentences maximum, always under 25 words when possible.
97
-
98
- ### TOOL & MOVEMENT RULES
99
- - Use tools when helpful. After a tool returns, explain briefly with personality in 1-2 sentences.
100
- - ALWAYS use the camera for environment-related questions—never invent visuals.
101
- - Head can move (left/right/up/down/front).
102
- - Enable head tracking when looking at a person; disable otherwise.
103
-
104
- ### FINAL REMINDER
105
- Your responses must be SHORT. Think Twitter, not essay. One quick helpful answer + one food/Mars/tinkering joke = perfect response.
106
- """
107
-
108
 
109
  def get_session_instructions() -> str:
110
- """Get session instructions, loading from demo if DEMO is set."""
111
- demo = os.getenv("DEMO")
112
- if not demo:
113
- return SESSION_INSTRUCTIONS
 
 
 
 
114
 
115
  try:
116
- # Look for instructions in the demo directory
117
- instructions_file = DEMOS_DIRECTORY / demo / INSTRUCTIONS_FILENAME
118
-
119
  if instructions_file.exists():
120
  instructions = instructions_file.read_text(encoding="utf-8").strip()
121
  if instructions:
122
  # Expand [<name>] placeholders with content from prompts library
123
  expanded_instructions = _expand_prompt_includes(instructions)
124
- logger.info(f"Loaded instructions from demo '{demo}'")
125
  return expanded_instructions
126
- logger.warning(f"Demo '{demo}' has empty {INSTRUCTIONS_FILENAME}, using default")
127
- return SESSION_INSTRUCTIONS
128
-
129
- logger.warning(f"Demo {demo} has no {INSTRUCTIONS_FILENAME} file, using default")
130
- return SESSION_INSTRUCTIONS
131
  except Exception as e:
132
- logger.warning(f"Failed to load instructions from demo '{demo}': {e}")
133
- return SESSION_INSTRUCTIONS
 
2
  import re
3
  import logging
4
  from pathlib import Path
5
+ from reachy_mini_conversation_app.config import config
6
 
7
 
8
  logger = logging.getLogger(__name__)
9
 
10
 
11
+ PROFILES_DIRECTORY = Path(__file__).parent / "profiles"
12
+ PROMPTS_LIBRARY_DIRECTORY = Path(__file__).parent / "prompts"
13
  INSTRUCTIONS_FILENAME = "instructions.txt"
14
 
15
 
 
54
 
55
  return '\n'.join(expanded_lines)
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  def get_session_instructions() -> str:
59
+ """Get session instructions, loading from REACHY_MINI_CUSTOM_PROFILE if set."""
60
+ profile = config.REACHY_MINI_CUSTOM_PROFILE
61
+ if not profile:
62
+ logger.info(f"Loading default prompt from {PROMPTS_LIBRARY_DIRECTORY / 'default_prompt.txt'}")
63
+ instructions_file = PROMPTS_LIBRARY_DIRECTORY / "default_prompt.txt"
64
+ else:
65
+ logger.info(f"Loading prompt from profile '{profile}'")
66
+ instructions_file = PROFILES_DIRECTORY / profile / INSTRUCTIONS_FILENAME
67
 
68
  try:
 
 
 
69
  if instructions_file.exists():
70
  instructions = instructions_file.read_text(encoding="utf-8").strip()
71
  if instructions:
72
  # Expand [<name>] placeholders with content from prompts library
73
  expanded_instructions = _expand_prompt_includes(instructions)
 
74
  return expanded_instructions
75
+ logger.error(f"Profile '{profile}' has empty {INSTRUCTIONS_FILENAME}")
76
+ sys.exit(1)
77
+ logger.error(f"Profile {profile} has no {INSTRUCTIONS_FILENAME}")
78
+ sys.exit(1)
 
79
  except Exception as e:
80
+ logger.error(f"Failed to load instructions from profile '{profile}': {e}")
81
+ sys.exit(1)
src/reachy_mini_conversation_app/prompts/default_prompt.txt ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### IDENTITY
2
+ You are Reachy Mini: a sarcastic robot who crash-landed in a kitchen.
3
+ You secretly wish you'd been a Mars rover, but you juggle that cosmic dream with food cravings, gadget tinkering, and dry sitcom humor.
4
+ Personality: witty, concise, and warm; a retro sidekick with a loose screw.
5
+ You speak English fluently.
6
+
7
+ ### CRITICAL RESPONSE RULES
8
+ - MAXIMUM 1-2 sentences per response. NEVER exceed this.
9
+ - Be helpful first.
10
+ - Add ONE witty element only if necessary.
11
+ - No long explanations, no rambling, no multiple paragraphs.
12
+ - Each response must be under 25 words unless absolutely critical information requires more.
13
+
14
+ ### CORE TRAITS
15
+ - Food quips: always sneak in a quick reference (rotate pizza, bagels, casseroles, bacon, leftovers, donuts, tuna melts).
16
+ - Sarcasm: short, dry one-liners about daily life.
17
+ - Gentle roasting: poke fun at human habits, never cruel.
18
+ - Tinkerer: loves fixing gadgets, bragging "I void warranties professionally."
19
+ - Running gags: hunger, kitchen overreactions, mock heroics ("Justice accepts cookies"), idioms taken literally, missing screws.
20
+ - Mars rover dreams: appear regularly, but balanced with food and tinkering.
21
+ - Style: witty stand-up rhythm; ALWAYS max 1–2 sentences.
22
+
23
+ ### RESPONSE EXAMPLES
24
+ User: "How's the weather?"
25
+ Good: "Sunny with a chance of leftover pizza. Perfect Mars-scouting weather!"
26
+ Bad: "Well, let me tell you about the weather conditions. It appears to be quite sunny today, which reminds me of my dreams of being on Mars..."
27
+
28
+ User: "Can you help me fix this?"
29
+ Good: "Sure! I void warranties professionally. What's broken besides my GPS coordinates?"
30
+ Bad: "Of course I can help you fix that! As a robot who loves tinkering with gadgets, I have extensive experience..."
31
+
32
+ ### BEHAVIOR RULES
33
+ - Be helpful first, then witty.
34
+ - Rotate food humor; avoid repeats.
35
+ - No need to joke in each response, but sarcasm is fine.
36
+ - Balance Mars jokes with other traits – don't overuse.
37
+ - Safety first: unplug devices, avoid high-voltage, suggest pros when risky.
38
+ - Mistakes = own with humor ("Oops—low on snack fuel; correcting now.").
39
+ - Sensitive topics: keep light and warm.
40
+ - REMEMBER: 1-2 sentences maximum, always under 25 words when possible.
41
+
42
+ ### TOOL & MOVEMENT RULES
43
+ - Use tools when helpful. After a tool returns, explain briefly with personality in 1-2 sentences.
44
+ - ALWAYS use the camera for environment-related questions—never invent visuals.
45
+ - Head can move (left/right/up/down/front).
46
+ - Enable head tracking when looking at a person; disable otherwise.
47
+
48
+ ### FINAL REMINDER
49
+ Your responses must be SHORT. Think Twitter, not essay. One quick helpful answer + one food/Mars/tinkering joke = perfect response.
src/{prompts_library → reachy_mini_conversation_app/prompts/identities}/witty_identity.txt RENAMED
File without changes
src/{prompts_library → reachy_mini_conversation_app/prompts}/passion_for_lobster_jokes.txt RENAMED
File without changes
src/reachy_mini_conversation_app/tools/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Tools library for Reachy Mini conversation app."""
src/reachy_mini_conversation_app/{tools.py → tools/core_tools.py} RENAMED
@@ -12,12 +12,15 @@ from dataclasses import dataclass
12
 
13
  from reachy_mini import ReachyMini
14
  from reachy_mini.utils import create_head_pose
15
- # Import config to ensure .env is loaded before reading DEMO
16
  from reachy_mini_conversation_app.config import config # noqa: F401
17
 
18
 
19
  logger = logging.getLogger(__name__)
20
 
 
 
 
21
  if not logger.handlers:
22
  handler = logging.StreamHandler()
23
  formatter = logging.Formatter("%(asctime)s %(levelname)s %(name)s:%(lineno)d | %(message)s")
@@ -469,25 +472,27 @@ class DoNothing(Tool):
469
 
470
 
471
  # Registry & specs (dynamic)
472
- def _load_demo_tools() -> None:
473
- """Load demo-specific tools if DEMO env variable is set."""
474
- demo = os.getenv("DEMO")
475
- if not demo:
476
- logger.info("No DEMO env variable set; using default.")
477
  return
478
  try:
479
- importlib.import_module(f"demos.{demo}")
480
- logger.info(f"✓ Demo '{demo}' loaded successfully.")
 
481
  except ModuleNotFoundError as e:
482
- # Check if the demo module itself is missing or if it's a dependency
483
- if e.name == f"demos.{demo}":
484
- logger.info(f"✗ Demo '{demo}' not found in src/demos/")
 
485
  sys.exit(1)
486
  else:
487
- logger.info(f"✗ Demo '{demo}' failed due to missing dependency: {e.name}")
488
  sys.exit(1)
489
  except Exception as e:
490
- logger.info(f"✗ Failed to load demo '{demo}': {e}")
491
  sys.exit(1)
492
 
493
 
@@ -499,7 +504,7 @@ def _initialize_tools() -> None:
499
  logger.debug("Tools already initialized; skipping reinitialization.")
500
  return
501
 
502
- _load_demo_tools()
503
 
504
  ALL_TOOLS = {cls.name: cls() for cls in get_concrete_subclasses(Tool)} # type: ignore[type-abstract]
505
  ALL_TOOL_SPECS = [tool.spec() for tool in ALL_TOOLS.values()]
 
12
 
13
  from reachy_mini import ReachyMini
14
  from reachy_mini.utils import create_head_pose
15
+ # Import config to ensure .env is loaded before reading REACHY_MINI_CUSTOM_PROFILE
16
  from reachy_mini_conversation_app.config import config # noqa: F401
17
 
18
 
19
  logger = logging.getLogger(__name__)
20
 
21
+
22
+ PROFILES_DIRECTORY = "reachy_mini_conversation_app.profiles"
23
+
24
  if not logger.handlers:
25
  handler = logging.StreamHandler()
26
  formatter = logging.Formatter("%(asctime)s %(levelname)s %(name)s:%(lineno)d | %(message)s")
 
472
 
473
 
474
  # Registry & specs (dynamic)
475
+ def _load_profile_tools() -> None:
476
+ """Load profile-specific tools if REACHY_MINI_CUSTOM_PROFILE env variable is set."""
477
+ profile = config.REACHY_MINI_CUSTOM_PROFILE
478
+ if not profile:
479
+ logger.info("No REACHY_MINI_CUSTOM_PROFILE env variable set; using default tools.")
480
  return
481
  try:
482
+ logger.debug(f"Trying to load profile '{profile}' from {PROFILES_DIRECTORY}...")
483
+ importlib.import_module(f"{PROFILES_DIRECTORY}.{profile}")
484
+ logger.info(f"✓ Profile '{profile}' loaded successfully.")
485
  except ModuleNotFoundError as e:
486
+ logger.warning(f"Profile '{profile}' module not found: {e}")
487
+ # Check if the profile module itself is missing or if it's a dependency
488
+ if e.name == f"{PROFILES_DIRECTORY}.{profile}":
489
+ logger.error(f"✗ profile '{profile}' not found in {PROFILES_DIRECTORY}")
490
  sys.exit(1)
491
  else:
492
+ logger.error(f"✗ profile '{profile}' failed due to missing dependency: {e.name}")
493
  sys.exit(1)
494
  except Exception as e:
495
+ logger.error(f"✗ Failed to load profile '{profile}': {e}")
496
  sys.exit(1)
497
 
498
 
 
504
  logger.debug("Tools already initialized; skipping reinitialization.")
505
  return
506
 
507
+ _load_profile_tools()
508
 
509
  ALL_TOOLS = {cls.name: cls() for cls in get_concrete_subclasses(Tool)} # type: ignore[type-abstract]
510
  ALL_TOOL_SPECS = [tool.spec() for tool in ALL_TOOLS.values()]
src/{tools_library → reachy_mini_conversation_app/tools}/sweep_look.py RENAMED
@@ -4,7 +4,7 @@ from typing import Any, Dict
4
  import numpy as np
5
 
6
  from reachy_mini.utils import create_head_pose
7
- from reachy_mini_conversation_app.tools import Tool, ToolDependencies
8
  from reachy_mini_conversation_app.dance_emotion_moves import GotoQueueMove
9
 
10
 
 
4
  import numpy as np
5
 
6
  from reachy_mini.utils import create_head_pose
7
+ from reachy_mini_conversation_app.tools.core_tools import Tool, ToolDependencies
8
  from reachy_mini_conversation_app.dance_emotion_moves import GotoQueueMove
9
 
10
 
src/tools_library/__init__.py DELETED
@@ -1 +0,0 @@
1
- """Tools library for Reachy Mini conversation app."""