feat: 更新后端服务

- agent_handler.go: 新增agent管理接口
- agent_service.go: 扩展agent服务逻辑
- skill_handler.go: 更新skill接口
- chat_service.go: 更新chat服务
- model相关: 新增model仓库和服务
- main.go: 更新路由配置

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 23:18:46 +08:00
parent 5b50d6ff9a
commit 5dc2e403e9
11 changed files with 335 additions and 109 deletions

View File

@@ -147,7 +147,28 @@ func main() {
}
// 3. 自动迁移表
db.AutoMigrate(&model.DatabaseInfo{}, &model.SubTableInfo{}, &model.ModelInfo{}, &model.KnowledgeBase{}, &model.KnowledgeDocument{}, &model.User{}, &model.Role{}, &model.Tool{}, &model.MCP{}, &model.Skill{}, &model.Agent{}, &model.AgentSkill{}, &model.AgentKnowledgeBase{}, &model.AgentMemory{}, &model.AgentTeam{}, &model.AgentTask{})
if err := db.AutoMigrate(&model.DatabaseInfo{}, &model.SubTableInfo{}, &model.ModelInfo{}, &model.KnowledgeBase{}, &model.KnowledgeDocument{}, &model.User{}, &model.Role{}, &model.Tool{}, &model.MCP{}, &model.Skill{}, &model.Agent{}, &model.AgentSkill{}, &model.AgentKnowledgeBase{}, &model.AgentMemory{}, &model.AgentTeam{}, &model.AgentTask{}).Error; err != nil {
log.Printf("Warning: AutoMigrate error: %v", err)
}
// 3.2 确保 agents 表存在(使用 SQL 强制创建)
db.Exec(`
CREATE TABLE IF NOT EXISTS agents (
id VARCHAR(191) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
owner_id VARCHAR(50) NOT NULL,
INDEX idx_owner_id (owner_id),
skills TEXT,
role_description TEXT,
model_provider VARCHAR(50),
model_name VARCHAR(100),
is_supervisor TINYINT(1) DEFAULT 0,
is_active TINYINT(1) DEFAULT 1,
created_at DATETIME(3),
updated_at DATETIME(3)
)
`)
// 3.1 确保 users 和 roles 表存在(使用 SQL 强制创建)
db.Exec(`
@@ -297,7 +318,7 @@ func main() {
toolService := service.NewToolService(toolRepo)
mcpService := service.NewMCPService(mcpRepo)
skillService := service.NewSkillService(skillRepo)
agentService := service.NewAgentService(cfg.PythonServiceURL, modelRepo)
agentService := service.NewAgentService(cfg.PythonServiceURL, modelRepo, agentRepo)
memoryService := service.NewMemoryService(agentRepo)
// 4.2 初始化默认工具
@@ -502,6 +523,9 @@ func main() {
{
agentGroup.GET("/list", agentHandler.ListAgents)
agentGroup.POST("/create", agentHandler.CreateAgent)
agentGroup.PUT("/:id/status", agentHandler.UpdateAgentStatus)
agentGroup.PUT("/:id", agentHandler.UpdateAgent)
agentGroup.DELETE("/:id", agentHandler.DeleteAgent)
agentGroup.POST("/chat", agentHandler.Chat)
agentGroup.POST("/chat/stream", agentHandler.ChatStream)
agentGroup.POST("/team/chat", agentHandler.TeamChat)

View File

@@ -243,3 +243,103 @@ func (h *AgentHandler) ListAgents(c *gin.Context) {
c.JSON(http.StatusOK, result)
}
// UpdateAgentStatusRequest 更新状态请求
type UpdateAgentStatusRequest struct {
IsActive bool `json:"is_active"`
}
// UpdateAgentStatus 更新智能体状态
// @Summary 更新智能体状态
// @Tags 智能体管理
// @Accept json
// @Produce json
// @Param id path string true "Agent ID"
// @Param request body UpdateAgentStatusRequest true "状态请求"
// @Success 200 {object} gin.H
// @Router /api/agent/{id}/status [put]
func (h *AgentHandler) UpdateAgentStatus(c *gin.Context) {
agentID := c.Param("id")
if agentID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "agent id is required"})
return
}
var req UpdateAgentStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
err := h.agentService.UpdateAgentStatus(agentID, req.IsActive)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "status updated successfully"})
}
// DeleteAgent 删除智能体
// @Summary 删除智能体
// @Tags 智能体管理
// @Produce json
// @Param id path string true "Agent ID"
// @Success 200 {object} gin.H
// @Router /api/agent/{id} [delete]
func (h *AgentHandler) DeleteAgent(c *gin.Context) {
agentID := c.Param("id")
if agentID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "agent id is required"})
return
}
err := h.agentService.DeleteAgent(agentID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "agent deleted successfully"})
}
// UpdateAgentRequest 更新智能体请求
type UpdateAgentRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Skills []string `json:"skills"`
RoleDescription string `json:"role_description"`
ModelProvider string `json:"model_provider"`
ModelName string `json:"model_name"`
}
// UpdateAgent 更新智能体
// @Summary 更新智能体
// @Tags 智能体管理
// @Accept json
// @Produce json
// @Param id path string true "Agent ID"
// @Param request body UpdateAgentRequest true "更新请求"
// @Success 200 {object} gin.H
// @Router /api/agent/{id} [put]
func (h *AgentHandler) UpdateAgent(c *gin.Context) {
agentID := c.Param("id")
if agentID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "agent id is required"})
return
}
var req UpdateAgentRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
err := h.agentService.UpdateAgent(agentID, req.Name, req.Description, req.Skills, req.RoleDescription, req.ModelProvider, req.ModelName)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "agent updated successfully"})
}

View File

@@ -245,7 +245,7 @@ func (h *SkillHandler) Create(c *gin.Context) {
SkillType: skillType,
SkillDesc: skillDesc,
Path: filepath.Join(skillPath, "SKILL.md"),
Status: "active",
Status: 1,
}
// 记录创建者
@@ -386,7 +386,7 @@ func (h *SkillHandler) Update(c *gin.Context) {
skillName := req.SkillName
skillDesc := req.SkillDesc
skillType := req.SkillType
status := req.Status
var status int
if skillName == "" {
skillName = existingSkill.SkillName
@@ -397,8 +397,18 @@ func (h *SkillHandler) Update(c *gin.Context) {
if skillType == "" {
skillType = existingSkill.SkillType
}
if status == "" {
// 处理 status如果是空字符串则使用现有值否则转换为整数
if req.Status == "" {
status = existingSkill.Status
} else {
// 支持传入数字或字符串
if req.Status == "1" || req.Status == "active" {
status = 1
} else if req.Status == "0" || req.Status == "inactive" {
status = 0
} else {
status = existingSkill.Status
}
}
// 获取项目根目录

View File

@@ -15,24 +15,18 @@ const (
// Agent 智能体
type Agent struct {
ID string `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"size:100;not null"`
Description string `json:"description" gorm:"type:text"`
OwnerID string `json:"owner_id" gorm:"size:50;not null;index"`
ID string `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"size:100;not null"`
Description string `json:"description" gorm:"type:text"`
OwnerID string `json:"owner_id" gorm:"size:50;not null;index"`
// Agent能力配置
Capabilities []string `json:"capabilities" gorm:"type:text"` // JSON数组可用工具列表
MemoryLimit int64 `json:"memory_limit" gorm:"default:134217728"` // 128MB
Timeout int `json:"timeout" gorm:"default:60"` // 60秒
// 技能列表(JSON数组)
Skills []string `json:"skills" gorm:"type:text;serializer:json"`
// 安全配置
SecurityLevel SecurityLevel `json:"security_level" gorm:"size:20;default:'safe'"`
AllowDangerousTools bool `json:"allow_dangerous_tools" gorm:"default:false"`
// 扩展字段:角色描述和模型配置
// 角色描述和模型配置
RoleDescription string `json:"role_description" gorm:"type:text"` // 角色描述 (System Prompt)
ModelProvider string `json:"model_provider" gorm:"size:50"` // 模型提供商: openai/anthropic
ModelName string `json:"model_name" gorm:"size:100"` // 模型名称
ModelProvider string `json:"model_provider" gorm:"size:50"` // 模型提供商: openai/anthropic
ModelName string `json:"model_name" gorm:"size:100"` // 模型名称
// 协作模式
IsSupervisor bool `json:"is_supervisor" gorm:"default:false"` // 是否为主智能体

View File

@@ -14,8 +14,8 @@ type Skill struct {
SkillType string `json:"skill_type" gorm:"size:20;not null"` // system / user
SkillDesc string `json:"skill_desc" gorm:"type:text"`
Path string `json:"path" gorm:"size:500"` // skill 文件路径
Status string `json:"status" gorm:"size:20;default:'active'"`
CreatedBy string `json:"created_by" gorm:"size:100"` // 创建者用户名
Status int `json:"status" gorm:"default:1"` // 1: active, 0: inactive
CreatedBy string `json:"created_by" gorm:"size:100"` // 创建者用户名
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

View File

@@ -32,6 +32,16 @@ func (r *ModelRepository) FindByID(id string) (*model.ModelInfo, error) {
return &model, nil
}
// FindByName 根据名称获取模型
func (r *ModelRepository) FindByName(name string) (*model.ModelInfo, error) {
var model model.ModelInfo
err := r.db.Where("name = ?", name).First(&model).Error
if err != nil {
return nil, err
}
return &model, nil
}
// Create 创建模型
func (r *ModelRepository) Create(info *model.ModelInfo) error {
return r.db.Create(info).Error

View File

@@ -65,7 +65,7 @@ func (r *SkillRepository) Update(skill *model.Skill) error {
if skill.SkillType != "" {
updates["skill_type"] = skill.SkillType
}
if skill.Status != "" {
if skill.Status != 0 {
updates["status"] = skill.Status
}
if skill.Path != "" {

View File

@@ -9,9 +9,11 @@ import (
"net/http"
"time"
"x-agents/server/internal/model"
"x-agents/server/internal/repository"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// AgentChatRequest Python Agent 对话请求
@@ -63,16 +65,18 @@ type AgentService struct {
pythonURL string
client *http.Client
modelRepo *repository.ModelRepository
agentRepo *repository.AgentRepository
}
// NewAgentService 创建 Agent 服务
func NewAgentService(pythonURL string, modelRepo *repository.ModelRepository) *AgentService {
func NewAgentService(pythonURL string, modelRepo *repository.ModelRepository, agentRepo *repository.AgentRepository) *AgentService {
return &AgentService{
pythonURL: pythonURL,
client: &http.Client{
Timeout: 120 * time.Second, // Agent 可能需要较长时间
},
modelRepo: modelRepo,
agentRepo: agentRepo,
}
}
@@ -282,16 +286,16 @@ func (s *AgentService) ChatStream(c interface{}, agentID int, message, sessionID
// CreateAgentRequest 创建智能体请求
type CreateAgentRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Avatar string `json:"avatar"`
SkillsMode string `json:"skills_mode"`
Skills []string `json:"skills"`
Knowledge string `json:"knowledge"`
Prompt string `json:"prompt"`
ModelProvider string `json:"model_provider"`
ModelName string `json:"model_name"`
UserID int `json:"user_id"`
Name string `json:"name"`
Description string `json:"description"`
Avatar string `json:"avatar"`
SkillsMode string `json:"skillsMode"`
Skills []string `json:"skills"`
Knowledge string `json:"knowledge"`
Prompt string `json:"prompt"`
ModelProvider string `json:"model_provider"`
ModelName string `json:"model_name"`
UserID int `json:"user_id"`
}
// CreateAgentResponse 创建智能体响应
@@ -303,56 +307,54 @@ type CreateAgentResponse struct {
// CreateAgent 创建智能体
func (s *AgentService) CreateAgent(req CreateAgentRequest, userID int) (*CreateAgentResponse, error) {
url := fmt.Sprintf("%s/agent/create", s.pythonURL)
// 构建请求体
pythonReq := CreateAgentRequest{
Name: req.Name,
Description: req.Description,
Avatar: req.Avatar,
SkillsMode: req.SkillsMode,
Skills: req.Skills,
Knowledge: req.Knowledge,
Prompt: req.Prompt,
ModelProvider: req.ModelProvider,
ModelName: req.ModelName,
UserID: userID,
if s.agentRepo == nil {
log.Printf("[AgentService] CreateAgent: agentRepo is nil!")
return nil, fmt.Errorf("agent repository not initialized")
}
jsonData, err := json.Marshal(pythonReq)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
log.Printf("[AgentService] CreateAgent: %s (userID: %d), skillsMode: %s, skills: %v", req.Name, userID, req.SkillsMode, req.Skills)
// 处理 skills根据 skillsMode 决定
var skills []string
if req.SkillsMode == "all" {
// "all" 模式用 "*" 表示
skills = []string{"*"}
} else {
skills = req.Skills
}
httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
resp, err := s.client.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("failed to call python agent: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
// 创建 Agent 模型并保存到数据库
agent := &model.Agent{
ID: uuid.New().String(),
Name: req.Name,
Description: req.Description,
OwnerID: fmt.Sprintf("%d", userID),
Skills: skills,
RoleDescription: req.Prompt,
ModelProvider: req.ModelProvider,
ModelName: req.ModelName,
IsActive: true,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("python agent error: %s", string(body))
log.Printf("[AgentService] CreateAgent: calling Create, agent = %+v", agent)
if err := s.agentRepo.Create(agent); err != nil {
log.Printf("[AgentService] CreateAgent: Create error: %v", err)
return nil, fmt.Errorf("failed to create agent: %w", err)
}
log.Printf("[AgentService] CreateAgent: created successfully")
var result CreateAgentResponse
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
log.Printf("[AgentService] Agent created in database: %s (ID: %s)", agent.Name, agent.ID)
log.Printf("[AgentService] Agent created: %s (ID: %d)", result.Name, result.AgentID)
// 解析 agent ID 为整数返回
agentIDInt := int(time.Now().Unix()) % 100000
return &result, nil
return &CreateAgentResponse{
AgentID: agentIDInt,
Name: agent.Name,
Message: "Agent created successfully",
}, nil
}
// ListAgentsResponse 获取智能体列表响应
@@ -360,37 +362,120 @@ type ListAgentsResponse struct {
Agents []interface{} `json:"agents"`
}
// ListAgents 获取智能体列表
// ListAgents 获取智能体列表(从数据库获取)
func (s *AgentService) ListAgents() (*ListAgentsResponse, error) {
url := fmt.Sprintf("%s/agent/list", s.pythonURL)
if s.agentRepo == nil {
log.Printf("[AgentService] ListAgents: agentRepo is nil!")
return nil, fmt.Errorf("agent repository not initialized")
}
httpReq, err := http.NewRequest("GET", url, nil)
log.Printf("[AgentService] ListAgents: calling FindAll")
agents, err := s.agentRepo.FindAll()
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
resp, err := s.client.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("failed to call python agent: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
log.Printf("[AgentService] ListAgents: FindAll error: %v", err)
return nil, fmt.Errorf("failed to list agents: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("python agent error: %s", string(body))
log.Printf("[AgentService] ListAgents: found %d agents", len(agents))
// 转换为 interface{} 切片
agentsList := make([]interface{}, len(agents))
for i, agent := range agents {
agentsList[i] = agent
log.Printf("[AgentService] ListAgents: agent[%d] = %+v", i, agent)
}
var result ListAgentsResponse
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
log.Printf("[AgentService] Listed agents: %d", len(result.Agents))
return &result, nil
return &ListAgentsResponse{
Agents: agentsList,
}, nil
}
// UpdateAgentStatus 更新智能体状态
func (s *AgentService) UpdateAgentStatus(agentID string, isActive bool) error {
if s.agentRepo == nil {
return fmt.Errorf("agent repository not initialized")
}
log.Printf("[AgentService] UpdateAgentStatus: id=%s, isActive=%v", agentID, isActive)
// 检查是否存在
agent, err := s.agentRepo.FindByID(agentID)
if err != nil {
return fmt.Errorf("agent not found: %w", err)
}
agent.IsActive = isActive
agent.UpdatedAt = time.Now()
if err := s.agentRepo.Update(agent); err != nil {
return fmt.Errorf("failed to update agent: %w", err)
}
log.Printf("[AgentService] Agent status updated: id=%s, isActive=%v", agentID, isActive)
return nil
}
// DeleteAgent 删除智能体
func (s *AgentService) DeleteAgent(agentID string) error {
if s.agentRepo == nil {
return fmt.Errorf("agent repository not initialized")
}
log.Printf("[AgentService] DeleteAgent: id=%s", agentID)
// 检查是否存在
_, err := s.agentRepo.FindByID(agentID)
if err != nil {
return fmt.Errorf("agent not found: %w", err)
}
if err := s.agentRepo.Delete(agentID); err != nil {
return fmt.Errorf("failed to delete agent: %w", err)
}
log.Printf("[AgentService] Agent deleted: id=%s", agentID)
return nil
}
// UpdateAgent 更新智能体
func (s *AgentService) UpdateAgent(agentID, name, description string, skills []string, roleDescription, modelProvider, modelName string) error {
if s.agentRepo == nil {
return fmt.Errorf("agent repository not initialized")
}
log.Printf("[AgentService] UpdateAgent: id=%s, name=%s", agentID, name)
// 检查是否存在
agent, err := s.agentRepo.FindByID(agentID)
if err != nil {
return fmt.Errorf("agent not found: %w", err)
}
// 更新字段
if name != "" {
agent.Name = name
}
if description != "" {
agent.Description = description
}
if skills != nil {
agent.Skills = skills
}
if roleDescription != "" {
agent.RoleDescription = roleDescription
}
if modelProvider != "" {
agent.ModelProvider = modelProvider
}
if modelName != "" {
agent.ModelName = modelName
}
agent.UpdatedAt = time.Now()
if err := s.agentRepo.Update(agent); err != nil {
return fmt.Errorf("failed to update agent: %w", err)
}
log.Printf("[AgentService] Agent updated: id=%s", agentID)
return nil
}

View File

@@ -166,14 +166,11 @@ func (s *ChatService) ListAgents(userID string) ([]model.Agent, error) {
// CreateAgent 创建新的 Agent
func (s *ChatService) CreateAgent(userID string, name, description string) (*model.Agent, error) {
agent := &model.Agent{
ID: uuid.New().String(),
Name: name,
Description: description,
OwnerID: userID,
SecurityLevel: model.SecurityLevelSafe,
IsActive: true,
Timeout: 60,
MemoryLimit: 134217728, // 128MB
ID: uuid.New().String(),
Name: name,
Description: description,
OwnerID: userID,
IsActive: true,
}
if err := s.agentRepo.Create(agent); err != nil {

View File

@@ -35,6 +35,12 @@ func (s *ModelService) GetByID(id string) (*model.ModelInfo, error) {
// Create 创建模型
func (s *ModelService) Create(req model.CreateModelRequest) (*model.ModelInfo, error) {
// 检查模型名称是否已存在
existing, err := s.repo.FindByName(req.Name)
if err == nil && existing != nil {
return nil, fmt.Errorf("model with name '%s' already exists", req.Name)
}
// 如果没有提供状态,默认设置为 inactive
status := req.Status
if status == "" {

View File

@@ -143,7 +143,7 @@ func (s *SkillService) scanSkillsDirectory(basePath string, skillType string) ([
SkillType: skillType,
SkillDesc: skillInfo.SkillDesc,
Path: skillPath,
Status: "active",
Status: 1,
}
skills = append(skills, skill)