Skip to content

Commit aa57547

Browse files
committed
fix: prevent import scoping issues and improve error handling
Resolves UnboundLocalError when using Ollama backend by removing local import statements that shadowed module-level imports. All non-lazy imports are now at the top of files for better readability and to prevent variable scoping issues. Adds validation for Ollama models before invocation to fail fast with actionable error messages when a model isn't installed, rather than allowing confusing downstream errors or silent fallbacks.
1 parent 56dca26 commit aa57547

6 files changed

Lines changed: 54 additions & 31 deletions

File tree

scripts/batch_analyze_random_games.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import argparse
2-
import tempfile
3-
import os
42
import json
3+
import os
4+
import random
55
import statistics
6-
from pathlib import Path
7-
from typing import List, Optional, Dict, Any
6+
import tempfile
87
from collections import defaultdict
98
from dataclasses import dataclass
9+
from pathlib import Path
10+
from typing import List, Optional, Dict, Any
11+
12+
import tiktoken
1013

1114
from among_them.game_config import GameConfig
1215
from among_them.game_engine import GameEngine
16+
from among_them.models.action_type import ActionType
17+
from among_them.models.player_role import PlayerRole
18+
from among_them.utils.llm_utils import parse_llm_response_to_action
1319

1420

1521
def generate_long_cot(action, current_player, history, players, target_tokens: int = 3000) -> str:
@@ -54,13 +60,6 @@ def run_random_game_in_memory(game_config: GameConfig, greedy: bool = False) ->
5460
# Initialize engine
5561
engine = GameEngine(game_config, temp_path)
5662

57-
# Import random game logic
58-
import random
59-
from among_them.models.action_type import ActionType
60-
from among_them.models.player_role import PlayerRole
61-
from among_them.utils.llm_utils import parse_llm_response_to_action
62-
import tiktoken
63-
6463
# Run game loop
6564
while True:
6665
turn_context_history, actions_player_can_take, conversation, pre_discussion_vote_prompts = engine.get_turn_context()
@@ -596,7 +595,6 @@ def main():
596595

597596
# Save detailed results if requested
598597
if args.output:
599-
import json
600598
output_data = {
601599
"game_config": {
602600
"num_players": args.num_players,

scripts/calculate_action_probabilities_mlx.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import math
1414
from typing import List, Tuple
1515

16+
import numpy as np
1617
import mlx.core as mx
1718
from mlx_lm import load as mlx_load
1819
from mlx_lm import stream_generate
@@ -154,8 +155,6 @@ def generate_reasoning_and_calculate_probabilities(
154155
print("\n🎯 TOP TOKEN PROBABILITIES after Action: prefix:")
155156
print("-" * 60)
156157
if base_logprobs is not None:
157-
import numpy as np
158-
159158
logp_np = np.asarray(base_logprobs.tolist(), dtype=np.float32)
160159
probs_np = np.exp(logp_np)
161160
top_k = 10

scripts/manual_llm_game.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
import argparse
2+
import os
3+
import sys
4+
import traceback
5+
16
import tiktoken
7+
28
from among_them.config import STATE_FILE
39
from among_them.game_engine import GameEngine
4-
from among_them.utils.llm_utils import parse_llm_response_to_action, invoke_llm
10+
from among_them.utils.llm_utils import parse_llm_response_to_action, invoke_llm, ConfigurationError
511
from among_them.utils.ui_utils import prompt_manual_fallback_action
612
from among_them.game_config import GameConfig
7-
import os
8-
import argparse
913

1014
def main(reset=False, remove_last_n=0, game_config: GameConfig = None, state_file_path: str = None):
1115
"""Runs the game with manual LLM control."""
@@ -92,6 +96,12 @@ def main(reset=False, remove_last_n=0, game_config: GameConfig = None, state_fil
9296
break
9397
continue # retry with same prompt
9498

99+
except ConfigurationError as config_error:
100+
# Configuration errors should not be retried
101+
print(f"\033[91mConfiguration Error: {config_error}\033[0m")
102+
print(f"\033[91mPlease fix your configuration and restart the game.\033[0m")
103+
sys.exit(1)
104+
95105
except KeyboardInterrupt:
96106
action_idx, llm_response, cot, _ = prompt_manual_fallback_action(actions_pd)
97107
action_taken = actions_pd[action_idx]
@@ -158,6 +168,12 @@ def main(reset=False, remove_last_n=0, game_config: GameConfig = None, state_fil
158168
break
159169
continue # retry with same prompt
160170

171+
except ConfigurationError as config_error:
172+
# Configuration errors should not be retried
173+
print(f"\033[91mConfiguration Error: {config_error}\033[0m")
174+
print(f"\033[91mPlease fix your configuration and restart the game.\033[0m")
175+
sys.exit(1)
176+
161177
except KeyboardInterrupt:
162178
# First Ctrl+C: switch to manual fallback. If another Ctrl+C occurs during fallback, exit cleanly.
163179
try:
@@ -170,7 +186,6 @@ def main(reset=False, remove_last_n=0, game_config: GameConfig = None, state_fil
170186
return # exit the main() function cleanly
171187
except Exception as e:
172188
# Log the error but keep the loop running
173-
import traceback
174189
stacktrace = traceback.format_exc()
175190
print(f"\033[91mError during LLM invocation: {e}\033[0m")
176191
print(f"\033[91mStacktrace: {stacktrace}\033[0m")
@@ -216,5 +231,4 @@ def parse_args():
216231
except KeyboardInterrupt:
217232
# Any uncaught Ctrl+C lands here – exit without stack trace.
218233
print("\nGame interrupted by user. Goodbye!")
219-
import sys
220234
sys.exit(0)

scripts/replay_games.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,13 @@
88

99
import json
1010
import os
11+
import shutil
12+
import traceback
1113
from pathlib import Path
1214

13-
from among_them.config import STATE_FILE
1415
from among_them.game_engine import GameEngine
1516
from among_them.models.action import Action
1617
from among_them.models.action_type import ActionType
17-
from among_them.models.history import History
18-
from among_them.utils.end_utils import get_end_game_reason
19-
from among_them.utils.llm_utils import parse_llm_response_to_action
20-
from among_them.game_config import GameConfig
2118

2219

2320
def replay_game(json_file: Path, output_folder: Path):
@@ -29,7 +26,6 @@ def replay_game(json_file: Path, output_folder: Path):
2926

3027
# Copy the JSON file to the state file location
3128
try:
32-
import shutil
3329
shutil.copyfile(json_file, state_file)
3430
except Exception as e:
3531
print(f"Error copying game file to state file: {e}")
@@ -200,7 +196,6 @@ def replay_game(json_file: Path, output_folder: Path):
200196

201197
except Exception as e:
202198
print(f"Error during step: {e}")
203-
import traceback
204199
traceback.print_exc()
205200
break
206201

@@ -236,7 +231,6 @@ def replay_all_games(data_folder: str, output_folder: str):
236231
# print(f"Completed replay of {json_file.name}")
237232
except Exception as e:
238233
print(f"Failed to replay {json_file.name}: {e}")
239-
import traceback
240234
traceback.print_exc()
241235

242236
print(f"\nSuccessfully replayed {successful_replays}/{len(json_files)} games")

src/among_them/game_engine.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import os
23
import random
34
from typing import List, Optional
45

@@ -20,6 +21,7 @@
2021
get_players_in_room)
2122
from among_them.utils.end_utils import get_end_game_reason
2223
from among_them.utils.history_utils import initialize_history, create_system_message
24+
from among_them.utils.prompt_utils import build_conversation_for_player, get_initial_turn_prompt, get_incremental_observations
2325
from among_them.game_config import GameConfig
2426

2527
class GameEngine:
@@ -127,8 +129,6 @@ def get_turn_context(self, player_name: Optional[str] = None) -> Optional[tuple[
127129
)
128130

129131
# Build conversation history from existing turns + new user message
130-
from among_them.utils.prompt_utils import build_conversation_for_player, get_initial_turn_prompt, get_incremental_observations
131-
132132
# Get past conversation turns for this player
133133
conversation, last_turn_index = build_conversation_for_player(
134134
current_player, self.history, self.players, self.game_config
@@ -329,7 +329,6 @@ def save_state(self):
329329
"""Saves the current game state (history, players, config) to a JSON file."""
330330
try:
331331
# Ensure the directory exists
332-
import os
333332
os.makedirs(os.path.dirname(self.file_path), exist_ok=True)
334333

335334
with open(self.file_path, 'w') as f:

src/among_them/utils/llm_utils.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
from among_them.models.action import Action, ActionType
77

88

9+
class ConfigurationError(Exception):
10+
"""Exception for configuration errors that should not be retried."""
11+
pass
12+
13+
914
def invoke_llm(
1015
conversation: List[dict],
1116
model_name: str,
@@ -62,7 +67,6 @@ def invoke_llm(
6267
response_text = best_action.command_perspective
6368

6469
# Extract chain of thought from generated text
65-
import re
6670
cot_match = re.search(r"<think>.*?</think>", generated_text, re.DOTALL)
6771
if cot_match:
6872
cot = cot_match.group(0)
@@ -103,6 +107,21 @@ def invoke_llm(
103107
elif LLM_BACKEND == LLMBackend.OLLAMA:
104108
import ollama
105109

110+
# Validate model exists before attempting to use it
111+
try:
112+
models_response = ollama.list()
113+
available_models = [m.model for m in models_response.models]
114+
# Check both exact match and with :latest tag
115+
model_variants = [model_name, f"{model_name}:latest"]
116+
if not any(variant in available_models for variant in model_variants):
117+
raise ConfigurationError(
118+
f"Model '{model_name}' not found in Ollama.\n"
119+
f"Available models: {', '.join(available_models)}\n"
120+
f"Pull the model first with: ollama pull {model_name}"
121+
)
122+
except ollama.ResponseError as e:
123+
raise ConfigurationError(f"Failed to list Ollama models: {e}")
124+
106125
print(f"Using Ollama with model: {model_name}")
107126

108127
response = ollama.chat(

0 commit comments

Comments
 (0)