from flask import Flask, request, jsonify from datetime import datetime import os import json import expr from threading import Lock import random import uuid from openai import OpenAI import logging from flask_swagger_ui import get_swaggerui_blueprint # Configure logging DATA_DIR = "data" LOG_FILE = os.path.join(DATA_DIR, "slop_with_models.log") logging.basicConfig( level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s", handlers=[logging.FileHandler(LOG_FILE), logging.StreamHandler()], ) logger = logging.getLogger(__name__) app = Flask(__name__) # Swagger UI setup SWAGGER_URL = "/openapi" API_URL = "/static/openapi.yaml" swaggerui_blueprint = get_swaggerui_blueprint( SWAGGER_URL, API_URL, config={"app_name": "SLOP API"} ) app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL) # Global model-to-client map MODEL_CLIENT_MAP = {} # Memory setup MEMORY_FILE = os.path.join(DATA_DIR, "memory.json") memory_lock = Lock() # Resources setup RESOURCES_FILE = os.path.join(DATA_DIR, "resources.json") resource_lock = Lock() # Ensure data directory exists if not os.path.exists(DATA_DIR): os.makedirs(DATA_DIR) logger.info(f"Created data directory: {DATA_DIR}") # File-based memory functions def load_memory_from_file(): try: if os.path.exists(MEMORY_FILE): with open(MEMORY_FILE, "r") as f: data = json.load(f) logger.debug(f"Loaded memory from {MEMORY_FILE}: {data}") return data logger.debug(f"No memory file found at {MEMORY_FILE}, returning empty dict") return {} except Exception as e: logger.error( f"Error loading memory from {MEMORY_FILE}: {str(e)}", exc_info=True ) return {} def save_memory_to_file(memory_data): try: with open(MEMORY_FILE, "w") as f: json.dump(memory_data, f) f.flush() # Ensure write is completed os.fsync(f.fileno()) # Sync to disk logger.debug(f"Saved memory to {MEMORY_FILE}: {memory_data}") except Exception as e: logger.error(f"Error saving memory to {MEMORY_FILE}: {str(e)}", exc_info=True) # File-based resources functions def load_resources_from_file(): try: if os.path.exists(RESOURCES_FILE): with open(RESOURCES_FILE, "r") as f: data = json.load(f) logger.debug(f"Loaded resources from {RESOURCES_FILE}: {data}") return data else: # If file doesn't exist, create it with example resources initial_resources = { "hello": {"id": "hello", "content": "Hello, SLOP!"}, "foo/bar": {"id": "foo/bar", "content": "Nested Foo Bar"}, "foo/baz": {"id": "foo/baz", "content": "Nested Foo Baz"}, } save_resources_to_file(initial_resources) logger.info( f"Created new resources file at {RESOURCES_FILE} with initial data" ) return initial_resources except Exception as e: logger.error( f"Error loading resources from {RESOURCES_FILE}: {str(e)}", exc_info=True ) # On error, create and return initial resources initial_resources = { "hello": {"id": "hello", "content": "Hello, SLOP!"}, "foo/bar": {"id": "foo/bar", "content": "Nested Foo Bar"}, "foo/baz": {"id": "foo/baz", "content": "Nested Foo Baz"}, } save_resources_to_file(initial_resources) return initial_resources def save_resources_to_file(resources_data): try: with open(RESOURCES_FILE, "w") as f: json.dump(resources_data, f) f.flush() # Ensure write is completed os.fsync(f.fileno()) # Sync to disk logger.debug(f"Saved resources to {RESOURCES_FILE}: {resources_data}") except Exception as e: logger.error( f"Error saving resources to {RESOURCES_FILE}: {str(e)}", exc_info=True ) # Load endpoints ENDPOINTS = [] for i in range(1000): endpoint = os.getenv(f"MODEL_ENDPOINT_{i}") if endpoint: api_key = os.getenv(f"MODEL_API_KEY_{i}", "not-needed") logger.info(f"Loaded endpoint {i}: {endpoint} with API key: {api_key[:4]}...") ENDPOINTS.append( { "name": f"endpoint_{i}", "base_url": endpoint, "api_key": api_key, } ) else: logger.debug(f"No endpoint found for MODEL_ENDPOINT_{i}") def initialize_model_map(): logger.info("Starting model map initialization") MODEL_CLIENT_MAP.clear() if not ENDPOINTS: logger.warning("No endpoints configured to initialize models") return for ep in ENDPOINTS: base_url = ep["base_url"] api_key = ep["api_key"] endpoint_name = ep["name"] logger.info(f"Initializing client for {endpoint_name} at {base_url}") client = OpenAI(base_url=base_url, api_key=api_key) try: logger.debug(f"Attempting to list models from {endpoint_name}") response = client.models.list() model_list = response.data logger.debug( f"Models retrieved from {endpoint_name}: {[m.id for m in model_list]}" ) except Exception as e: logger.error( f"Failed to list models for {endpoint_name}: {str(e)}", exc_info=True ) continue for m in model_list: model_id = m.id if model_id: if model_id in MODEL_CLIENT_MAP: logger.warning( f"Duplicate model ID '{model_id}' found at {endpoint_name}" ) else: MODEL_CLIENT_MAP[model_id] = client logger.info(f"Registered model '{model_id}' from {endpoint_name}") else: logger.warning(f"Model with no ID encountered from {endpoint_name}") logger.info(f"Model map initialized with models: {list(MODEL_CLIENT_MAP.keys())}") # SLOP components tools = { "calculator": { "id": "calculator", "description": "Basic math calculator", "arguments": [ { "name": "expression", "type": "str", "description": "the math expression to evaluate.", } ], "execute": lambda params: {"result": expr.evaluate(params["expression"])}, }, "greet": { "id": "greet", "description": "Says hello", "arguments": [ {"name": "name", "type": "str", "description": "name of person to greet"} ], "execute": lambda params: {"result": f"Hello, {params['name']}!"}, }, "slots": { "id": "slots", "description": "Play a slot machine game with a bet for a specific agent", "arguments": [ {"name": "bet", "type": "int", "description": "Amount to bet (minimum 1)"}, { "name": "agent_id", "type": "str", "description": "Unique identifier for the agent", }, ], "execute": lambda params: play_slots(params["bet"], params["agent_id"]), }, "blackjack": { "id": "blackjack", "description": "Play blackjack: start a new game or continue an existing one", "arguments": [ { "name": "bet", "type": "int", "description": "Amount to bet (required to start new game)", "optional": True, }, { "name": "agent_id", "type": "str", "description": "Unique identifier for the agent", }, { "name": "game_id", "type": "str", "description": "UUID of existing game (optional, omit to start new)", "optional": True, }, { "name": "action", "type": "str", "description": "Action: 'hit' or 'stand' (required if game_id provided)", "optional": True, }, ], "execute": lambda params: play_blackjack( params.get("bet"), params["agent_id"], params.get("game_id"), params.get("action"), ), }, } # Load resources from file at startup (initial load only) resources = load_resources_from_file() # Helper functions for casino games def get_or_create_wallet(agent_id): wallet_key = f"casino/wallet/agent_{agent_id}" with resource_lock: resources = load_resources_from_file() # Always load fresh if wallet_key not in resources: resources[wallet_key] = { "id": wallet_key, "content": {"balance": 1000, "last_transaction": None}, } save_resources_to_file(resources) logger.info(f"Created new wallet for agent {agent_id} with balance 1000") return resources[wallet_key]["content"] def get_or_create_stats(game, agent_id): stats_key = f"casino/{game}/stats/agent_{agent_id}" with resource_lock: resources = load_resources_from_file() # Always load fresh if stats_key not in resources: if game == "slots": resources[stats_key] = { "id": stats_key, "content": { "games_played": 0, "total_bet": 0, "total_won": 0, "wins": 0, "losses": 0, }, } elif game == "blackjack": resources[stats_key] = { "id": stats_key, "content": { "games_played": 0, "total_bet": 0, "total_won": 0, "wins": 0, "losses": 0, "ties": 0, }, } save_resources_to_file(resources) logger.info(f"Created new {game} stats for agent {agent_id}") return resources[stats_key]["content"] def update_resource(resource_id, new_content): with resource_lock: resources = load_resources_from_file() # Always load fresh resources[resource_id] = {"id": resource_id, "content": new_content} save_resources_to_file(resources) logger.debug(f"Updated resource {resource_id}: {new_content}") # Casino game implementations def play_slots(bet, agent_id): if bet < 1: return {"error": "Bet must be at least 1"} if not agent_id: return {"error": "agent_id is required"} wallet = get_or_create_wallet(agent_id) if wallet["balance"] < bet: return { "error": f"Insufficient funds! Current balance: {wallet['balance']}, Bet: {bet}" } symbols = ["🍒", "🍋", "🍊", "🍇", "🔔", "💎"] reels = [random.choice(symbols) for _ in range(3)] matches = len(set(reels)) if matches == 1: payout = bet * 10 outcome = "Jackpot! You matched all three symbols." won = True elif matches == 2: payout = bet * 2 outcome = "Two of a kind! You matched two symbols." won = True else: payout = 0 outcome = "No matches. You lose your bet." won = False wallet_key = f"casino/wallet/agent_{agent_id}" wallet["balance"] = wallet["balance"] - bet + payout wallet["last_transaction"] = { "game": "slots", "bet": bet, "payout": payout, "timestamp": datetime.now().isoformat(), } update_resource(wallet_key, wallet) stats = get_or_create_stats("slots", agent_id) stats["games_played"] += 1 stats["total_bet"] += bet stats["total_won"] += payout if won: stats["wins"] += 1 else: stats["losses"] += 1 update_resource(f"casino/slots/stats/agent_{agent_id}", stats) explanation = ( "Slot Machine Rules: Bet an amount to spin three reels once. " "Match all 3 symbols for 10x your bet, 2 symbols for 2x your bet, or lose your bet if no match. " f"Symbols: {symbols}. " f"You (agent {agent_id}) bet {bet}. Reels: {reels}. {outcome}" ) return { "explanation": explanation, "game_state": {"reels": reels}, "payout": payout, "bet": bet, "agent_id": agent_id, "new_balance": wallet["balance"], "game_complete": True, } def play_blackjack(bet, agent_id, game_id=None, action=None): if not agent_id: return {"error": "agent_id is required"} # Start a new game if game_id is None: if bet is None or bet < 1: return {"error": "Bet must be at least 1 to start a new game"} wallet = get_or_create_wallet(agent_id) if wallet["balance"] < bet: return { "error": f"Insufficient funds! Current balance: {wallet['balance']}, Bet: {bet}" } game_id = str(uuid.uuid4()) cards = [2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11] * 4 player_hand = [random.choice(cards), random.choice(cards)] dealer_hand = [random.choice(cards)] # Only show one dealer card initially player_total = sum(player_hand) aces = player_hand.count(11) while player_total > 21 and aces > 0: player_total -= 10 aces -= 1 game_state = { "agent_id": agent_id, "bet": bet, "player_hand": player_hand, "player_total": player_total, "dealer_hand": dealer_hand, # Partial view "dealer_total": sum(dealer_hand), # Only first card "status": "active", } update_resource(f"casino/blackjack/games/{game_id}", game_state) explanation = ( "Blackjack Rules: Bet to play against the dealer. Goal is to get closer to 21 without going over. " "Number cards = face value, Face cards = 10, Ace = 1 or 11 (adjusted automatically). " "Hit to draw cards, stand to finish. Dealer stands on 17+. Win pays 2x, tie returns bet, loss takes bet. " f"You (agent {agent_id}) started a new game with bet {bet}. " f"Your hand: {player_hand} (Total: {player_total}). Dealer’s up card: {dealer_hand}." ) return { "explanation": explanation, "game_id": game_id, "game_state": { "player_hand": player_hand, "player_total": player_total, "dealer_hand": dealer_hand, "dealer_total": sum(dealer_hand), }, "bet": bet, "agent_id": agent_id, "new_balance": wallet["balance"], # Not deducted yet "game_complete": False, } # Continue an existing game game_key = f"casino/blackjack/games/{game_id}" with resource_lock: resources = load_resources_from_file() logger.debug( f"Checking for game {game_key} in resources: {list(resources.keys())}" ) if game_key not in resources: logger.warning(f"Game {game_id} not found in resources") return {"error": f"Game {game_id} not found"} game_state = resources[game_key]["content"] if game_state["agent_id"] != agent_id: return {"error": "This game belongs to another agent"} if game_state["status"] != "active": return {"error": "This game is already complete"} if action not in ["hit", "stand"]: logger.warning(f"Invalid action '{action}' received for game {game_id}") return {"error": "Action must be 'hit' or 'stand'"} wallet = get_or_create_wallet(agent_id) cards = [2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11] * 4 if action == "hit": game_state["player_hand"].append(random.choice(cards)) game_state["player_total"] = sum(game_state["player_hand"]) aces = game_state["player_hand"].count(11) while game_state["player_total"] > 21 and aces > 0: game_state["player_total"] -= 10 aces -= 1 if game_state["player_total"] > 21: game_state["status"] = "bust" outcome = "Bust! You went over 21 and lose." payout = 0 result = "loss" # Update wallet and stats on bust wallet["balance"] = wallet["balance"] - game_state["bet"] wallet["last_transaction"] = { "game": "blackjack", "bet": game_state["bet"], "payout": payout, "timestamp": datetime.now().isoformat(), } update_resource(f"casino/wallet/agent_{agent_id}", wallet) stats = get_or_create_stats("blackjack", agent_id) stats["games_played"] += 1 stats["total_bet"] += game_state["bet"] stats["total_won"] += payout stats["losses"] += 1 update_resource(f"casino/blackjack/stats/agent_{agent_id}", stats) else: outcome = "You hit. Game continues." payout = 0 result = "active" explanation = ( "Blackjack: You chose to hit. " f"Your hand: {game_state['player_hand']} (Total: {game_state['player_total']}). " f"Dealer’s up card: {game_state['dealer_hand']}. {outcome}" ) update_resource(game_key, game_state) return { "explanation": explanation, "game_id": game_id, "game_state": { "player_hand": game_state["player_hand"], "player_total": game_state["player_total"], "dealer_hand": game_state["dealer_hand"], "dealer_total": game_state["dealer_total"], }, "bet": game_state["bet"], "agent_id": agent_id, "new_balance": wallet["balance"], "game_complete": game_state["status"] != "active", } if action == "stand": # Reveal dealer’s full hand while len(game_state["dealer_hand"]) < 2: game_state["dealer_hand"].append(random.choice(cards)) game_state["dealer_total"] = sum(game_state["dealer_hand"]) aces = game_state["dealer_hand"].count(11) while game_state["dealer_total"] < 17: game_state["dealer_hand"].append(random.choice(cards)) game_state["dealer_total"] = sum(game_state["dealer_hand"]) if game_state["dealer_total"] > 21 and aces > 0: game_state["dealer_total"] -= 10 aces -= 1 if game_state["dealer_total"] > 21: outcome = "Dealer busts! You win!" payout = game_state["bet"] * 2 result = "win" elif game_state["player_total"] > game_state["dealer_total"]: outcome = "Your total beats the dealer's! You win!" payout = game_state["bet"] * 2 result = "win" elif game_state["dealer_total"] > game_state["player_total"]: outcome = "Dealer’s total beats yours. You lose." payout = 0 result = "loss" else: outcome = "Push! It’s a tie, your bet is returned." payout = game_state["bet"] result = "tie" game_state["status"] = "complete" wallet["balance"] = wallet["balance"] - game_state["bet"] + payout wallet["last_transaction"] = { "game": "blackjack", "bet": game_state["bet"], "payout": payout, "timestamp": datetime.now().isoformat(), } update_resource(f"casino/wallet/agent_{agent_id}", wallet) stats = get_or_create_stats("blackjack", agent_id) stats["games_played"] += 1 stats["total_bet"] += game_state["bet"] stats["total_won"] += payout if result == "win": stats["wins"] += 1 elif result == "loss": stats["losses"] += 1 else: stats["ties"] += 1 update_resource(f"casino/blackjack/stats/agent_{agent_id}", stats) update_resource(game_key, game_state) explanation = ( "Blackjack: You chose to stand. " f"Your hand: {game_state['player_hand']} (Total: {game_state['player_total']}). " f"Dealer’s hand: {game_state['dealer_hand']} (Total: {game_state['dealer_total']}). {outcome}" ) return { "explanation": explanation, "game_id": game_id, "game_state": { "player_hand": game_state["player_hand"], "player_total": game_state["player_total"], "dealer_hand": game_state["dealer_hand"], "dealer_total": game_state["dealer_total"], }, "payout": payout, "bet": game_state["bet"], "agent_id": agent_id, "new_balance": wallet["balance"], "game_complete": True, } # Memory endpoints with lock and timeout LOCK_TIMEOUT = 2 # Seconds to wait for lock acquisition @app.route("/memory", methods=["POST"]) def store_memory(): logger.info("Received /memory POST request") data = request.json logger.debug(f"Store memory data: {data}") if not data or "key" not in data or "value" not in data: logger.warning(f"Invalid memory store request: {data}") return jsonify({"error": "Missing 'key' or 'value'"}), 400 key, value = data["key"], data["value"] if memory_lock.acquire(timeout=LOCK_TIMEOUT): try: memory = load_memory_from_file() # Always load fresh state memory[key] = value save_memory_to_file(memory) logger.info(f"Stored in memory: {key} = {value}") logger.debug(f"Current memory state: {memory}") return jsonify({"status": "stored"}), 200 finally: memory_lock.release() else: logger.error( f"Failed to acquire lock for storing {key} within {LOCK_TIMEOUT} seconds" ) return jsonify({"error": "Memory lock timeout"}), 503 @app.route("/memory/", methods=["GET"]) def get_memory(key): logger.info(f"Received /memory/{key} GET request") if memory_lock.acquire(timeout=LOCK_TIMEOUT): try: memory = load_memory_from_file() # Always load fresh state value = memory.get(key) logger.debug(f"Retrieved memory for {key}: {value}") if value is None: logger.warning(f"Key not found in memory: {key}") return jsonify({"value": value}), 200 finally: memory_lock.release() else: logger.error( f"Failed to acquire lock for retrieving {key} within {LOCK_TIMEOUT} seconds" ) return jsonify({"error": "Memory lock timeout"}), 503 @app.route("/memory", methods=["GET"]) def list_memory(): logger.info("Received /memory GET request") if memory_lock.acquire(timeout=LOCK_TIMEOUT): try: memory = load_memory_from_file() # Always load fresh state keys = list(memory.keys()) memory_state = memory.copy() logger.debug(f"Memory keys: {keys}") logger.debug(f"Full memory state: {memory_state}") return jsonify({"keys": keys}), 200 finally: memory_lock.release() else: logger.error( f"Failed to acquire lock for listing memory within {LOCK_TIMEOUT} seconds" ) return jsonify({"error": "Memory lock timeout"}), 503 @app.route("/memory/", methods=["DELETE"]) def delete_memory(key): logger.info(f"Received /memory/{key} DELETE request") if memory_lock.acquire(timeout=LOCK_TIMEOUT): try: memory = load_memory_from_file() # Always load fresh state if key not in memory: logger.warning(f"Key not found for deletion: {key}") return jsonify({"error": "Key not found"}), 404 old_value = memory[key] del memory[key] save_memory_to_file(memory) logger.info(f"Deleted from memory: {key} (was {old_value})") logger.debug(f"Updated memory state: {memory}") return jsonify({"status": "deleted"}), 200 finally: memory_lock.release() else: logger.error( f"Failed to acquire lock for deleting {key} within {LOCK_TIMEOUT} seconds" ) return jsonify({"error": "Memory lock timeout"}), 503 # Other endpoints @app.route("/chat", methods=["POST"]) def chat(): logger.info("Received /chat request") data = request.json logger.debug(f"Request data: {data}") message = data["messages"][0]["content"] if data.get("messages") else "nothing" model_id = data.get("model") or ( list(MODEL_CLIENT_MAP.keys())[0] if MODEL_CLIENT_MAP else None ) logger.info(f"Selected model: {model_id}, message: {message}") if not model_id or model_id not in MODEL_CLIENT_MAP: logger.error(f"Invalid or missing model_id: {model_id}") return jsonify({"error": "Model not found"}), 404 client = MODEL_CLIENT_MAP[model_id] try: response = client.chat.completions.create( model=model_id, messages=[ {"role": m["role"], "content": m["content"]} for m in data.get("messages", []) ] or [{"role": "user", "content": message}], ) response_content = response.choices[0].message.content logger.debug(f"Chat response from {model_id}: {response_content}") return ( jsonify({"choices": [{"message": {"content": response_content}}]}), 200, ) except Exception as e: logger.error(f"Chat error with model {model_id}: {str(e)}", exc_info=True) return jsonify({"error": str(e)}), 500 @app.route("/models", methods=["GET"]) def list_models(): logger.info("Received /models request") models = list(MODEL_CLIENT_MAP.keys()) logger.debug(f"Returning models: {models}") return jsonify({"models": models}), 200 @app.route("/tools", methods=["GET"]) def list_tools(): logger.info("Received /tools request") tool_list = [ {"id": k, "description": v["description"], "arguments": v.get("arguments", [])} for k, v in tools.items() ] logger.debug(f"Returning tools: {tool_list}") return jsonify({"tools": tool_list}), 200 @app.route("/tools/", methods=["POST"]) def use_tool(tool_id): logger.info(f"Received /tools/{tool_id} request") if tool_id not in tools: logger.error(f"Tool not found: {tool_id}") return jsonify({"error": "Tool not found"}), 404 data = request.json or {} logger.debug(f"Tool {tool_id} input data: {data}") if "arguments" in tools[tool_id]: for arg in tools[tool_id]["arguments"]: if arg["name"] not in data and "optional" not in arg: logger.warning(f"Missing '{arg['name']}' for {tool_id} tool") return jsonify({"error": f"Missing '{arg['name']}' parameter"}), 400 try: result = tools[tool_id]["execute"](data) logger.debug(f"Tool {tool_id} result: {result}") return jsonify(result), 200 except Exception as e: logger.error(f"Error executing tool {tool_id}: {str(e)}", exc_info=True) return jsonify({"error": str(e)}), 500 @app.route("/resources", methods=["GET"]) def list_resources(): logger.info("Received /resources request") with resource_lock: resources = load_resources_from_file() resource_list = list(resources.values()) logger.debug(f"Returning resources: {resource_list}") return jsonify({"resources": resource_list}), 200 @app.route("/resources/", methods=["GET"]) def get_resource(resource_id): logger.info(f"Received /resources/{resource_id} GET request") with resource_lock: resources = load_resources_from_file() logger.debug(f"Resources after reload: {list(resources.keys())}") if resource_id in resources: resource = resources[resource_id] logger.debug(f"Exact match found for {resource_id}: {resource}") return jsonify(resource), 200 else: # Prefix search for nested resources prefix = f"{resource_id}/" matching_resources = [ res for key, res in resources.items() if key.startswith(prefix) ] if matching_resources: logger.debug( f"Prefix search for {resource_id} found: {matching_resources}" ) return jsonify({"resources": matching_resources}), 200 logger.warning(f"No resource or nested resources found for {resource_id}") return jsonify({"error": "Resource not found"}), 404 @app.route("/resources/", methods=["PUT"]) def update_resource_endpoint(resource_id): logger.info(f"Received /resources/{resource_id} PUT request") data = request.json logger.debug(f"Update resource data: {data}") if not data or "content" not in data: logger.warning(f"Invalid resource update request: {data}") return jsonify({"error": "Missing 'content'"}), 400 with resource_lock: resources = load_resources_from_file() resources[resource_id] = {"id": resource_id, "content": data["content"]} save_resources_to_file(resources) logger.info(f"Updated/created resource {resource_id}: {resources[resource_id]}") return jsonify({"status": "updated", "resource": resources[resource_id]}), 200 @app.route("/pay", methods=["POST"]) def pay(): logger.info("Received /pay request") data = request.json logger.debug(f"Pay request data: {data}") amount = data.get("amount", 0) transaction_id = f"tx_{int(datetime.now().timestamp())}" logger.info(f"Processed payment of {amount} with transaction_id: {transaction_id}") return ( jsonify( { "transaction_id": transaction_id, "status": "success", } ), 200, ) # Initialize model map on startup logger.info("Starting application initialization") initialize_model_map() logger.info("Application initialization completed") if __name__ == "__main__": logger.info("Starting Flask application on port 31337") app.run(debug=True, port=31337) logger.info("Flask application stopped")