mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-01 21:37:33 +08:00
fix: don't persist ephemeral sessions in multi-model chat completions (#17637)
This commit is contained in:
@@ -127,6 +127,8 @@ type ChatCompletionsRequest struct {
|
||||
LLMID string `json:"llm_id,omitempty"`
|
||||
PassAllHistoryMessages *bool `json:"pass_all_history_messages,omitempty"`
|
||||
PassAllHistory *bool `json:"pass_all_history,omitempty"`
|
||||
StoreHistoryMessages *bool `json:"store_history_messages,omitempty"`
|
||||
StoreHistory *bool `json:"store_history,omitempty"`
|
||||
Legacy bool `json:"legacy,omitempty"`
|
||||
Stream *bool `json:"stream"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
@@ -205,12 +207,26 @@ func (h *ChatSessionHandler) ChatCompletions(c *gin.Context) {
|
||||
passAllHistory = *req.PassAllHistoryMessages
|
||||
}
|
||||
|
||||
// Resolve store_history from either alias (default true, mirrors Python)
|
||||
storeHistory := true
|
||||
if req.StoreHistory != nil {
|
||||
storeHistory = *req.StoreHistory
|
||||
}
|
||||
if req.StoreHistoryMessages != nil {
|
||||
storeHistory = *req.StoreHistoryMessages
|
||||
}
|
||||
if !storeHistory && !passAllHistory {
|
||||
common.ErrorWithCode(c, common.CodeDataError, "`pass_all_history_messages` must be true when `store_history_messages` is false.")
|
||||
return
|
||||
}
|
||||
|
||||
// Remove known keys from rawBody; what remains is passthrough kwargs
|
||||
knownKeys := []string{
|
||||
"chat_id", "session_id", "conversation_id",
|
||||
"messages", "question", "files",
|
||||
"llm_id",
|
||||
"pass_all_history_messages", "pass_all_history",
|
||||
"store_history_messages", "store_history",
|
||||
"legacy", "stream",
|
||||
"temperature", "top_p", "frequency_penalty", "presence_penalty", "max_tokens",
|
||||
}
|
||||
@@ -241,7 +257,7 @@ func (h *ChatSessionHandler) ChatCompletions(c *gin.Context) {
|
||||
req.ChatID, sessionID,
|
||||
req.Messages, req.Question, req.Files,
|
||||
req.LLMID, genConfig, kwargs,
|
||||
passAllHistory, req.Legacy,
|
||||
passAllHistory, storeHistory, req.Legacy,
|
||||
true, streamChan,
|
||||
)
|
||||
}()
|
||||
@@ -261,7 +277,7 @@ func (h *ChatSessionHandler) ChatCompletions(c *gin.Context) {
|
||||
req.ChatID, sessionID,
|
||||
req.Messages, req.Question, req.Files,
|
||||
req.LLMID, genConfig, kwargs,
|
||||
passAllHistory, req.Legacy,
|
||||
passAllHistory, storeHistory, req.Legacy,
|
||||
false, nil,
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -321,6 +321,7 @@ func (s *ChatChannelService) HandleIncomingMessage(ctx context.Context, msg Chat
|
||||
nil,
|
||||
map[string]interface{}{"quote": false},
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
|
||||
@@ -1449,7 +1449,7 @@ func (s *ChatSessionService) ChatCompletions(
|
||||
chatID string, sessionID string,
|
||||
messages []map[string]interface{}, question string, files []interface{},
|
||||
llmID string, genConfig map[string]interface{}, kwargs map[string]interface{},
|
||||
passAllHistory bool, legacy bool,
|
||||
passAllHistory bool, storeHistory bool, legacy bool,
|
||||
stream bool, streamChan chan<- string,
|
||||
) (map[string]interface{}, error) {
|
||||
if ctx == nil {
|
||||
@@ -1505,7 +1505,7 @@ func (s *ChatSessionService) ChatCompletions(
|
||||
return fail(common.NewCodedError(common.CodeDataError, "Session does not belong to this chat!"))
|
||||
}
|
||||
} else {
|
||||
session, err = s.createSessionForCompletion(ctx, chatID, dialog, userID)
|
||||
session, err = s.createSessionForCompletion(ctx, chatID, dialog, userID, storeHistory)
|
||||
if err != nil {
|
||||
return fail(err)
|
||||
}
|
||||
@@ -1669,7 +1669,7 @@ func (s *ChatSessionService) ChatCompletions(
|
||||
sendOrCancel(fmt.Sprintf("data:%s\n\n", marshalJSONWithSpaces(wrapper)))
|
||||
|
||||
// Persist session state (matches Python's update_by_id after loop)
|
||||
if session != nil {
|
||||
if session != nil && storeHistory {
|
||||
s.updateSessionMessages(ctx, session, s.getSessionMessagesAsSlice(session), reference)
|
||||
}
|
||||
} else {
|
||||
@@ -1679,7 +1679,9 @@ func (s *ChatSessionService) ChatCompletions(
|
||||
if chatID != "" {
|
||||
result["chat_id"] = chatID
|
||||
}
|
||||
s.updateSessionMessages(ctx, session, s.getSessionMessagesAsSlice(session), reference)
|
||||
if storeHistory {
|
||||
s.updateSessionMessages(ctx, session, s.getSessionMessagesAsSlice(session), reference)
|
||||
}
|
||||
return sanitizeJSONFloats(result).(map[string]interface{}), nil
|
||||
}
|
||||
ans["id"] = messageID
|
||||
@@ -1830,8 +1832,7 @@ func (s *ChatSessionService) buildDefaultCompletionDialog(tenantID string) *enti
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ChatSessionService) createSessionForCompletion(ctx context.Context, chatID string, dialog *entity.Chat, userID string) (*entity.ChatSession, error) {
|
||||
newID := utility.GenerateUUID()
|
||||
func (s *ChatSessionService) createSessionForCompletion(ctx context.Context, chatID string, dialog *entity.Chat, userID string, saveSession bool) (*entity.ChatSession, error) {
|
||||
name := "New session"
|
||||
|
||||
prologue := "Hi! I'm your assistant. What can I do for you?"
|
||||
@@ -1848,13 +1849,18 @@ func (s *ChatSessionService) createSessionForCompletion(ctx context.Context, cha
|
||||
refJSON, _ := json.Marshal([]interface{}{})
|
||||
|
||||
session := &entity.ChatSession{
|
||||
ID: newID,
|
||||
DialogID: chatID,
|
||||
Name: &name,
|
||||
Message: msgJSON,
|
||||
UserID: &userID,
|
||||
Reference: refJSON,
|
||||
}
|
||||
if !saveSession {
|
||||
// Multi-model comparison sends store_history_messages=false; the
|
||||
// ephemeral session must not be persisted (mirrors Python).
|
||||
return session, nil
|
||||
}
|
||||
session.ID = utility.GenerateUUID()
|
||||
if err := s.chatSessionDAO.Create(ctx, dao.DB, session); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -930,6 +930,7 @@ func TestChatCompletionsPassesRequestUserIDToPipeline(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
@@ -1010,6 +1011,7 @@ func TestChatCompletionsStreamFinalCarriesDecoratedReference(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
streamChan,
|
||||
@@ -1106,6 +1108,7 @@ func TestChatCompletionsModelIDOverrideUsesModelResolver(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
@@ -1121,6 +1124,69 @@ func TestChatCompletionsModelIDOverrideUsesModelResolver(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-model comparison sends store_history_messages=false; the ephemeral
|
||||
// session must never be persisted, otherwise every model card adds a
|
||||
// "New session" row to the session list.
|
||||
func TestChatCompletionsStoreHistoryFalseDoesNotPersistSession(t *testing.T) {
|
||||
store := newFakeSessionStore()
|
||||
store.dialogs["dialog-1"] = &entity.Chat{
|
||||
ID: "dialog-1",
|
||||
TenantID: "tenant-owner",
|
||||
LLMID: "chat@factory",
|
||||
LLMSetting: entity.JSONMap{},
|
||||
PromptConfig: entity.JSONMap{
|
||||
"parameters": []interface{}{},
|
||||
"prologue": "Welcome!",
|
||||
},
|
||||
}
|
||||
store.dialogExists["tenant-owner|dialog-1"] = true
|
||||
|
||||
pipeline := &fakePipeline{
|
||||
resultChan: makeResultChan(
|
||||
AsyncChatResult{Answer: "ok", Final: true, Reference: map[string]interface{}{"chunks": []interface{}{}}},
|
||||
),
|
||||
}
|
||||
|
||||
svc := &ChatSessionService{
|
||||
chatSessionDAO: store,
|
||||
userTenantDAO: &fakeTenantStore{tenantIDs: []string{"tenant-owner"}},
|
||||
pipeline: pipeline,
|
||||
}
|
||||
|
||||
result, err := svc.ChatCompletions(
|
||||
context.Background(),
|
||||
"user-1",
|
||||
"dialog-1",
|
||||
"",
|
||||
[]map[string]interface{}{{"role": "user", "content": "hi"}},
|
||||
"",
|
||||
nil,
|
||||
"",
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("ChatCompletions failed: %v", err)
|
||||
}
|
||||
if result == nil {
|
||||
t.Fatal("expected a non-nil answer")
|
||||
}
|
||||
if len(store.createCalled) != 0 {
|
||||
t.Fatalf("session must not be created when store_history_messages=false, got %d creates", len(store.createCalled))
|
||||
}
|
||||
if len(store.updateCalled) != 0 {
|
||||
t.Fatalf("session must not be updated when store_history_messages=false, got %d updates", len(store.updateCalled))
|
||||
}
|
||||
if len(store.sessions) != 0 {
|
||||
t.Fatalf("no session should be stored, got %d", len(store.sessions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompletion_EmptyMessages(t *testing.T) {
|
||||
svc := &ChatSessionService{
|
||||
chatSessionDAO: &fakeSessionStore{},
|
||||
|
||||
Reference in New Issue
Block a user