Files
Agent-System/internal/server/server.go
T

219 lines
6.2 KiB
Go

package server
import (
"context"
"fmt"
"net/http"
"sync"
"time"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/chat"
"chat-agent-system/internal/config"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/intent"
)
// Server 提供基于 HTTP RESTful 与 SSE 的商务与研发参谋接口服务
type Server struct {
cfg *config.Config
svc *bizdev.Service
rec intent.Recognizer
engine *coordinator.CoordinatorEngine
sessions map[string]*chat.Session
sessionsMu sync.RWMutex
httpServer *http.Server
}
// NewServer 构造函数
func NewServer(cfg *config.Config, svc *bizdev.Service) *Server {
if cfg == nil {
cfg, _ = config.Load("")
}
if svc == nil {
svc = bizdev.NewService(cfg)
}
rec := intent.NewRecognizer()
coordEngine := coordinator.NewCoordinatorEngine(svc)
s := &Server{
cfg: cfg,
svc: svc,
rec: rec,
engine: coordEngine,
sessions: make(map[string]*chat.Session),
}
return s
}
// Routes 构建 HTTP 路由分发器
func (s *Server) Routes() http.Handler {
mux := http.NewServeMux()
// 1. 健康检查与系统自检
mux.HandleFunc("GET /health", s.handleHealth)
mux.HandleFunc("GET /api/v1/health", s.handleHealth)
// 2. 项目状态与会话查询
mux.HandleFunc("GET /api/v1/projects", s.handleGetProject)
// 3. 对话协同服务 (标准 JSON 阻塞)
mux.HandleFunc("POST /api/v1/chat", s.handleChat)
// 4. 对话协同服务 (核心 SSE 流式长连接)
mux.HandleFunc("POST /api/v1/chat/stream", s.handleChatStream)
mux.HandleFunc("GET /api/v1/chat/stream", s.handleChatStream)
// 5. 独立任务 / 阶段执行
mux.HandleFunc("POST /api/v1/tasks/run", s.handleRunTask)
mux.HandleFunc("POST /api/v1/tasks/run/stream", s.handleRunTaskStream)
mux.HandleFunc("GET /api/v1/tasks/run/stream", s.handleRunTaskStream)
// 6. 多级上下文与偏好记忆管理
mux.HandleFunc("GET /api/v1/context/user", s.handleGetUserContext)
mux.HandleFunc("PUT /api/v1/context/user/preference", s.handleUpdateUserPreference)
mux.HandleFunc("GET /api/v1/context/project", s.handleGetProjectContext)
// 7. 历史多轮会话管理与标杆案例语义检索
mux.HandleFunc("GET /api/v1/chat/sessions", s.handleListChatSessions)
mux.HandleFunc("GET /api/v1/chat/messages", s.handleGetChatMessages)
mux.HandleFunc("GET /api/v1/context/cases/recommend", s.handleRecommendCases)
// 8. 受控文档编制、审核、批准生命周期与认领治理
mux.HandleFunc("GET /api/v1/docgov/document", s.handleDocGovGetDocument)
mux.HandleFunc("POST /api/v1/docgov/claim", s.handleDocGovClaim)
mux.HandleFunc("POST /api/v1/docgov/submit-review", s.handleDocGovSubmitReview)
mux.HandleFunc("POST /api/v1/docgov/review-decision", s.handleDocGovReviewDecision)
mux.HandleFunc("POST /api/v1/docgov/approver-decision", s.handleDocGovApproverDecision)
mux.HandleFunc("POST /api/v1/docgov/proposal/decide", s.handleDocGovDecideProposal)
mux.HandleFunc("GET /api/v1/docgov/comments", s.handleDocGovListComments)
mux.HandleFunc("POST /api/v1/docgov/comments", s.handleDocGovAddComment)
mux.HandleFunc("GET /api/v1/docgov/audit-logs", s.handleDocGovListAuditLogs)
return s.corsMiddleware(mux)
}
// Start 启动 HTTP 守护进程
func (s *Server) Start() error {
addr := fmt.Sprintf("%s:%d", s.cfg.HTTPHost, s.cfg.HTTPPort)
s.httpServer = &http.Server{
Addr: addr,
Handler: s.Routes(),
ReadHeaderTimeout: 10 * time.Second,
}
return s.httpServer.ListenAndServe()
}
// Shutdown 优雅停机
func (s *Server) Shutdown(ctx context.Context) error {
if s.httpServer != nil {
return s.httpServer.Shutdown(ctx)
}
return nil
}
// getOrCreateSession 获取或动态构建会话
func (s *Server) getOrCreateSession(
sessionID string,
projectName string,
customerName string,
deviceType string,
intendedUse string,
targetMarket string,
workDir string,
userName string,
userRole string,
) *chat.Session {
if sessionID == "" {
sessionID = fmt.Sprintf("sess_%d", time.Now().UnixNano())
}
s.sessionsMu.RLock()
existing, exists := s.sessions[sessionID]
s.sessionsMu.RUnlock()
if exists && existing != nil {
return existing
}
s.sessionsMu.Lock()
defer s.sessionsMu.Unlock()
// 双重检查
if existing, exists := s.sessions[sessionID]; exists && existing != nil {
return existing
}
if userName == "" {
userName = "用户"
}
if userRole == "" {
userRole = "engineer"
}
u := coordinator.UserInfo{
ID: "u_" + sessionID,
Name: userName,
Role: userRole,
ProjectRole: "composer",
}
var userCtx *coordinator.UserProjectContext
if workDir != "" {
assembler := coordinator.NewContextAssembler()
userCtx, _ = assembler.AssembleFromWorkspace(workDir, u)
}
if userCtx == nil {
if projectName == "" {
projectName = "医疗器械研发项目"
}
if customerName == "" {
customerName = "水木东方客户"
}
userCtx = &coordinator.UserProjectContext{
SnapshotID: fmt.Sprintf("ctx_%d", time.Now().UnixNano()),
AssembledAt: time.Now(),
User: u,
WorkDir: workDir,
Project: coordinator.ProjectInfo{
Name: projectName,
CustomerName: customerName,
DeviceType: deviceType,
IntendedUse: intendedUse,
TargetMarket: targetMarket,
CurrentWorkflow: "intake",
CurrentStage: bizdev.StageV01_Triage,
},
BlackboardFacts: make(map[string]string),
AvailableActions: coordinator.ComputeAvailableActions(bizdev.StageV01_Triage, u.ProjectRole),
}
if targetMarket != "" {
userCtx.BlackboardFacts["target_market"] = targetMarket
}
if deviceType != "" {
userCtx.BlackboardFacts["device_type"] = deviceType
}
}
sess := chat.NewSession(sessionID, userCtx, s.rec, s.engine)
if s.svc != nil {
sess.CtxMgr = s.svc.ContextManager()
}
s.sessions[sessionID] = sess
return sess
}
func (s *Server) corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}