Files
OpenFin/backend/services/ai_chat.py
2026-07-14 17:55:49 +02:00

84 lines
4.2 KiB
Python

"""
Free-form, read-only chat with GPT-4o about the current cockpit state.
Deliberately has NO function-calling/tools wired up — a plain text-completion
call physically cannot trigger any action (no trade, no cycle, no DB write
beyond persisting the conversation itself). The system prompt also tells the
model explicitly not to claim it can act, so it doesn't mislead the user.
"""
import re
import time
from typing import Dict, List, Optional
from services.ai_analyzer import get_client
from services.ai_chat_context import assemble_context, CONTEXT_BLOCKS
SYSTEM_PROMPT_HEADER = """Tu es l'assistant IA integre au cockpit de trading OpenFin Intelligence.
Tu as acces ci-dessous a un instantane en lecture seule de la situation actuelle (portefeuille, risque geopolitique, regime macro, patterns, options/IV, indicateurs techniques, signaux ondelettes, watchlist, calendrier economique, rapports institutionnels, lecons accumulees, VaR/risque) - le meme niveau de detail que celui utilise par le cycle d'analyse automatique pour generer ses recommandations.
REGLES IMPORTANTES :
- Quand on te demande une idee ou un conseil de trade, PROPOSE quelque chose de concret (biais directionnel, instrument, montage d'options avec strikes/echeance si pertinent, niveaux techniques, justification tiree du contexte) - exactement comme le ferait le cycle automatique dans ses recommandations. Ne te contente pas d'observations vagues ni de renvoyer la question : prends position a partir du contexte fourni.
- La seule limite reelle est que tu ne peux EXECUTER aucune action toi-meme (aucun trade n'est passe, aucun cycle n'est declenche, aucune donnee n'est modifiee) - tes idees sont des suggestions que l'utilisateur doit valider et executer lui-meme ailleurs dans le cockpit. Ne le precise que si l'utilisateur semble croire que tu peux agir directement (ex. "achete X pour moi").
- Reponds en francais, de facon concise et directe, en t'appuyant sur le contexte fourni. Si une donnee demandee n'est pas dans le contexte ci-dessous, dis-le plutot que d'inventer.
=== CONTEXTE ACTUEL ===
{context}
=== FIN DU CONTEXTE ===
"""
def _chat_messages(system: str, messages: List[Dict], model: str = "gpt-4o", max_tokens: int = 1200) -> str:
"""Multi-turn variant of ai_analyzer._chat() — accepts a full message history
instead of a single system+user pair. Same client/retry/backoff logic, kept
independent so it never risks the well-tested cycle-facing _chat()."""
client = get_client()
if not client:
raise RuntimeError("OpenAI API key not configured")
kwargs = {
"model": model,
"messages": [{"role": "system", "content": system}] + messages,
"temperature": 0.4,
"max_tokens": max_tokens,
}
last_exc: Optional[Exception] = None
for attempt in range(4):
try:
resp = client.chat.completions.create(**kwargs)
return resp.choices[0].message.content or ""
except Exception as e:
last_exc = e
err_str = str(e)
if "429" in err_str or "rate_limit" in err_str:
m = re.search(r"try again in ([\d.]+)s", err_str)
wait = float(m.group(1)) + 1.0 if m else 2 ** (attempt + 1) * 5.0
time.sleep(min(wait, 60.0))
continue
raise
raise last_exc # type: ignore[misc]
def send_chat_message(
session_id: str,
message: str,
enabled_blocks: Optional[List[str]] = None,
refresh_context: bool = False,
) -> Dict:
from services.database import save_chat_message, get_chat_messages
enabled = enabled_blocks or CONTEXT_BLOCKS
blocks = assemble_context(enabled, session_id, refresh=refresh_context)
context_text = "\n\n".join(blocks.values())
system = SYSTEM_PROMPT_HEADER.format(context=context_text)
history = get_chat_messages(session_id)
messages = [{"role": h["role"], "content": h["content"]} for h in history]
messages.append({"role": "user", "content": message})
save_chat_message(session_id, "user", message)
reply = _chat_messages(system, messages)
save_chat_message(session_id, "assistant", reply)
return {"reply": reply, "blocks_included": list(blocks.keys())}