slop.unturf.com/slop_with_models.py

1029 lines
38 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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}). Dealers 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"Dealers 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"Dealers 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"Dealers 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 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
# 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}: Dealers total beats yours. You lose."
payout = 0
result = "loss"
else:
outcome = f"Hand {i + 1}: Push! Its 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"Dealers 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")