mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-25 09:53:29 +08:00
fix: support message content (#16980)
### Summary As Title <img width="3241" height="1934" alt="image" src="https://github.com/user-attachments/assets/cb22031c-f200-462a-b871-b4a739e7a66a" />
This commit is contained in:
@@ -402,7 +402,11 @@ func (h *AgentHandler) RunAgent(c *gin.Context) {
|
||||
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
||||
c.Writer.Header().Set("Cache-Control", "no-cache")
|
||||
c.Writer.Header().Set("Connection", "keep-alive")
|
||||
doneSent := false
|
||||
for ev := range events {
|
||||
if ev.Type == "done" {
|
||||
doneSent = true
|
||||
}
|
||||
if err := service.WriteChatbotRunEvent(c.Writer, ev); err != nil {
|
||||
common.Debug("agent run: client disconnected",
|
||||
zap.String("canvas_id", canvasID),
|
||||
@@ -412,6 +416,15 @@ func (h *AgentHandler) RunAgent(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !doneSent {
|
||||
if err := service.WriteChatbotRunEvent(c.Writer, canvas.RunEvent{Type: "done"}); err != nil {
|
||||
common.Debug("agent run: failed to write [DONE]",
|
||||
zap.String("canvas_id", canvasID),
|
||||
zap.String("session_id", sessionID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readUserInput extracts the user_input field from the JSON body if
|
||||
@@ -813,7 +826,7 @@ func (h *AgentHandler) DeleteAgentSession(c *gin.Context) {
|
||||
// api/apps/restful_apis/agent_api.py:1440-1676.
|
||||
//
|
||||
// - When `stream` is true: streams SSE — one `data: {...}\n\n` frame per
|
||||
// canvas RunEvent, terminated by `data: [DONE]\n\n`.
|
||||
// canvas RunEvent, terminated by `data:[DONE]\n\n`.
|
||||
// - When `stream` is omitted or false (default, matches the Python
|
||||
// agent_chat_completion contract where `req.get("stream", False)`
|
||||
// defaults to non-streaming): collects all canvas events and returns a
|
||||
@@ -1008,7 +1021,7 @@ func (h *AgentHandler) AgentChatCompletions(c *gin.Context) {
|
||||
|
||||
if req.Stream {
|
||||
// SSE streaming: one `data: {...}\n\n` frame per canvas RunEvent,
|
||||
// terminated by `data: [DONE]\n\n`. We do NOT emit an SSE `event:`
|
||||
// terminated by `data:[DONE]\n\n`. We do NOT emit an SSE `event:`
|
||||
// line — the front-end's use-send-message.ts parser feeds each
|
||||
// `data:` line directly into JSON.parse and expects the event type
|
||||
// in the JSON object's top-level `event` field.
|
||||
@@ -1016,8 +1029,12 @@ func (h *AgentHandler) AgentChatCompletions(c *gin.Context) {
|
||||
c.Writer.Header().Set("Cache-Control", "no-cache")
|
||||
c.Writer.Header().Set("Connection", "keep-alive")
|
||||
emitted := false
|
||||
doneSent := false
|
||||
for ev := range events {
|
||||
emitted = true
|
||||
if ev.Type == "done" {
|
||||
doneSent = true
|
||||
}
|
||||
common.Debug("agent chat completions: streaming event",
|
||||
zap.String("agent_id", req.AgentID),
|
||||
zap.String("session_id", req.SessionID),
|
||||
@@ -1037,7 +1054,7 @@ func (h *AgentHandler) AgentChatCompletions(c *gin.Context) {
|
||||
// Canvas produced no events (e.g. empty query). Echo the
|
||||
// session_id so the client can resume the conversation
|
||||
// (fixes #15169). The [DONE] terminator must be emitted
|
||||
// here explicitly because the canvas never sends a
|
||||
// after this branch because the canvas never sends a
|
||||
// "done" event on this path.
|
||||
common.Info("empty agent output - returning session_id",
|
||||
zap.String("agent_id", req.AgentID),
|
||||
@@ -1050,7 +1067,9 @@ func (h *AgentHandler) AgentChatCompletions(c *gin.Context) {
|
||||
SessionID: req.SessionID,
|
||||
}
|
||||
_ = service.WriteChatbotRunEvent(c.Writer, event)
|
||||
if _, err := c.Writer.Write([]byte("data: [DONE]\n\n")); err != nil {
|
||||
}
|
||||
if !doneSent {
|
||||
if err := service.WriteChatbotRunEvent(c.Writer, canvas.RunEvent{Type: "done"}); err != nil {
|
||||
common.Debug("agent chat completions: failed to write [DONE]",
|
||||
zap.Error(err),
|
||||
)
|
||||
|
||||
@@ -708,7 +708,7 @@ func TestAgentChatCompletions_OpenAICompat_EmptyMessages(t *testing.T) {
|
||||
// SSE tests. It emits a pre-configured sequence of canvas.RunEvent
|
||||
// values on its RunAgent channel and then closes — enough to verify
|
||||
// the SSE wire format (Content-Type, one `data: {...}\n\n` frame per
|
||||
// event, trailing `data: [DONE]\n\n`) without standing up the eino
|
||||
// event, trailing `data:[DONE]\n\n`) without standing up the eino
|
||||
// runner or a live DB.
|
||||
type stubChatRunner struct {
|
||||
events []canvas.RunEvent
|
||||
@@ -729,7 +729,7 @@ func (s *stubChatRunner) RunAgent(_ context.Context, _, _, _, _ string, _ any) (
|
||||
|
||||
// TestAgentChatCompletions_StreamSetsContentType covers the SSE
|
||||
// path: the handler streams canvas.RunEvent frames as
|
||||
// `data: {...}\n\n` with a trailing `data: [DONE]\n\n` terminator.
|
||||
// `data: {...}\n\n` with a trailing `data:[DONE]\n\n` terminator.
|
||||
// The frame shape is the Python agent-canvas envelope
|
||||
// {event,message_id,task_id,session_id,data:{content}}. See
|
||||
// service.WriteChatbotRunEvent.
|
||||
@@ -765,7 +765,58 @@ func TestAgentChatCompletions_StreamSetsContentType(t *testing.T) {
|
||||
!strings.Contains(body, `"content":"hi back"`) {
|
||||
t.Errorf("body should contain flat agent event with content, got %q", body)
|
||||
}
|
||||
if !strings.HasSuffix(body, "data: [DONE]\n\n") {
|
||||
if !strings.HasSuffix(body, "data:[DONE]\n\n") {
|
||||
t.Errorf("body should end with [DONE] terminator, got %q", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentChatCompletions_StreamAddsDoneWhenRunnerCloses(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = httptest.NewRequest("POST", "/api/v1/agents/chat/completions",
|
||||
strings.NewReader(`{"agent_id":"a1","stream":true,"query":"hi"}`))
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
c.Set("user", &entity.User{ID: "u1"})
|
||||
c.Set("user_id", "u1")
|
||||
|
||||
runner := &stubChatRunner{events: []canvas.RunEvent{
|
||||
{Type: "message", MessageID: "msg-1", TaskID: "task-1", SessionID: "sess-1", Data: `{"content":"hi back"}`},
|
||||
}}
|
||||
h := &AgentHandler{chatRunner: runner}
|
||||
h.AgentChatCompletions(c)
|
||||
|
||||
body := w.Body.String()
|
||||
if got := strings.Count(body, "data:[DONE]\n\n"); got != 1 {
|
||||
t.Fatalf("expected exactly one [DONE] terminator, got %d in %q", got, body)
|
||||
}
|
||||
if !strings.HasSuffix(body, "data:[DONE]\n\n") {
|
||||
t.Errorf("body should end with [DONE] terminator, got %q", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunAgent_StreamAddsDoneWhenRunnerCloses(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Params = gin.Params{{Key: "canvas_id", Value: "a1"}}
|
||||
c.Request = httptest.NewRequest("POST", "/api/v1/agents/a1/run",
|
||||
strings.NewReader(`{"user_input":"hi"}`))
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
c.Set("user", &entity.User{ID: "u1"})
|
||||
c.Set("user_id", "u1")
|
||||
|
||||
runner := &stubChatRunner{events: []canvas.RunEvent{
|
||||
{Type: "message", MessageID: "msg-1", TaskID: "task-1", SessionID: "sess-1", Data: `{"content":"hi back"}`},
|
||||
}}
|
||||
h := &AgentHandler{chatRunner: runner}
|
||||
h.RunAgent(c)
|
||||
|
||||
body := w.Body.String()
|
||||
if got := strings.Count(body, "data:[DONE]\n\n"); got != 1 {
|
||||
t.Fatalf("expected exactly one [DONE] terminator, got %d in %q", got, body)
|
||||
}
|
||||
if !strings.HasSuffix(body, "data:[DONE]\n\n") {
|
||||
t.Errorf("body should end with [DONE] terminator, got %q", body)
|
||||
}
|
||||
}
|
||||
@@ -804,7 +855,7 @@ func TestAgentChatCompletions_DefaultBranchNonStreaming(t *testing.T) {
|
||||
!strings.Contains(body, `"hello back"`) {
|
||||
t.Errorf("body should contain agent event with content in data, got %q", body)
|
||||
}
|
||||
if strings.Contains(body, "data: [DONE]") {
|
||||
if strings.Contains(body, "data:[DONE]") {
|
||||
t.Errorf("body should not contain [DONE] terminator in non-streaming mode, got %q", body)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user