NathMen12 commited on
Commit
2286da7
·
verified ·
1 Parent(s): f3698bb

Upload 17 files

Browse files
Files changed (3) hide show
  1. app.py +53 -0
  2. static/index.html +3 -3
  3. static/js/app.js +44 -2
app.py CHANGED
@@ -1492,6 +1492,9 @@ async def run_job(job_id: str, params: dict):
1492
  from datetime import datetime, timezone
1493
  now = datetime.now(timezone.utc)
1494
  system += f"\n\nDate et heure actuelles (UTC) : {now.strftime('%Y-%m-%d %H:%M:%S')} ({now.strftime('%A %d %B %Y')})"
 
 
 
1495
  # FILTRER les messages tool et assistant.avec_tool_calls de l'historique :
1496
  # - Les messages tool n'ont pas de tool_call_id valide (le client ne peut pas le reconstruire)
1497
  # - Les assistant.avec_tool_calls doivent être suivis de tool messages, sinon l'API rejette
@@ -1616,6 +1619,56 @@ async def run_job(job_id: str, params: dict):
1616
  if job["tool_calls"] and provider in ("openai", "groq", "custom"):
1617
  await _execute_tool_calls(job, params)
1618
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1619
  job["status"] = "cancelled" if job["cancel_event"].is_set() else "done"
1620
 
1621
  # 5) Log usage
 
1492
  from datetime import datetime, timezone
1493
  now = datetime.now(timezone.utc)
1494
  system += f"\n\nDate et heure actuelles (UTC) : {now.strftime('%Y-%m-%d %H:%M:%S')} ({now.strftime('%A %d %B %Y')})"
1495
+ # Instruction de sentinelle <end> : le modèle DOIT terminer sa réponse par <end>
1496
+ # pour indiquer qu'il a fini. Si absent, on retry.
1497
+ system += "\n\nIMPORTANT : Tu DOIS terminer chaque réponse finale par la balise <end> (sans autre texte après). Cette balise indique que ta réponse est complète."
1498
  # FILTRER les messages tool et assistant.avec_tool_calls de l'historique :
1499
  # - Les messages tool n'ont pas de tool_call_id valide (le client ne peut pas le reconstruire)
1500
  # - Les assistant.avec_tool_calls doivent être suivis de tool messages, sinon l'API rejette
 
1619
  if job["tool_calls"] and provider in ("openai", "groq", "custom"):
1620
  await _execute_tool_calls(job, params)
1621
 
1622
+ # 5) Vérifier que la réponse n'est pas vide et contient <end>.
1623
+ # Si elle est vide OU ne contient pas <end>, on retry jusqu'à 3 fois
1624
+ # en demandant au modèle de compléter sa réponse.
1625
+ max_retries = 3
1626
+ for retry in range(max_retries):
1627
+ # Si le job a été annulé, ne pas retry
1628
+ if job["cancel_event"].is_set():
1629
+ break
1630
+ current_buffer = job["buffer"]
1631
+ # Vérifier si on a une réponse non vide ET contenant <end>
1632
+ has_end = "<end>" in current_buffer
1633
+ has_content = any(seg.get("type") == "text" and seg.get("content", "").strip()
1634
+ for seg in job.get("segments", []))
1635
+ if has_end and has_content:
1636
+ break # Réponse complète, on sort
1637
+ if retry == max_retries - 1:
1638
+ break # Dernier retry, on accepte ce qu'on a
1639
+ # Retry : demander au modèle de compléter
1640
+ print(f"[INFO] Réponse incomplète (empty={not has_content}, end={has_end}), retry {retry+1}/{max_retries}")
1641
+ job["status_text"] = "Finalisation de la réponse..."
1642
+ retry_messages = list(params.get("messages", []))
1643
+ # Ajouter le dernier message assistant (même incomplet)
1644
+ retry_messages.append({"role": "assistant", "content": current_buffer or "(vide)"})
1645
+ retry_messages.append({"role": "user", "content": "Ta réponse précédente semble incomplète ou vide. Donne-moi maintenant une réponse complète et détaillée. N'oublie pas de terminer par la balise <end>."})
1646
+ buffer_before_retry = len(job["buffer"])
1647
+ job["tool_calls"] = [] # reset pour éviter de re-exécuter les tools
1648
+ try:
1649
+ if provider == "openai":
1650
+ await run_openai_like(job, "https://api.openai.com/v1/chat/completions", api_key, model, system, retry_messages, tools=None, reasoning_effort=reasoning_effort)
1651
+ elif provider == "groq":
1652
+ await run_openai_like(job, "https://api.groq.com/openai/v1/chat/completions", api_key, model, system, retry_messages, tools=None, reasoning_effort=reasoning_effort)
1653
+ elif provider == "custom":
1654
+ await run_openai_like(job, base_url.rstrip("/") + "/chat/completions", api_key, model, system, retry_messages, tools=None, reasoning_effort=reasoning_effort)
1655
+ # Ajouter le nouveau texte comme segment
1656
+ retry_text = job["buffer"][buffer_before_retry:]
1657
+ if retry_text:
1658
+ job["segments"].append({"type": "text", "content": retry_text})
1659
+ except Exception as retry_err:
1660
+ print(f"[WARN] Retry {retry+1} échoué: {retry_err}")
1661
+ break
1662
+ job["status_text"] = None
1663
+
1664
+ # 6) Strip la balise <end> du buffer et des segments
1665
+ if "<end>" in job["buffer"]:
1666
+ job["buffer"] = job["buffer"].replace("<end>", "").rstrip()
1667
+ if job.get("segments"):
1668
+ for seg in job["segments"]:
1669
+ if seg.get("type") == "text" and "<end>" in (seg.get("content") or ""):
1670
+ seg["content"] = seg["content"].replace("<end>", "").rstrip()
1671
+
1672
  job["status"] = "cancelled" if job["cancel_event"].is_set() else "done"
1673
 
1674
  # 5) Log usage
static/index.html CHANGED
@@ -9,7 +9,7 @@
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
10
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
11
  <script src="https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js"></script>
12
- <link rel="stylesheet" href="/static/css/main.css?v=18">
13
  </head>
14
  <body>
15
 
@@ -335,7 +335,7 @@
335
  </div>
336
  </div>
337
 
338
- <script src="/static/js/i18n.js?v=18"></script>
339
- <script src="/static/js/app.js?v=18"></script>
340
  </body>
341
  </html>
 
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
10
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
11
  <script src="https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js"></script>
12
+ <link rel="stylesheet" href="/static/css/main.css?v=20">
13
  </head>
14
  <body>
15
 
 
335
  </div>
336
  </div>
337
 
338
+ <script src="/static/js/i18n.js?v=20"></script>
339
+ <script src="/static/js/app.js?v=20"></script>
340
  </body>
341
  </html>
static/js/app.js CHANGED
@@ -805,6 +805,48 @@ const App = {
805
  }
806
  },
807
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
808
  async sendMessage() {
809
  const input = document.getElementById('messageInput');
810
  const text = input.value.trim();
@@ -856,7 +898,7 @@ const App = {
856
  const apiKey = '';
857
  const system = `Tu es un assistant IA utile, précis et honnête. Réponds dans la langue de l'utilisateur. Formatte tes réponses en Markdown quand pertinent.`;
858
 
859
- const messages = chat.messages.filter(m => m.role !== 'assistant' || m.content).slice(-20).map(m => ({ role: m.role, content: m.content }));
860
 
861
  // Séparer images et fichiers texte pour l'envoi au serveur
862
  const allAttachments = attachments.length > 0 ? attachments : (userMsg.attachments || []);
@@ -1074,7 +1116,7 @@ const App = {
1074
  const model = chat.model;
1075
  const customProvider = chat.customProviderName || '';
1076
  const baseUrl = chat.baseUrl || '';
1077
- const messages = chat.messages.filter(m => m.role !== 'assistant' || m.content).slice(-20).map(m => ({ role: m.role, content: m.content }));
1078
  // Récupérer les attachments du dernier message user (pour retry avec images/fichiers)
1079
  const userAttachments = lastUser.attachments || [];
1080
  const images = userAttachments.filter(a => a.base64).map(a => ({ base64: a.base64, mime: a.mime }));
 
805
  }
806
  },
807
 
808
+ // Reconstruit les messages envoyés à l'API en incluant l'historique des tool calls et leurs résultats.
809
+ // Pour chaque message assistant qui a utilisé des tools, on inclut:
810
+ // - Le texte généré
811
+ // - Un résumé des tools appelés + leurs résultats (en format texte dans le contenu)
812
+ // Le modèle reçoit donc le contexte complet de ses recherches précédentes.
813
+ buildApiMessages(chat) {
814
+ const msgs = [];
815
+ const recent = chat.messages.slice(-20); // Garder les 20 derniers messages
816
+ for (const m of recent) {
817
+ if (m.role === 'user') {
818
+ msgs.push({ role: 'user', content: m.content || '' });
819
+ } else if (m.role === 'assistant') {
820
+ let content = m.content || '';
821
+ // Si le message a des segments, reconstruire le contenu avec les tool calls
822
+ if (m.segments && m.segments.length > 0) {
823
+ content = '';
824
+ for (const seg of m.segments) {
825
+ if (seg.type === 'text') {
826
+ content += seg.content || '';
827
+ } else if (seg.type === 'tool_call') {
828
+ let argsStr = '';
829
+ try { argsStr = JSON.stringify(seg.args); } catch (e) { argsStr = String(seg.args || ''); }
830
+ content += `\n\n[Outil utilisé: ${seg.name}]\nArguments: ${argsStr}\nRésultat: ${(seg.result || '').substring(0, 2000)}\n`;
831
+ }
832
+ }
833
+ }
834
+ // Aussi inclure les toolCalls (ancien format) si segments absent
835
+ if (!m.segments && m.toolCalls && m.toolCalls.length > 0) {
836
+ for (const tc of m.toolCalls) {
837
+ let argsStr = '';
838
+ try { argsStr = JSON.stringify(tc.args); } catch (e) { argsStr = String(tc.args || ''); }
839
+ content += `\n\n[Outil utilisé: ${tc.tool || tc.name}]\nArguments: ${argsStr}\nRésultat: ${(tc.result_preview || '').substring(0, 2000)}\n`;
840
+ }
841
+ }
842
+ if (content.trim()) {
843
+ msgs.push({ role: 'assistant', content: content.trim() });
844
+ }
845
+ }
846
+ }
847
+ return msgs;
848
+ },
849
+
850
  async sendMessage() {
851
  const input = document.getElementById('messageInput');
852
  const text = input.value.trim();
 
898
  const apiKey = '';
899
  const system = `Tu es un assistant IA utile, précis et honnête. Réponds dans la langue de l'utilisateur. Formatte tes réponses en Markdown quand pertinent.`;
900
 
901
+ const messages = this.buildApiMessages(chat);
902
 
903
  // Séparer images et fichiers texte pour l'envoi au serveur
904
  const allAttachments = attachments.length > 0 ? attachments : (userMsg.attachments || []);
 
1116
  const model = chat.model;
1117
  const customProvider = chat.customProviderName || '';
1118
  const baseUrl = chat.baseUrl || '';
1119
+ const messages = this.buildApiMessages(chat);
1120
  // Récupérer les attachments du dernier message user (pour retry avec images/fichiers)
1121
  const userAttachments = lastUser.attachments || [];
1122
  const images = userAttachments.filter(a => a.base64).map(a => ({ base64: a.base64, mime: a.mime }));