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