NeonClary Cursor commited on
Commit
99ae678
·
1 Parent(s): 7c3d69c

Fix gpt-5.4-mini tool calls and stuck chat thinking indicators.

Browse files

Omit reasoning_effort when calling OpenAI with function tools, clear thinking state on stream complete, and surface expired JWT so chat no longer silently stalls.

Co-authored-by: Cursor <cursoragent@cursor.com>

multi_llm_chatbot_backend/app/llm/openai_fallback_client.py CHANGED
@@ -36,7 +36,11 @@ class OpenAIFallbackClient(LLMClient):
36
 
37
  _ALLOWED_ROLES = {"system", "assistant", "user", "function", "tool", "developer"}
38
 
39
- def _reasoning_kwargs(self) -> Dict[str, Any]:
 
 
 
 
40
  if not self.reasoning_effort or self.reasoning_effort == "none":
41
  return {}
42
  return {"reasoning_effort": self.reasoning_effort}
@@ -120,6 +124,7 @@ class OpenAIFallbackClient(LLMClient):
120
  ]
121
  openai_tools = tool_definitions or []
122
  all_tool_calls: List[ToolCallInfo] = []
 
123
 
124
  try:
125
  for _round in range(self._MAX_TOOL_ROUNDS):
@@ -129,7 +134,7 @@ class OpenAIFallbackClient(LLMClient):
129
  messages=self._normalize_messages(messages),
130
  tools=openai_tools or None,
131
  **{token_kwarg: max_tokens},
132
- **self._reasoning_kwargs(),
133
  )
134
  if not self._uses_completion_tokens_param():
135
  tool_kwargs["temperature"] = temperature
 
36
 
37
  _ALLOWED_ROLES = {"system", "assistant", "user", "function", "tool", "developer"}
38
 
39
+ def _reasoning_kwargs(self, *, with_tools: bool = False) -> Dict[str, Any]:
40
+ # gpt-5.4-mini (and similar) reject reasoning_effort together with
41
+ # function tools on /v1/chat/completions. Omit it for tool calls.
42
+ if with_tools:
43
+ return {}
44
  if not self.reasoning_effort or self.reasoning_effort == "none":
45
  return {}
46
  return {"reasoning_effort": self.reasoning_effort}
 
124
  ]
125
  openai_tools = tool_definitions or []
126
  all_tool_calls: List[ToolCallInfo] = []
127
+ has_tools = bool(openai_tools)
128
 
129
  try:
130
  for _round in range(self._MAX_TOOL_ROUNDS):
 
134
  messages=self._normalize_messages(messages),
135
  tools=openai_tools or None,
136
  **{token_kwarg: max_tokens},
137
+ **self._reasoning_kwargs(with_tools=has_tools),
138
  )
139
  if not self._uses_completion_tokens_param():
140
  tool_kwargs["temperature"] = temperature
phd-advisor-frontend/src/pages/ChatPage.js CHANGED
@@ -242,14 +242,14 @@ const ChatPage = ({ user, authToken, onNavigateToHome, onNavigateToCanvas, onSig
242
  setCurrentSessionTitle(newSession.title);
243
 
244
  console.log('MongoDB session created:', newSession.id);
245
- return newSession.id;
246
  } else {
247
  console.error('Failed to create new session');
248
- return null;
249
  }
250
  } catch (error) {
251
  console.error('Error creating new session:', error);
252
- return null;
253
  }
254
  };
255
 
@@ -394,7 +394,8 @@ const handleNewChat = async (sessionId = null) => {
394
  const result = await response.json();
395
  if (result.status === 'success') {
396
  // Step 2: Immediately create MongoDB session
397
- const newSessionId = await createNewSession(`Chat ${new Date().toLocaleDateString()}`);
 
398
 
399
  if (newSessionId) {
400
  // Reset all state to fresh with the new session
@@ -482,9 +483,22 @@ const handleNewChat = async (sessionId = null) => {
482
  // Create new session if we don't have one
483
  let sessionId = currentSessionId;
484
  if (!sessionId) {
485
- sessionId = await createNewSession(inputMessage);
 
486
  if (!sessionId) {
487
  console.error('Failed to create session');
 
 
 
 
 
 
 
 
 
 
 
 
488
  return;
489
  }
490
  }
@@ -514,12 +528,16 @@ const handleNewChat = async (sessionId = null) => {
514
  setThinkingAdvisors(['system']);
515
 
516
  try {
 
 
 
517
  const response = await fetch(`${process.env.REACT_APP_API_URL}/chat-stream`, {
518
  method: 'POST',
519
  headers: {
520
  'Content-Type': 'application/json',
521
  'Authorization': `Bearer ${authToken}`,
522
  },
 
523
  body: JSON.stringify({
524
  user_input: inputMessage,
525
  response_length: 'medium',
@@ -564,7 +582,9 @@ const handleNewChat = async (sessionId = null) => {
564
  };
565
  setMessages(prev => [...prev, msg]);
566
  setThinkingAdvisors(prev => prev.filter(a => a !== d.persona_id));
567
- await saveMessageToSession(msg, sessionId);
 
 
568
  break;
569
  }
570
  case 'clarification':
@@ -587,6 +607,7 @@ const handleNewChat = async (sessionId = null) => {
587
  break;
588
  }
589
  if (d.phase === 'complete') {
 
590
  break;
591
  }
592
  if (d.persona_id != null) {
@@ -607,12 +628,17 @@ const handleNewChat = async (sessionId = null) => {
607
  }
608
  }
609
 
 
 
610
  } catch (error) {
611
  console.error('Error sending message:', error);
 
612
  setMessages(prev => [...prev, {
613
  id: generateMessageId(),
614
  type: 'error',
615
- content: `Failed to send message: ${error.message}`,
 
 
616
  timestamp: new Date()
617
  }]);
618
  } finally {
@@ -626,9 +652,11 @@ const handleNewChat = async (sessionId = null) => {
626
  // Ensure we have a session before proceeding
627
  let sessionId = currentSessionId;
628
  if (!sessionId) {
629
- sessionId = await createNewSession(inputMessage);
 
630
  if (!sessionId) {
631
  console.error('Failed to create session for reply');
 
632
  return;
633
  }
634
  }
 
242
  setCurrentSessionTitle(newSession.title);
243
 
244
  console.log('MongoDB session created:', newSession.id);
245
+ return { id: newSession.id, status: response.status };
246
  } else {
247
  console.error('Failed to create new session');
248
+ return { id: null, status: response.status };
249
  }
250
  } catch (error) {
251
  console.error('Error creating new session:', error);
252
+ return { id: null, status: 0 };
253
  }
254
  };
255
 
 
394
  const result = await response.json();
395
  if (result.status === 'success') {
396
  // Step 2: Immediately create MongoDB session
397
+ const created = await createNewSession(`Chat ${new Date().toLocaleDateString()}`);
398
+ const newSessionId = created?.id || null;
399
 
400
  if (newSessionId) {
401
  // Reset all state to fresh with the new session
 
483
  // Create new session if we don't have one
484
  let sessionId = currentSessionId;
485
  if (!sessionId) {
486
+ const created = await createNewSession(inputMessage);
487
+ sessionId = created?.id || null;
488
  if (!sessionId) {
489
  console.error('Failed to create session');
490
+ const authExpired = created?.status === 401;
491
+ setMessages(prev => [...prev, {
492
+ id: generateMessageId(),
493
+ type: 'error',
494
+ content: authExpired
495
+ ? 'Your login expired. Please sign in again to continue chatting.'
496
+ : 'Could not start a chat session. Please try again.',
497
+ timestamp: new Date()
498
+ }]);
499
+ if (authExpired && onSignOut) {
500
+ onSignOut();
501
+ }
502
  return;
503
  }
504
  }
 
528
  setThinkingAdvisors(['system']);
529
 
530
  try {
531
+ const controller = new AbortController();
532
+ const streamTimeout = setTimeout(() => controller.abort(), 120000);
533
+
534
  const response = await fetch(`${process.env.REACT_APP_API_URL}/chat-stream`, {
535
  method: 'POST',
536
  headers: {
537
  'Content-Type': 'application/json',
538
  'Authorization': `Bearer ${authToken}`,
539
  },
540
+ signal: controller.signal,
541
  body: JSON.stringify({
542
  user_input: inputMessage,
543
  response_length: 'medium',
 
582
  };
583
  setMessages(prev => [...prev, msg]);
584
  setThinkingAdvisors(prev => prev.filter(a => a !== d.persona_id));
585
+ saveMessageToSession(msg, sessionId).catch(err =>
586
+ console.error('Failed to persist advisor message:', err)
587
+ );
588
  break;
589
  }
590
  case 'clarification':
 
607
  break;
608
  }
609
  if (d.phase === 'complete') {
610
+ setThinkingAdvisors([]);
611
  break;
612
  }
613
  if (d.persona_id != null) {
 
628
  }
629
  }
630
 
631
+ clearTimeout(streamTimeout);
632
+
633
  } catch (error) {
634
  console.error('Error sending message:', error);
635
+ const isAbort = error?.name === 'AbortError';
636
  setMessages(prev => [...prev, {
637
  id: generateMessageId(),
638
  type: 'error',
639
+ content: isAbort
640
+ ? 'The advisor panel took too long to respond. Please try again.'
641
+ : `Failed to send message: ${error.message}`,
642
  timestamp: new Date()
643
  }]);
644
  } finally {
 
652
  // Ensure we have a session before proceeding
653
  let sessionId = currentSessionId;
654
  if (!sessionId) {
655
+ const created = await createNewSession(inputMessage);
656
+ sessionId = created?.id || null;
657
  if (!sessionId) {
658
  console.error('Failed to create session for reply');
659
+ if (created?.status === 401 && onSignOut) onSignOut();
660
  return;
661
  }
662
  }