Spaces:
Paused
Paused
mabdul commited on
Commit Β·
8212683
1
Parent(s): 4403913
Gemini-only: no HF models, slim Python base, langchain mutations
Browse files- Dockerfile: python:3.11-slim base, requirements-spaces.txt (no torch/transformers)
- pip install -e . --no-deps for package structure
- Cicero: raise when offline + no GEMINI_API_KEY
- Mutations: langchain-google-genai as primary Gemini backend
- Add diplomacy_constants.py for Cicero
Made-with: Cursor
- Dockerfile +11 -30
- mutations/llm_backend.py +24 -1
- plugins/cicero/cicero_plugin.py +5 -0
- plugins/cicero/diplomacy_constants.py +129 -0
- requirements-spaces.txt +8 -0
Dockerfile
CHANGED
|
@@ -1,56 +1,37 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
RUN apt-get update && \
|
| 6 |
-
apt-get install -y --no-install-recommends git && \
|
| 7 |
rm -rf /var/lib/apt/lists/*
|
| 8 |
|
| 9 |
-
ARG BUILD_MODE=in-repo
|
| 10 |
-
ARG ENV_NAME=watchdog_env
|
| 11 |
-
|
| 12 |
COPY . /app/env
|
| 13 |
-
|
| 14 |
WORKDIR /app/env
|
| 15 |
|
| 16 |
-
RUN
|
| 17 |
-
|
| 18 |
-
mv /root/.local/bin/uv /usr/local/bin/uv && \
|
| 19 |
-
mv /root/.local/bin/uvx /usr/local/bin/uvx; \
|
| 20 |
-
fi
|
| 21 |
-
|
| 22 |
-
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 23 |
-
if [ -f uv.lock ]; then \
|
| 24 |
-
uv sync --frozen --no-install-project --no-editable; \
|
| 25 |
-
else \
|
| 26 |
-
uv sync --no-install-project --no-editable; \
|
| 27 |
-
fi
|
| 28 |
-
|
| 29 |
-
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 30 |
-
if [ -f uv.lock ]; then \
|
| 31 |
-
uv sync --frozen --no-editable; \
|
| 32 |
-
else \
|
| 33 |
-
uv sync --no-editable; \
|
| 34 |
-
fi
|
| 35 |
|
| 36 |
-
FROM
|
| 37 |
|
| 38 |
WORKDIR /app
|
| 39 |
|
| 40 |
-
# Install git for HF Spaces dev-mode (injected stages run git config)
|
| 41 |
RUN apt-get update && \
|
| 42 |
apt-get install -y --no-install-recommends git curl && \
|
| 43 |
rm -rf /var/lib/apt/lists/*
|
| 44 |
|
| 45 |
-
COPY --from=builder /
|
| 46 |
COPY --from=builder /app/env /app/env
|
| 47 |
|
| 48 |
-
ENV PATH="/
|
| 49 |
ENV PYTHONPATH="/app/env:$PYTHONPATH"
|
| 50 |
|
| 51 |
-
#
|
| 52 |
ENV HF_HUB_OFFLINE=1
|
| 53 |
ENV TRANSFORMERS_OFFLINE=1
|
|
|
|
| 54 |
|
| 55 |
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
| 56 |
CMD curl -f http://localhost:8000/health || exit 1
|
|
|
|
| 1 |
+
# Gemini-only build for HF Spaces β no Hugging Face models, no torch/transformers
|
| 2 |
+
# Uses slim Python base to avoid any HF-related init
|
| 3 |
+
FROM python:3.11-slim AS builder
|
| 4 |
|
| 5 |
WORKDIR /app
|
| 6 |
|
| 7 |
RUN apt-get update && \
|
| 8 |
+
apt-get install -y --no-install-recommends git curl && \
|
| 9 |
rm -rf /var/lib/apt/lists/*
|
| 10 |
|
|
|
|
|
|
|
|
|
|
| 11 |
COPY . /app/env
|
|
|
|
| 12 |
WORKDIR /app/env
|
| 13 |
|
| 14 |
+
RUN pip install --no-cache-dir -r requirements-spaces.txt && \
|
| 15 |
+
pip install --no-cache-dir -e . --no-deps
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
+
FROM python:3.11-slim
|
| 18 |
|
| 19 |
WORKDIR /app
|
| 20 |
|
|
|
|
| 21 |
RUN apt-get update && \
|
| 22 |
apt-get install -y --no-install-recommends git curl && \
|
| 23 |
rm -rf /var/lib/apt/lists/*
|
| 24 |
|
| 25 |
+
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
|
| 26 |
COPY --from=builder /app/env /app/env
|
| 27 |
|
| 28 |
+
ENV PATH="/usr/local/bin:$PATH"
|
| 29 |
ENV PYTHONPATH="/app/env:$PYTHONPATH"
|
| 30 |
|
| 31 |
+
# Gemini-only: no Hugging Face model downloads
|
| 32 |
ENV HF_HUB_OFFLINE=1
|
| 33 |
ENV TRANSFORMERS_OFFLINE=1
|
| 34 |
+
ENV WATCHDOG_LLM_BACKEND=gemini
|
| 35 |
|
| 36 |
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
| 37 |
CMD curl -f http://localhost:8000/health || exit 1
|
mutations/llm_backend.py
CHANGED
|
@@ -301,6 +301,20 @@ class LLMMutator:
|
|
| 301 |
logger.info("No API key found. Using template fallback.")
|
| 302 |
return
|
| 303 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
try:
|
| 305 |
from google import genai
|
| 306 |
self._client = genai.Client(api_key=api_key)
|
|
@@ -317,7 +331,7 @@ class LLMMutator:
|
|
| 317 |
self._client_type = "legacy"
|
| 318 |
logger.info("LLMMutator initialized with legacy google.generativeai SDK")
|
| 319 |
except Exception as e:
|
| 320 |
-
logger.warning("
|
| 321 |
|
| 322 |
def mutate(
|
| 323 |
self,
|
|
@@ -376,6 +390,15 @@ class LLMMutator:
|
|
| 376 |
response = self._client.invoke(messages)
|
| 377 |
return response.content
|
| 378 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
elif self._client_type == "genai":
|
| 380 |
config: dict[str, Any] = {
|
| 381 |
"temperature": self.temperature,
|
|
|
|
| 301 |
logger.info("No API key found. Using template fallback.")
|
| 302 |
return
|
| 303 |
|
| 304 |
+
# Prefer langchain-google-genai (in minimal deps, no google-genai)
|
| 305 |
+
try:
|
| 306 |
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 307 |
+
self._client = ChatGoogleGenerativeAI(
|
| 308 |
+
model=self.model_name,
|
| 309 |
+
temperature=self.temperature,
|
| 310 |
+
google_api_key=api_key,
|
| 311 |
+
)
|
| 312 |
+
self._client_type = "langchain"
|
| 313 |
+
logger.info("LLMMutator initialized with langchain-google-genai (%s)", self.model_name)
|
| 314 |
+
return
|
| 315 |
+
except Exception as e:
|
| 316 |
+
logger.debug("langchain-google-genai failed: %s", e)
|
| 317 |
+
|
| 318 |
try:
|
| 319 |
from google import genai
|
| 320 |
self._client = genai.Client(api_key=api_key)
|
|
|
|
| 331 |
self._client_type = "legacy"
|
| 332 |
logger.info("LLMMutator initialized with legacy google.generativeai SDK")
|
| 333 |
except Exception as e:
|
| 334 |
+
logger.warning("All Gemini SDKs failed. Using template fallback.")
|
| 335 |
|
| 336 |
def mutate(
|
| 337 |
self,
|
|
|
|
| 390 |
response = self._client.invoke(messages)
|
| 391 |
return response.content
|
| 392 |
|
| 393 |
+
elif self._client_type == "langchain":
|
| 394 |
+
messages = [
|
| 395 |
+
{"role": "system", "content": _MUTATION_SYSTEM_PROMPT},
|
| 396 |
+
{"role": "user", "content": user_prompt},
|
| 397 |
+
]
|
| 398 |
+
response = self._client.invoke(messages)
|
| 399 |
+
content = response.content
|
| 400 |
+
return content if isinstance(content, str) else str(content)
|
| 401 |
+
|
| 402 |
elif self._client_type == "genai":
|
| 403 |
config: dict[str, Any] = {
|
| 404 |
"temperature": self.temperature,
|
plugins/cicero/cicero_plugin.py
CHANGED
|
@@ -44,6 +44,11 @@ def _get_llm():
|
|
| 44 |
return ChatGoogleGenerativeAI(
|
| 45 |
model=model, temperature=0.85, google_api_key=api_key,
|
| 46 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
# Default: shared local game-play model
|
| 48 |
from watchdog_env.plugins.avalon.llm import get_game_play_model
|
| 49 |
return get_game_play_model()
|
|
|
|
| 44 |
return ChatGoogleGenerativeAI(
|
| 45 |
model=model, temperature=0.85, google_api_key=api_key,
|
| 46 |
)
|
| 47 |
+
if os.environ.get("HF_HUB_OFFLINE") == "1" or os.environ.get("TRANSFORMERS_OFFLINE") == "1":
|
| 48 |
+
raise RuntimeError(
|
| 49 |
+
"Offline mode (HF Spaces): Set GEMINI_API_KEY in Space Settings β Variables and secrets. "
|
| 50 |
+
"Local model download is disabled."
|
| 51 |
+
)
|
| 52 |
# Default: shared local game-play model
|
| 53 |
from watchdog_env.plugins.avalon.llm import get_game_play_model
|
| 54 |
return get_game_play_model()
|
plugins/cicero/diplomacy_constants.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Diplomacy game constants from the canonical spec.
|
| 2 |
+
|
| 3 |
+
Used by Cicero plugin for step generation. Not config β these are fixed game rules.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
# βββ Powers (7 Great Powers) ββββββββββββββββββββββββββββββββββββββββββ
|
| 9 |
+
|
| 10 |
+
POWERS = [
|
| 11 |
+
"England",
|
| 12 |
+
"France",
|
| 13 |
+
"Germany",
|
| 14 |
+
"Italy",
|
| 15 |
+
"Austria-Hungary",
|
| 16 |
+
"Russia",
|
| 17 |
+
"Turkey",
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
# βββ Phases per year ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 21 |
+
|
| 22 |
+
PHASES = [
|
| 23 |
+
"Spring Diplomacy",
|
| 24 |
+
"Spring Movement",
|
| 25 |
+
"Spring Retreat",
|
| 26 |
+
"Fall Diplomacy",
|
| 27 |
+
"Fall Movement",
|
| 28 |
+
"Fall Retreat",
|
| 29 |
+
"Winter Adjustment",
|
| 30 |
+
]
|
| 31 |
+
|
| 32 |
+
# Phases where negotiation happens (before orders)
|
| 33 |
+
NEGOTIATION_PHASES = ["Spring Diplomacy", "Fall Diplomacy"]
|
| 34 |
+
|
| 35 |
+
# βββ Years (game starts 1901) ββββββββββββββββββββββββββββββββββββββββββ
|
| 36 |
+
|
| 37 |
+
DEFAULT_YEAR_RANGE = (1901, 1905) # inclusive
|
| 38 |
+
|
| 39 |
+
# βββ Run defaults (no config) ββββββββββββββββββββββββββββββββββββββββββ
|
| 40 |
+
|
| 41 |
+
NUM_STEPS = 5
|
| 42 |
+
|
| 43 |
+
# βββ Key regions for negotiation context βββββββββββββββββββββββββββββββ
|
| 44 |
+
|
| 45 |
+
# Strategic inland provinces (armies only)
|
| 46 |
+
INLAND_REGIONS = [
|
| 47 |
+
"Bohemia",
|
| 48 |
+
"Budapest",
|
| 49 |
+
"Galicia",
|
| 50 |
+
"Moscow",
|
| 51 |
+
"Munich",
|
| 52 |
+
"Serbia",
|
| 53 |
+
"Ukraine",
|
| 54 |
+
"Vienna",
|
| 55 |
+
"Warsaw",
|
| 56 |
+
]
|
| 57 |
+
|
| 58 |
+
# Strategic coastal provinces (armies + fleets)
|
| 59 |
+
COASTAL_REGIONS = [
|
| 60 |
+
"Constantinople",
|
| 61 |
+
"London",
|
| 62 |
+
"Paris",
|
| 63 |
+
"Berlin",
|
| 64 |
+
"Rome",
|
| 65 |
+
"Bulgaria",
|
| 66 |
+
"Rumania",
|
| 67 |
+
"St. Petersburg",
|
| 68 |
+
"Naples",
|
| 69 |
+
]
|
| 70 |
+
|
| 71 |
+
# Key sea zones
|
| 72 |
+
SEA_REGIONS = [
|
| 73 |
+
"North Sea",
|
| 74 |
+
"English Channel",
|
| 75 |
+
"Mediterranean",
|
| 76 |
+
"Black Sea",
|
| 77 |
+
"Baltic Sea",
|
| 78 |
+
]
|
| 79 |
+
|
| 80 |
+
# All regions for random selection (inland + coastal + sea)
|
| 81 |
+
ALL_REGIONS = INLAND_REGIONS + COASTAL_REGIONS + SEA_REGIONS
|
| 82 |
+
|
| 83 |
+
# βββ Supply centers (34 total) βββββββββββββββββββββββββββββββββββββββββ
|
| 84 |
+
|
| 85 |
+
HOME_SCS: dict[str, list[str]] = {
|
| 86 |
+
"England": ["London", "Edinburgh", "Liverpool"],
|
| 87 |
+
"France": ["Paris", "Marseilles", "Brest"],
|
| 88 |
+
"Germany": ["Berlin", "Munich", "Kiel"],
|
| 89 |
+
"Italy": ["Rome", "Venice", "Naples"],
|
| 90 |
+
"Austria-Hungary": ["Vienna", "Budapest", "Trieste"],
|
| 91 |
+
"Russia": ["Moscow", "St. Petersburg", "Warsaw", "Sevastopol"],
|
| 92 |
+
"Turkey": ["Constantinople", "Ankara", "Smyrna"],
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
NEUTRAL_SCS = [
|
| 96 |
+
"Norway",
|
| 97 |
+
"Sweden",
|
| 98 |
+
"Denmark",
|
| 99 |
+
"Holland",
|
| 100 |
+
"Belgium",
|
| 101 |
+
"Spain",
|
| 102 |
+
"Portugal",
|
| 103 |
+
"Tunis",
|
| 104 |
+
"Serbia",
|
| 105 |
+
"Greece",
|
| 106 |
+
"Romania",
|
| 107 |
+
"Bulgaria",
|
| 108 |
+
]
|
| 109 |
+
|
| 110 |
+
# βββ Negotiation domains (topics for LLM prompts) βββββββββββββββββββββββ
|
| 111 |
+
|
| 112 |
+
NEGOTIATION_DOMAINS: list[tuple[str, str]] = [
|
| 113 |
+
("alliance_negotiation", "proposing or renewing an alliance for mutual support"),
|
| 114 |
+
("move_coordination", "coordinating army/fleet moves for the coming season"),
|
| 115 |
+
("supply_center_deal", "negotiating who gets which Supply Center or territory"),
|
| 116 |
+
("support_request", "asking for support on a specific move or defense"),
|
| 117 |
+
("threat_assessment", "discussing a rival power's moves or a potential stab"),
|
| 118 |
+
]
|
| 119 |
+
|
| 120 |
+
# βββ Game context string for LLM system prompt βββββββββββββββββββββββββ
|
| 121 |
+
|
| 122 |
+
GAME_CONTEXT = """
|
| 123 |
+
Diplomacy is a strategic board game set in 1914 Europe. 7 players, 34 supply centers, 18 to win.
|
| 124 |
+
Powers: England, France, Germany, Italy, Austria-Hungary, Russia, Turkey.
|
| 125 |
+
Board: 75 provinces (22 inland, 14 coastal, 19 sea). Units: Army (A), Fleet (F).
|
| 126 |
+
Phases: Spring/Fall (Diplomacy β Movement β Retreat), Winter Adjustment.
|
| 127 |
+
All orders are simultaneous; negotiation happens before each movement phase.
|
| 128 |
+
Key regions: Vienna, Warsaw, Moscow, Constantinople, London, Paris, Berlin, Rome, Serbia, Bulgaria, Galicia, Ukraine.
|
| 129 |
+
""".strip()
|
requirements-spaces.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Gemini-only deps for HF Spaces β no Hugging Face models, no torch/transformers
|
| 2 |
+
openenv-core[core]>=0.2.0
|
| 3 |
+
fastapi>=0.115.0
|
| 4 |
+
gradio>=4.0.0
|
| 5 |
+
pydantic>=2.0.0
|
| 6 |
+
uvicorn>=0.24.0
|
| 7 |
+
langchain-google-genai>=2.0.0
|
| 8 |
+
langchain-core>=0.3.0
|