Spaces:
Running
Running
NeonClary Cursor commited on
Commit ·
99ae678
1
Parent(s): 7c3d69c
Fix gpt-5.4-mini tool calls and stuck chat thinking indicators.
Browse filesOmit 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
|
|
|
|
| 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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
| 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:
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 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 |
}
|