mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
🎯 fix: Key Prefix State to the Generation and Resolve the Run Model
The cleared-prefix state was keyed to the stream id, which never changes within a conversation: request.js sets streamId = conversationId, so once a reconnect cleared the state every later edited resubmission in that conversation dispatched run steps and labels with no offset and could overwrite the prefix it retained. It is now keyed to the response message id, the only per-generation identity available here -- minted per submission and carried through a resume unchanged. That is the third identity tried for this state. isResume missed the deduplicated-retry path (a lost response returns resumed: true for a new generation); the stream id is conversation-scoped. The response id is the boundary that actually matches a generation. current_model labels now resolve the model the run is really using. initializeAgent merges the request's endpointOption override into model_parameters and the run gives it precedence, so preferring the saved agent.model could send labels to a different, potentially unavailable or more expensive model than the conversation is on.
This commit is contained in:
parent
8ed6947307
commit
af2c838708
2 changed files with 24 additions and 14 deletions
|
|
@ -557,9 +557,11 @@ export default function useResumableSSE(
|
|||
* `editPrefixLength` must no longer be applied — by run steps or labels.
|
||||
*/
|
||||
const editPrefixClearedRef = useRef(false);
|
||||
/** Stream the cleared-prefix state above belongs to, so it is dropped when
|
||||
* the generation changes rather than when a subscribe happens to be live. */
|
||||
const prefixStateStreamIdRef = useRef<string | null>(null);
|
||||
/** Generation the cleared-prefix state above belongs to, so it is dropped
|
||||
* when a new generation starts rather than when a subscribe happens to be
|
||||
* live. Keyed by response message id — the stream id is the conversation
|
||||
* id and is therefore shared by every generation within it. */
|
||||
const prefixStateGenerationIdRef = useRef<string | null>(null);
|
||||
|
||||
/** Removes the pending chip once its steer is injected (the inline content
|
||||
* part becomes the durable record), and records the id so a 202 ACK that
|
||||
|
|
@ -705,15 +707,19 @@ export default function useResumableSSE(
|
|||
* would otherwise dispatch with no offset and overwrite the content it
|
||||
* kept.
|
||||
*
|
||||
* Keyed on the STREAM, not on `isResume`: a submission whose POST
|
||||
* succeeded server-side but lost its response is retried, comes back
|
||||
* `resumed: true`, and subscribes in resume mode despite being a new
|
||||
* generation — so an `isResume` check skips the reset exactly when it
|
||||
* is needed. The stream id changes with the generation and does not
|
||||
* change across reconnects of one, which is the boundary that matters.
|
||||
* Keyed on the RESPONSE MESSAGE id — the only per-generation identity
|
||||
* available here. Not `isResume`: a submission whose POST succeeded but
|
||||
* lost its response is retried, returns `resumed: true`, and subscribes
|
||||
* in resume mode despite being a new generation. And not the stream id:
|
||||
* `request.js` sets `streamId = conversationId`, so every generation in
|
||||
* a conversation shares it and the state would never clear. The
|
||||
* response id is minted per submission and is carried through a resume
|
||||
* unchanged, which is exactly the boundary that matters.
|
||||
*/
|
||||
if (prefixStateStreamIdRef.current !== currentStreamId) {
|
||||
prefixStateStreamIdRef.current = currentStreamId;
|
||||
const generationId =
|
||||
(currentSubmission.initialResponse as TMessage | undefined)?.messageId ?? currentStreamId;
|
||||
if (prefixStateGenerationIdRef.current !== generationId) {
|
||||
prefixStateGenerationIdRef.current = generationId;
|
||||
editPrefixClearedRef.current = false;
|
||||
}
|
||||
let { userMessage } = currentSubmission;
|
||||
|
|
|
|||
|
|
@ -189,11 +189,15 @@ export async function resolveActivityLabelModel({
|
|||
activity.model != null && activity.model !== Constants.CURRENT_MODEL
|
||||
? activity.model
|
||||
: undefined;
|
||||
/** `model_parameters.model` FIRST: `initializeAgent` merges the request's
|
||||
* `endpointOption` override into it and the run itself gives it precedence,
|
||||
* so the saved `agent.model` can be a stale or entirely different model.
|
||||
* Reading it first is what makes "current model" mean the model the
|
||||
* conversation is actually running on. */
|
||||
const runModel = agent.model_parameters?.model ?? agent.model;
|
||||
const model =
|
||||
activityModel ??
|
||||
(titleModel != null && titleModel !== Constants.CURRENT_MODEL
|
||||
? titleModel
|
||||
: (agent.model ?? agent.model_parameters?.model));
|
||||
(titleModel != null && titleModel !== Constants.CURRENT_MODEL ? titleModel : runModel);
|
||||
const options = await providerConfig.getOptions({
|
||||
req,
|
||||
endpoint,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue