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 CHANGED
@@ -1,56 +1,37 @@
1
- FROM ghcr.io/meta-pytorch/openenv-base:latest AS builder
 
 
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 if ! command -v uv >/dev/null 2>&1; then \
17
- curl -LsSf https://astral.sh/uv/install.sh | sh && \
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 ghcr.io/meta-pytorch/openenv-base:latest
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 /app/env/.venv /app/.venv
46
  COPY --from=builder /app/env /app/env
47
 
48
- ENV PATH="/app/.venv/bin:$PATH"
49
  ENV PYTHONPATH="/app/env:$PYTHONPATH"
50
 
51
- # Prevent any HuggingFace model downloads (use Gemini API instead)
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("Both Gemini SDKs failed: %s. Using template fallback.", e)
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