feat: chatbot
This commit is contained in:
83
backend/services/ai_chat.py
Normal file
83
backend/services/ai_chat.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
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())}
|
||||
Reference in New Issue
Block a user