1029 lines
38 KiB
Python
1029 lines
38 KiB
Python
from flask import Flask, request, jsonify, render_template_string
|
||
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_hands": [player_hand], # List to support splitting
|
||
"player_totals": [player_total],
|
||
"dealer_hand": dealer_hand,
|
||
"dealer_total": sum(dealer_hand),
|
||
"status": "active",
|
||
"active_hand": 0, # Index of the current hand being played
|
||
}
|
||
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, split if two cards match. Dealer stands on 17+. "
|
||
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_hands": [player_hand],
|
||
"player_totals": [player_total],
|
||
"dealer_hand": dealer_hand,
|
||
"dealer_total": sum(dealer_hand),
|
||
"can_split": len(player_hand) == 2 and player_hand[0] == player_hand[1],
|
||
},
|
||
"bet": bet,
|
||
"agent_id": agent_id,
|
||
"new_balance": wallet["balance"],
|
||
"game_complete": False,
|
||
}
|
||
|
||
# Continue an existing game
|
||
game_key = f"casino/blackjack/games/{game_id}"
|
||
with resource_lock:
|
||
resources = load_resources_from_file()
|
||
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", "split"]:
|
||
return {"error": "Action must be 'hit', 'stand', or 'split'"}
|
||
|
||
wallet = get_or_create_wallet(agent_id)
|
||
cards = [2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11] * 4
|
||
|
||
if (
|
||
action == "split"
|
||
and len(game_state["player_hands"]) == 1
|
||
and len(game_state["player_hands"][0]) == 2
|
||
and game_state["player_hands"][0][0] == game_state["player_hands"][0][1]
|
||
):
|
||
if wallet["balance"] < game_state["bet"] * 2:
|
||
return {
|
||
"error": f"Insufficient funds to split! Need {game_state['bet'] * 2}, have {wallet['balance']}"
|
||
}
|
||
|
||
hand = game_state["player_hands"][0]
|
||
game_state["player_hands"] = [[hand[0]], [hand[1]]]
|
||
game_state["player_hands"][0].append(random.choice(cards))
|
||
game_state["player_hands"][1].append(random.choice(cards))
|
||
game_state["player_totals"] = [
|
||
sum(game_state["player_hands"][0]),
|
||
sum(game_state["player_hands"][1]),
|
||
]
|
||
for i in range(2):
|
||
aces = game_state["player_hands"][i].count(11)
|
||
while game_state["player_totals"][i] > 21 and aces > 0:
|
||
game_state["player_totals"][i] -= 10
|
||
aces -= 1
|
||
game_state["bet"] *= 2 # Double the bet for two hands
|
||
|
||
explanation = (
|
||
f"Blackjack: You chose to split your hand {hand}. "
|
||
f"Hand 1: {game_state['player_hands'][0]} (Total: {game_state['player_totals'][0]}). "
|
||
f"Hand 2: {game_state['player_hands'][1]} (Total: {game_state['player_totals'][1]}). "
|
||
f"Dealer’s up card: {game_state['dealer_hand']}. Game continues."
|
||
)
|
||
update_resource(game_key, game_state)
|
||
return {
|
||
"explanation": explanation,
|
||
"game_id": game_id,
|
||
"game_state": {
|
||
"player_hands": game_state["player_hands"],
|
||
"player_totals": game_state["player_totals"],
|
||
"dealer_hand": game_state["dealer_hand"],
|
||
"dealer_total": game_state["dealer_total"],
|
||
"can_split": False, # No further splitting after initial split
|
||
},
|
||
"bet": game_state["bet"],
|
||
"agent_id": agent_id,
|
||
"new_balance": wallet["balance"],
|
||
"game_complete": False,
|
||
}
|
||
|
||
if action == "hit":
|
||
active_hand = game_state["active_hand"]
|
||
game_state["player_hands"][active_hand].append(random.choice(cards))
|
||
game_state["player_totals"][active_hand] = sum(
|
||
game_state["player_hands"][active_hand]
|
||
)
|
||
aces = game_state["player_hands"][active_hand].count(11)
|
||
while game_state["player_totals"][active_hand] > 21 and aces > 0:
|
||
game_state["player_totals"][active_hand] -= 10
|
||
aces -= 1
|
||
|
||
if game_state["player_totals"][active_hand] > 21:
|
||
if active_hand + 1 < len(game_state["player_hands"]):
|
||
game_state["active_hand"] += 1 # Move to next hand if split
|
||
outcome = (
|
||
f"Bust on hand {active_hand + 1}! Moving to hand {active_hand + 2}."
|
||
)
|
||
else:
|
||
game_state["status"] = "bust"
|
||
outcome = "Bust! You went over 21 and lose."
|
||
payout = 0
|
||
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 = f"You hit on hand {active_hand + 1}. Game continues."
|
||
payout = 0
|
||
|
||
explanation = (
|
||
f"Blackjack: You chose to hit on hand {active_hand + 1}. "
|
||
f"Hand {active_hand + 1}: {game_state['player_hands'][active_hand]} (Total: {game_state['player_totals'][active_hand]}). "
|
||
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_hands": game_state["player_hands"],
|
||
"player_totals": game_state["player_totals"],
|
||
"dealer_hand": game_state["dealer_hand"],
|
||
"dealer_total": game_state["dealer_total"],
|
||
"can_split": False,
|
||
},
|
||
"bet": game_state["bet"],
|
||
"agent_id": agent_id,
|
||
"new_balance": wallet["balance"],
|
||
"game_complete": game_state["status"] != "active",
|
||
}
|
||
|
||
if action == "stand":
|
||
active_hand = game_state["active_hand"]
|
||
if active_hand + 1 < len(game_state["player_hands"]):
|
||
game_state["active_hand"] += 1 # Move to next hand if split
|
||
explanation = (
|
||
f"Blackjack: You chose to stand on hand {active_hand + 1}. "
|
||
f"Hand {active_hand + 1}: {game_state['player_hands'][active_hand]} (Total: {game_state['player_totals'][active_hand]}). "
|
||
f"Dealer’s up card: {game_state['dealer_hand']}. Moving to hand {active_hand + 2}."
|
||
)
|
||
update_resource(game_key, game_state)
|
||
return {
|
||
"explanation": explanation,
|
||
"game_id": game_id,
|
||
"game_state": {
|
||
"player_hands": game_state["player_hands"],
|
||
"player_totals": game_state["player_totals"],
|
||
"dealer_hand": game_state["dealer_hand"],
|
||
"dealer_total": game_state["dealer_total"],
|
||
"can_split": False,
|
||
},
|
||
"bet": game_state["bet"],
|
||
"agent_id": agent_id,
|
||
"new_balance": wallet["balance"],
|
||
"game_complete": False,
|
||
}
|
||
|
||
# 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
|
||
|
||
# Resolve all hands
|
||
outcomes = []
|
||
payouts = []
|
||
bet_per_hand = game_state["bet"] // len(game_state["player_hands"])
|
||
for i, total in enumerate(game_state["player_totals"]):
|
||
if game_state["dealer_total"] > 21:
|
||
outcome = f"Hand {i + 1}: Dealer busts! You win!"
|
||
payout = bet_per_hand * 2
|
||
result = "win"
|
||
elif total > game_state["dealer_total"]:
|
||
outcome = f"Hand {i + 1}: Your total beats the dealer's! You win!"
|
||
payout = bet_per_hand * 2
|
||
result = "win"
|
||
elif game_state["dealer_total"] > total:
|
||
outcome = f"Hand {i + 1}: Dealer’s total beats yours. You lose."
|
||
payout = 0
|
||
result = "loss"
|
||
else:
|
||
outcome = f"Hand {i + 1}: Push! It’s a tie, your bet is returned."
|
||
payout = bet_per_hand
|
||
result = "tie"
|
||
outcomes.append(outcome)
|
||
payouts.append(payout)
|
||
|
||
game_state["status"] = "complete"
|
||
total_payout = sum(payouts)
|
||
wallet["balance"] = wallet["balance"] - game_state["bet"] + total_payout
|
||
wallet["last_transaction"] = {
|
||
"game": "blackjack",
|
||
"bet": game_state["bet"],
|
||
"payout": total_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"] += total_payout
|
||
for result in [o.split(": ")[1].split("!")[0] for o in outcomes]:
|
||
if "win" in result.lower():
|
||
stats["wins"] += 1
|
||
elif "lose" in result.lower():
|
||
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 hands: {', '.join([f'Hand {i+1}: {h} (Total: {t})' for i, (h, t) in enumerate(zip(game_state['player_hands'], game_state['player_totals']))])}. "
|
||
f"Dealer’s hand: {game_state['dealer_hand']} (Total: {game_state['dealer_total']}). "
|
||
f"{' '.join(outcomes)}"
|
||
)
|
||
|
||
return {
|
||
"explanation": explanation,
|
||
"game_id": game_id,
|
||
"game_state": {
|
||
"player_hands": game_state["player_hands"],
|
||
"player_totals": game_state["player_totals"],
|
||
"dealer_hand": game_state["dealer_hand"],
|
||
"dealer_total": game_state["dealer_total"],
|
||
"can_split": False,
|
||
},
|
||
"payout": total_payout,
|
||
"bet": game_state["bet"],
|
||
"agent_id": agent_id,
|
||
"new_balance": wallet["balance"],
|
||
"game_complete": True,
|
||
"outcomes": outcomes,
|
||
}
|
||
|
||
|
||
# Memory endpoints with lock and timeout
|
||
LOCK_TIMEOUT = 2 # Seconds to wait for lock acquisition
|
||
|
||
|
||
@app.route("/", methods=["GET"])
|
||
def index():
|
||
logger.info("Received / GET request")
|
||
html_template = """
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>SLOP API Server</title>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
max-width: 800px;
|
||
margin: 50px auto;
|
||
padding: 20px;
|
||
background-color: #f5f5f5;
|
||
}
|
||
h1 {
|
||
color: #333;
|
||
border-bottom: 2px solid #4CAF50;
|
||
padding-bottom: 10px;
|
||
}
|
||
.links {
|
||
margin: 20px 0;
|
||
}
|
||
.links a {
|
||
display: block;
|
||
margin: 10px 0;
|
||
padding: 10px;
|
||
background-color: white;
|
||
border: 1px solid #ddd;
|
||
border-radius: 5px;
|
||
text-decoration: none;
|
||
color: #333;
|
||
transition: all 0.3s;
|
||
}
|
||
.links a:hover {
|
||
background-color: #4CAF50;
|
||
color: white;
|
||
border-color: #4CAF50;
|
||
}
|
||
.endpoints {
|
||
background-color: white;
|
||
padding: 20px;
|
||
border-radius: 5px;
|
||
margin-top: 20px;
|
||
}
|
||
.endpoints h2 {
|
||
color: #666;
|
||
font-size: 1.2em;
|
||
}
|
||
.endpoints ul {
|
||
list-style-type: none;
|
||
padding-left: 0;
|
||
}
|
||
.endpoints li {
|
||
padding: 5px 0;
|
||
color: #555;
|
||
}
|
||
.endpoints code {
|
||
background-color: #f0f0f0;
|
||
padding: 2px 5px;
|
||
border-radius: 3px;
|
||
font-family: monospace;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>SLOP API Server</h1>
|
||
<p>Welcome to the SLOP API Server v1.0</p>
|
||
|
||
<div class="links">
|
||
<h2>Available Interfaces:</h2>
|
||
<a href="https://streamlit.slop.unturf.com/">Streamlit UI</a>
|
||
<a href="/openapi">OpenAPI Documentation</a>
|
||
</div>
|
||
|
||
<div class="endpoints">
|
||
<h2>API Endpoints:</h2>
|
||
<ul>
|
||
<li><code>/memory</code> - Memory storage endpoints</li>
|
||
<li><code>/chat</code> - Chat completion endpoint</li>
|
||
<li><code>/models</code> - List available models</li>
|
||
<li><code>/tools</code> - Tool execution endpoints</li>
|
||
<li><code>/resources</code> - Resource management</li>
|
||
<li><code>/pay</code> - Payment processing</li>
|
||
</ul>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
"""
|
||
return render_template_string(html_template)
|
||
|
||
|
||
@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/<key>", 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/<key>", 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/<tool_id>", 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/<path:resource_id>", 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/<resource_id>", 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")
|