2026-03-13 14:31:42 +08:00
|
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2026-03-13 21:26:27 +08:00
|
|
|
|
"bytes"
|
|
|
|
|
|
"encoding/json"
|
2026-03-13 14:31:42 +08:00
|
|
|
|
"errors"
|
2026-03-13 21:26:27 +08:00
|
|
|
|
"fmt"
|
|
|
|
|
|
"io"
|
|
|
|
|
|
"log"
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
"strconv"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
2026-03-13 14:31:42 +08:00
|
|
|
|
"x-agents/server/internal/model"
|
|
|
|
|
|
"x-agents/server/internal/repository"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 错误定义
|
|
|
|
|
|
var (
|
2026-03-13 21:26:27 +08:00
|
|
|
|
ErrNoAgents = errors.New("no agents provided")
|
2026-03-13 14:31:42 +08:00
|
|
|
|
ErrAgentNotFound = errors.New("agent not found")
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ChatGroupService 群聊服务
|
|
|
|
|
|
type ChatGroupService struct {
|
2026-03-13 21:26:27 +08:00
|
|
|
|
groupRepo *repository.ChatGroupRepository
|
|
|
|
|
|
agentRepo *repository.AgentRepository
|
|
|
|
|
|
pythonURL string
|
|
|
|
|
|
client *http.Client
|
2026-03-13 14:31:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewChatGroupService 创建群聊服务
|
2026-03-13 21:26:27 +08:00
|
|
|
|
func NewChatGroupService(groupRepo *repository.ChatGroupRepository, agentRepo *repository.AgentRepository, pythonURL string) *ChatGroupService {
|
2026-03-13 14:31:42 +08:00
|
|
|
|
return &ChatGroupService{
|
|
|
|
|
|
groupRepo: groupRepo,
|
|
|
|
|
|
agentRepo: agentRepo,
|
2026-03-13 21:26:27 +08:00
|
|
|
|
pythonURL: pythonURL,
|
|
|
|
|
|
client: &http.Client{
|
|
|
|
|
|
Timeout: 120 * time.Second,
|
|
|
|
|
|
},
|
2026-03-13 14:31:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CreateGroup 创建群聊
|
|
|
|
|
|
func (s *ChatGroupService) CreateGroup(req model.CreateGroupRequest) (*model.ChatGroup, error) {
|
|
|
|
|
|
group := &model.ChatGroup{
|
|
|
|
|
|
ID: uuid.New().String(),
|
|
|
|
|
|
UserID: req.UserID,
|
|
|
|
|
|
Name: req.Name,
|
|
|
|
|
|
Description: req.Description,
|
|
|
|
|
|
AgentIDs: req.AgentIDs,
|
|
|
|
|
|
Status: "active",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
err := s.groupRepo.Create(group)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return group, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetGroup 获取群聊详情
|
|
|
|
|
|
func (s *ChatGroupService) GetGroup(id string) (*model.ChatGroup, error) {
|
|
|
|
|
|
return s.groupRepo.FindByID(id)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ListGroups 获取群聊列表
|
|
|
|
|
|
func (s *ChatGroupService) ListGroups(userID string, limit, offset int) ([]model.ChatGroup, int64, error) {
|
|
|
|
|
|
if limit <= 0 {
|
|
|
|
|
|
limit = 20
|
|
|
|
|
|
}
|
|
|
|
|
|
return s.groupRepo.FindByUserID(userID, limit, offset)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UpdateGroup 更新群聊
|
|
|
|
|
|
func (s *ChatGroupService) UpdateGroup(id string, req model.UpdateGroupRequest) (*model.ChatGroup, error) {
|
|
|
|
|
|
group, err := s.groupRepo.FindByID(id)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if req.Name != "" {
|
|
|
|
|
|
group.Name = req.Name
|
|
|
|
|
|
}
|
|
|
|
|
|
if req.Description != "" {
|
|
|
|
|
|
group.Description = req.Description
|
|
|
|
|
|
}
|
|
|
|
|
|
if req.AgentIDs != "" {
|
|
|
|
|
|
group.AgentIDs = req.AgentIDs
|
|
|
|
|
|
}
|
|
|
|
|
|
if req.Status != "" {
|
|
|
|
|
|
group.Status = req.Status
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
err = s.groupRepo.Update(group)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return group, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DeleteGroup 删除群聊
|
|
|
|
|
|
func (s *ChatGroupService) DeleteGroup(id string) error {
|
|
|
|
|
|
return s.groupRepo.Delete(id)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GroupChat 群聊对话
|
|
|
|
|
|
func (s *ChatGroupService) GroupChat(userID, message, agentIDs, sessionID string) (*model.GroupChatResponse, error) {
|
|
|
|
|
|
// 解析 Agent IDs
|
|
|
|
|
|
agentIDList := parseAgentIDs(agentIDs)
|
|
|
|
|
|
|
|
|
|
|
|
if len(agentIDList) == 0 {
|
|
|
|
|
|
return nil, ErrNoAgents
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有 Agent 信息
|
|
|
|
|
|
agents, err := s.agentRepo.FindByIDs(agentIDList)
|
|
|
|
|
|
if err != nil || len(agents) == 0 {
|
|
|
|
|
|
return nil, ErrAgentNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 21:26:27 +08:00
|
|
|
|
// 解析 userID 为整数
|
|
|
|
|
|
userIDInt, err := strconv.Atoi(userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
userIDInt = 1 // 默认值
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将 agent UUIDs 转换为整数 IDs
|
|
|
|
|
|
memberAgentIDs := make([]int, len(agents))
|
|
|
|
|
|
for i := range agents {
|
|
|
|
|
|
// 使用索引+1 作为 agent ID(因为 Python 端使用整数 ID)
|
|
|
|
|
|
memberAgentIDs[i] = i + 1
|
|
|
|
|
|
log.Printf("[ChatGroupService] Agent: %s (ID: %s)", agents[i].Name, agents[i].ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 调用 Python TeamAgent 进行群聊
|
|
|
|
|
|
teamReq := TeamChatRequest{
|
|
|
|
|
|
SupervisorAgentID: 0, // 没有 supervisor,使用并行策略
|
|
|
|
|
|
MemberAgentIDs: memberAgentIDs,
|
|
|
|
|
|
Message: message,
|
|
|
|
|
|
UserID: userIDInt,
|
|
|
|
|
|
SessionID: sessionID,
|
|
|
|
|
|
Strategy: "parallel",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
url := fmt.Sprintf("%s/api/v1/agent/team/chat", s.pythonURL)
|
|
|
|
|
|
jsonData, err := json.Marshal(teamReq)
|
|
|
|
|
|
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 team 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 team agent error: %s", string(body))
|
2026-03-13 14:31:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 21:26:27 +08:00
|
|
|
|
var teamResp TeamChatResponse
|
|
|
|
|
|
if err := json.Unmarshal(body, &teamResp); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Printf("[ChatGroupService] Raw teamResp: %+v", teamResp)
|
|
|
|
|
|
log.Printf("[ChatGroupService] SubtaskResults count: %d", len(teamResp.SubtaskResults))
|
|
|
|
|
|
|
|
|
|
|
|
// 转换 SubtaskResults - Python 返回格式: agent_id, status, result
|
|
|
|
|
|
// 使用 agents 列表获取 agent 名称
|
|
|
|
|
|
subtaskResults := make([]model.SubtaskResult, 0)
|
|
|
|
|
|
for i, sr := range teamResp.SubtaskResults {
|
|
|
|
|
|
srMap, ok := sr.(map[string]interface{})
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取 agent 名称(从已查询的 agents 列表)
|
|
|
|
|
|
agentName := ""
|
|
|
|
|
|
if i < len(agents) {
|
|
|
|
|
|
agentName = agents[i].Name
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理 agent_id (Python 返回 int,Go 需要 string)
|
|
|
|
|
|
agentIDVal := srMap["agent_id"]
|
|
|
|
|
|
var agentIDStr string
|
|
|
|
|
|
switch v := agentIDVal.(type) {
|
|
|
|
|
|
case float64:
|
|
|
|
|
|
agentIDStr = fmt.Sprintf("%d", int(v))
|
|
|
|
|
|
case string:
|
|
|
|
|
|
agentIDStr = v
|
|
|
|
|
|
default:
|
|
|
|
|
|
agentIDStr = "0"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有从 agents 列表获取到名称,使用 agent_id
|
|
|
|
|
|
if agentName == "" {
|
|
|
|
|
|
agentName = agentIDStr
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取 result 作为 reply
|
|
|
|
|
|
resultVal, _ := srMap["result"]
|
|
|
|
|
|
var reply string
|
|
|
|
|
|
switch v := resultVal.(type) {
|
|
|
|
|
|
case string:
|
|
|
|
|
|
reply = v
|
|
|
|
|
|
default:
|
|
|
|
|
|
reply = fmt.Sprintf("%v", resultVal)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
subtaskResults = append(subtaskResults, model.SubtaskResult{
|
|
|
|
|
|
AgentID: agentIDStr,
|
|
|
|
|
|
AgentName: agentName,
|
|
|
|
|
|
Reply: reply,
|
|
|
|
|
|
TokensUsed: 0, // Python 暂时未返回
|
|
|
|
|
|
DurationMs: 0, // Python 暂时未返回
|
|
|
|
|
|
})
|
2026-03-13 14:31:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 汇总结果
|
|
|
|
|
|
response := &model.GroupChatResponse{
|
|
|
|
|
|
SessionID: sessionID,
|
2026-03-13 21:26:27 +08:00
|
|
|
|
Reply: teamResp.Response,
|
|
|
|
|
|
DurationMs: teamResp.DurationMs,
|
|
|
|
|
|
Strategy: teamResp.Strategy,
|
2026-03-13 14:31:42 +08:00
|
|
|
|
SubtaskResults: subtaskResults,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 辅助函数:解析 Agent IDs
|
|
|
|
|
|
func parseAgentIDs(agentIDs string) []string {
|
|
|
|
|
|
if agentIDs == "" {
|
|
|
|
|
|
return []string{}
|
|
|
|
|
|
}
|
2026-03-13 21:26:27 +08:00
|
|
|
|
// 尝试解析 JSON 数组格式
|
|
|
|
|
|
var ids []string
|
|
|
|
|
|
if err := json.Unmarshal([]byte(agentIDs), &ids); err == nil {
|
|
|
|
|
|
return ids
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果解析失败,可能是逗号分隔的字符串
|
|
|
|
|
|
if strings.Contains(agentIDs, ",") {
|
|
|
|
|
|
return strings.Split(agentIDs, ",")
|
|
|
|
|
|
}
|
|
|
|
|
|
// 简单处理,直接返回
|
2026-03-13 14:31:42 +08:00
|
|
|
|
return []string{agentIDs}
|
|
|
|
|
|
}
|