Fix LLM model list, Castopod API, and server runner
- Remove gpt-4o-realtime (WebSocket-only) from OpenRouter models - Increase OpenRouter timeout to 60s and max_tokens to 150 - Handle empty LLM responses - Fix publish_episode.py for current Castopod API fields - Add port conflict check and graceful shutdown to run.sh Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,10 +7,10 @@ from ..config import settings
|
||||
|
||||
# Available OpenRouter models
|
||||
OPENROUTER_MODELS = [
|
||||
"anthropic/claude-3-haiku",
|
||||
"anthropic/claude-3.5-sonnet",
|
||||
"openai/gpt-4o-mini",
|
||||
"openai/gpt-4o",
|
||||
"anthropic/claude-3-haiku",
|
||||
"anthropic/claude-3.5-sonnet",
|
||||
"google/gemini-flash-1.5",
|
||||
"google/gemini-pro-1.5",
|
||||
"meta-llama/llama-3.1-8b-instruct",
|
||||
@@ -114,7 +114,7 @@ class LLMService:
|
||||
"""Call OpenRouter API with retry"""
|
||||
for attempt in range(2): # Try twice
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||
response = await client.post(
|
||||
"https://openrouter.ai/api/v1/chat/completions",
|
||||
headers={
|
||||
@@ -124,12 +124,16 @@ class LLMService:
|
||||
json={
|
||||
"model": self.openrouter_model,
|
||||
"messages": messages,
|
||||
"max_tokens": 100,
|
||||
"max_tokens": 150,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
return data["choices"][0]["message"]["content"]
|
||||
content = data["choices"][0]["message"]["content"]
|
||||
if not content or not content.strip():
|
||||
print(f"OpenRouter returned empty response")
|
||||
return ""
|
||||
return content
|
||||
except (httpx.TimeoutException, httpx.ReadTimeout):
|
||||
print(f"OpenRouter timeout (attempt {attempt + 1})")
|
||||
if attempt == 0:
|
||||
|
||||
@@ -155,11 +155,12 @@ Respond with ONLY valid JSON, no markdown or explanation."""
|
||||
return metadata
|
||||
|
||||
|
||||
def create_episode(audio_path: str, metadata: dict, duration: int) -> dict:
|
||||
def create_episode(audio_path: str, metadata: dict, episode_number: int) -> dict:
|
||||
"""Create episode on Castopod."""
|
||||
print("[3/5] Creating episode on Castopod...")
|
||||
|
||||
headers = get_auth_header()
|
||||
slug = re.sub(r'[^a-z0-9]+', '-', metadata["title"].lower()).strip('-')
|
||||
|
||||
# Upload audio and create episode
|
||||
with open(audio_path, "rb") as f:
|
||||
@@ -168,21 +169,25 @@ def create_episode(audio_path: str, metadata: dict, duration: int) -> dict:
|
||||
}
|
||||
data = {
|
||||
"title": metadata["title"],
|
||||
"description_markdown": metadata["description"],
|
||||
"slug": slug,
|
||||
"description": metadata["description"],
|
||||
"parental_advisory": "explicit",
|
||||
"type": "full",
|
||||
"created_by": "1"
|
||||
"podcast_id": str(PODCAST_ID),
|
||||
"created_by": "1",
|
||||
"updated_by": "1",
|
||||
"episode_number": str(episode_number),
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{CASTOPOD_URL}/api/rest/v1/podcasts/{PODCAST_ID}/episodes",
|
||||
f"{CASTOPOD_URL}/api/rest/v1/episodes",
|
||||
headers=headers,
|
||||
files=files,
|
||||
data=data
|
||||
)
|
||||
|
||||
if response.status_code not in (200, 201):
|
||||
print(f"Error creating episode: {response.text}")
|
||||
print(f"Error creating episode: {response.status_code} {response.text}")
|
||||
sys.exit(1)
|
||||
|
||||
episode = response.json()
|
||||
@@ -312,7 +317,7 @@ def get_next_episode_number() -> int:
|
||||
headers = get_auth_header()
|
||||
|
||||
response = requests.get(
|
||||
f"{CASTOPOD_URL}/api/rest/v1/podcasts/{PODCAST_ID}/episodes",
|
||||
f"{CASTOPOD_URL}/api/rest/v1/episodes",
|
||||
headers=headers
|
||||
)
|
||||
|
||||
@@ -323,7 +328,12 @@ def get_next_episode_number() -> int:
|
||||
if not episodes:
|
||||
return 1
|
||||
|
||||
max_num = max(ep.get("number", 0) for ep in episodes)
|
||||
# Filter to our podcast
|
||||
our_episodes = [ep for ep in episodes if ep.get("podcast_id") == PODCAST_ID]
|
||||
if not our_episodes:
|
||||
return 1
|
||||
|
||||
max_num = max(ep.get("number", 0) or 0 for ep in our_episodes)
|
||||
return max_num + 1
|
||||
|
||||
|
||||
@@ -373,7 +383,7 @@ def main():
|
||||
return
|
||||
|
||||
# Step 3: Create episode
|
||||
episode = create_episode(str(audio_path), metadata, transcript["duration"])
|
||||
episode = create_episode(str(audio_path), metadata, episode_number)
|
||||
|
||||
# Step 4: Publish
|
||||
episode = publish_episode(episode["id"])
|
||||
|
||||
45
run.sh
45
run.sh
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
# AI Radio Show - Server Runner with restart support
|
||||
|
||||
PORT=8000
|
||||
LOG_FILE="/tmp/ai-radio-show.log"
|
||||
RESTART_FLAG="/tmp/ai-radio-show.restart"
|
||||
STOP_FLAG="/tmp/ai-radio-show.stop"
|
||||
@@ -13,16 +14,46 @@ source venv/bin/activate
|
||||
# Cleanup old flags
|
||||
rm -f "$RESTART_FLAG" "$STOP_FLAG"
|
||||
|
||||
# Check if port is already in use
|
||||
if lsof -i ":$PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then
|
||||
EXISTING_PID=$(lsof -i ":$PORT" -sTCP:LISTEN -t 2>/dev/null | head -1)
|
||||
echo "ERROR: Port $PORT is already in use by PID $EXISTING_PID"
|
||||
echo "Run: kill $EXISTING_PID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
kill_server() {
|
||||
local pid=$1
|
||||
if ! kill -0 "$pid" 2>/dev/null; then
|
||||
return
|
||||
fi
|
||||
kill "$pid" 2>/dev/null
|
||||
# Wait up to 5 seconds for graceful shutdown
|
||||
for i in $(seq 1 10); do
|
||||
if ! kill -0 "$pid" 2>/dev/null; then
|
||||
return
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
# Force kill if still alive
|
||||
echo "[$(date)] Server didn't stop gracefully, force killing..." | tee -a "$LOG_FILE"
|
||||
kill -9 "$pid" 2>/dev/null
|
||||
wait "$pid" 2>/dev/null
|
||||
}
|
||||
|
||||
echo "AI Radio Show Server Runner"
|
||||
echo "Log file: $LOG_FILE"
|
||||
echo "Press Ctrl+C to stop"
|
||||
echo ""
|
||||
|
||||
while true; do
|
||||
echo "[$(date)] Starting server..." | tee -a "$LOG_FILE"
|
||||
# Handle Ctrl+C
|
||||
trap 'echo ""; echo "[$(date)] Interrupted" | tee -a "$LOG_FILE"; kill_server $SERVER_PID; exit 0' INT TERM
|
||||
|
||||
# Start uvicorn with output to both console and log file
|
||||
python -m uvicorn backend.main:app --host 0.0.0.0 --port 8000 2>&1 | tee -a "$LOG_FILE" &
|
||||
while true; do
|
||||
echo "[$(date)] Starting server on port $PORT..." | tee -a "$LOG_FILE"
|
||||
|
||||
# Start uvicorn directly (not through tee pipe so we get the real PID)
|
||||
python -m uvicorn backend.main:app --host 0.0.0.0 --port $PORT >> "$LOG_FILE" 2>&1 &
|
||||
SERVER_PID=$!
|
||||
|
||||
# Wait for server to exit or restart signal
|
||||
@@ -30,8 +61,7 @@ while true; do
|
||||
if [ -f "$RESTART_FLAG" ]; then
|
||||
echo "[$(date)] Restart requested..." | tee -a "$LOG_FILE"
|
||||
rm -f "$RESTART_FLAG"
|
||||
kill $SERVER_PID 2>/dev/null
|
||||
wait $SERVER_PID 2>/dev/null
|
||||
kill_server $SERVER_PID
|
||||
sleep 1
|
||||
break
|
||||
fi
|
||||
@@ -39,8 +69,7 @@ while true; do
|
||||
if [ -f "$STOP_FLAG" ]; then
|
||||
echo "[$(date)] Stop requested..." | tee -a "$LOG_FILE"
|
||||
rm -f "$STOP_FLAG"
|
||||
kill $SERVER_PID 2>/dev/null
|
||||
wait $SERVER_PID 2>/dev/null
|
||||
kill_server $SERVER_PID
|
||||
echo "[$(date)] Server stopped." | tee -a "$LOG_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user