From 111ebf245a38e49270c7898985f3e3105ec1af2b Mon Sep 17 00:00:00 2001 From: Laurent Kretz Date: Sat, 25 Oct 2025 11:22:12 +0200 Subject: [PATCH] Implement TradingAgents FastAPI microservice --- app.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 app.py diff --git a/app.py b/app.py new file mode 100644 index 00000000..5f61e460 --- /dev/null +++ b/app.py @@ -0,0 +1,73 @@ +# app.py — micro-service FastAPI pour TradingAgents +import os, hmac, hashlib, time, datetime as dt +from fastapi import FastAPI, Header, HTTPException, Request +from pydantic import BaseModel +from tradingagents.graph.trading_graph import TradingAgentsGraph +from tradingagents.default_config import DEFAULT_CONFIG + +app = FastAPI(title="TradingAgents Service") + +# --------- Sécurité ---------- +SERVICE_SECRET = os.getenv("SERVICE_SECRET", "") +ALLOWED_DRIFT_SEC = 120 # horodatage valide +/- 2 min + +def verify_hmac(x_timestamp: str | None, x_signature: str | None, body_bytes: bytes): + if not SERVICE_SECRET: + return # pas d’auth activée + if not x_timestamp or not x_signature: + raise HTTPException(status_code=401, detail="Missing auth headers") + try: + ts = int(x_timestamp) + except Exception: + raise HTTPException(status_code=401, detail="Bad timestamp") + + now = int(time.time()) + if abs(now - ts) > ALLOWED_DRIFT_SEC: + raise HTTPException(status_code=401, detail="Stale request") + + # signature = HMAC_SHA256(secret, f"{timestamp}.{body}") + msg = f"{x_timestamp}.".encode("utf-8") + body_bytes + expected = hmac.new(SERVICE_SECRET.encode("utf-8"), msg, hashlib.sha256).hexdigest() + if not hmac.compare_digest(expected, x_signature): + raise HTTPException(status_code=401, detail="Bad signature") + +# --------- Schéma requête ---------- +class RunBody(BaseModel): + ticker: str + date: str | None = None # "YYYY-MM-DD" (optionnel) + deep_llm: str = "gpt-4.1-mini" + fast_llm: str = "gpt-4.1-mini" + debates: int = 1 + +def build_graph(deep_llm: str, fast_llm: str, debates: int): + cfg = DEFAULT_CONFIG.copy() + cfg["deep_think_llm"] = deep_llm + cfg["quick_think_llm"] = fast_llm + cfg["max_debate_rounds"] = debates + return TradingAgentsGraph(debug=False, config=cfg) + +@app.get("/") +def health(): + return {"ok": True, "service": "TradingAgents"} + +@app.post("/run") +async def run(request: Request, + x_secret: str | None = Header(default=None), + x_timestamp: str | None = Header(default=None), + x_signature: str | None = Header(default=None)): + # 1) Secret statique (simple) + if SERVICE_SECRET and x_secret != SERVICE_SECRET: + raise HTTPException(status_code=401, detail="Unauthorized") + + # 2) HMAC anti-rejeu (recommandé). Dé-commente si tu veux l’activer en plus du secret: + # body_bytes = await request.body() + # verify_hmac(x_timestamp, x_signature, body_bytes) + # body = RunBody.model_validate_json(body_bytes) + + # Si tu n'actives pas l'HMAC, on parse normalement : + body = RunBody.model_validate(await request.json()) + + date = body.date or dt.date.today().isoformat() + graph = build_graph(body.deep_llm, body.fast_llm, body.debates) + _, decision = graph.propagate(body.ticker.upper(), date) + return decision