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:
2026-03-11 16:25:48 +08:00
parent c6a4b28bf6
commit fc1204a033
11 changed files with 1179 additions and 4 deletions

View 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,
})
}

View 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"})
}

View 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"})
}

View File

@@ -29,6 +29,14 @@ type Agent struct {
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"` // 模型名称
// 协作模式
IsSupervisor bool `json:"is_supervisor" gorm:"default:false"` // 是否为主智能体
// 状态
IsActive bool `json:"is_active" gorm:"default:true"`
@@ -36,6 +44,62 @@ type Agent struct {
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 聊天请求
type AgentRequest struct {
AgentID string `json:"agent_id" binding:"required"`

View 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
}

View File

@@ -46,3 +46,108 @@ func (r *AgentRepository) Update(agent *model.Agent) error {
func (r *AgentRepository) Delete(id string) 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
}

View 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
}

View 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
}

View 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)
}

View 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
}

View 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);