slop-polis-3-casino-games

This commit is contained in:
Russell Ballestrini 2025-04-05 20:52:58 +00:00
parent d071f6f37e
commit 294e264808
3 changed files with 943 additions and 70 deletions

View file

@ -4,6 +4,8 @@ import os
import json import json
import expr import expr
from threading import Lock from threading import Lock
import random
import uuid
from openai import OpenAI from openai import OpenAI
import logging import logging
from flask_swagger_ui import get_swaggerui_blueprint 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( logging.basicConfig(
level=logging.DEBUG, level=logging.DEBUG,
format="%(asctime)s - %(levelname)s - %(message)s", 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__) logger = logging.getLogger(__name__)
@ -35,11 +37,16 @@ MODEL_CLIENT_MAP = {}
MEMORY_FILE = os.path.join(DATA_DIR, "memory.json") MEMORY_FILE = os.path.join(DATA_DIR, "memory.json")
memory_lock = Lock() memory_lock = Lock()
# Resources setup
RESOURCES_FILE = os.path.join(DATA_DIR, "resources.json")
resource_lock = Lock()
# Ensure data directory exists # Ensure data directory exists
if not os.path.exists(DATA_DIR): if not os.path.exists(DATA_DIR):
os.makedirs(DATA_DIR) os.makedirs(DATA_DIR)
logger.info(f"Created data directory: {DATA_DIR}") logger.info(f"Created data directory: {DATA_DIR}")
# File-based memory functions # File-based memory functions
def load_memory_from_file(): def load_memory_from_file():
try: try:
@ -51,9 +58,12 @@ def load_memory_from_file():
logger.debug(f"No memory file found at {MEMORY_FILE}, returning empty dict") logger.debug(f"No memory file found at {MEMORY_FILE}, returning empty dict")
return {} return {}
except Exception as e: 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 {} return {}
def save_memory_to_file(memory_data): def save_memory_to_file(memory_data):
try: try:
with open(MEMORY_FILE, "w") as f: with open(MEMORY_FILE, "w") as f:
@ -62,6 +72,52 @@ def save_memory_to_file(memory_data):
except Exception as e: except Exception as e:
logger.error(f"Error saving memory to {MEMORY_FILE}: {str(e)}", exc_info=True) 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 # Load endpoints
ENDPOINTS = [] ENDPOINTS = []
for i in range(1000): for i in range(1000):
@ -79,6 +135,7 @@ for i in range(1000):
else: else:
logger.debug(f"No endpoint found for MODEL_ENDPOINT_{i}") logger.debug(f"No endpoint found for MODEL_ENDPOINT_{i}")
def initialize_model_map(): def initialize_model_map():
logger.info("Starting model map initialization") logger.info("Starting model map initialization")
MODEL_CLIENT_MAP.clear() MODEL_CLIENT_MAP.clear()
@ -95,15 +152,21 @@ def initialize_model_map():
logger.debug(f"Attempting to list models from {endpoint_name}") logger.debug(f"Attempting to list models from {endpoint_name}")
response = client.models.list() response = client.models.list()
model_list = response.data 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: 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 continue
for m in model_list: for m in model_list:
model_id = m.id model_id = m.id
if model_id: if model_id:
if model_id in MODEL_CLIENT_MAP: 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: else:
MODEL_CLIENT_MAP[model_id] = client MODEL_CLIENT_MAP[model_id] = client
logger.info(f"Registered model '{model_id}' from {endpoint_name}") 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.warning(f"Model with no ID encountered from {endpoint_name}")
logger.info(f"Model map initialized with models: {list(MODEL_CLIENT_MAP.keys())}") logger.info(f"Model map initialized with models: {list(MODEL_CLIENT_MAP.keys())}")
# SLOP components # SLOP components
tools = { tools = {
"calculator": { "calculator": {
"id": "calculator", "id": "calculator",
"description": "Basic math calculator", "description": "Basic math calculator",
"arguments": [ "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"])}, "execute": lambda params: {"result": expr.evaluate(params["expression"])},
}, },
@ -129,16 +197,395 @@ tools = {
], ],
"execute": lambda params: {"result": f"Hello, {params['name']}!"}, "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!"}, # Load resources from file at startup
"foo/bar": {"id": "foo/bar", "content": "Nested Foo Bar"}, resources = load_resources_from_file()
"foo/baz": {"id": "foo/baz", "content": "Nested Foo Baz"},
# 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}). Dealers 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"Dealers 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 dealers 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 = "Dealers total beats yours. You lose."
payout = 0
result = "loss"
else:
outcome = "Push! Its 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"Dealers 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 # Memory endpoints with lock and timeout
LOCK_TIMEOUT = 2 # Seconds to wait for lock acquisition LOCK_TIMEOUT = 2 # Seconds to wait for lock acquisition
@app.route("/memory", methods=["POST"]) @app.route("/memory", methods=["POST"])
def store_memory(): def store_memory():
logger.info("Received /memory POST request") logger.info("Received /memory POST request")
@ -159,9 +606,12 @@ def store_memory():
finally: finally:
memory_lock.release() memory_lock.release()
else: 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 return jsonify({"error": "Memory lock timeout"}), 503
@app.route("/memory/<key>", methods=["GET"]) @app.route("/memory/<key>", methods=["GET"])
def get_memory(key): def get_memory(key):
logger.info(f"Received /memory/{key} GET request") logger.info(f"Received /memory/{key} GET request")
@ -176,9 +626,12 @@ def get_memory(key):
finally: finally:
memory_lock.release() memory_lock.release()
else: 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 return jsonify({"error": "Memory lock timeout"}), 503
@app.route("/memory", methods=["GET"]) @app.route("/memory", methods=["GET"])
def list_memory(): def list_memory():
logger.info("Received /memory GET request") logger.info("Received /memory GET request")
@ -193,9 +646,12 @@ def list_memory():
finally: finally:
memory_lock.release() memory_lock.release()
else: 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 return jsonify({"error": "Memory lock timeout"}), 503
@app.route("/memory/<key>", methods=["DELETE"]) @app.route("/memory/<key>", methods=["DELETE"])
def delete_memory(key): def delete_memory(key):
logger.info(f"Received /memory/{key} DELETE request") logger.info(f"Received /memory/{key} DELETE request")
@ -214,10 +670,13 @@ def delete_memory(key):
finally: finally:
memory_lock.release() memory_lock.release()
else: 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 return jsonify({"error": "Memory lock timeout"}), 503
# Other endpoints (unchanged for brevity)
# Other endpoints
@app.route("/chat", methods=["POST"]) @app.route("/chat", methods=["POST"])
def chat(): def chat():
logger.info("Received /chat request") logger.info("Received /chat request")
@ -244,19 +703,14 @@ def chat():
response_content = response.choices[0].message.content response_content = response.choices[0].message.content
logger.debug(f"Chat response from {model_id}: {response_content}") logger.debug(f"Chat response from {model_id}: {response_content}")
return ( return (
jsonify( jsonify({"choices": [{"message": {"content": response_content}}]}),
{
"choices": [
{"message": {"content": response_content}}
]
}
),
200, 200,
) )
except Exception as e: except Exception as e:
logger.error(f"Chat error with model {model_id}: {str(e)}", exc_info=True) logger.error(f"Chat error with model {model_id}: {str(e)}", exc_info=True)
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@app.route("/models", methods=["GET"]) @app.route("/models", methods=["GET"])
def list_models(): def list_models():
logger.info("Received /models request") logger.info("Received /models request")
@ -264,13 +718,18 @@ def list_models():
logger.debug(f"Returning models: {models}") logger.debug(f"Returning models: {models}")
return jsonify({"models": models}), 200 return jsonify({"models": models}), 200
@app.route("/tools", methods=["GET"]) @app.route("/tools", methods=["GET"])
def list_tools(): def list_tools():
logger.info("Received /tools request") 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}") logger.debug(f"Returning tools: {tool_list}")
return jsonify({"tools": tool_list}), 200 return jsonify({"tools": tool_list}), 200
@app.route("/tools/<tool_id>", methods=["POST"]) @app.route("/tools/<tool_id>", methods=["POST"])
def use_tool(tool_id): def use_tool(tool_id):
logger.info(f"Received /tools/{tool_id} request") logger.info(f"Received /tools/{tool_id} request")
@ -282,7 +741,7 @@ def use_tool(tool_id):
if "arguments" in tools[tool_id]: if "arguments" in tools[tool_id]:
for arg in tools[tool_id]["arguments"]: 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") logger.warning(f"Missing '{arg['name']}' for {tool_id} tool")
return jsonify({"error": f"Missing '{arg['name']}' parameter"}), 400 return jsonify({"error": f"Missing '{arg['name']}' parameter"}), 400
@ -294,16 +753,20 @@ def use_tool(tool_id):
logger.error(f"Error executing tool {tool_id}: {str(e)}", exc_info=True) logger.error(f"Error executing tool {tool_id}: {str(e)}", exc_info=True)
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@app.route("/resources", methods=["GET"]) @app.route("/resources", methods=["GET"])
def list_resources(): def list_resources():
logger.info("Received /resources request") logger.info("Received /resources request")
with resource_lock:
resource_list = list(resources.values()) resource_list = list(resources.values())
logger.debug(f"Returning resources: {resource_list}") logger.debug(f"Returning resources: {resource_list}")
return jsonify({"resources": resource_list}), 200 return jsonify({"resources": resource_list}), 200
@app.route("/resources/<resource_id>", methods=["GET"]) @app.route("/resources/<resource_id>", methods=["GET"])
def get_resource(resource_id): def get_resource(resource_id):
logger.info(f"Received /resources/{resource_id} GET request") logger.info(f"Received /resources/{resource_id} GET request")
with resource_lock:
if resource_id in resources: if resource_id in resources:
resource = resources[resource_id] resource = resources[resource_id]
logger.debug(f"Exact match found for {resource_id}: {resource}") logger.debug(f"Exact match found for {resource_id}: {resource}")
@ -315,27 +778,29 @@ def get_resource(resource_id):
res for key, res in resources.items() if key.startswith(prefix) res for key, res in resources.items() if key.startswith(prefix)
] ]
if matching_resources: if matching_resources:
logger.debug(f"Prefix search for {resource_id} found: {matching_resources}") logger.debug(
f"Prefix search for {resource_id} found: {matching_resources}"
)
return jsonify({"resources": matching_resources}), 200 return jsonify({"resources": matching_resources}), 200
logger.warning(f"No resource or nested resources found for {resource_id}") logger.warning(f"No resource or nested resources found for {resource_id}")
return jsonify({"error": "Resource not found"}), 404 return jsonify({"error": "Resource not found"}), 404
@app.route("/resources/<resource_id>", methods=["PUT"]) @app.route("/resources/<resource_id>", methods=["PUT"])
def update_resource(resource_id): def update_resource_endpoint(resource_id):
logger.info(f"Received /resources/{resource_id} PUT request") logger.info(f"Received /resources/{resource_id} PUT request")
data = request.json data = request.json
logger.debug(f"Update resource data: {data}") logger.debug(f"Update resource data: {data}")
if not data or "content" not in data: if not data or "content" not in data:
logger.warning(f"Invalid resource update request: {data}") logger.warning(f"Invalid resource update request: {data}")
return jsonify({"error": "Missing 'content'"}), 400 return jsonify({"error": "Missing 'content'"}), 400
# Update or create the resource with resource_lock:
resources[resource_id] = { resources[resource_id] = {"id": resource_id, "content": data["content"]}
"id": resource_id, save_resources_to_file(resources)
"content": data["content"]
}
logger.info(f"Updated/created resource {resource_id}: {resources[resource_id]}") logger.info(f"Updated/created resource {resource_id}: {resources[resource_id]}")
return jsonify({"status": "updated", "resource": resources[resource_id]}), 200 return jsonify({"status": "updated", "resource": resources[resource_id]}), 200
@app.route("/pay", methods=["POST"]) @app.route("/pay", methods=["POST"])
def pay(): def pay():
logger.info("Received /pay request") logger.info("Received /pay request")
@ -354,6 +819,7 @@ def pay():
200, 200,
) )
# Initialize model map on startup # Initialize model map on startup
logger.info("Starting application initialization") logger.info("Starting application initialization")
initialize_model_map() initialize_model_map()

View file

@ -1,12 +1,12 @@
openapi: 3.0.0 openapi: 3.0.0
info: info:
title: SLOP API 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 version: 1.0.0
servers: servers:
- url: https://slop.unturf.com - url: https://slop.unturf.com
description: Production unturf. SLOP server description: Production unturf SLOP server
- url: http://localhost:31337 - url: http://localhost:31337
description: Local development SLOP server description: Local development SLOP server
@ -76,20 +76,101 @@ paths:
required: true required: true
schema: schema:
type: string type: string
enum: [calculator, greet] enum: [calculator, greet, slots, blackjack]
requestBody: requestBody:
required: true required: true
content: content:
application/json: application/json:
schema: 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: responses:
'200': '200':
description: Tool execution result description: Tool execution result
content: content:
application/json: application/json:
schema: 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). Dealers 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). Dealers 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': '400':
description: Missing required parameter description: Missing required parameter
content: content:
@ -102,6 +183,12 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ErrorResponse' $ref: '#/components/schemas/ErrorResponse'
'500':
description: Server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/memory: /memory:
get: get:
summary: List all memory keys summary: List all memory keys
@ -252,6 +339,18 @@ paths:
content: "Nested Foo Bar" content: "Nested Foo Bar"
- id: "foo/baz" - id: "foo/baz"
content: "Nested 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': '404':
description: No resource or nested resources found description: No resource or nested resources found
content: content:
@ -307,6 +406,7 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/PayResponse' $ref: '#/components/schemas/PayResponse'
components: components:
schemas: schemas:
ChatRequest: ChatRequest:
@ -370,29 +470,192 @@ components:
type: string type: string
description: description:
type: string 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: required:
- id - id
- description - description
- arguments
required: required:
- tools - tools
ToolRequest: CalculatorToolRequest:
type: object type: object
properties: properties:
expression: expression:
type: string type: string
nullable: true description: The math expression to evaluate (e.g., "2 + 2")
required:
- expression
GreetToolRequest:
type: object
properties:
name: name:
type: string 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 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 type: object
properties: properties:
result: result:
oneOf: type: integer
- type: string description: The result of the calculation
- type: integer
required: required:
- result - 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: MemoryStoreRequest:
type: object type: object
properties: properties:
@ -439,7 +702,8 @@ components:
id: id:
type: string type: string
content: content:
type: string type: object
additionalProperties: true
required: required:
- id - id
- content - content
@ -451,7 +715,8 @@ components:
id: id:
type: string type: string
content: content:
type: string type: object
additionalProperties: true
required: required:
- id - id
- content - content

View file

@ -1,4 +1,3 @@
# streamlit_slop_with_models.py
import streamlit as st import streamlit as st
import requests import requests
@ -130,6 +129,145 @@ def tools_interface():
except requests.RequestException as e: except requests.RequestException as e:
st.error(f"Error: {str(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"**Dealers 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"**Dealers 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"**Dealers 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(): def memory_interface():
st.header("Memory") st.header("Memory")
@ -234,12 +372,16 @@ def resources_interface():
response = requests.put( response = requests.put(
f"{BASE_URL}/resources/{new_resource_id}", f"{BASE_URL}/resources/{new_resource_id}",
json={"content": content}, json={"content": content},
timeout=5 timeout=5,
) )
response.raise_for_status() response.raise_for_status()
result = response.json() result = response.json()
st.success(f"Resource {result['resource']['id']} updated/created successfully!") st.success(
st.write(f"**{result['resource']['id']}**: {result['resource']['content']}") f"Resource {result['resource']['id']} updated/created successfully!"
)
st.write(
f"**{result['resource']['id']}**: {result['resource']['content']}"
)
except requests.RequestException as e: except requests.RequestException as e:
st.error(f"Error: {str(e)}") st.error(f"Error: {str(e)}")