84 lines
4.2 KiB
Python
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())}
|