ifieryarrows commited on
Commit
8cfa204
·
verified ·
1 Parent(s): 94645fd

Sync from GitHub (tests passed)

Browse files
app/commentary.py CHANGED
@@ -383,6 +383,7 @@ Write as a seasoned commodities strategist would for institutional clients—pre
383
  "fallback_models": settings.openrouter_fallback_models_list,
384
  "chain_deadline_seconds": getattr(settings, "openrouter_chain_deadline_seconds", 120.0),
385
  "provider": {"require_parameters": True},
 
386
  "referer": "https://copper-mind.vercel.app",
387
  "title": "CopperMind Commentary",
388
  }
 
383
  "fallback_models": settings.openrouter_fallback_models_list,
384
  "chain_deadline_seconds": getattr(settings, "openrouter_chain_deadline_seconds", 120.0),
385
  "provider": {"require_parameters": True},
386
+ "extra_payload": {"reasoning": {"effort": "medium"}},
387
  "referer": "https://copper-mind.vercel.app",
388
  "title": "CopperMind Commentary",
389
  }
app/openrouter_client.py CHANGED
@@ -120,8 +120,14 @@ def _extract_message_text(data: dict[str, Any]) -> str:
120
 
121
  def _candidate_payload(base_payload: dict[str, Any], candidate: str) -> dict[str, Any]:
122
  payload = {**base_payload, "model": candidate}
123
- if candidate.lower().startswith("nvidia/nemotron") and "reasoning" not in payload:
124
- payload["reasoning"] = {"effort": "none"}
 
 
 
 
 
 
125
  return payload
126
 
127
 
@@ -178,12 +184,19 @@ async def create_chat_completion(
178
  if time.monotonic() >= deadline:
179
  raise OpenRouterError("OpenRouter model chain deadline exceeded", category="timeout", model=candidate, attempts=attempts)
180
  await _throttle_request(rpm)
 
 
 
 
181
  payload = _candidate_payload(base_payload, candidate)
182
  response: Optional[httpx.Response] = None
183
  try:
184
- response = await client.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=payload)
185
- except httpx.TimeoutException as exc:
186
- last_error = OpenRouterError(str(exc), category="timeout", model=candidate)
 
 
 
187
  except httpx.RequestError as exc:
188
  last_error = OpenRouterError(str(exc), category="network", model=candidate)
189
  else:
@@ -210,7 +223,14 @@ async def create_chat_completion(
210
 
211
  status = response.status_code if response is not None else None
212
  retryable = last_error is not None and last_error.category in retryable_categories
213
- will_retry = retryable and attempt_index < max(0, int(max_retries))
 
 
 
 
 
 
 
214
  attempts.append(_attempt(candidate, attempt_index + 1, last_error.category if last_error else "network", status, will_retry))
215
  if last_error and last_error.category == "auth":
216
  last_error.attempts = attempts
 
120
 
121
  def _candidate_payload(base_payload: dict[str, Any], candidate: str) -> dict[str, Any]:
122
  payload = {**base_payload, "model": candidate}
123
+ existing_reasoning = payload.get("reasoning")
124
+ if candidate.lower().startswith("nvidia/nemotron"):
125
+ if isinstance(existing_reasoning, dict) and "effort" in existing_reasoning:
126
+ payload["reasoning"] = {"effort": existing_reasoning["effort"]}
127
+ else:
128
+ payload["reasoning"] = {"effort": "low"}
129
+ elif isinstance(existing_reasoning, dict) and set(existing_reasoning.keys()) == {"effort"}:
130
+ payload.pop("reasoning", None)
131
  return payload
132
 
133
 
 
184
  if time.monotonic() >= deadline:
185
  raise OpenRouterError("OpenRouter model chain deadline exceeded", category="timeout", model=candidate, attempts=attempts)
186
  await _throttle_request(rpm)
187
+ remaining_before_call = deadline - time.monotonic()
188
+ if remaining_before_call <= 0:
189
+ raise OpenRouterError("OpenRouter model chain deadline exceeded", category="timeout", model=candidate, attempts=attempts)
190
+ attempt_timeout = min(float(timeout_seconds), max(remaining_before_call, 0.1))
191
  payload = _candidate_payload(base_payload, candidate)
192
  response: Optional[httpx.Response] = None
193
  try:
194
+ response = await asyncio.wait_for(
195
+ client.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=payload),
196
+ timeout=attempt_timeout,
197
+ )
198
+ except (httpx.TimeoutException, asyncio.TimeoutError, TimeoutError) as exc:
199
+ last_error = OpenRouterError(str(exc) or "OpenRouter request timed out", category="timeout", model=candidate)
200
  except httpx.RequestError as exc:
201
  last_error = OpenRouterError(str(exc), category="network", model=candidate)
202
  else:
 
223
 
224
  status = response.status_code if response is not None else None
225
  retryable = last_error is not None and last_error.category in retryable_categories
226
+ # When fallback models exist, advance immediately on a timeout instead of
227
+ # burning a second 45s window on the same unresponsive upstream model.
228
+ skip_same_model_timeout_retry = (
229
+ last_error is not None
230
+ and last_error.category == "timeout"
231
+ and len(models) > 1
232
+ )
233
+ will_retry = retryable and not skip_same_model_timeout_retry and attempt_index < max(0, int(max_retries))
234
  attempts.append(_attempt(candidate, attempt_index + 1, last_error.category if last_error else "network", status, will_retry))
235
  if last_error and last_error.category == "auth":
236
  last_error.attempts = attempts
app/settings.py CHANGED
@@ -92,15 +92,15 @@ class Settings(BaseSettings):
92
  # Deprecated - kept for backward compatibility. Keep this on a currently
93
  # listed free model so installations without an explicit env override do
94
  # not silently start with a retired OpenRouter slug.
95
- openrouter_model: str = "nvidia/nemotron-3.5-super-vl:free"
96
  # Keep fast and reliable roles independent so one provider/model removal
97
  # cannot collapse the entire LLM path. NVIDIA and Liquid passed the exact
98
  # credentialed scoring/commentary probe on 2026-09-27; the Gemma free
99
  # models were temporarily rate-limited by their shared upstream pool.
100
- openrouter_model_scoring: str = "nvidia/nemotron-3.5-super-vl:free"
101
- openrouter_model_scoring_fast: Optional[str] = "nvidia/nemotron-3.5-super-vl:free"
102
  openrouter_model_scoring_reliable: Optional[str] = "liquid/lfm-2.5-2.6b:free"
103
- openrouter_model_commentary: str = "nvidia/nemotron-3.5-super-vl:free"
104
  # Stay below the shared free-provider request ceiling. The worker still
105
  # uses bounded retries and a per-chain deadline for transient 429s.
106
  openrouter_rpm: int = 12
@@ -115,7 +115,8 @@ class Settings(BaseSettings):
115
  # client as transport-level fallbacks when the primary model 429s/5xx's.
116
  # Example: "google/gemini-flash-1.5:free,meta-llama/llama-3.1-8b-instruct:free"
117
  openrouter_fallback_models: Optional[str] = (
118
- "liquid/lfm-2.5-2.6b:free,nvidia/nemotron-3.5-super-vl:free,"
 
119
  "google/gemma-4-31b-it:free,google/gemma-4-26b-a4b-it:free"
120
  )
121
  tokenizers_parallelism: str = "false"
@@ -134,7 +135,7 @@ class Settings(BaseSettings):
134
 
135
  # LLM Sentiment Analysis
136
  # Deprecated - kept for backward compatibility
137
- llm_sentiment_model: str = "nvidia/nemotron-3.5-super-vl:free"
138
 
139
  # Pipeline trigger authentication
140
  pipeline_trigger_secret: Optional[str] = None
@@ -255,7 +256,7 @@ class Settings(BaseSettings):
255
  self.llm_sentiment_model,
256
  self.openrouter_model,
257
  )
258
- or "nvidia/nemotron-3.5-super-vl:free"
259
  )
260
 
261
  @property
@@ -273,7 +274,7 @@ class Settings(BaseSettings):
273
  self.llm_sentiment_model,
274
  self.openrouter_model_scoring,
275
  )
276
- or "nvidia/nemotron-3.5-super-vl:free"
277
  )
278
 
279
  @property
 
92
  # Deprecated - kept for backward compatibility. Keep this on a currently
93
  # listed free model so installations without an explicit env override do
94
  # not silently start with a retired OpenRouter slug.
95
+ openrouter_model: str = "nvidia/nemotron-3-super-120b-a12b:free"
96
  # Keep fast and reliable roles independent so one provider/model removal
97
  # cannot collapse the entire LLM path. NVIDIA and Liquid passed the exact
98
  # credentialed scoring/commentary probe on 2026-09-27; the Gemma free
99
  # models were temporarily rate-limited by their shared upstream pool.
100
+ openrouter_model_scoring: str = "nvidia/nemotron-3-super-120b-a12b:free"
101
+ openrouter_model_scoring_fast: Optional[str] = "nvidia/nemotron-3-super-120b-a12b:free"
102
  openrouter_model_scoring_reliable: Optional[str] = "liquid/lfm-2.5-2.6b:free"
103
+ openrouter_model_commentary: str = "nvidia/nemotron-3-super-120b-a12b:free"
104
  # Stay below the shared free-provider request ceiling. The worker still
105
  # uses bounded retries and a per-chain deadline for transient 429s.
106
  openrouter_rpm: int = 12
 
115
  # client as transport-level fallbacks when the primary model 429s/5xx's.
116
  # Example: "google/gemini-flash-1.5:free,meta-llama/llama-3.1-8b-instruct:free"
117
  openrouter_fallback_models: Optional[str] = (
118
+ "liquid/lfm-2.5-2.6b:free,nvidia/nemotron-3-super-120b-a12b:free,"
119
+ "nvidia/nemotron-3.5-super-vl:free,"
120
  "google/gemma-4-31b-it:free,google/gemma-4-26b-a4b-it:free"
121
  )
122
  tokenizers_parallelism: str = "false"
 
135
 
136
  # LLM Sentiment Analysis
137
  # Deprecated - kept for backward compatibility
138
+ llm_sentiment_model: str = "nvidia/nemotron-3-super-120b-a12b:free"
139
 
140
  # Pipeline trigger authentication
141
  pipeline_trigger_secret: Optional[str] = None
 
256
  self.llm_sentiment_model,
257
  self.openrouter_model,
258
  )
259
+ or "nvidia/nemotron-3-super-120b-a12b:free"
260
  )
261
 
262
  @property
 
274
  self.llm_sentiment_model,
275
  self.openrouter_model_scoring,
276
  )
277
+ or "nvidia/nemotron-3-super-120b-a12b:free"
278
  )
279
 
280
  @property
scripts/probe_openrouter_contracts.py CHANGED
@@ -106,9 +106,9 @@ async def run_probe(*, fast: str, reliable: str, commentary_model: str, batch_si
106
 
107
  def main() -> int:
108
  parser = argparse.ArgumentParser(description="Probe exact OpenRouter production contracts")
109
- parser.add_argument("--fast", default="nvidia/nemotron-3.5-super-vl:free")
110
  parser.add_argument("--reliable", default="liquid/lfm-2.5-2.6b:free")
111
- parser.add_argument("--commentary", default="nvidia/nemotron-3.5-super-vl:free")
112
  parser.add_argument("--batch-size", type=int, default=12, help="Synthetic scoring items, up to the worker's 12-item chunk")
113
  args = parser.parse_args()
114
  result = asyncio.run(run_probe(fast=args.fast, reliable=args.reliable, commentary_model=args.commentary, batch_size=args.batch_size))
 
106
 
107
  def main() -> int:
108
  parser = argparse.ArgumentParser(description="Probe exact OpenRouter production contracts")
109
+ parser.add_argument("--fast", default="nvidia/nemotron-3-super-120b-a12b:free")
110
  parser.add_argument("--reliable", default="liquid/lfm-2.5-2.6b:free")
111
+ parser.add_argument("--commentary", default="nvidia/nemotron-3-super-120b-a12b:free")
112
  parser.add_argument("--batch-size", type=int, default=12, help="Synthetic scoring items, up to the worker's 12-item chunk")
113
  args = parser.parse_args()
114
  result = asyncio.run(run_probe(fast=args.fast, reliable=args.reliable, commentary_model=args.commentary, batch_size=args.batch_size))