From 294e264808d6c4be2a3f54249494df7bfe0acd72 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 5 Apr 2025 20:52:58 +0000 Subject: [PATCH] slop-polis-3-casino-games --- slop_with_models.py | 566 +++++++++++++++++++++++++++++++--- static/openapi.yaml | 293 +++++++++++++++++- streamlit_slop_with_models.py | 154 ++++++++- 3 files changed, 943 insertions(+), 70 deletions(-) diff --git a/slop_with_models.py b/slop_with_models.py index 3e7af98..6c43ea2 100644 --- a/slop_with_models.py +++ b/slop_with_models.py @@ -4,6 +4,8 @@ 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 @@ -14,7 +16,7 @@ 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()] + handlers=[logging.FileHandler(LOG_FILE), logging.StreamHandler()], ) logger = logging.getLogger(__name__) @@ -35,11 +37,16 @@ MODEL_CLIENT_MAP = {} 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: @@ -51,9 +58,12 @@ def load_memory_from_file(): 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) + 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: @@ -62,6 +72,52 @@ def save_memory_to_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) + 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): @@ -79,6 +135,7 @@ for i in range(1000): 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() @@ -95,15 +152,21 @@ def initialize_model_map(): 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]}") + 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) + 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}") + 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}") @@ -111,13 +174,18 @@ def initialize_model_map(): 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."} + { + "name": "expression", + "type": "str", + "description": "the math expression to evaluate.", + } ], "execute": lambda params: {"result": expr.evaluate(params["expression"])}, }, @@ -125,20 +193,399 @@ tools = { "id": "greet", "description": "Says hello", "arguments": [ - { "name": "name", "type": "str", "description": "name of person to greet"} + {"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"), + ), + }, } -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"}, -} + +# Load resources from file at startup +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: + 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: + 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[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: + if game_key not 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"]: + 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") @@ -159,9 +606,12 @@ def store_memory(): finally: memory_lock.release() else: - logger.error(f"Failed to acquire lock for storing {key} within {LOCK_TIMEOUT} seconds") + 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") @@ -176,9 +626,12 @@ def get_memory(key): finally: memory_lock.release() else: - logger.error(f"Failed to acquire lock for retrieving {key} within {LOCK_TIMEOUT} seconds") + 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") @@ -193,9 +646,12 @@ def list_memory(): finally: memory_lock.release() else: - logger.error(f"Failed to acquire lock for listing memory within {LOCK_TIMEOUT} seconds") + 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") @@ -214,10 +670,13 @@ def delete_memory(key): finally: memory_lock.release() else: - logger.error(f"Failed to acquire lock for deleting {key} within {LOCK_TIMEOUT} seconds") + logger.error( + f"Failed to acquire lock for deleting {key} within {LOCK_TIMEOUT} seconds" + ) return jsonify({"error": "Memory lock timeout"}), 503 -# Other endpoints (unchanged for brevity) + +# Other endpoints @app.route("/chat", methods=["POST"]) def chat(): logger.info("Received /chat request") @@ -244,19 +703,14 @@ def chat(): response_content = response.choices[0].message.content logger.debug(f"Chat response from {model_id}: {response_content}") return ( - jsonify( - { - "choices": [ - {"message": {"content": response_content}} - ] - } - ), + 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") @@ -264,13 +718,18 @@ def list_models(): 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()] + 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") @@ -279,10 +738,10 @@ def use_tool(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: + 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 @@ -294,48 +753,54 @@ def use_tool(tool_id): 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") - resource_list = list(resources.values()) + with resource_lock: + 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") - 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 + with resource_lock: + 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(resource_id): +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 - # Update or create the resource - resources[resource_id] = { - "id": resource_id, - "content": data["content"] - } + with resource_lock: + 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") @@ -354,6 +819,7 @@ def pay(): 200, ) + # Initialize model map on startup logger.info("Starting application initialization") initialize_model_map() @@ -362,4 +828,4 @@ 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") \ No newline at end of file + logger.info("Flask application stopped") diff --git a/static/openapi.yaml b/static/openapi.yaml index 71a0bb6..ec2dd9d 100644 --- a/static/openapi.yaml +++ b/static/openapi.yaml @@ -1,12 +1,12 @@ openapi: 3.0.0 info: title: SLOP API - description: A SLOP pattern implementation with dynamic model endpoints, tools, memory, resources, and payment simulation. + description: A SLOP pattern implementation with dynamic model endpoints, tools, memory, resources, and payment simulation, including casino games. version: 1.0.0 servers: - url: https://slop.unturf.com - description: Production unturf. SLOP server + description: Production unturf SLOP server - url: http://localhost:31337 description: Local development SLOP server @@ -76,20 +76,101 @@ paths: required: true schema: type: string - enum: [calculator, greet] + enum: [calculator, greet, slots, blackjack] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/ToolRequest' + oneOf: + - $ref: '#/components/schemas/CalculatorToolRequest' + - $ref: '#/components/schemas/GreetToolRequest' + - $ref: '#/components/schemas/SlotsToolRequest' + - $ref: '#/components/schemas/BlackjackToolRequest' + examples: + calculator: + summary: Calculator tool request + value: + expression: "2 + 2" + greet: + summary: Greet tool request + value: + name: "Alice" + slots: + summary: Slots game request + value: + bet: 50 + agent_id: "player1" + blackjackStart: + summary: Start a new blackjack game + value: + bet: 100 + agent_id: "player1" + blackjackHit: + summary: Hit in an existing blackjack game + value: + agent_id: "player1" + game_id: "550e8400-e29b-41d4-a716-446655440000" + action: "hit" responses: '200': description: Tool execution result content: application/json: schema: - $ref: '#/components/schemas/ToolResponse' + oneOf: + - $ref: '#/components/schemas/CalculatorToolResponse' + - $ref: '#/components/schemas/GreetToolResponse' + - $ref: '#/components/schemas/SlotsToolResponse' + - $ref: '#/components/schemas/BlackjackToolResponse' + examples: + calculator: + summary: Calculator result + value: + result: 4 + greet: + summary: Greet result + value: + result: "Hello, Alice!" + slots: + summary: Slots game result + value: + 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. Symbols: ['🍒', '🍋', '🍊', '🍇', '🔔', '💎']. You (agent player1) bet 50. Reels: ['🍒', '🍒', '🍒']. Jackpot! You matched all three symbols." + game_state: + reels: ["🍒", "🍒", "🍒"] + payout: 500 + bet: 50 + agent_id: "player1" + new_balance: 1450 + game_complete: true + blackjackStart: + summary: Start of a blackjack game + value: + 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. You (agent player1) started a new game with bet 100. Your hand: [10, 7] (Total: 17). Dealer’s up card: [6]." + game_id: "550e8400-e29b-41d4-a716-446655440000" + game_state: + player_hand: [10, 7] + player_total: 17 + dealer_hand: [6] + dealer_total: 6 + bet: 100 + agent_id: "player1" + new_balance: 1000 + game_complete: false + blackjackHitBust: + summary: Hit resulting in bust + value: + explanation: "Blackjack: You chose to hit. Your hand: [10, 7, 8] (Total: 25). Dealer’s up card: [6]. Bust! You went over 21 and lose." + game_id: "550e8400-e29b-41d4-a716-446655440000" + game_state: + player_hand: [10, 7, 8] + player_total: 25 + dealer_hand: [6] + dealer_total: 6 + bet: 100 + agent_id: "player1" + new_balance: 900 + game_complete: true '400': description: Missing required parameter content: @@ -102,6 +183,12 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' /memory: get: summary: List all memory keys @@ -252,6 +339,18 @@ paths: content: "Nested Foo Bar" - id: "foo/baz" content: "Nested Foo Baz" + blackjackGame: + summary: Blackjack game state + value: + id: "casino/blackjack/games/550e8400-e29b-41d4-a716-446655440000" + content: + agent_id: "player1" + bet: 100 + player_hand: [10, 7] + player_total: 17 + dealer_hand: [6] + dealer_total: 6 + status: "active" '404': description: No resource or nested resources found content: @@ -307,6 +406,7 @@ paths: application/json: schema: $ref: '#/components/schemas/PayResponse' + components: schemas: ChatRequest: @@ -370,29 +470,192 @@ components: type: string description: type: string + arguments: + type: array + items: + type: object + properties: + name: + type: string + type: + type: string + description: + type: string + optional: + type: boolean + required: + - name + - type + - description required: - id - description + - arguments required: - tools - ToolRequest: + CalculatorToolRequest: type: object properties: expression: type: string - nullable: true + description: The math expression to evaluate (e.g., "2 + 2") + required: + - expression + GreetToolRequest: + type: object + properties: name: type: string + description: The name of the person to greet + required: + - name + SlotsToolRequest: + type: object + properties: + bet: + type: integer + minimum: 1 + description: Amount to bet on the slot spin + agent_id: + type: string + description: Unique identifier for the agent/player + required: + - bet + - agent_id + BlackjackToolRequest: + type: object + properties: + bet: + type: integer + minimum: 1 + description: Amount to bet (required to start a new game) nullable: true - ToolResponse: + agent_id: + type: string + description: Unique identifier for the agent/player + game_id: + type: string + description: UUID of an existing game (required for hit/stand actions) + nullable: true + action: + type: string + enum: [hit, stand] + description: Action to take in an existing game (required if game_id is provided) + nullable: true + required: + - agent_id + CalculatorToolResponse: type: object properties: result: - oneOf: - - type: string - - type: integer + type: integer + description: The result of the calculation required: - result + GreetToolResponse: + type: object + properties: + result: + type: string + description: The greeting message + required: + - result + SlotsToolResponse: + type: object + properties: + explanation: + type: string + description: Description of the game rules and outcome + game_state: + type: object + properties: + reels: + type: array + items: + type: string + description: The symbols on the slot reels + required: + - reels + payout: + type: integer + description: Amount won from the spin (0 if lost) + bet: + type: integer + description: The amount bet on the spin + agent_id: + type: string + description: The agent/player identifier + new_balance: + type: integer + description: Updated balance after the spin + game_complete: + type: boolean + description: Indicates if the game is complete (always true for slots) + required: + - explanation + - game_state + - payout + - bet + - agent_id + - new_balance + - game_complete + BlackjackToolResponse: + type: object + properties: + explanation: + type: string + description: Description of the game rules and current action outcome + game_id: + type: string + description: UUID of the blackjack game (returned when starting a new game) + nullable: true + game_state: + type: object + properties: + player_hand: + type: array + items: + type: integer + description: Cards in the player's hand + player_total: + type: integer + description: Total value of the player's hand + dealer_hand: + type: array + items: + type: integer + description: Cards in the dealer's hand (partial view until game ends) + dealer_total: + type: integer + description: Total value of the dealer's hand (partial until game ends) + required: + - player_hand + - player_total + - dealer_hand + - dealer_total + payout: + type: integer + description: Amount won (0 if ongoing or lost, set when game completes) + nullable: true + bet: + type: integer + description: The amount bet on the game + agent_id: + type: string + description: The agent/player identifier + new_balance: + type: integer + description: Updated balance (updated only when game completes) + game_complete: + type: boolean + description: Indicates if the game is complete (true on bust or stand) + required: + - explanation + - game_state + - bet + - agent_id + - new_balance + - game_complete MemoryStoreRequest: type: object properties: @@ -439,7 +702,8 @@ components: id: type: string content: - type: string + type: object + additionalProperties: true required: - id - content @@ -451,7 +715,8 @@ components: id: type: string content: - type: string + type: object + additionalProperties: true required: - id - content @@ -499,4 +764,4 @@ components: error: type: string required: - - error \ No newline at end of file + - error diff --git a/streamlit_slop_with_models.py b/streamlit_slop_with_models.py index 2717984..6666b9a 100644 --- a/streamlit_slop_with_models.py +++ b/streamlit_slop_with_models.py @@ -1,4 +1,3 @@ -# streamlit_slop_with_models.py import streamlit as st import requests @@ -130,6 +129,145 @@ def tools_interface(): except requests.RequestException as e: st.error(f"Error: {str(e)}") + elif tool_id == "slots": + st.subheader("Slots") + agent_id = st.text_input("Your Agent ID", value="player1") + bet = st.number_input("Bet Amount", min_value=1, step=1, value=10) + if st.button("Spin"): + try: + response = requests.post( + f"{BASE_URL}/tools/slots", + json={"bet": bet, "agent_id": agent_id}, + timeout=5, + ) + response.raise_for_status() + result = response.json() + if "error" in result: + st.error(f"Error: {result['error']}") + else: + st.write(f"**Result**: {result['explanation']}") + st.write(f"**Reels**: {result['game_state']['reels']}") + st.write(f"**Payout**: {result['payout']}") + st.write(f"**New Balance**: {result['new_balance']}") + except requests.RequestException as e: + st.error(f"Error: {str(e)}") + + elif tool_id == "blackjack": + st.subheader("Blackjack") + agent_id = st.text_input( + "Your Agent ID", value="player1", key="blackjack_agent" + ) + + # Initialize session state for blackjack game ID + if "blackjack_game_id" not in st.session_state: + st.session_state.blackjack_game_id = None + + # Start a new game + if st.session_state.blackjack_game_id is None: + bet = st.number_input("Bet Amount", min_value=1, step=1, value=10) + if st.button("Start Game"): + try: + response = requests.post( + f"{BASE_URL}/tools/blackjack", + json={"bet": bet, "agent_id": agent_id}, + timeout=5, + ) + response.raise_for_status() + result = response.json() + if "error" in result: + st.error(f"Error: {result['error']}") + else: + st.session_state.blackjack_game_id = result["game_id"] + st.write(f"**Game Started**: {result['explanation']}") + st.write( + f"**Your Hand**: {result['game_state']['player_hand']} (Total: {result['game_state']['player_total']})" + ) + st.write( + f"**Dealer’s Up Card**: {result['game_state']['dealer_hand']}" + ) + st.rerun() + except requests.RequestException as e: + st.error(f"Error: {str(e)}") + else: + # Display current game state and allow hit/stand + try: + response = requests.get( + f"{BASE_URL}/resources/casino/blackjack/games/{st.session_state.blackjack_game_id}", + timeout=5, + ) + response.raise_for_status() + game_state = response.json()["content"] + st.write( + f"**Current Game (ID: {st.session_state.blackjack_game_id})**:" + ) + st.write( + f"**Your Hand**: {game_state['player_hand']} (Total: {game_state['player_total']})" + ) + st.write(f"**Dealer’s Up Card**: {game_state['dealer_hand']}") + st.write(f"**Bet**: {game_state['bet']}") + + col1, col2 = st.columns(2) + with col1: + if st.button("Hit"): + try: + response = requests.post( + f"{BASE_URL}/tools/blackjack", + json={ + "agent_id": agent_id, + "game_id": st.session_state.blackjack_game_id, + "action": "hit", + }, + timeout=5, + ) + response.raise_for_status() + result = response.json() + if "error" in result: + st.error(f"Error: {result['error']}") + else: + st.write(f"**Result**: {result['explanation']}") + if result["game_complete"]: + st.write(f"**Payout**: {result['payout']}") + st.write( + f"**New Balance**: {result['new_balance']}" + ) + st.session_state.blackjack_game_id = None + st.rerun() + except requests.RequestException as e: + st.error(f"Error: {str(e)}") + with col2: + if st.button("Stand"): + try: + response = requests.post( + f"{BASE_URL}/tools/blackjack", + json={ + "agent_id": agent_id, + "game_id": st.session_state.blackjack_game_id, + "action": "stand", + }, + timeout=5, + ) + response.raise_for_status() + result = response.json() + if "error" in result: + st.error(f"Error: {result['error']}") + else: + st.write(f"**Result**: {result['explanation']}") + st.write( + f"**Your Hand**: {result['game_state']['player_hand']} (Total: {result['game_state']['player_total']})" + ) + st.write( + f"**Dealer’s Hand**: {result['game_state']['dealer_hand']} (Total: {result['game_state']['dealer_total']})" + ) + st.write(f"**Payout**: {result['payout']}") + st.write(f"**New Balance**: {result['new_balance']}") + st.session_state.blackjack_game_id = None + st.rerun() + except requests.RequestException as e: + st.error(f"Error: {str(e)}") + except requests.RequestException as e: + st.error(f"Error fetching game state: {str(e)}") + st.session_state.blackjack_game_id = None # Reset if game not found + def memory_interface(): st.header("Memory") @@ -190,7 +328,7 @@ def memory_interface(): def resources_interface(): st.header("Resources") - + # Fetch available resources try: response = requests.get(f"{BASE_URL}/resources", timeout=5) @@ -228,18 +366,22 @@ def resources_interface(): new_resource_id = st.text_input("Resource ID", value=resource_id) content = st.text_area("Content", height=100) submit_button = st.form_submit_button(label="Update/Create", type="primary") - + if submit_button and new_resource_id and content: try: response = requests.put( f"{BASE_URL}/resources/{new_resource_id}", json={"content": content}, - timeout=5 + timeout=5, ) response.raise_for_status() result = response.json() - st.success(f"Resource {result['resource']['id']} updated/created successfully!") - st.write(f"**{result['resource']['id']}**: {result['resource']['content']}") + st.success( + f"Resource {result['resource']['id']} updated/created successfully!" + ) + st.write( + f"**{result['resource']['id']}**: {result['resource']['content']}" + ) except requests.RequestException as e: st.error(f"Error: {str(e)}")