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"})
|
||||||
|
}
|
||||||
@@ -15,10 +15,10 @@ const (
|
|||||||
|
|
||||||
// Agent 智能体
|
// Agent 智能体
|
||||||
type Agent struct {
|
type Agent struct {
|
||||||
ID string `json:"id" gorm:"primaryKey"`
|
ID string `json:"id" gorm:"primaryKey"`
|
||||||
Name string `json:"name" gorm:"size:100;not null"`
|
Name string `json:"name" gorm:"size:100;not null"`
|
||||||
Description string `json:"description" gorm:"type:text"`
|
Description string `json:"description" gorm:"type:text"`
|
||||||
OwnerID string `json:"owner_id" gorm:"size:50;not null;index"`
|
OwnerID string `json:"owner_id" gorm:"size:50;not null;index"`
|
||||||
|
|
||||||
// Agent能力配置
|
// Agent能力配置
|
||||||
Capabilities []string `json:"capabilities" gorm:"type:text"` // JSON数组,可用工具列表
|
Capabilities []string `json:"capabilities" gorm:"type:text"` // JSON数组,可用工具列表
|
||||||
@@ -29,6 +29,14 @@ type Agent struct {
|
|||||||
SecurityLevel SecurityLevel `json:"security_level" gorm:"size:20;default:'safe'"`
|
SecurityLevel SecurityLevel `json:"security_level" gorm:"size:20;default:'safe'"`
|
||||||
AllowDangerousTools bool `json:"allow_dangerous_tools" gorm:"default:false"`
|
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"` // 模型名称
|
||||||
|
|
||||||
|
// 协作模式
|
||||||
|
IsSupervisor bool `json:"is_supervisor" gorm:"default:false"` // 是否为主智能体
|
||||||
|
|
||||||
// 状态
|
// 状态
|
||||||
IsActive bool `json:"is_active" gorm:"default:true"`
|
IsActive bool `json:"is_active" gorm:"default:true"`
|
||||||
|
|
||||||
@@ -36,6 +44,62 @@ type Agent struct {
|
|||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AgentSkill 智能体-技能绑定
|
||||||
|
type AgentSkill struct {
|
||||||
|
ID string `json:"id" gorm:"primaryKey"`
|
||||||
|
AgentID string `json:"agent_id" gorm:"size:191;index"`
|
||||||
|
SkillID string `json:"skill_id" gorm:"size:191;index"`
|
||||||
|
SkillConfig string `json:"skill_config" gorm:"type:text"` // JSON 配置
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentKnowledgeBase 智能体-知识库绑定
|
||||||
|
type AgentKnowledgeBase struct {
|
||||||
|
ID string `json:"id" gorm:"primaryKey"`
|
||||||
|
AgentID string `json:"agent_id" gorm:"size:191;index"`
|
||||||
|
KnowledgeBaseID string `json:"knowledge_base_id" gorm:"size:191;index"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentMemory 长期记忆
|
||||||
|
type AgentMemory struct {
|
||||||
|
ID string `json:"id" gorm:"primaryKey"`
|
||||||
|
AgentID string `json:"agent_id" gorm:"size:191;index"`
|
||||||
|
UserID string `json:"user_id" gorm:"size:191;index"`
|
||||||
|
Content string `json:"content" gorm:"type:text"`
|
||||||
|
MemoryType string `json:"memory_type" gorm:"size:20"` // experience/preference/conversation
|
||||||
|
Importance int `json:"importance" gorm:"default:5"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentTeam 多智能体协作配置
|
||||||
|
type AgentTeam struct {
|
||||||
|
ID string `json:"id" gorm:"primaryKey"`
|
||||||
|
SupervisorAgentID string `json:"supervisor_agent_id" gorm:"size:191;index"`
|
||||||
|
MemberAgentID string `json:"member_agent_id" gorm:"size:191;index"`
|
||||||
|
DispatchStrategy string `json:"dispatch_strategy" gorm:"size:20;default:'parallel'"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentTask 任务记录
|
||||||
|
type AgentTask struct {
|
||||||
|
ID string `json:"id" gorm:"primaryKey"`
|
||||||
|
AgentID string `json:"agent_id" gorm:"size:191;index"`
|
||||||
|
UserID string `json:"user_id" gorm:"size:191;index"`
|
||||||
|
UserInput string `json:"user_input" gorm:"type:text"`
|
||||||
|
AgentResponse string `json:"agent_response" gorm:"type:text"`
|
||||||
|
Status string `json:"status" gorm:"size:20"` // pending/running/completed/failed
|
||||||
|
TokensUsed int `json:"tokens_used" gorm:"default:0"`
|
||||||
|
DurationMs int `json:"duration_ms"`
|
||||||
|
SessionID string `json:"session_id" gorm:"size:191;index"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
CompletedAt time.Time `json:"completed_at"`
|
||||||
|
}
|
||||||
|
|
||||||
// AgentRequest 聊天请求
|
// AgentRequest 聊天请求
|
||||||
type AgentRequest struct {
|
type AgentRequest struct {
|
||||||
AgentID string `json:"agent_id" binding:"required"`
|
AgentID string `json:"agent_id" binding:"required"`
|
||||||
|
|||||||
28
server/internal/model/skill.go
Normal file
28
server/internal/model/skill.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Skill 技能
|
||||||
|
type Skill struct {
|
||||||
|
ID string `json:"id" gorm:"primaryKey"`
|
||||||
|
SkillName string `json:"skill_name" gorm:"size:200;not null"`
|
||||||
|
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'"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforeCreate 创建前自动生成ID
|
||||||
|
func (s *Skill) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if s.ID == "" {
|
||||||
|
s.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -46,3 +46,108 @@ func (r *AgentRepository) Update(agent *model.Agent) error {
|
|||||||
func (r *AgentRepository) Delete(id string) error {
|
func (r *AgentRepository) Delete(id string) error {
|
||||||
return r.db.Delete(&model.Agent{}, "id = ?", id).Error
|
return r.db.Delete(&model.Agent{}, "id = ?", id).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AgentSkill 相关方法
|
||||||
|
|
||||||
|
func (r *AgentRepository) CreateAgentSkill(as *model.AgentSkill) error {
|
||||||
|
return r.db.Create(as).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) FindSkillsByAgentID(agentID string) ([]model.AgentSkill, error) {
|
||||||
|
var agentSkills []model.AgentSkill
|
||||||
|
err := r.db.Where("agent_id = ?", agentID).Find(&agentSkills).Error
|
||||||
|
return agentSkills, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) DeleteAgentSkills(agentID string) error {
|
||||||
|
return r.db.Delete(&model.AgentSkill{}, "agent_id = ?", agentID).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentKnowledgeBase 相关方法
|
||||||
|
|
||||||
|
func (r *AgentRepository) CreateAgentKnowledgeBase(akb *model.AgentKnowledgeBase) error {
|
||||||
|
return r.db.Create(akb).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) FindKnowledgeBasesByAgentID(agentID string) ([]model.AgentKnowledgeBase, error) {
|
||||||
|
var agentKBs []model.AgentKnowledgeBase
|
||||||
|
err := r.db.Where("agent_id = ?", agentID).Find(&agentKBs).Error
|
||||||
|
return agentKBs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) DeleteAgentKnowledgeBases(agentID string) error {
|
||||||
|
return r.db.Delete(&model.AgentKnowledgeBase{}, "agent_id = ?", agentID).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentMemory 相关方法
|
||||||
|
|
||||||
|
func (r *AgentRepository) CreateMemory(memory *model.AgentMemory) error {
|
||||||
|
return r.db.Create(memory).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) FindMemoriesByAgentID(agentID string, limit int) ([]model.AgentMemory, error) {
|
||||||
|
var memories []model.AgentMemory
|
||||||
|
err := r.db.Where("agent_id = ?", agentID).Order("importance DESC, created_at DESC").Limit(limit).Find(&memories).Error
|
||||||
|
return memories, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) FindMemoriesByUserID(agentID, userID string, limit int) ([]model.AgentMemory, error) {
|
||||||
|
var memories []model.AgentMemory
|
||||||
|
err := r.db.Where("agent_id = ? AND user_id = ?", agentID, userID).Order("importance DESC, created_at DESC").Limit(limit).Find(&memories).Error
|
||||||
|
return memories, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) DeleteMemory(id string) error {
|
||||||
|
return r.db.Delete(&model.AgentMemory{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentTeam 相关方法
|
||||||
|
|
||||||
|
func (r *AgentRepository) CreateAgentTeam(team *model.AgentTeam) error {
|
||||||
|
return r.db.Create(team).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) FindTeamMembers(supervisorAgentID string) ([]model.AgentTeam, error) {
|
||||||
|
var teams []model.AgentTeam
|
||||||
|
err := r.db.Where("supervisor_agent_id = ?", supervisorAgentID).Find(&teams).Error
|
||||||
|
return teams, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) FindByMemberAgentID(memberAgentID string) ([]model.AgentTeam, error) {
|
||||||
|
var teams []model.AgentTeam
|
||||||
|
err := r.db.Where("member_agent_id = ?", memberAgentID).Find(&teams).Error
|
||||||
|
return teams, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) DeleteAgentTeam(id string) error {
|
||||||
|
return r.db.Delete(&model.AgentTeam{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) DeleteTeamMembers(supervisorAgentID string) error {
|
||||||
|
return r.db.Delete(&model.AgentTeam{}, "supervisor_agent_id = ?", supervisorAgentID).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentTask 相关方法
|
||||||
|
|
||||||
|
func (r *AgentRepository) CreateTask(task *model.AgentTask) error {
|
||||||
|
return r.db.Create(task).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) UpdateTask(task *model.AgentTask) error {
|
||||||
|
return r.db.Save(task).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) FindTasksByAgentID(agentID string, limit int) ([]model.AgentTask, error) {
|
||||||
|
var tasks []model.AgentTask
|
||||||
|
err := r.db.Where("agent_id = ?", agentID).Order("created_at DESC").Limit(limit).Find(&tasks).Error
|
||||||
|
return tasks, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AgentRepository) FindTaskByID(id string) (*model.AgentTask, error) {
|
||||||
|
var task model.AgentTask
|
||||||
|
err := r.db.First(&task, "id = ?", id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &task, nil
|
||||||
|
}
|
||||||
|
|||||||
90
server/internal/repository/skill_repo.go
Normal file
90
server/internal/repository/skill_repo.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"x-agents/server/internal/model"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SkillRepository struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSkillRepository(db *gorm.DB) *SkillRepository {
|
||||||
|
return &SkillRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DB 获取数据库连接
|
||||||
|
func (r *SkillRepository) DB() *gorm.DB {
|
||||||
|
return r.db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SkillRepository) Create(skill *model.Skill) error {
|
||||||
|
return r.db.Create(skill).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SkillRepository) FindAll() ([]model.Skill, error) {
|
||||||
|
var skills []model.Skill
|
||||||
|
err := r.db.Order("skill_type, skill_name").Find(&skills).Error
|
||||||
|
return skills, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SkillRepository) FindByID(id string) (*model.Skill, error) {
|
||||||
|
var skill model.Skill
|
||||||
|
err := r.db.First(&skill, "id = ?", id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &skill, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SkillRepository) FindByType(skillType string) ([]model.Skill, error) {
|
||||||
|
var skills []model.Skill
|
||||||
|
err := r.db.Where("skill_type = ?", skillType).Order("skill_name").Find(&skills).Error
|
||||||
|
return skills, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SkillRepository) FindByName(skillName string) (*model.Skill, error) {
|
||||||
|
var skill model.Skill
|
||||||
|
err := r.db.First(&skill, "skill_name = ?", skillName).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &skill, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SkillRepository) Update(skill *model.Skill) error {
|
||||||
|
return r.db.Save(skill).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SkillRepository) Delete(id string) error {
|
||||||
|
return r.db.Delete(&model.Skill{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpsertBatch 批量upsert skills
|
||||||
|
func (r *SkillRepository) UpsertBatch(skills []model.Skill) error {
|
||||||
|
for _, skill := range skills {
|
||||||
|
var existing model.Skill
|
||||||
|
err := r.db.First(&existing, "skill_name = ? AND skill_type = ?", skill.SkillName, skill.SkillType).Error
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
if err := r.db.Create(&skill).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
existing.SkillDesc = skill.SkillDesc
|
||||||
|
existing.Path = skill.Path
|
||||||
|
existing.Status = skill.Status
|
||||||
|
if err := r.db.Save(&existing).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByType 根据类型删除
|
||||||
|
func (r *SkillRepository) DeleteByType(skillType string) error {
|
||||||
|
return r.db.Where("skill_type = ?", skillType).Delete(&model.Skill{}).Error
|
||||||
|
}
|
||||||
145
server/internal/service/agent_service.go
Normal file
145
server/internal/service/agent_service.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AgentChatRequest Python Agent 对话请求
|
||||||
|
type AgentChatRequest struct {
|
||||||
|
AgentID int `json:"agent_id"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
UserID int `json:"user_id"`
|
||||||
|
SessionID string `json:"session_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentChatResponse Python Agent 对话响应
|
||||||
|
type AgentChatResponse struct {
|
||||||
|
AgentID int `json:"agent_id"`
|
||||||
|
Response string `json:"response"`
|
||||||
|
ToolCalls []interface{} `json:"tool_calls"`
|
||||||
|
TokensUsed int `json:"tokens_used"`
|
||||||
|
DurationMs int `json:"duration_ms"`
|
||||||
|
SessionID string `json:"session_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamChatRequest 多智能体群聊请求
|
||||||
|
type TeamChatRequest struct {
|
||||||
|
SupervisorAgentID int `json:"supervisor_agent_id"`
|
||||||
|
MemberAgentIDs []int `json:"member_agent_ids"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
UserID int `json:"user_id"`
|
||||||
|
SessionID string `json:"session_id,omitempty"`
|
||||||
|
Strategy string `json:"strategy,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamChatResponse 多智能体群聊响应
|
||||||
|
type TeamChatResponse struct {
|
||||||
|
SupervisorAgentID int `json:"supervisor_agent_id"`
|
||||||
|
Response string `json:"response"`
|
||||||
|
SubtaskResults []interface{} `json:"subtask_results"`
|
||||||
|
Strategy string `json:"strategy"`
|
||||||
|
DurationMs int `json:"duration_ms"`
|
||||||
|
SessionID string `json:"session_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentService Python Agent 服务
|
||||||
|
type AgentService struct {
|
||||||
|
pythonURL string
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAgentService 创建 Agent 服务
|
||||||
|
func NewAgentService(pythonURL string) *AgentService {
|
||||||
|
return &AgentService{
|
||||||
|
pythonURL: pythonURL,
|
||||||
|
client: &http.Client{
|
||||||
|
Timeout: 120 * time.Second, // Agent 可能需要较长时间
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chat 单智能体对话
|
||||||
|
func (s *AgentService) Chat(req AgentChatRequest) (*AgentChatResponse, error) {
|
||||||
|
url := fmt.Sprintf("%s/agent/chat", s.pythonURL)
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("python agent error: %s", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var result AgentChatResponse
|
||||||
|
if err := json.Unmarshal(body, &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamChat 多智能体群聊
|
||||||
|
func (s *AgentService) TeamChat(req TeamChatRequest) (*TeamChatResponse, error) {
|
||||||
|
url := fmt.Sprintf("%s/agent/team/chat", s.pythonURL)
|
||||||
|
|
||||||
|
// 设置默认策略
|
||||||
|
if req.Strategy == "" {
|
||||||
|
req.Strategy = "parallel"
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("python agent error: %s", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var result TeamChatResponse
|
||||||
|
if err := json.Unmarshal(body, &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
50
server/internal/service/memory_service.go
Normal file
50
server/internal/service/memory_service.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"x-agents/server/internal/repository"
|
||||||
|
|
||||||
|
"x-agents/server/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MemoryService 记忆服务
|
||||||
|
type MemoryService struct {
|
||||||
|
agentRepo *repository.AgentRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMemoryService 创建记忆服务
|
||||||
|
func NewMemoryService(agentRepo *repository.AgentRepository) *MemoryService {
|
||||||
|
return &MemoryService{
|
||||||
|
agentRepo: agentRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMemory 创建记忆
|
||||||
|
func (s *MemoryService) CreateMemory(agentID, userID, content, memoryType string, importance int) (*model.AgentMemory, error) {
|
||||||
|
memory := &model.AgentMemory{
|
||||||
|
AgentID: agentID,
|
||||||
|
UserID: userID,
|
||||||
|
Content: content,
|
||||||
|
MemoryType: memoryType,
|
||||||
|
Importance: importance,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.agentRepo.CreateMemory(memory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return memory, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMemories 获取记忆列表
|
||||||
|
func (s *MemoryService) GetMemories(agentID string, userID string, limit int) ([]model.AgentMemory, error) {
|
||||||
|
if userID != "" {
|
||||||
|
return s.agentRepo.FindMemoriesByUserID(agentID, userID, limit)
|
||||||
|
}
|
||||||
|
return s.agentRepo.FindMemoriesByAgentID(agentID, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMemory 删除记忆
|
||||||
|
func (s *MemoryService) DeleteMemory(memoryID string) error {
|
||||||
|
return s.agentRepo.DeleteMemory(memoryID)
|
||||||
|
}
|
||||||
226
server/internal/service/skill_service.go
Normal file
226
server/internal/service/skill_service.go
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"x-agents/server/internal/model"
|
||||||
|
"x-agents/server/internal/repository"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SkillService struct {
|
||||||
|
skillRepo *repository.SkillRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSkillService(skillRepo *repository.SkillRepository) *SkillService {
|
||||||
|
return &SkillService{skillRepo: skillRepo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillService) GetAllSkills() ([]model.Skill, error) {
|
||||||
|
return s.skillRepo.FindAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillService) GetSkillByID(id string) (*model.Skill, error) {
|
||||||
|
return s.skillRepo.FindByID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillService) GetSkillsByType(skillType string) ([]model.Skill, error) {
|
||||||
|
return s.skillRepo.FindByType(skillType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillService) CreateSkill(skill *model.Skill) error {
|
||||||
|
return s.skillRepo.Create(skill)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillService) UpdateSkill(skill *model.Skill) error {
|
||||||
|
return s.skillRepo.Update(skill)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillService) DeleteSkill(id string) error {
|
||||||
|
return s.skillRepo.Delete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitSkills 初始化扫描所有 skills 目录
|
||||||
|
func (s *SkillService) InitSkills() error {
|
||||||
|
log.Println("[SkillService] Starting init skills...")
|
||||||
|
|
||||||
|
// 获取项目根目录
|
||||||
|
projectRoot := s.getProjectRoot()
|
||||||
|
if projectRoot == "" {
|
||||||
|
log.Println("[SkillService] Cannot determine project root, skipping skill init")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫描 system skills: account/admin/skills
|
||||||
|
systemSkillsPath := filepath.Join(projectRoot, "account", "admin", "skills")
|
||||||
|
if _, err := os.Stat(systemSkillsPath); err == nil {
|
||||||
|
log.Printf("[SkillService] Scanning system skills from: %s", systemSkillsPath)
|
||||||
|
systemSkills, err := s.scanSkillsDirectory(systemSkillsPath, "system")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[SkillService] Error scanning system skills: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("[SkillService] Found %d system skills", len(systemSkills))
|
||||||
|
// 先删除旧的 system skills
|
||||||
|
s.skillRepo.DeleteByType("system")
|
||||||
|
// 批量插入
|
||||||
|
if err := s.skillRepo.UpsertBatch(systemSkills); err != nil {
|
||||||
|
log.Printf("[SkillService] Error saving system skills: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫描 user skills: account/{username}/skills (除了 admin)
|
||||||
|
accountPath := filepath.Join(projectRoot, "account")
|
||||||
|
entries, err := os.ReadDir(accountPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[SkillService] Error reading account directory: %v", err)
|
||||||
|
} else {
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !entry.IsDir() || entry.Name() == "admin" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
userSkillsPath := filepath.Join(accountPath, entry.Name(), "skills")
|
||||||
|
if _, err := os.Stat(userSkillsPath); err == nil {
|
||||||
|
log.Printf("[SkillService] Scanning user skills for %s from: %s", entry.Name(), userSkillsPath)
|
||||||
|
userSkills, err := s.scanSkillsDirectory(userSkillsPath, "user")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[SkillService] Error scanning user skills for %s: %v", entry.Name(), err)
|
||||||
|
} else {
|
||||||
|
log.Printf("[SkillService] Found %d user skills for %s", len(userSkills), entry.Name())
|
||||||
|
// 批量插入
|
||||||
|
if err := s.skillRepo.UpsertBatch(userSkills); err != nil {
|
||||||
|
log.Printf("[SkillService] Error saving user skills for %s: %v", entry.Name(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("[SkillService] Skills initialized successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanSkillsDirectory 扫描指定目录下的所有 skill
|
||||||
|
func (s *SkillService) scanSkillsDirectory(basePath string, skillType string) ([]model.Skill, error) {
|
||||||
|
var skills []model.Skill
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(basePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
skillDir := filepath.Join(basePath, entry.Name())
|
||||||
|
skillPath := filepath.Join(skillDir, "SKILL.md")
|
||||||
|
|
||||||
|
// 尝试 skill.md(大小写不敏感)
|
||||||
|
if _, err := os.Stat(skillPath); os.IsNotExist(err) {
|
||||||
|
skillPath = filepath.Join(skillDir, "skill.md")
|
||||||
|
}
|
||||||
|
|
||||||
|
skillInfo, err := s.parseSkillFile(skillPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[SkillService] Error parsing skill file %s: %v", skillPath, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有从文件解析到名称,使用目录名
|
||||||
|
if skillInfo.SkillName == "" {
|
||||||
|
skillInfo.SkillName = entry.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
skill := model.Skill{
|
||||||
|
SkillName: skillInfo.SkillName,
|
||||||
|
SkillType: skillType,
|
||||||
|
SkillDesc: skillInfo.SkillDesc,
|
||||||
|
Path: skillPath,
|
||||||
|
Status: "active",
|
||||||
|
}
|
||||||
|
|
||||||
|
skills = append(skills, skill)
|
||||||
|
}
|
||||||
|
|
||||||
|
return skills, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSkillFile 解析 SKILL.md 文件,提取 YAML front matter
|
||||||
|
func (s *SkillService) parseSkillFile(skillPath string) (*model.Skill, error) {
|
||||||
|
content, err := os.ReadFile(skillPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有 YAML front matter
|
||||||
|
contentStr := string(content)
|
||||||
|
if !strings.HasPrefix(contentStr, "---") {
|
||||||
|
// 没有 front matter,返回空
|
||||||
|
return &model.Skill{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到结束标记
|
||||||
|
lines := strings.Split(contentStr, "\n")
|
||||||
|
var endIdx int
|
||||||
|
for i := 1; i < len(lines); i++ {
|
||||||
|
if strings.TrimSpace(lines[i]) == "---" {
|
||||||
|
endIdx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if endIdx == 0 {
|
||||||
|
return &model.Skill{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取 YAML 内容
|
||||||
|
yamlContent := strings.Join(lines[1:endIdx], "\n")
|
||||||
|
|
||||||
|
// 解析 YAML
|
||||||
|
var frontMatter map[string]string
|
||||||
|
if err := yaml.Unmarshal([]byte(yamlContent), &frontMatter); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
skill := &model.Skill{
|
||||||
|
SkillName: frontMatter["name"],
|
||||||
|
SkillDesc: frontMatter["description"],
|
||||||
|
}
|
||||||
|
|
||||||
|
return skill, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getProjectRoot 获取项目根目录
|
||||||
|
func (s *SkillService) getProjectRoot() string {
|
||||||
|
execPath, _ := os.Getwd()
|
||||||
|
projectRoot := execPath
|
||||||
|
|
||||||
|
// 如果当前目录名为 server,向上找一级
|
||||||
|
baseName := filepath.Base(execPath)
|
||||||
|
if baseName == "server" {
|
||||||
|
projectRoot = filepath.Dir(execPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试向上查找包含 .git 的目录
|
||||||
|
if _, err := os.Stat(filepath.Join(projectRoot, ".git")); os.IsNotExist(err) {
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
parent := filepath.Dir(projectRoot)
|
||||||
|
if parent == projectRoot {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(parent, ".git")); err == nil {
|
||||||
|
projectRoot = parent
|
||||||
|
break
|
||||||
|
}
|
||||||
|
projectRoot = parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectRoot
|
||||||
|
}
|
||||||
63
server/migrations/agent_system.sql
Normal file
63
server/migrations/agent_system.sql
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
-- =====================================================
|
||||||
|
-- Agent System Database Migration
|
||||||
|
-- Run this script if you want vector-based memory storage
|
||||||
|
-- Note: Requires pgvector extension for PostgreSQL
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
-- Enable pgvector extension (PostgreSQL only)
|
||||||
|
-- CREATE EXTENSION IF NOT EXISTS vector;
|
||||||
|
|
||||||
|
-- Agent Memory with Vector Embedding (Optional - for vector search)
|
||||||
|
-- This table is optional. If not using vector search, use agent_memories table instead.
|
||||||
|
-- CREATE TABLE IF NOT EXISTS agent_memory_vectors (
|
||||||
|
-- id VARCHAR(191) PRIMARY KEY,
|
||||||
|
-- agent_id VARCHAR(191) NOT NULL,
|
||||||
|
-- user_id VARCHAR(191),
|
||||||
|
-- content TEXT NOT NULL,
|
||||||
|
-- embedding vector(1536), -- Adjust dimension based on your embedding model
|
||||||
|
-- memory_type VARCHAR(20), -- experience/preference/conversation
|
||||||
|
-- importance INT DEFAULT 5,
|
||||||
|
-- created_at DATETIME(3),
|
||||||
|
-- updated_at DATETIME(3),
|
||||||
|
-- INDEX idx_agent_vector USING ivfflat (agent_id, embedding vector_cosine_ops),
|
||||||
|
-- INDEX idx_importance (agent_id, importance DESC)
|
||||||
|
-- );
|
||||||
|
|
||||||
|
-- For MySQL (without vector support, use text search instead)
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_memory_vectors (
|
||||||
|
id VARCHAR(191) PRIMARY KEY,
|
||||||
|
agent_id VARCHAR(191) NOT NULL,
|
||||||
|
user_id VARCHAR(191),
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
embedding_text TEXT, -- Store serialized vector for MySQL
|
||||||
|
memory_type VARCHAR(20),
|
||||||
|
importance INT DEFAULT 5,
|
||||||
|
created_at DATETIME(3),
|
||||||
|
updated_at DATETIME(3),
|
||||||
|
INDEX idx_agent_memory_agent (agent_id),
|
||||||
|
INDEX idx_agent_memory_user (agent_id, user_id),
|
||||||
|
INDEX idx_importance (agent_id, importance DESC)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Agent Task Indexes
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_tasks_agent_user ON agent_tasks(agent_id, user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_tasks_session ON agent_tasks(session_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_tasks_status ON agent_tasks(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_tasks_created ON agent_tasks(created_at DESC);
|
||||||
|
|
||||||
|
-- Agent Team Indexes
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_team_supervisor ON agent_teams(supervisor_agent_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_team_member ON agent_teams(member_agent_id);
|
||||||
|
|
||||||
|
-- Agent Memory Indexes
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_memory_agent ON agent_memories(agent_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_memory_user ON agent_memories(agent_id, user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_memory_type ON agent_memories(memory_type);
|
||||||
|
|
||||||
|
-- Agent Skills Indexes
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_skills_agent ON agent_skills(agent_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_skills_skill ON agent_skills(skill_id);
|
||||||
|
|
||||||
|
-- Agent Knowledge Base Indexes
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_kb_agent ON agent_knowledge_bases(agent_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_kb_kb ON agent_knowledge_bases(knowledge_base_id);
|
||||||
Reference in New Issue
Block a user