webmazta commited on
Commit
4b270d1
Β·
1 Parent(s): 22d62d7

feat(llm): add 90s cooldown + auto-retry on OpenRouter rate limit (429)

Browse files
Files changed (3) hide show
  1. app.py +26 -1
  2. article_rewriter.py +54 -21
  3. main.py +49 -22
app.py CHANGED
@@ -39,6 +39,27 @@ from dotenv import load_dotenv
39
 
40
  load_dotenv()
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  # ──────────────────────────────────────────────
43
  # App Setup
44
  # ──────────────────────────────────────────────
@@ -328,7 +349,11 @@ def screen_article(title, summary, criteria):
328
  if not key: return True
329
  prompt = f"Decide if this article matches criteria: {criteria}\nTitle: {title}\nSummary: {summary}\nRespond ONLY with JSON: {{\"publish\": true/false, \"reason\": \"...\"}}"
330
  client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=key)
331
- resp = client.chat.completions.create(model="qwen/qwen3.6-plus:free", messages=[{"role":"user","content":prompt}], response_format={"type":"json_object"})
 
 
 
 
332
  result = json.loads(resp.choices[0].message.content)
333
  print(f"[AGENT] Screening: {result.get('publish')} - {result.get('reason')}")
334
  return result.get("publish", True)
 
39
 
40
  load_dotenv()
41
 
42
+ # ──────────────────────────────────────────────
43
+ # OpenRouter Retry Helper
44
+ # ──────────────────────────────────────────────
45
+
46
+ def _openrouter_with_retry(api_call_fn, max_retries=1):
47
+ """
48
+ Retry an OpenRouter API call with 90-second cooldown on rate limit (HTTP 429).
49
+ Falls through to caller's exception handling after max retries exhausted.
50
+ """
51
+ import time
52
+ for attempt in range(max_retries + 1):
53
+ try:
54
+ return api_call_fn()
55
+ except Exception as e:
56
+ error_str = str(e)
57
+ if "429" in error_str and attempt < max_retries:
58
+ print(f" [RATE LIMIT] OpenRouter rate limited (429). Cooling down for 90 seconds... (retry {attempt + 1}/{max_retries})")
59
+ time.sleep(90)
60
+ else:
61
+ raise
62
+
63
  # ──────────────────────────────────────────────
64
  # App Setup
65
  # ──────────────────────────────────────────────
 
349
  if not key: return True
350
  prompt = f"Decide if this article matches criteria: {criteria}\nTitle: {title}\nSummary: {summary}\nRespond ONLY with JSON: {{\"publish\": true/false, \"reason\": \"...\"}}"
351
  client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=key)
352
+
353
+ def _do_screen():
354
+ return client.chat.completions.create(model="qwen/qwen3.6-plus:free", messages=[{"role":"user","content":prompt}], response_format={"type":"json_object"})
355
+
356
+ resp = _openrouter_with_retry(_do_screen)
357
  result = json.loads(resp.choices[0].message.content)
358
  print(f"[AGENT] Screening: {result.get('publish')} - {result.get('reason')}")
359
  return result.get("publish", True)
article_rewriter.py CHANGED
@@ -51,6 +51,32 @@ load_dotenv()
51
 
52
  OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
53
  GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  WP_URL = os.getenv("WP_URL", "").rstrip("/")
55
  WP_USERNAME = os.getenv("WP_USERNAME", "")
56
  WP_APP_PASSWORD = os.getenv("WP_APP_PASSWORD", "")
@@ -590,21 +616,24 @@ def rewrite_article(original_title: str, original_body: str, source_url: str) ->
590
  try:
591
  print(f"✍️ Rewriting with OpenRouter ({LLM_MODEL})...")
592
  client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=OPENROUTER_API_KEY)
593
-
594
- completion = client.chat.completions.create(
595
- model=LLM_MODEL,
596
- messages=[
597
- {"role": "system", "content": "You are an expert content writer and SEO specialist."},
598
- {"role": "user", "content": prompt}
599
- ],
600
- temperature=0.8,
601
- response_format={"type": "json_object"},
602
- extra_body={
603
- "reasoning": {
604
- "max_tokens": 2048
 
 
605
  }
606
- }
607
- )
 
608
  raw_text = completion.choices[0].message.content.strip()
609
 
610
  # Log reasoning tokens (OpenRouter specific)
@@ -853,13 +882,17 @@ def filter_irrelevant_images(image_urls: list[str], article_title: str, article_
853
  try:
854
  print(f" [AI-FILTER] Trying OpenRouter (qwen/qwen3.6-plus:free)...")
855
  client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=openrouter_key)
856
- completion = client.chat.completions.create(
857
- model="qwen/qwen3.6-plus:free",
858
- messages=[{"role": "user", "content": prompt}],
859
- temperature=0.1,
860
- response_format={"type": "json_object"},
861
- extra_body={"reasoning": {"max_tokens": 512}}
862
- )
 
 
 
 
863
  raw_text = completion.choices[0].message.content.strip()
864
  except Exception as e:
865
  print(f" [WARN] OpenRouter image filter failed: {e}")
 
51
 
52
  OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
53
  GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
54
+
55
+
56
+ # ──────────────────────────────────────────────
57
+ # 0b. OpenRouter Retry Helper
58
+ # ──────────────────────────────────────────────
59
+
60
+ def _openrouter_with_retry(api_call_fn, max_retries=1):
61
+ """
62
+ Retry an OpenRouter API call with 90-second cooldown on rate limit (HTTP 429).
63
+ Falls through to caller's exception handling after max retries exhausted.
64
+ """
65
+ for attempt in range(max_retries + 1):
66
+ try:
67
+ return api_call_fn()
68
+ except Exception as e:
69
+ error_str = str(e)
70
+ if "429" in error_str and attempt < max_retries:
71
+ print(f" [RATE LIMIT] OpenRouter rate limited (429). Cooling down for 90 seconds... (retry {attempt + 1}/{max_retries})")
72
+ time.sleep(90)
73
+ else:
74
+ raise
75
+
76
+ # ──────────────────────────────────────────────
77
+ # Remaining Configuration
78
+ # ──────────────────────────────────────────────
79
+
80
  WP_URL = os.getenv("WP_URL", "").rstrip("/")
81
  WP_USERNAME = os.getenv("WP_USERNAME", "")
82
  WP_APP_PASSWORD = os.getenv("WP_APP_PASSWORD", "")
 
616
  try:
617
  print(f"✍️ Rewriting with OpenRouter ({LLM_MODEL})...")
618
  client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=OPENROUTER_API_KEY)
619
+
620
+ def _do_rewrite():
621
+ return client.chat.completions.create(
622
+ model=LLM_MODEL,
623
+ messages=[
624
+ {"role": "system", "content": "You are an expert content writer and SEO specialist."},
625
+ {"role": "user", "content": prompt}
626
+ ],
627
+ temperature=0.8,
628
+ response_format={"type": "json_object"},
629
+ extra_body={
630
+ "reasoning": {
631
+ "max_tokens": 2048
632
+ }
633
  }
634
+ )
635
+
636
+ completion = _openrouter_with_retry(_do_rewrite)
637
  raw_text = completion.choices[0].message.content.strip()
638
 
639
  # Log reasoning tokens (OpenRouter specific)
 
882
  try:
883
  print(f" [AI-FILTER] Trying OpenRouter (qwen/qwen3.6-plus:free)...")
884
  client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=openrouter_key)
885
+
886
+ def _do_filter():
887
+ return client.chat.completions.create(
888
+ model="qwen/qwen3.6-plus:free",
889
+ messages=[{"role": "user", "content": prompt}],
890
+ temperature=0.1,
891
+ response_format={"type": "json_object"},
892
+ extra_body={"reasoning": {"max_tokens": 512}}
893
+ )
894
+
895
+ completion = _openrouter_with_retry(_do_filter)
896
  raw_text = completion.choices[0].message.content.strip()
897
  except Exception as e:
898
  print(f" [WARN] OpenRouter image filter failed: {e}")
main.py CHANGED
@@ -707,6 +707,27 @@ def extract_frames(video_path: Path, count: int = 3) -> list[Image.Image]:
707
  return frames
708
 
709
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
710
  # ──────────────────────────────────────────────
711
  # 3. Video Analysis with Gemini
712
  # ──────────────────────────────────────────────
@@ -765,7 +786,7 @@ def _analyze_frames(frames: list[Image.Image]) -> dict:
765
  try:
766
  print(f" [ENGINE] Trying OpenRouter ({LLM_MODEL})...")
767
  client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=openrouter_key)
768
-
769
  messages_content = [{"type": "text", "text": prompt}]
770
  for frame in frames[:60]:
771
  b64_image = _image_to_base64(frame)
@@ -774,17 +795,20 @@ def _analyze_frames(frames: list[Image.Image]) -> dict:
774
  "image_url": {"url": f"data:image/jpeg;base64,{b64_image}"}
775
  })
776
 
777
- completion = client.chat.completions.create(
778
- model=LLM_MODEL,
779
- messages=[{"role": "user", "content": messages_content}],
780
- temperature=0.2,
781
- response_format={"type": "json_object"},
782
- extra_body={
783
- "reasoning": {
784
- "max_tokens": 2048
 
 
785
  }
786
- }
787
- )
 
788
  raw_text = completion.choices[0].message.content.strip()
789
 
790
  # Log reasoning tokens if available (OpenRouter specific)
@@ -897,18 +921,21 @@ def generate_content(analysis: any, source_url: str) -> dict:
897
  try:
898
  print(f"[CONTENT] Generating with OpenRouter ({LLM_MODEL})...")
899
  client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=openrouter_key)
900
-
901
- completion = client.chat.completions.create(
902
- model=LLM_MODEL,
903
- messages=[{"role": "user", "content": prompt}],
904
- temperature=0.7,
905
- response_format={"type": "json_object"},
906
- extra_body={
907
- "reasoning": {
908
- "max_tokens": 2048
 
 
909
  }
910
- }
911
- )
 
912
  raw_text = completion.choices[0].message.content.strip()
913
 
914
  # Log reasoning tokens (OpenRouter specific)
 
707
  return frames
708
 
709
 
710
+ # ──────────────────────────────────────────────
711
+ # 2b. OpenRouter Retry Helper
712
+ # ──────────────────────────────────────────────
713
+
714
+ def _openrouter_with_retry(api_call_fn, max_retries=1):
715
+ """
716
+ Retry an OpenRouter API call with 90-second cooldown on rate limit (HTTP 429).
717
+ Falls through to caller's exception handling after max retries exhausted.
718
+ """
719
+ for attempt in range(max_retries + 1):
720
+ try:
721
+ return api_call_fn()
722
+ except Exception as e:
723
+ error_str = str(e)
724
+ if "429" in error_str and attempt < max_retries:
725
+ print(f" [RATE LIMIT] OpenRouter rate limited (429). Cooling down for 90 seconds... (retry {attempt + 1}/{max_retries})")
726
+ time.sleep(90)
727
+ else:
728
+ raise
729
+
730
+
731
  # ──────────────────────────────────────────────
732
  # 3. Video Analysis with Gemini
733
  # ──────────────────────────────────────────────
 
786
  try:
787
  print(f" [ENGINE] Trying OpenRouter ({LLM_MODEL})...")
788
  client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=openrouter_key)
789
+
790
  messages_content = [{"type": "text", "text": prompt}]
791
  for frame in frames[:60]:
792
  b64_image = _image_to_base64(frame)
 
795
  "image_url": {"url": f"data:image/jpeg;base64,{b64_image}"}
796
  })
797
 
798
+ def _do_analyze():
799
+ return client.chat.completions.create(
800
+ model=LLM_MODEL,
801
+ messages=[{"role": "user", "content": messages_content}],
802
+ temperature=0.2,
803
+ response_format={"type": "json_object"},
804
+ extra_body={
805
+ "reasoning": {
806
+ "max_tokens": 2048
807
+ }
808
  }
809
+ )
810
+
811
+ completion = _openrouter_with_retry(_do_analyze)
812
  raw_text = completion.choices[0].message.content.strip()
813
 
814
  # Log reasoning tokens if available (OpenRouter specific)
 
921
  try:
922
  print(f"[CONTENT] Generating with OpenRouter ({LLM_MODEL})...")
923
  client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=openrouter_key)
924
+
925
+ def _do_generate():
926
+ return client.chat.completions.create(
927
+ model=LLM_MODEL,
928
+ messages=[{"role": "user", "content": prompt}],
929
+ temperature=0.7,
930
+ response_format={"type": "json_object"},
931
+ extra_body={
932
+ "reasoning": {
933
+ "max_tokens": 2048
934
+ }
935
  }
936
+ )
937
+
938
+ completion = _openrouter_with_retry(_do_generate)
939
  raw_text = completion.choices[0].message.content.strip()
940
 
941
  # Log reasoning tokens (OpenRouter specific)