copper-mind / scripts /probe_openrouter_contracts.py
ifieryarrows's picture
Sync from GitHub (tests passed)
527abf4 verified
Raw History Blame Contribute Delete
5.85 kB
"""Live, non-mocked probe for the exact sentiment and commentary contracts."""
from __future__ import annotations
import argparse
import asyncio
import json
import pathlib
import sys
from types import SimpleNamespace
BACKEND_ROOT = pathlib.Path(__file__).resolve().parents[1]
if str(BACKEND_ROOT) not in sys.path:
sys.path.insert(0, str(BACKEND_ROOT))
from app import ai_engine, commentary
from app.settings import get_settings
async def run_probe(*, fast: str, reliable: str, commentary_model: str, batch_size: int = 12) -> dict:
runtime = get_settings()
api_keys = runtime.openrouter_api_keys
if not api_keys:
return {"ok": False, "error": "OpenRouter credential is not configured"}
probe_settings = SimpleNamespace(
openrouter_api_key=runtime.openrouter_api_key,
openrouter_api_key_backup=runtime.openrouter_api_key_backup,
openrouter_api_keys=api_keys,
openrouter_max_retries=1,
openrouter_rpm=runtime.openrouter_rpm,
openrouter_timeout_seconds=runtime.openrouter_timeout_seconds,
openrouter_chain_deadline_seconds=runtime.openrouter_chain_deadline_seconds,
openrouter_fallback_models_list=[],
resolved_scoring_fast_model=fast,
resolved_scoring_reliable_model=reliable,
resolved_commentary_model=commentary_model,
)
original_ai_settings = ai_engine.get_settings
original_commentary_settings = commentary.get_settings
ai_engine.get_settings = lambda: probe_settings
commentary.get_settings = lambda: probe_settings
try:
# Match the worker's 12-article V2 scoring chunk, using synthetic
# scenarios so the probe never depends on mutable market data.
scenarios = [
("Copper mine outage", "A temporary outage reduces concentrate shipments."),
("Refinery restart", "A smelter resumes production after scheduled maintenance."),
("Grid demand rises", "New grid projects increase near-term copper orders."),
("Dollar strengthens", "The US dollar rises against major currencies."),
("Warehouse inventories fall", "Exchange-registered copper stocks decline."),
("Scrap supply expands", "More secondary copper enters the market."),
("Housing starts slow", "Construction demand weakens this quarter."),
("Electronics orders improve", "Manufacturers report stronger copper demand."),
("Port disruption ends", "Concentrate exports resume after a short delay."),
("Oil price changes", "Crude oil moves with no stated copper link."),
("New mine commissioning", "Additional copper capacity begins operating."),
("Mixed industrial data", "Orders rise in one region and fall in another."),
]
articles = [
{"id": index + 1, "title": f"Synthetic contract probe: {title}", "description": description}
for index, (title, description) in enumerate(scenarios[:max(1, min(batch_size, 12))])
]
scoring = {}
for role, model, repair in (
("fast", fast, reliable),
("reliable", reliable, fast),
):
valid, failed, metrics, rate_limited = await ai_engine._score_subset_with_model_v2(
settings=probe_settings,
model_name=model,
repair_model_name=repair,
articles=articles,
horizon_days=5,
)
result = valid.get(1)
scoring[role] = {
"requested_model": model,
"actual_model": result.get("llm_model") if result else None,
"item_count": len(articles),
"valid_count": len(valid),
"ok_without_repair": bool(len(valid) == len(articles) and not failed and metrics.get("repair_success_count", 0) == 0),
"failure_category": metrics.get("failure_category"),
"rate_limited": bool(rate_limited),
}
commentary_result = await commentary._generate_commentary_and_stance(
current_price=6.6,
predicted_price=6.62,
predicted_return=0.003,
sentiment_index=0.2,
sentiment_label="Bullish",
top_influencers=[{"feature": "sentiment__index", "importance": 0.2}],
news_count=8,
)
commentary_probe = {
"requested_model": commentary_model,
"actual_model": commentary_result.model_name,
"generation_mode": commentary_result.generation_mode,
"fallback_reason": commentary_result.fallback_reason,
"ok_without_repair": commentary_result.generation_mode == "llm",
}
ok = all(item["ok_without_repair"] for item in scoring.values()) and commentary_probe["ok_without_repair"]
return {"ok": ok, "scoring": scoring, "commentary": commentary_probe}
finally:
ai_engine.get_settings = original_ai_settings
commentary.get_settings = original_commentary_settings
def main() -> int:
parser = argparse.ArgumentParser(description="Probe exact OpenRouter production contracts")
parser.add_argument("--fast", default="nvidia/nemotron-3.5-super-vl:free")
parser.add_argument("--reliable", default="google/gemma-4-31b-it:free")
parser.add_argument("--commentary", default="google/gemma-4-31b-it:free")
parser.add_argument("--batch-size", type=int, default=12, help="Synthetic scoring items, up to the worker's 12-item chunk")
args = parser.parse_args()
result = asyncio.run(run_probe(fast=args.fast, reliable=args.reliable, commentary_model=args.commentary, batch_size=args.batch_size))
print(json.dumps(result, indent=2))
return 0 if result.get("ok") else 1
if __name__ == "__main__":
raise SystemExit(main())