mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-22 16:36:47 +08:00
feat[Go]: implement /api/v1/agents/<agent_id>/sessions (#15705)
### What problem does this PR solve? As Title Codes were tested by Postman ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@@ -131,6 +131,182 @@ func (h *AgentHandler) ListAgents(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// ListAgentSessions List all sessions
|
||||
func (h *AgentHandler) ListAgentSessions(c *gin.Context) {
|
||||
user, errorCode, errorMessage := GetUser(c)
|
||||
if errorCode != common.CodeSuccess {
|
||||
jsonError(c, errorCode, errorMessage)
|
||||
return
|
||||
}
|
||||
|
||||
agentID := c.Param("agent_id")
|
||||
if agentID == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": common.CodeArgumentError,
|
||||
"data": nil,
|
||||
"message": "agent_id is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
page := parsePositiveIntQuery(c, "page", 1)
|
||||
pageSize := parsePositiveIntQuery(c, "page_size", 30)
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
req := service.ListAgentSessionsRequest{
|
||||
SessionID: c.Query("id"),
|
||||
UserID: c.Query("user_id"),
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Keywords: c.Query("keywords"),
|
||||
FromDate: c.Query("from_date"),
|
||||
ToDate: c.Query("to_date"),
|
||||
OrderBy: defaultQueryString(c.Query("orderby"), "update_time"),
|
||||
ExpUserID: c.Query("exp_user_id"),
|
||||
Desc: c.Query("desc") != "False" && c.Query("desc") != "false",
|
||||
IncludeDSL: c.Query("dsl") != "False" && c.Query("dsl") != "false",
|
||||
}
|
||||
|
||||
tenantID := user.ID
|
||||
result, code, err := h.agentService.ListAgentSessions(user.ID, tenantID, agentID, req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": code,
|
||||
"data": nil,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": common.CodeSuccess,
|
||||
"data": result.Data,
|
||||
"message": "success",
|
||||
"total": result.Total,
|
||||
})
|
||||
}
|
||||
|
||||
func parsePositiveIntQuery(c *gin.Context, key string, defaultValue int) int {
|
||||
raw := c.Query(key)
|
||||
if raw == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
value, err := strconv.Atoi(raw)
|
||||
if err != nil || value <= 0 {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func defaultQueryString(value, defaultValue string) string {
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (h *AgentHandler) GetAgentSession(c *gin.Context) {
|
||||
user, errorCode, errorMessage := GetUser(c)
|
||||
if errorCode != common.CodeSuccess {
|
||||
jsonError(c, errorCode, errorMessage)
|
||||
return
|
||||
}
|
||||
|
||||
agentID := c.Param("agent_id")
|
||||
if agentID == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": common.CodeOperatingError,
|
||||
"data": nil,
|
||||
"message": "agent_id is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
sessionID := c.Param("session_id")
|
||||
if sessionID == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": common.CodeDataError,
|
||||
"data": nil,
|
||||
"message": "session_id is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
userID := user.ID
|
||||
userID = strings.TrimSpace(userID)
|
||||
sessionID = strings.TrimSpace(sessionID)
|
||||
agentID = strings.TrimSpace(agentID)
|
||||
|
||||
data, code, err := h.agentService.GetAgentSession(userID, agentID, sessionID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": code,
|
||||
"data": nil,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": common.CodeSuccess,
|
||||
"data": data,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AgentHandler) DeleteAgentSessionItem(c *gin.Context) {
|
||||
user, errorCode, errorMessage := GetUser(c)
|
||||
if errorCode != common.CodeSuccess {
|
||||
jsonError(c, errorCode, errorMessage)
|
||||
return
|
||||
}
|
||||
|
||||
agentID := c.Param("agent_id")
|
||||
if agentID == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": common.CodeOperatingError,
|
||||
"data": nil,
|
||||
"message": "agent_id is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
sessionID := c.Param("session_id")
|
||||
if sessionID == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": common.CodeDataError,
|
||||
"data": nil,
|
||||
"message": "session_id is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
userID := user.ID
|
||||
userID = strings.TrimSpace(userID)
|
||||
sessionID = strings.TrimSpace(sessionID)
|
||||
agentID = strings.TrimSpace(agentID)
|
||||
|
||||
ok, code, err := h.agentService.DeleteAgentSessionItem(userID, agentID, sessionID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": code,
|
||||
"data": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": code,
|
||||
"data": ok,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
// ListAgentVersions returns versions for a specific agent.
|
||||
// @Summary List Agent Versions
|
||||
// @Description Returns all versions for a specific agent, ordered by update_time DESC.
|
||||
|
||||
@@ -49,6 +49,7 @@ func setupHandlerAgentsTestDB(t *testing.T) *gorm.DB {
|
||||
&entity.User{},
|
||||
&entity.UserCanvas{},
|
||||
&entity.UserCanvasVersion{},
|
||||
&entity.API4Conversation{},
|
||||
); err != nil {
|
||||
t.Fatalf("failed to migrate: %v", err)
|
||||
}
|
||||
@@ -262,6 +263,251 @@ func TestGetAgentVersionHandler_VersionNotFound(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAgentSessionsHandlerSuccess(t *testing.T) {
|
||||
c, w, db := setupGinContextWithUserAndDB(t, http.MethodGet, "/api/v1/agents/canvas-1/sessions")
|
||||
c.Params = gin.Params{{Key: "agent_id", Value: "canvas-1"}}
|
||||
|
||||
db.Create(&entity.UserCanvas{
|
||||
ID: "canvas-1",
|
||||
UserID: "user-1",
|
||||
Title: sptr("Test Agent"),
|
||||
})
|
||||
db.Create(&entity.API4Conversation{
|
||||
ID: "session-1",
|
||||
DialogID: "canvas-1",
|
||||
UserID: "user-1",
|
||||
Message: json.RawMessage(`[{"role":"assistant","content":"hello","prompt":"hidden"}]`),
|
||||
Reference: json.RawMessage(`[]`),
|
||||
BaseModel: entity.BaseModel{
|
||||
UpdateTime: ptr(time.Now().UnixMilli()),
|
||||
},
|
||||
})
|
||||
db.Create(&entity.API4Conversation{
|
||||
ID: "session-2",
|
||||
DialogID: "canvas-1",
|
||||
UserID: "user-1",
|
||||
Message: json.RawMessage(`[{"role":"user","content":"question"}]`),
|
||||
Reference: json.RawMessage(`[]`),
|
||||
BaseModel: entity.BaseModel{
|
||||
UpdateTime: ptr(time.Now().Add(-time.Hour).UnixMilli()),
|
||||
},
|
||||
})
|
||||
db.Create(&entity.API4Conversation{
|
||||
ID: "session-other-agent",
|
||||
DialogID: "canvas-other",
|
||||
UserID: "user-1",
|
||||
Message: json.RawMessage(`[{"role":"assistant","content":"other"}]`),
|
||||
Reference: json.RawMessage(`[]`),
|
||||
})
|
||||
|
||||
h := NewAgentHandler(service.NewAgentService(), nil)
|
||||
h.ListAgentSessions(c)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp map[string]interface{}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
if resp["code"] != float64(common.CodeSuccess) {
|
||||
t.Fatalf("expected code %d, got %v: %v", common.CodeSuccess, resp["code"], resp["message"])
|
||||
}
|
||||
if resp["total"] != float64(2) {
|
||||
t.Fatalf("expected total 2, got %v", resp["total"])
|
||||
}
|
||||
|
||||
data, ok := resp["data"].([]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expected data array, got %T", resp["data"])
|
||||
}
|
||||
if len(data) != 2 {
|
||||
t.Fatalf("expected 2 sessions, got %d", len(data))
|
||||
}
|
||||
|
||||
first := data[0].(map[string]interface{})
|
||||
if first["agent_id"] != "canvas-1" {
|
||||
t.Fatalf("expected agent_id canvas-1, got %v", first["agent_id"])
|
||||
}
|
||||
messages := first["message"].([]interface{})
|
||||
message := messages[0].(map[string]interface{})
|
||||
if _, ok := message["prompt"]; ok {
|
||||
t.Fatalf("expected prompt to be stripped from list response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAgentSessionHandlerSuccess(t *testing.T) {
|
||||
c, w, db := setupGinContextWithUserAndDB(t, http.MethodGet, "/api/v1/agents/canvas-1/sessions/session-1")
|
||||
c.Params = gin.Params{{Key: "agent_id", Value: "canvas-1"}, {Key: "session_id", Value: "session-1"}}
|
||||
|
||||
db.Create(&entity.UserCanvas{
|
||||
ID: "canvas-1",
|
||||
UserID: "user-1",
|
||||
Title: sptr("Test Agent"),
|
||||
})
|
||||
db.Create(&entity.API4Conversation{
|
||||
ID: "session-1",
|
||||
DialogID: "canvas-1",
|
||||
UserID: "user-1",
|
||||
Message: json.RawMessage(`[{"role":"assistant","content":"hello"}]`),
|
||||
Reference: json.RawMessage(`[]`),
|
||||
})
|
||||
|
||||
h := NewAgentHandler(service.NewAgentService(), nil)
|
||||
h.GetAgentSession(c)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp map[string]interface{}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
if resp["code"] != float64(common.CodeSuccess) {
|
||||
t.Fatalf("expected code %d, got %v: %v", common.CodeSuccess, resp["code"], resp["message"])
|
||||
}
|
||||
|
||||
data, ok := resp["data"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expected data object, got %T", resp["data"])
|
||||
}
|
||||
if data["id"] != "session-1" {
|
||||
t.Fatalf("expected session-1, got %v", data["id"])
|
||||
}
|
||||
if data["dialog_id"] != "canvas-1" {
|
||||
t.Fatalf("expected dialog_id canvas-1, got %v", data["dialog_id"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAgentSessionHandlerRejectsSessionFromAnotherAgent(t *testing.T) {
|
||||
c, w, db := setupGinContextWithUserAndDB(t, http.MethodGet, "/api/v1/agents/canvas-1/sessions/session-other")
|
||||
c.Params = gin.Params{{Key: "agent_id", Value: "canvas-1"}, {Key: "session_id", Value: "session-other"}}
|
||||
|
||||
db.Create(&entity.UserCanvas{
|
||||
ID: "canvas-1",
|
||||
UserID: "user-1",
|
||||
Title: sptr("Test Agent"),
|
||||
})
|
||||
db.Create(&entity.API4Conversation{
|
||||
ID: "session-other",
|
||||
DialogID: "canvas-other",
|
||||
UserID: "user-1",
|
||||
Message: json.RawMessage(`[{"role":"assistant","content":"other"}]`),
|
||||
Reference: json.RawMessage(`[]`),
|
||||
})
|
||||
|
||||
h := NewAgentHandler(service.NewAgentService(), nil)
|
||||
h.GetAgentSession(c)
|
||||
|
||||
var resp map[string]interface{}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
if resp["code"] == float64(common.CodeSuccess) {
|
||||
t.Fatalf("expected non-success for cross-agent session, got response %v", resp)
|
||||
}
|
||||
if resp["data"] != nil {
|
||||
t.Fatalf("expected nil data for cross-agent session, got %v", resp["data"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAgentSessionItemHandlerDeletesOnlyMatchingAgent(t *testing.T) {
|
||||
c, w, db := setupGinContextWithUserAndDB(t, http.MethodDelete, "/api/v1/agents/canvas-1/sessions/session-1")
|
||||
c.Params = gin.Params{{Key: "agent_id", Value: "canvas-1"}, {Key: "session_id", Value: "session-1"}}
|
||||
|
||||
db.Create(&entity.UserCanvas{
|
||||
ID: "canvas-1",
|
||||
UserID: "user-1",
|
||||
Title: sptr("Test Agent"),
|
||||
})
|
||||
db.Create(&entity.API4Conversation{
|
||||
ID: "session-1",
|
||||
DialogID: "canvas-1",
|
||||
UserID: "user-1",
|
||||
Message: json.RawMessage(`[]`),
|
||||
Reference: json.RawMessage(`[]`),
|
||||
})
|
||||
db.Create(&entity.API4Conversation{
|
||||
ID: "session-other",
|
||||
DialogID: "canvas-other",
|
||||
UserID: "user-1",
|
||||
Message: json.RawMessage(`[]`),
|
||||
Reference: json.RawMessage(`[]`),
|
||||
})
|
||||
|
||||
h := NewAgentHandler(service.NewAgentService(), nil)
|
||||
h.DeleteAgentSessionItem(c)
|
||||
|
||||
var resp map[string]interface{}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
if resp["code"] != float64(common.CodeSuccess) {
|
||||
t.Fatalf("expected code %d, got %v: %v", common.CodeSuccess, resp["code"], resp["message"])
|
||||
}
|
||||
if resp["data"] != true {
|
||||
t.Fatalf("expected data true, got %v", resp["data"])
|
||||
}
|
||||
|
||||
var deletedCount int64
|
||||
if err := db.Model(&entity.API4Conversation{}).Where("id = ?", "session-1").Count(&deletedCount).Error; err != nil {
|
||||
t.Fatalf("failed to count deleted session: %v", err)
|
||||
}
|
||||
if deletedCount != 0 {
|
||||
t.Fatalf("expected session-1 to be deleted, count=%d", deletedCount)
|
||||
}
|
||||
|
||||
var otherCount int64
|
||||
if err := db.Model(&entity.API4Conversation{}).Where("id = ?", "session-other").Count(&otherCount).Error; err != nil {
|
||||
t.Fatalf("failed to count other session: %v", err)
|
||||
}
|
||||
if otherCount != 1 {
|
||||
t.Fatalf("expected session-other to remain, count=%d", otherCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAgentSessionItemHandlerIgnoresSessionFromAnotherAgent(t *testing.T) {
|
||||
c, w, db := setupGinContextWithUserAndDB(t, http.MethodDelete, "/api/v1/agents/canvas-1/sessions/session-other")
|
||||
c.Params = gin.Params{{Key: "agent_id", Value: "canvas-1"}, {Key: "session_id", Value: "session-other"}}
|
||||
|
||||
db.Create(&entity.UserCanvas{
|
||||
ID: "canvas-1",
|
||||
UserID: "user-1",
|
||||
Title: sptr("Test Agent"),
|
||||
})
|
||||
db.Create(&entity.API4Conversation{
|
||||
ID: "session-other",
|
||||
DialogID: "canvas-other",
|
||||
UserID: "user-1",
|
||||
Message: json.RawMessage(`[]`),
|
||||
Reference: json.RawMessage(`[]`),
|
||||
})
|
||||
|
||||
h := NewAgentHandler(service.NewAgentService(), nil)
|
||||
h.DeleteAgentSessionItem(c)
|
||||
|
||||
var resp map[string]interface{}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
if resp["code"] != float64(common.CodeSuccess) {
|
||||
t.Fatalf("expected code %d, got %v: %v", common.CodeSuccess, resp["code"], resp["message"])
|
||||
}
|
||||
if resp["data"] != false {
|
||||
t.Fatalf("expected data false, got %v", resp["data"])
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := db.Model(&entity.API4Conversation{}).Where("id = ?", "session-other").Count(&count).Error; err != nil {
|
||||
t.Fatalf("failed to count other session: %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Fatalf("expected cross-agent session to remain, count=%d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAgentTagsHandlerSuccess(t *testing.T) {
|
||||
c, w, db := setupGinContextWithUserAndDB(t, http.MethodPut, "/api/v1/agents/canvas-1/tags")
|
||||
c.Request = httptest.NewRequest(http.MethodPut, "/api/v1/agents/canvas-1/tags", strings.NewReader(`{"tags":["alpha","beta","alpha"]}`))
|
||||
|
||||
Reference in New Issue
Block a user