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
|
# Available OpenRouter models
|
||||||
OPENROUTER_MODELS = [
|
OPENROUTER_MODELS = [
|
||||||
"anthropic/claude-3-haiku",
|
|
||||||
"anthropic/claude-3.5-sonnet",
|
|
||||||
"openai/gpt-4o-mini",
|
"openai/gpt-4o-mini",
|
||||||
"openai/gpt-4o",
|
"openai/gpt-4o",
|
||||||
|
"anthropic/claude-3-haiku",
|
||||||
|
"anthropic/claude-3.5-sonnet",
|
||||||
"google/gemini-flash-1.5",
|
"google/gemini-flash-1.5",
|
||||||
"google/gemini-pro-1.5",
|
"google/gemini-pro-1.5",
|
||||||
"meta-llama/llama-3.1-8b-instruct",
|
"meta-llama/llama-3.1-8b-instruct",
|
||||||
@@ -114,7 +114,7 @@ class LLMService:
|
|||||||
"""Call OpenRouter API with retry"""
|
"""Call OpenRouter API with retry"""
|
||||||
for attempt in range(2): # Try twice
|
for attempt in range(2): # Try twice
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
"https://openrouter.ai/api/v1/chat/completions",
|
"https://openrouter.ai/api/v1/chat/completions",
|
||||||
headers={
|
headers={
|
||||||
@@ -124,12 +124,16 @@ class LLMService:
|
|||||||
json={
|
json={
|
||||||
"model": self.openrouter_model,
|
"model": self.openrouter_model,
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
"max_tokens": 100,
|
"max_tokens": 150,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
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):
|
except (httpx.TimeoutException, httpx.ReadTimeout):
|
||||||
print(f"OpenRouter timeout (attempt {attempt + 1})")
|
print(f"OpenRouter timeout (attempt {attempt + 1})")
|
||||||
if attempt == 0:
|
if attempt == 0:
|
||||||
|
|||||||
@@ -155,11 +155,12 @@ Respond with ONLY valid JSON, no markdown or explanation."""
|
|||||||
return metadata
|
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."""
|
"""Create episode on Castopod."""
|
||||||
print("[3/5] Creating episode on Castopod...")
|
print("[3/5] Creating episode on Castopod...")
|
||||||
|
|
||||||
headers = get_auth_header()
|
headers = get_auth_header()
|
||||||
|
slug = re.sub(r'[^a-z0-9]+', '-', metadata["title"].lower()).strip('-')
|
||||||
|
|
||||||
# Upload audio and create episode
|
# Upload audio and create episode
|
||||||
with open(audio_path, "rb") as f:
|
with open(audio_path, "rb") as f:
|
||||||
@@ -168,21 +169,25 @@ def create_episode(audio_path: str, metadata: dict, duration: int) -> dict:
|
|||||||
}
|
}
|
||||||
data = {
|
data = {
|
||||||
"title": metadata["title"],
|
"title": metadata["title"],
|
||||||
"description_markdown": metadata["description"],
|
"slug": slug,
|
||||||
|
"description": metadata["description"],
|
||||||
"parental_advisory": "explicit",
|
"parental_advisory": "explicit",
|
||||||
"type": "full",
|
"type": "full",
|
||||||
"created_by": "1"
|
"podcast_id": str(PODCAST_ID),
|
||||||
|
"created_by": "1",
|
||||||
|
"updated_by": "1",
|
||||||
|
"episode_number": str(episode_number),
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{CASTOPOD_URL}/api/rest/v1/podcasts/{PODCAST_ID}/episodes",
|
f"{CASTOPOD_URL}/api/rest/v1/episodes",
|
||||||
headers=headers,
|
headers=headers,
|
||||||
files=files,
|
files=files,
|
||||||
data=data
|
data=data
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code not in (200, 201):
|
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)
|
sys.exit(1)
|
||||||
|
|
||||||
episode = response.json()
|
episode = response.json()
|
||||||
@@ -312,7 +317,7 @@ def get_next_episode_number() -> int:
|
|||||||
headers = get_auth_header()
|
headers = get_auth_header()
|
||||||
|
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"{CASTOPOD_URL}/api/rest/v1/podcasts/{PODCAST_ID}/episodes",
|
f"{CASTOPOD_URL}/api/rest/v1/episodes",
|
||||||
headers=headers
|
headers=headers
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -323,7 +328,12 @@ def get_next_episode_number() -> int:
|
|||||||
if not episodes:
|
if not episodes:
|
||||||
return 1
|
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
|
return max_num + 1
|
||||||
|
|
||||||
|
|
||||||
@@ -373,7 +383,7 @@ def main():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Step 3: Create episode
|
# 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
|
# Step 4: Publish
|
||||||
episode = publish_episode(episode["id"])
|
episode = publish_episode(episode["id"])
|
||||||
|
|||||||
45
run.sh
45
run.sh
@@ -1,6 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# AI Radio Show - Server Runner with restart support
|
# AI Radio Show - Server Runner with restart support
|
||||||
|
|
||||||
|
PORT=8000
|
||||||
LOG_FILE="/tmp/ai-radio-show.log"
|
LOG_FILE="/tmp/ai-radio-show.log"
|
||||||
RESTART_FLAG="/tmp/ai-radio-show.restart"
|
RESTART_FLAG="/tmp/ai-radio-show.restart"
|
||||||
STOP_FLAG="/tmp/ai-radio-show.stop"
|
STOP_FLAG="/tmp/ai-radio-show.stop"
|
||||||
@@ -13,16 +14,46 @@ source venv/bin/activate
|
|||||||
# Cleanup old flags
|
# Cleanup old flags
|
||||||
rm -f "$RESTART_FLAG" "$STOP_FLAG"
|
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 "AI Radio Show Server Runner"
|
||||||
echo "Log file: $LOG_FILE"
|
echo "Log file: $LOG_FILE"
|
||||||
echo "Press Ctrl+C to stop"
|
echo "Press Ctrl+C to stop"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
while true; do
|
# Handle Ctrl+C
|
||||||
echo "[$(date)] Starting server..." | tee -a "$LOG_FILE"
|
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
|
while true; do
|
||||||
python -m uvicorn backend.main:app --host 0.0.0.0 --port 8000 2>&1 | tee -a "$LOG_FILE" &
|
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=$!
|
SERVER_PID=$!
|
||||||
|
|
||||||
# Wait for server to exit or restart signal
|
# Wait for server to exit or restart signal
|
||||||
@@ -30,8 +61,7 @@ while true; do
|
|||||||
if [ -f "$RESTART_FLAG" ]; then
|
if [ -f "$RESTART_FLAG" ]; then
|
||||||
echo "[$(date)] Restart requested..." | tee -a "$LOG_FILE"
|
echo "[$(date)] Restart requested..." | tee -a "$LOG_FILE"
|
||||||
rm -f "$RESTART_FLAG"
|
rm -f "$RESTART_FLAG"
|
||||||
kill $SERVER_PID 2>/dev/null
|
kill_server $SERVER_PID
|
||||||
wait $SERVER_PID 2>/dev/null
|
|
||||||
sleep 1
|
sleep 1
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
@@ -39,8 +69,7 @@ while true; do
|
|||||||
if [ -f "$STOP_FLAG" ]; then
|
if [ -f "$STOP_FLAG" ]; then
|
||||||
echo "[$(date)] Stop requested..." | tee -a "$LOG_FILE"
|
echo "[$(date)] Stop requested..." | tee -a "$LOG_FILE"
|
||||||
rm -f "$STOP_FLAG"
|
rm -f "$STOP_FLAG"
|
||||||
kill $SERVER_PID 2>/dev/null
|
kill_server $SERVER_PID
|
||||||
wait $SERVER_PID 2>/dev/null
|
|
||||||
echo "[$(date)] Server stopped." | tee -a "$LOG_FILE"
|
echo "[$(date)] Server stopped." | tee -a "$LOG_FILE"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user