feat: 新增 Agent、Memory、Skill 模块
- handler: agent_handler, memory_handler, skill_handler - model: agent.go, skill.go - repository: agent_repo, skill_repo - service: agent_service, memory_service, skill_service - 新增 migrations 目录 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
141
server/internal/handler/agent_handler.go
Normal file
141
server/internal/handler/agent_handler.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"x-agents/server/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AgentHandler Agent 处理器
|
||||
type AgentHandler struct {
|
||||
agentService *service.AgentService
|
||||
}
|
||||
|
||||
// NewAgentHandler 创建 Agent 处理器
|
||||
func NewAgentHandler(agentService *service.AgentService) *AgentHandler {
|
||||
return &AgentHandler{
|
||||
agentService: agentService,
|
||||
}
|
||||
}
|
||||
|
||||
// ChatRequest 对话请求
|
||||
type ChatRequest struct {
|
||||
AgentID int `json:"agent_id" binding:"required"`
|
||||
Message string `json:"message" binding:"required"`
|
||||
SessionID string `json:"session_id"`
|
||||
}
|
||||
|
||||
// ChatResponse 对话响应
|
||||
type ChatResponse struct {
|
||||
AgentID int `json:"agent_id"`
|
||||
Reply string `json:"reply"`
|
||||
ToolsUsed []string `json:"tools_used"`
|
||||
SessionID string `json:"session_id"`
|
||||
TokensUsed int `json:"tokens_used"`
|
||||
DurationMs int `json:"duration_ms"`
|
||||
Metadata interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
// Chat 单智能体对话
|
||||
func (h *AgentHandler) Chat(c *gin.Context) {
|
||||
var req ChatRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取用户 ID(从认证中间件获取)
|
||||
userID := 1 // TODO: 从 c.Get("user_id") 获取
|
||||
|
||||
pythonReq := service.AgentChatRequest{
|
||||
AgentID: req.AgentID,
|
||||
Message: req.Message,
|
||||
UserID: userID,
|
||||
SessionID: req.SessionID,
|
||||
}
|
||||
|
||||
result, err := h.agentService.Chat(pythonReq)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 转换工具调用
|
||||
toolsUsed := make([]string, 0)
|
||||
for _, tool := range result.ToolCalls {
|
||||
if toolMap, ok := tool.(map[string]interface{}); ok {
|
||||
if skillID, ok := toolMap["skill_id"].(string); ok {
|
||||
toolsUsed = append(toolsUsed, skillID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, ChatResponse{
|
||||
AgentID: result.AgentID,
|
||||
Reply: result.Response,
|
||||
ToolsUsed: toolsUsed,
|
||||
SessionID: result.SessionID,
|
||||
TokensUsed: result.TokensUsed,
|
||||
DurationMs: result.DurationMs,
|
||||
Metadata: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// TeamChatRequest 多智能体群聊请求
|
||||
type TeamChatRequest struct {
|
||||
SupervisorAgentID int `json:"supervisor_agent_id" binding:"required"`
|
||||
MemberAgentIDs []int `json:"member_agent_ids" binding:"required"`
|
||||
Message string `json:"message" binding:"required"`
|
||||
SessionID string `json:"session_id"`
|
||||
Strategy string `json:"strategy"`
|
||||
}
|
||||
|
||||
// TeamChatResponse 多智能体群聊响应
|
||||
type TeamChatResponse struct {
|
||||
SupervisorAgentID int `json:"supervisor_agent_id"`
|
||||
Reply string `json:"reply"`
|
||||
SubtaskResults interface{} `json:"subtask_results"`
|
||||
Strategy string `json:"strategy"`
|
||||
SessionID string `json:"session_id"`
|
||||
DurationMs int `json:"duration_ms"`
|
||||
Metadata interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
// TeamChat 多智能体群聊
|
||||
func (h *AgentHandler) TeamChat(c *gin.Context) {
|
||||
var req TeamChatRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取用户 ID
|
||||
userID := 1 // TODO: 从 c.Get("user_id") 获取
|
||||
|
||||
pythonReq := service.TeamChatRequest{
|
||||
SupervisorAgentID: req.SupervisorAgentID,
|
||||
MemberAgentIDs: req.MemberAgentIDs,
|
||||
Message: req.Message,
|
||||
UserID: userID,
|
||||
SessionID: req.SessionID,
|
||||
Strategy: req.Strategy,
|
||||
}
|
||||
|
||||
result, err := h.agentService.TeamChat(pythonReq)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, TeamChatResponse{
|
||||
SupervisorAgentID: result.SupervisorAgentID,
|
||||
Reply: result.Response,
|
||||
SubtaskResults: result.SubtaskResults,
|
||||
Strategy: result.Strategy,
|
||||
SessionID: result.SessionID,
|
||||
DurationMs: result.DurationMs,
|
||||
Metadata: nil,
|
||||
})
|
||||
}
|
||||
91
server/internal/handler/memory_handler.go
Normal file
91
server/internal/handler/memory_handler.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"x-agents/server/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// MemoryHandler 记忆处理器
|
||||
type MemoryHandler struct {
|
||||
memoryService *service.MemoryService
|
||||
}
|
||||
|
||||
// NewMemoryHandler 创建记忆处理器
|
||||
func NewMemoryHandler(memoryService *service.MemoryService) *MemoryHandler {
|
||||
return &MemoryHandler{
|
||||
memoryService: memoryService,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateMemoryRequest 创建记忆请求
|
||||
type CreateMemoryRequest struct {
|
||||
AgentID string `json:"agent_id" binding:"required"`
|
||||
UserID string `json:"user_id"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
MemoryType string `json:"memory_type"`
|
||||
Importance int `json:"importance"`
|
||||
}
|
||||
|
||||
// CreateMemory 创建记忆
|
||||
func (h *MemoryHandler) CreateMemory(c *gin.Context) {
|
||||
var req CreateMemoryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
memoryType := req.MemoryType
|
||||
if memoryType == "" {
|
||||
memoryType = "conversation"
|
||||
}
|
||||
importance := req.Importance
|
||||
if importance == 0 {
|
||||
importance = 5
|
||||
}
|
||||
|
||||
memory, err := h.memoryService.CreateMemory(req.AgentID, req.UserID, req.Content, memoryType, importance)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, memory)
|
||||
}
|
||||
|
||||
// GetMemories 获取记忆列表
|
||||
func (h *MemoryHandler) GetMemories(c *gin.Context) {
|
||||
agentID := c.Param("id")
|
||||
userID := c.Query("user_id")
|
||||
limitStr := c.DefaultQuery("limit", "10")
|
||||
|
||||
limit, err := strconv.Atoi(limitStr)
|
||||
if err != nil {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
memories, err := h.memoryService.GetMemories(agentID, userID, limit)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, memories)
|
||||
}
|
||||
|
||||
// DeleteMemory 删除记忆
|
||||
func (h *MemoryHandler) DeleteMemory(c *gin.Context) {
|
||||
memoryID := c.Param("memory_id")
|
||||
|
||||
err := h.memoryService.DeleteMemory(memoryID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Memory deleted successfully"})
|
||||
}
|
||||
172
server/internal/handler/skill_handler.go
Normal file
172
server/internal/handler/skill_handler.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"x-agents/server/internal/model"
|
||||
"x-agents/server/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// SkillHandler 技能处理器
|
||||
type SkillHandler struct {
|
||||
skillService *service.SkillService
|
||||
}
|
||||
|
||||
// NewSkillHandler 创建技能处理器
|
||||
func NewSkillHandler(skillService *service.SkillService) *SkillHandler {
|
||||
return &SkillHandler{skillService: skillService}
|
||||
}
|
||||
|
||||
// List 获取技能列表
|
||||
// @Summary 获取技能列表
|
||||
// @Description 获取所有技能列表,支持按类型筛选(system/user)
|
||||
// @Tags 技能管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param type query string false "技能类型: system(系统技能)/user(用户技能)"
|
||||
// @Success 200 {object} map[string]interface{} "{"list": [], "total": 0}"
|
||||
// @Router /skill/list [get]
|
||||
func (h *SkillHandler) List(c *gin.Context) {
|
||||
skillType := c.Query("type")
|
||||
|
||||
var skills []model.Skill
|
||||
var err error
|
||||
|
||||
if skillType != "" {
|
||||
skills, err = h.skillService.GetSkillsByType(skillType)
|
||||
} else {
|
||||
skills, err = h.skillService.GetAllSkills()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"list": skills, "total": len(skills)})
|
||||
}
|
||||
|
||||
// Sync 手动同步 skills
|
||||
// @Summary 手动同步技能
|
||||
// @Description 从文件系统扫描 skills 目录并同步到数据库。扫描 account/admin/skills(系统技能) 和 account/{username}/skills(用户技能)
|
||||
// @Tags 技能管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{} "{"message": "skills synced", "count": 0}"
|
||||
// @Router /skill/sync [get]
|
||||
func (h *SkillHandler) Sync(c *gin.Context) {
|
||||
if err := h.skillService.InitSkills(); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
skills, _ := h.skillService.GetAllSkills()
|
||||
c.JSON(http.StatusOK, gin.H{"message": "skills synced", "count": len(skills)})
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取技能
|
||||
// @Summary 获取技能详情
|
||||
// @Description 根据ID获取技能详情
|
||||
// @Tags 技能管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "技能ID"
|
||||
// @Success 200 {object} map[string]interface{} "{"skill": {}}"
|
||||
// @Router /skill/{id} [get]
|
||||
func (h *SkillHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "skill id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
skill, err := h.skillService.GetSkillByID(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "skill not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"skill": skill})
|
||||
}
|
||||
|
||||
// Create 创建技能
|
||||
// @Summary 创建技能
|
||||
// @Description 创建新的技能
|
||||
// @Tags 技能管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param skill body model.Skill true "技能信息"
|
||||
// @Success 200 {object} map[string]interface{} "{"message": "skill created", "skill": {}}"
|
||||
// @Router /skill/add [post]
|
||||
func (h *SkillHandler) Create(c *gin.Context) {
|
||||
var skill model.Skill
|
||||
if err := c.ShouldBindJSON(&skill); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.skillService.CreateSkill(&skill); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "skill created", "skill": skill})
|
||||
}
|
||||
|
||||
// Update 更新技能
|
||||
// @Summary 更新技能
|
||||
// @Description 更新技能信息
|
||||
// @Tags 技能管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "技能ID"
|
||||
// @Param skill body model.Skill true "技能信息"
|
||||
// @Success 200 {object} map[string]interface{} "{"message": "skill updated"}"
|
||||
// @Router /skill/{id} [put]
|
||||
func (h *SkillHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "skill id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
var skill model.Skill
|
||||
if err := c.ShouldBindJSON(&skill); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
skill.ID = id
|
||||
if err := h.skillService.UpdateSkill(&skill); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "skill updated"})
|
||||
}
|
||||
|
||||
// Delete 删除技能
|
||||
// @Summary 删除技能
|
||||
// @Description 删除技能
|
||||
// @Tags 技能管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "技能ID"
|
||||
// @Success 200 {object} map[string]interface{} "{"message": "skill deleted"}"
|
||||
// @Router /skill/{id} [delete]
|
||||
func (h *SkillHandler) Delete(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "skill id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.skillService.DeleteSkill(id); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "skill deleted"})
|
||||
}
|
||||
Reference in New Issue
Block a user