mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-10-12 02:16:39 +00:00
Refactor: refine chat message to use question only (#20654)
This commit is contained in:
@@ -3845,13 +3845,11 @@ Starts a chat completion request. The same endpoint supports three modes:
|
||||
- `'Authorization: Bearer <YOUR_API_KEY>'`
|
||||
- Body:
|
||||
|
||||
- `"messages"`: `list[object]`
|
||||
- `"question"`: `string`
|
||||
- `"stream"`: `boolean`
|
||||
- `"chat_id"`: `string` (optional)
|
||||
- `"session_id"`: `string` (optional)
|
||||
- `"llm_id"`: `string` (optional)
|
||||
- `"pass_all_history_messages"`: `boolean` (optional)
|
||||
- `"legacy"`: `boolean` (optional)
|
||||
|
||||
##### Request example
|
||||
@@ -3893,10 +3891,8 @@ curl --request POST \
|
||||
|
||||
##### Request Parameters
|
||||
|
||||
- `"messages"`: (*Body Parameter*), `list[object]`
|
||||
The latest user message, or the conversation messages sent to the model when `pass_all_history_messages` is `true`. Either `messages` or `question` is required.
|
||||
- `"question"`: (*Body Parameter*), `string`
|
||||
Latest user question. This is equivalent to passing `messages: [{"role": "user", "content": question}]`.
|
||||
The user question.
|
||||
- `"stream"`: (*Body Parameter*), `boolean`
|
||||
Enables streaming output:
|
||||
- `true`: Enable streaming (default).
|
||||
@@ -3907,8 +3903,6 @@ curl --request POST \
|
||||
Optional session ID. If `chat_id` is provided but `session_id` is omitted, a new session will be generated automatically.
|
||||
- `"llm_id"`: (*Body Parameter*), `string`
|
||||
Optional model override when a specific chat model should be used for this request.
|
||||
- `"pass_all_history_messages"`: (*Body Parameter*), `boolean`
|
||||
When `chat_id` and `session_id` are provided, defaults to `false`, so the server uses stored session history and only the latest user message from the request. Set to `true` to replace/use the submitted full `messages` history, and overrides the stored session history.
|
||||
- `"legacy"`: (*Body Parameter*), `boolean`
|
||||
Defaults to `false`. Enables backward compatibility with RAGFlow v0.23.0 for streaming responses. When set to `true`:
|
||||
- Cumulative output: The `"answer"` field in each chunk returns the entire text generated so far, rather than just the new tokens (deltas).
|
||||
|
||||
@@ -163,12 +163,14 @@ interface UserGroupButtonProps extends Partial<IRemoveMessageById> {
|
||||
content: string;
|
||||
regenerateMessage?: () => void;
|
||||
sendLoading: boolean;
|
||||
deleteDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const UserGroupButton = ({
|
||||
content,
|
||||
messageId,
|
||||
sendLoading,
|
||||
deleteDisabled = false,
|
||||
removeMessageById,
|
||||
regenerateMessage,
|
||||
}: UserGroupButtonProps) => {
|
||||
@@ -211,7 +213,7 @@ export const UserGroupButton = ({
|
||||
size="icon-xs"
|
||||
className="border-0"
|
||||
onClick={onRemoveMessage}
|
||||
disabled={loading}
|
||||
disabled={loading || deleteDisabled}
|
||||
>
|
||||
<LucideTrash2 />
|
||||
</Button>
|
||||
|
||||
@@ -43,6 +43,7 @@ interface IProps extends Partial<IRemoveMessageById>, IRegenerateMessage {
|
||||
reference: IReference;
|
||||
loading?: boolean;
|
||||
sendLoading?: boolean;
|
||||
deleteDisabled?: boolean;
|
||||
visibleAvatar?: boolean;
|
||||
nickname?: string;
|
||||
avatar?: string;
|
||||
@@ -61,6 +62,7 @@ const MessageItem = ({
|
||||
avatar,
|
||||
avatarDialog,
|
||||
sendLoading = false,
|
||||
deleteDisabled = false,
|
||||
clickDocumentButton,
|
||||
index,
|
||||
removeMessageById,
|
||||
@@ -152,6 +154,7 @@ const MessageItem = ({
|
||||
removeMessageById={removeMessageById}
|
||||
regenerateMessage={regenerateMessage && handleRegenerateMessage}
|
||||
sendLoading={sendLoading}
|
||||
deleteDisabled={deleteDisabled}
|
||||
></UserGroupButton>
|
||||
)}
|
||||
{/* Show message content if there's any text besides the download */}
|
||||
|
||||
@@ -850,9 +850,6 @@ export const useRegenerateMessage = ({
|
||||
if (message.id) {
|
||||
removeMessagesAfterCurrentMessage(message.id);
|
||||
const index = messages.findIndex((x) => x.id === message.id);
|
||||
// Always pass the truncated history explicitly, even when it is
|
||||
// empty (regenerating the first question), so the backend can
|
||||
// overwrite the session with it via pass_all_history_messages.
|
||||
const nextMessages = index !== -1 ? messages.slice(0, index) : [];
|
||||
sendMessage({
|
||||
// Keep the original id so the question/answer pair id stays
|
||||
|
||||
@@ -221,6 +221,7 @@ export interface IExternalChatInfo {
|
||||
|
||||
export interface IMessage extends Message {
|
||||
id: string;
|
||||
awaitingServerId?: boolean;
|
||||
reference?: IReference; // the latest news has reference
|
||||
conversationId?: string; // To distinguish which conversation the message belongs to
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ import { useChatStreamStore } from './store';
|
||||
export type RunChatCompletionStreamParams = {
|
||||
conversationId: string;
|
||||
chatId?: string;
|
||||
messages: IMessage[];
|
||||
question: string;
|
||||
files?: IMessage['files'];
|
||||
enableThinking?: string;
|
||||
enableInternet?: boolean;
|
||||
llmSetting?: Variable;
|
||||
@@ -31,7 +32,8 @@ export type RunChatCompletionStreamResult = {
|
||||
export async function runChatCompletionStream({
|
||||
conversationId,
|
||||
chatId,
|
||||
messages,
|
||||
question,
|
||||
files,
|
||||
enableThinking,
|
||||
enableInternet,
|
||||
llmSetting,
|
||||
@@ -75,7 +77,8 @@ export async function runChatCompletionStream({
|
||||
{
|
||||
chatId,
|
||||
sessionId: conversationId,
|
||||
messages,
|
||||
question,
|
||||
files,
|
||||
enableThinking,
|
||||
enableInternet,
|
||||
llmSetting,
|
||||
|
||||
@@ -246,15 +246,28 @@ export const useChatStreamStore = create<ChatStreamState>()(
|
||||
// placeholder) were just deleted: without it, the flush would slice
|
||||
// off whatever message is now last.
|
||||
if (!previous.isStreaming) return state;
|
||||
const messages = previous.messages.slice(0, -1);
|
||||
const question = messages.at(-1);
|
||||
// The server assigns the turn ID for question-only requests.
|
||||
// Keep the local question and streamed answer paired under that ID.
|
||||
if (
|
||||
answer.id &&
|
||||
question?.role === MessageType.User &&
|
||||
(question.id !== answer.id || question.awaitingServerId)
|
||||
) {
|
||||
messages[messages.length - 1] = {
|
||||
...question,
|
||||
id: answer.id,
|
||||
awaitingServerId: false,
|
||||
};
|
||||
}
|
||||
messages.push(buildAssistantMessageFromAnswer(answer));
|
||||
return {
|
||||
sessions: {
|
||||
...state.sessions,
|
||||
[conversationId]: {
|
||||
...previous,
|
||||
messages: [
|
||||
...previous.messages.slice(0, -1),
|
||||
buildAssistantMessageFromAnswer(answer),
|
||||
],
|
||||
messages,
|
||||
lastActiveAt: Date.now(),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -26,6 +26,7 @@ export function buildQuestionAndPlaceholder(message: IMessage): IMessage[] {
|
||||
{
|
||||
...message,
|
||||
id: buildMessageUuid(message),
|
||||
awaitingServerId: true,
|
||||
},
|
||||
{
|
||||
role: MessageType.Assistant,
|
||||
|
||||
@@ -103,6 +103,7 @@ export function SingleChatBox({ conversation }: IProps) {
|
||||
index={i}
|
||||
isLast={i === messages.length - 1}
|
||||
removeMessageById={removeMessageById}
|
||||
deleteDisabled={message.awaitingServerId}
|
||||
regenerateMessage={regenerateMessage}
|
||||
sendLoading={sendLoading}
|
||||
/>
|
||||
|
||||
@@ -99,13 +99,11 @@ export const useSendMessage = () => {
|
||||
async ({
|
||||
message,
|
||||
currentConversationId,
|
||||
messages: explicitMessages,
|
||||
enableInternet,
|
||||
enableThinking,
|
||||
}: {
|
||||
message: IMessage;
|
||||
currentConversationId?: string;
|
||||
messages?: IMessage[];
|
||||
} & NextMessageInputOnPressEnterParameter) => {
|
||||
const sessionId = currentConversationId ?? conversationId;
|
||||
|
||||
@@ -114,12 +112,8 @@ export const useSendMessage = () => {
|
||||
const { ok, aborted } = await runChatCompletionStream({
|
||||
conversationId: sessionId,
|
||||
chatId,
|
||||
// An explicitly provided list is authoritative, even when empty
|
||||
// (e.g. regenerating the first question must truncate history).
|
||||
messages: [
|
||||
...(Array.isArray(explicitMessages) ? explicitMessages : messages),
|
||||
message,
|
||||
],
|
||||
question: message.content,
|
||||
files: message.files,
|
||||
enableThinking,
|
||||
enableInternet,
|
||||
llmSetting: currentDialog?.llm_setting,
|
||||
@@ -134,14 +128,7 @@ export const useSendMessage = () => {
|
||||
notification.error({ message: t('message.requestError') });
|
||||
}
|
||||
},
|
||||
[
|
||||
conversationId,
|
||||
chatId,
|
||||
messages,
|
||||
failStream,
|
||||
t,
|
||||
currentDialog?.llm_setting,
|
||||
],
|
||||
[conversationId, chatId, failStream, t, currentDialog?.llm_setting],
|
||||
);
|
||||
|
||||
// Hand a failed question back to the input box, but only once the box is
|
||||
@@ -237,21 +224,11 @@ export const useSendMessage = () => {
|
||||
|
||||
// Route the question to the conversation it was asked in, not whichever
|
||||
// is currently displayed.
|
||||
//
|
||||
// Snapshot the history before appendQuestion writes the question and its
|
||||
// assistant placeholder into the store.
|
||||
const history =
|
||||
useChatStreamStore.getState().sessions[targetConversationId]
|
||||
?.messages ?? [];
|
||||
|
||||
appendQuestion(targetConversationId, questionMessage);
|
||||
|
||||
setValue('');
|
||||
sendMessage({
|
||||
currentConversationId: targetConversationId,
|
||||
// For an existing conversation currentMessages is empty; fall back to
|
||||
// the store's message list instead of sending an empty history.
|
||||
messages: currentMessages.length > 0 ? currentMessages : history,
|
||||
message: {
|
||||
id,
|
||||
content: value.trim(),
|
||||
|
||||
@@ -86,7 +86,6 @@ export function useSendSingleMessage({
|
||||
...(storeHistoryMessages === undefined
|
||||
? {}
|
||||
: { store_history_messages: storeHistoryMessages }),
|
||||
pass_all_history_messages: true,
|
||||
},
|
||||
controller,
|
||||
);
|
||||
|
||||
@@ -19,7 +19,8 @@ import { EventSourceParserStream } from 'eventsource-parser/stream';
|
||||
export type ChatCompletionStreamParams = {
|
||||
chatId?: string;
|
||||
sessionId: string;
|
||||
messages: IMessage[];
|
||||
question: string;
|
||||
files?: IMessage['files'];
|
||||
enableThinking?: string;
|
||||
enableInternet?: boolean;
|
||||
llmSetting?: Variable;
|
||||
@@ -42,7 +43,8 @@ export function requestChatCompletionStream(
|
||||
{
|
||||
chatId,
|
||||
sessionId,
|
||||
messages,
|
||||
question,
|
||||
files,
|
||||
enableThinking,
|
||||
enableInternet,
|
||||
llmSetting,
|
||||
@@ -68,8 +70,8 @@ export function requestChatCompletionStream(
|
||||
body: JSON.stringify({
|
||||
chat_id: chatId,
|
||||
session_id: sessionId,
|
||||
messages,
|
||||
pass_all_history_messages: true,
|
||||
question,
|
||||
files,
|
||||
reasoning: Number(enableThinking),
|
||||
internet: enableInternet,
|
||||
...(temperature === undefined ? {} : { temperature }),
|
||||
|
||||
Reference in New Issue
Block a user