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

84 lines
3.6 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).
REGLES IMPORTANTES :
- Tu ne peux declencher AUCUNE action (pas de trade, pas de cycle, pas de modification) - tu es uniquement la pour discuter, expliquer et aider a comprendre la situation ou une idee (ex. un montage d'options, un signal ondelette, pourquoi un pattern a tel score).
- Si on te demande d'agir, rappelle clairement que tu ne peux qu'expliquer/discuter, pas executer.
- 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())}