mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-23 05:34:41 +00:00
fix(chat): ignore response IDs from previous conversations
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
9530fbe2bf
commit
6870608610
2 changed files with 55 additions and 5 deletions
|
|
@ -8,7 +8,10 @@ import { fetchAvailableModels } from "@/components/llm_calls/fetch_models";
|
|||
|
||||
const { mockMakeOpenAIResponsesRequest, shellState } = vi.hoisted(() => ({
|
||||
mockMakeOpenAIResponsesRequest: vi.fn(),
|
||||
shellState: { storageUnavailable: false },
|
||||
shellState: {
|
||||
activeConversationIdOverride: null as string | null,
|
||||
storageUnavailable: false,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
|
|
@ -42,6 +45,16 @@ vi.mock("react-syntax-highlighter/dist/esm/styles/prism", () => ({ coy: {}, oneD
|
|||
vi.mock("@/contexts/ChatShellContext", () => ({
|
||||
useChatShell: () => {
|
||||
const history = useChatHistory(null, "metrics-test-user");
|
||||
const activeConversationId = shellState.activeConversationIdOverride ?? history.currentActiveId;
|
||||
const activeConversation = shellState.activeConversationIdOverride
|
||||
? {
|
||||
id: shellState.activeConversationIdOverride,
|
||||
title: "Second conversation",
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
messages: [],
|
||||
}
|
||||
: history.activeConversation;
|
||||
return {
|
||||
accessToken: "sk-test",
|
||||
userId: "metrics-test-user",
|
||||
|
|
@ -51,8 +64,8 @@ vi.mock("@/contexts/ChatShellContext", () => ({
|
|||
selectedMCPServers: [],
|
||||
setSelectedMCPServers: vi.fn(),
|
||||
conversations: history.conversations,
|
||||
activeConversation: history.activeConversation,
|
||||
activeConversationId: history.currentActiveId,
|
||||
activeConversation,
|
||||
activeConversationId,
|
||||
storageUnavailable: shellState.storageUnavailable,
|
||||
staleId: false,
|
||||
createConversation: history.createConversation,
|
||||
|
|
@ -70,20 +83,22 @@ const ON_TIMING_DATA_INDEX = 7;
|
|||
const ON_USAGE_DATA_INDEX = 8;
|
||||
const ON_TOTAL_LATENCY_INDEX = 24;
|
||||
|
||||
async function sendOneMessage(onUrlUpdate?: OnUrlUpdateFunction): Promise<void> {
|
||||
renderWithProviders(<ChatConversationPage />, { onUrlUpdate });
|
||||
async function sendOneMessage(onUrlUpdate?: OnUrlUpdateFunction) {
|
||||
const result = renderWithProviders(<ChatConversationPage />, { onUrlUpdate });
|
||||
expect(await screen.findByRole("button", { name: /gpt-5\.4-mini/ })).toBeInTheDocument();
|
||||
fireEvent.change(screen.getByPlaceholderText("How can I help you today?"), {
|
||||
target: { value: "How much did this cost?" },
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: "Send" }));
|
||||
await waitFor(() => expect(mockMakeOpenAIResponsesRequest).toHaveBeenCalledTimes(1));
|
||||
return result;
|
||||
}
|
||||
|
||||
describe("/ui/chat request metrics", () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
mockMakeOpenAIResponsesRequest.mockReset();
|
||||
shellState.activeConversationIdOverride = null;
|
||||
shellState.storageUnavailable = false;
|
||||
vi.mocked(fetchAvailableModels).mockResolvedValue([{ model_group: "gpt-5.4-mini" }]);
|
||||
});
|
||||
|
|
@ -192,6 +207,30 @@ describe("/ui/chat request metrics", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it("ignores late response IDs after changing conversations", async () => {
|
||||
const firstCompletion = Promise.withResolvers<void>();
|
||||
mockMakeOpenAIResponsesRequest.mockImplementationOnce(async (...args: unknown[]) => {
|
||||
await firstCompletion.promise;
|
||||
(args[1] as (role: string, delta: string) => void)("assistant", "First answer");
|
||||
(args[15] as (id: string) => void)("response-first");
|
||||
});
|
||||
mockMakeOpenAIResponsesRequest.mockResolvedValue(undefined);
|
||||
|
||||
const { rerender } = await sendOneMessage();
|
||||
shellState.activeConversationIdOverride = "conversation-b";
|
||||
rerender(<ChatConversationPage />);
|
||||
|
||||
await act(async () => firstCompletion.resolve());
|
||||
fireEvent.change(screen.getByRole("textbox"), { target: { value: "Follow up" } });
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Send" })).toBeEnabled());
|
||||
fireEvent.click(screen.getByRole("button", { name: "Send" }));
|
||||
await waitFor(() => expect(mockMakeOpenAIResponsesRequest).toHaveBeenCalledTimes(2));
|
||||
|
||||
const second = mockMakeOpenAIResponsesRequest.mock.calls[1];
|
||||
expect(second[14]).toBeNull();
|
||||
expect(second[0]).toEqual([{ role: "user", content: "Follow up" }]);
|
||||
});
|
||||
|
||||
it("renders latency, TTFT, token counts and cost reported for the assistant turn", async () => {
|
||||
mockMakeOpenAIResponsesRequest.mockImplementation(async (...args: unknown[]) => {
|
||||
const updateTextUI = args[1] as (role: string, delta: string) => void;
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ export default function ChatConversationPage() {
|
|||
|
||||
const [responsesSessionId, setResponsesSessionId] = useState<string | null>(null);
|
||||
const responsesSessionGenerationRef = useRef(0);
|
||||
const previousResponsesConversationRef = useRef(activeConversationId);
|
||||
const [prevConversationIdForSessionReset, setPrevConversationIdForSessionReset] = useState(activeConversationId);
|
||||
const [isStreaming, setIsStreaming] = useState(false);
|
||||
const [inputText, setInputText] = useState("");
|
||||
|
|
@ -122,6 +123,16 @@ export default function ChatConversationPage() {
|
|||
.finally(() => setIsLoadingModels(false));
|
||||
}, [accessToken]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (
|
||||
previousResponsesConversationRef.current !== null &&
|
||||
previousResponsesConversationRef.current !== activeConversationId
|
||||
) {
|
||||
responsesSessionGenerationRef.current += 1;
|
||||
}
|
||||
previousResponsesConversationRef.current = activeConversationId;
|
||||
}, [activeConversationId]);
|
||||
|
||||
// Reset the responses session when switching between conversations so that
|
||||
// previous_response_id from conversation A is never sent for conversation B.
|
||||
if (activeConversationId !== prevConversationIdForSessionReset) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue