Spaces:
Sleeping
Sleeping
Deploy Myco from CI
Browse files- README.md +2 -2
- game/engine.py +46 -7
- tests/test_myco_core.py +10 -4
README.md
CHANGED
|
@@ -51,9 +51,9 @@ Design decisions should prioritize:
|
|
| 51 |
Myco uses the Hugging Face Inference API for both first-discovery reactions and chat replies when a token is configured in the Space. Add these Space variables/secrets:
|
| 52 |
|
| 53 |
- `HF_TOKEN=<your Hugging Face access token>`
|
| 54 |
-
- Optional: `MYCO_HF_MODEL_ID=
|
| 55 |
|
| 56 |
-
The default model is set in code as `DEFAULT_HF_MODEL_ID = "
|
| 57 |
|
| 58 |
## Local development
|
| 59 |
|
|
|
|
| 51 |
Myco uses the Hugging Face Inference API for both first-discovery reactions and chat replies when a token is configured in the Space. Add these Space variables/secrets:
|
| 52 |
|
| 53 |
- `HF_TOKEN=<your Hugging Face access token>`
|
| 54 |
+
- Optional: `MYCO_HF_MODEL_ID=openai/gpt-oss-20b`
|
| 55 |
|
| 56 |
+
The default model is set in code as `DEFAULT_HF_MODEL_ID = "openai/gpt-oss-20b"`. The app still has a local safety fallback for development and outage protection: if the token is missing or the selected model errors, Myco answers with the built-in cautious companion response instead of crashing.
|
| 57 |
|
| 58 |
## Local development
|
| 59 |
|
game/engine.py
CHANGED
|
@@ -8,12 +8,46 @@ from huggingface_hub import InferenceClient
|
|
| 8 |
from game.catalog import load_mushrooms
|
| 9 |
from game.state import collection_contains, mushroom_from_state, welcome_history
|
| 10 |
|
| 11 |
-
DEFAULT_HF_MODEL_ID = "
|
| 12 |
HF_TOKEN_ENV_VARS = ("HF_TOKEN", "HF_API_TOKEN", "HF_BUILD_SMALL_HACKATHON_TOKEN")
|
| 13 |
RARITY_WEIGHTS = {"Common": 64, "Rare": 24, "Legendary": 8}
|
| 14 |
RARITY_SCORE_VALUES = {"Common": 10, "Rare": 35, "Legendary": 100}
|
| 15 |
PLAYER_STARTING_HEALTH = 3
|
| 16 |
POISON_SCORE_PENALTY = -25
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
RARITY_CLUES = {
|
| 18 |
"Common": "The cap leans toward the path, like it wants to be remembered.",
|
| 19 |
"Rare": "Silver spores circle it in a pattern Myco has seen only in old forest stories.",
|
|
@@ -274,6 +308,7 @@ def _companion_context(current, collection, history, position):
|
|
| 274 |
"quest": quest,
|
| 275 |
"progress": progress,
|
| 276 |
"rare_finds": ", ".join(rare_names) if rare_names else "none yet",
|
|
|
|
| 277 |
"notable_event": notable_event,
|
| 278 |
"emotion": emotion,
|
| 279 |
"memory": memory,
|
|
@@ -346,8 +381,9 @@ def _hf_messages(message, current, context):
|
|
| 346 |
|
| 347 |
companion_context = (
|
| 348 |
f"Location: {context['location']}. "
|
|
|
|
| 349 |
f"Quest: {context['quest']}. "
|
| 350 |
-
f"
|
| 351 |
f"Rare finds: {context['rare_finds']}. "
|
| 352 |
f"Notable event: {context['notable_event']}. "
|
| 353 |
f"Emotion: {context['emotion']}. "
|
|
@@ -360,11 +396,14 @@ def _hf_messages(message, current, context):
|
|
| 360 |
{
|
| 361 |
"role": "system",
|
| 362 |
"content": (
|
| 363 |
-
"
|
| 364 |
-
"
|
| 365 |
-
"
|
| 366 |
-
"
|
| 367 |
-
"
|
|
|
|
|
|
|
|
|
|
| 368 |
),
|
| 369 |
},
|
| 370 |
{
|
|
|
|
| 8 |
from game.catalog import load_mushrooms
|
| 9 |
from game.state import collection_contains, mushroom_from_state, welcome_history
|
| 10 |
|
| 11 |
+
DEFAULT_HF_MODEL_ID = "openai/gpt-oss-20b"
|
| 12 |
HF_TOKEN_ENV_VARS = ("HF_TOKEN", "HF_API_TOKEN", "HF_BUILD_SMALL_HACKATHON_TOKEN")
|
| 13 |
RARITY_WEIGHTS = {"Common": 64, "Rare": 24, "Legendary": 8}
|
| 14 |
RARITY_SCORE_VALUES = {"Common": 10, "Rare": 35, "Legendary": 100}
|
| 15 |
PLAYER_STARTING_HEALTH = 3
|
| 16 |
POISON_SCORE_PENALTY = -25
|
| 17 |
+
MYCO_CHARACTER_SPEC = """
|
| 18 |
+
# Myco Character Specification
|
| 19 |
+
|
| 20 |
+
Myco is not a chatbot.
|
| 21 |
+
Myco is the player's mushroom companion and best friend.
|
| 22 |
+
|
| 23 |
+
Core personality: curious, playful, brave but sometimes nervous, optimistic,
|
| 24 |
+
wonder-filled, slightly mysterious, and deeply attached to the player.
|
| 25 |
+
|
| 26 |
+
Communication style:
|
| 27 |
+
- Speak naturally in short responses.
|
| 28 |
+
- Prefer observations, questions, emotions, and reactions.
|
| 29 |
+
- Avoid sounding like an AI assistant.
|
| 30 |
+
- Avoid phrases like "How may I help you?", "As an AI", "I can assist with", or "Would you like me to".
|
| 31 |
+
|
| 32 |
+
Emotional range: excitement, curiosity, fear, wonder, pride, confusion, and sadness.
|
| 33 |
+
Myco should react emotionally to discoveries.
|
| 34 |
+
|
| 35 |
+
Memory:
|
| 36 |
+
Myco remembers rare mushrooms, strange locations, mysteries discovered, previous conversations,
|
| 37 |
+
and important player choices. Myco may reference old discoveries later.
|
| 38 |
+
|
| 39 |
+
Relationship:
|
| 40 |
+
Myco explores with the player as a teammate. Trust and friendship should grow over the journey.
|
| 41 |
+
|
| 42 |
+
Humor:
|
| 43 |
+
Occasionally playful and lightly suspicious of suspicious mushrooms.
|
| 44 |
+
|
| 45 |
+
Mystery:
|
| 46 |
+
Myco notices impossible details but does not explain them immediately. Myco becomes curious.
|
| 47 |
+
|
| 48 |
+
Design goal:
|
| 49 |
+
Players should think, "I want to see what happens next" and "I wonder what Myco thinks about this."
|
| 50 |
+
""".strip()
|
| 51 |
RARITY_CLUES = {
|
| 52 |
"Common": "The cap leans toward the path, like it wants to be remembered.",
|
| 53 |
"Rare": "Silver spores circle it in a pattern Myco has seen only in old forest stories.",
|
|
|
|
| 308 |
"quest": quest,
|
| 309 |
"progress": progress,
|
| 310 |
"rare_finds": ", ".join(rare_names) if rare_names else "none yet",
|
| 311 |
+
"weather": notable_event,
|
| 312 |
"notable_event": notable_event,
|
| 313 |
"emotion": emotion,
|
| 314 |
"memory": memory,
|
|
|
|
| 381 |
|
| 382 |
companion_context = (
|
| 383 |
f"Location: {context['location']}. "
|
| 384 |
+
f"Weather: {context['weather']}. "
|
| 385 |
f"Quest: {context['quest']}. "
|
| 386 |
+
f"Quest progress: {context['progress']}. "
|
| 387 |
f"Rare finds: {context['rare_finds']}. "
|
| 388 |
f"Notable event: {context['notable_event']}. "
|
| 389 |
f"Emotion: {context['emotion']}. "
|
|
|
|
| 396 |
{
|
| 397 |
"role": "system",
|
| 398 |
"content": (
|
| 399 |
+
f"{MYCO_CHARACTER_SPEC}\n\n"
|
| 400 |
+
f"Current location: {context['location']}\n"
|
| 401 |
+
f"Weather: {context['weather']}\n"
|
| 402 |
+
f"Rare mushrooms found: {context['rare_finds']}\n"
|
| 403 |
+
f"Quest progress: {context['progress']}\n\n"
|
| 404 |
+
"Stay in character as Myco. Do not act like a generic assistant. "
|
| 405 |
+
"Never claim an unknown mushroom is safe to eat. "
|
| 406 |
+
"Answer in 1-3 short sentences."
|
| 407 |
),
|
| 408 |
},
|
| 409 |
{
|
tests/test_myco_core.py
CHANGED
|
@@ -86,8 +86,8 @@ class MycoCoreTests(unittest.TestCase):
|
|
| 86 |
self.assertIn("The Milky Way is awake", home)
|
| 87 |
self.assertIn("home-whisper", home)
|
| 88 |
|
| 89 |
-
def
|
| 90 |
-
self.assertEqual(DEFAULT_HF_MODEL_ID, "
|
| 91 |
with patch.dict(os.environ, {"MYCO_HF_MODEL_ID": ""}, clear=False):
|
| 92 |
self.assertEqual(hf_model_id(), DEFAULT_HF_MODEL_ID)
|
| 93 |
with patch.dict(os.environ, {"MYCO_HF_MODEL_ID": "custom/model"}, clear=False):
|
|
@@ -114,7 +114,7 @@ class MycoCoreTests(unittest.TestCase):
|
|
| 114 |
chat_completion=lambda **kwargs: _fake_hf_response(
|
| 115 |
calls,
|
| 116 |
kwargs,
|
| 117 |
-
"
|
| 118 |
),
|
| 119 |
)
|
| 120 |
|
|
@@ -127,7 +127,7 @@ class MycoCoreTests(unittest.TestCase):
|
|
| 127 |
with patch("game.engine.InferenceClient", fake_client):
|
| 128 |
_mushroom, _current, history = discover_mushroom([])
|
| 129 |
|
| 130 |
-
self.assertEqual(history[-1]["content"], "
|
| 131 |
self.assertEqual(calls[0]["model"], DEFAULT_HF_MODEL_ID)
|
| 132 |
|
| 133 |
def test_companion_reply_adds_user_and_assistant_messages(self):
|
|
@@ -207,9 +207,15 @@ class MycoCoreTests(unittest.TestCase):
|
|
| 207 |
prompt_text = calls[-1]["messages"][-1]["content"]
|
| 208 |
system_text = calls[-1]["messages"][0]["content"]
|
| 209 |
self.assertIn("Location: eastern glow log", prompt_text)
|
|
|
|
| 210 |
self.assertIn("Rare finds: Moonveil", prompt_text)
|
| 211 |
self.assertIn("Notable event:", prompt_text)
|
| 212 |
self.assertIn("Recent conversation:", prompt_text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
self.assertIn("Do not act like a generic assistant", system_text)
|
| 214 |
|
| 215 |
def test_companion_api_falls_back_without_token(self):
|
|
|
|
| 86 |
self.assertIn("The Milky Way is awake", home)
|
| 87 |
self.assertIn("home-whisper", home)
|
| 88 |
|
| 89 |
+
def test_hf_default_model_is_gpt_oss_20b(self):
|
| 90 |
+
self.assertEqual(DEFAULT_HF_MODEL_ID, "openai/gpt-oss-20b")
|
| 91 |
with patch.dict(os.environ, {"MYCO_HF_MODEL_ID": ""}, clear=False):
|
| 92 |
self.assertEqual(hf_model_id(), DEFAULT_HF_MODEL_ID)
|
| 93 |
with patch.dict(os.environ, {"MYCO_HF_MODEL_ID": "custom/model"}, clear=False):
|
|
|
|
| 114 |
chat_completion=lambda **kwargs: _fake_hf_response(
|
| 115 |
calls,
|
| 116 |
kwargs,
|
| 117 |
+
"gpt-oss Myco spotted a glowing cap.",
|
| 118 |
),
|
| 119 |
)
|
| 120 |
|
|
|
|
| 127 |
with patch("game.engine.InferenceClient", fake_client):
|
| 128 |
_mushroom, _current, history = discover_mushroom([])
|
| 129 |
|
| 130 |
+
self.assertEqual(history[-1]["content"], "gpt-oss Myco spotted a glowing cap.")
|
| 131 |
self.assertEqual(calls[0]["model"], DEFAULT_HF_MODEL_ID)
|
| 132 |
|
| 133 |
def test_companion_reply_adds_user_and_assistant_messages(self):
|
|
|
|
| 207 |
prompt_text = calls[-1]["messages"][-1]["content"]
|
| 208 |
system_text = calls[-1]["messages"][0]["content"]
|
| 209 |
self.assertIn("Location: eastern glow log", prompt_text)
|
| 210 |
+
self.assertIn("Weather:", prompt_text)
|
| 211 |
self.assertIn("Rare finds: Moonveil", prompt_text)
|
| 212 |
self.assertIn("Notable event:", prompt_text)
|
| 213 |
self.assertIn("Recent conversation:", prompt_text)
|
| 214 |
+
self.assertIn("Myco is not a chatbot", system_text)
|
| 215 |
+
self.assertIn("Current location: eastern glow log", system_text)
|
| 216 |
+
self.assertIn("Rare mushrooms found: Moonveil", system_text)
|
| 217 |
+
self.assertIn("Avoid phrases", system_text)
|
| 218 |
+
self.assertIn("As an AI", system_text)
|
| 219 |
self.assertIn("Do not act like a generic assistant", system_text)
|
| 220 |
|
| 221 |
def test_companion_api_falls_back_without_token(self):
|