feat(server): 新增会话列表、多轮历史消息查询与标杆案例语义检索 RESTful API

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WeiCHEN
2026-09-13 02:50:19 +00:00
committed by weichen
co-authored by Cursor
parent d0c9670ebc
commit 2a6dd442bb
4 changed files with 242 additions and 1 deletions
+3
View File
@@ -57,6 +57,9 @@ func main() {
fmt.Println(" • [GET] /api/v1/context/user - 获取单用户私有上下文与习惯偏好画像")
fmt.Println(" • [PUT] /api/v1/context/user/preference - 整理或更新用户个性化产出风格")
fmt.Println(" • [GET] /api/v1/context/project - 获取项目多方协同黑板事实与里程碑决策流")
fmt.Println(" • [GET] /api/v1/chat/sessions - 分页查询用户/项目历史会话列表")
fmt.Println(" • [GET] /api/v1/chat/messages - 分页拉取指定会话多轮历史消息记录")
fmt.Println(" • [GET] /api/v1/context/cases/recommend - 往期受控标杆案例与历史报价语义自推荐")
fmt.Println(" • [GET] /api/v1/docgov/document - 查询受控文档治理状态看板")
fmt.Println(" • [POST] /api/v1/docgov/claim - 查阅并认领受控文档 (正式稿方可认领)")
fmt.Println(" • [POST] /api/v1/docgov/proposal/decide - 编制负责人拍板同意/拒绝变更提案")
+117
View File
@@ -0,0 +1,117 @@
package server_test
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/config"
"chat-agent-system/internal/ctxmgr"
"chat-agent-system/internal/docgov"
"chat-agent-system/internal/server"
)
func TestServer_ChatHistoryAndCaseRecommendationAPIs(t *testing.T) {
cfg := &config.Config{
HTTPHost: "127.0.0.1",
HTTPPort: 8888,
DataDir: t.TempDir(),
}
svc := bizdev.NewService(cfg)
srv := server.NewServer(cfg, svc)
handler := srv.Routes()
ctx := context.Background()
sessID := "sess_api_history_001"
userID := int64(108)
// 1. 模拟写入对话消息
_ = svc.ContextManager().RecordUserMessage(ctx, sessID, userID, "user", "我们准备立项一款便携无线掌上超声,预算大约150万", "SUPPLEMENT_INFO", "EXPECTATION_HIT", nil)
_ = svc.ContextManager().RecordUserMessage(ctx, sessID, 0, "assistant", "已为您建立超声设备立项基准,预估研发周期6个月", "ADVANCE_STAGE", "EXPECTATION_HIT", nil)
// 2. 测试 GET /api/v1/chat/sessions
reqSessions := httptest.NewRequest("GET", "/api/v1/chat/sessions?user_id=108", nil)
rrSessions := httptest.NewRecorder()
handler.ServeHTTP(rrSessions, reqSessions)
if rrSessions.Code != http.StatusOK {
t.Fatalf("GET /api/v1/chat/sessions expected 200, got %d: %s", rrSessions.Code, rrSessions.Body.String())
}
var sessionList []ctxmgr.ChatSessionSummary
if err := json.Unmarshal(rrSessions.Body.Bytes(), &sessionList); err != nil {
t.Fatalf("unmarshal sessions failed: %v", err)
}
if len(sessionList) != 1 || sessionList[0].SessionID != sessID {
t.Fatalf("unexpected sessions result: %+v", sessionList)
}
t.Logf("✓ 成功查询到历史会话列表: 包含会话=%s, 消息数=%d", sessionList[0].SessionID, sessionList[0].MessageCount)
// 3. 测试 GET /api/v1/chat/messages
reqMsgs := httptest.NewRequest("GET", "/api/v1/chat/messages?session_id="+sessID, nil)
rrMsgs := httptest.NewRecorder()
handler.ServeHTTP(rrMsgs, reqMsgs)
if rrMsgs.Code != http.StatusOK {
t.Fatalf("GET /api/v1/chat/messages expected 200, got %d: %s", rrMsgs.Code, rrMsgs.Body.String())
}
var msgList []ctxmgr.ChatMessageRecord
if err := json.Unmarshal(rrMsgs.Body.Bytes(), &msgList); err != nil {
t.Fatalf("unmarshal messages failed: %v", err)
}
if len(msgList) != 2 {
t.Fatalf("expected 2 messages, got %d", len(msgList))
}
t.Logf("✓ 成功分页获取会话消息历史: 轮次数=%d, 首条内容=%s", len(msgList), msgList[0].Content)
// 4. 预置一个受控文档终态并索引为标杆案例
now := time.Now()
doc := &docgov.DocumentRecord{
DocID: "doc_benchmark_api_01",
ProjectID: 501,
TaskKey: "feasibility_reg",
Title: "无线掌上超声探头可行性评估报告",
Stage: "V0.5_feasibility",
CurrentState: docgov.StateApproved,
IsLocked: true,
ApprovedAt: &now,
AuthorName: "王总工",
}
docBody := `# 无线掌上超声探头可行性评估报告
## 概述与器械分类
主要用于临床床旁急诊腹部与浅表超声扫查,属于二类或三类医疗器械。
## 关键性能与型检预估
符合 GB 9706.1-2020 和 YY 0644 标准要求,探头无线射频满足 SRRC 与无线电合规要求。`
_, err := svc.ContextManager().IndexApprovedDocument(ctx, doc, docBody)
if err != nil {
t.Fatalf("IndexApprovedDocument failed: %v", err)
}
// 5. 测试 GET /api/v1/context/cases/recommend
reqCase := httptest.NewRequest("GET", "/api/v1/context/cases/recommend?query=无线超声探头型检与注册要求&limit=3", nil)
rrCase := httptest.NewRecorder()
handler.ServeHTTP(rrCase, reqCase)
if rrCase.Code != http.StatusOK {
t.Fatalf("GET /api/v1/context/cases/recommend expected 200, got %d: %s", rrCase.Code, rrCase.Body.String())
}
var cases []ctxmgr.CaseRecommendationItem
if err := json.Unmarshal(rrCase.Body.Bytes(), &cases); err != nil {
t.Fatalf("unmarshal cases failed: %v", err)
}
if len(cases) == 0 {
t.Fatalf("expected recommended cases, got 0")
}
t.Logf("✓ 成功通过 API 检索到标杆案例推荐: 案例=%s, 匹配章节=%s, 得分=%.2f, 提要=%s",
cases[0].ArtifactTitle, cases[0].SectionTitle, cases[0].SimilarityScore, cases[0].SummarySnippet)
}
+116
View File
@@ -591,3 +591,119 @@ func (s *Server) handleDocGovListAuditLogs(w http.ResponseWriter, r *http.Reques
writeJSONResponse(w, http.StatusOK, logs)
}
// -----------------------------------------------------------------------------
// 历史多轮会话管理与标杆案例语义检索 RESTful 控制器
// -----------------------------------------------------------------------------
// handleListChatSessions 分页查询用户/项目关联的历史会话列表
func (s *Server) handleListChatSessions(w http.ResponseWriter, r *http.Request) {
if s.svc == nil || s.svc.ContextManager() == nil {
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
return
}
userIDStr := r.URL.Query().Get("user_id")
userID, _ := strconv.ParseInt(userIDStr, 10, 64)
projectIDStr := r.URL.Query().Get("project_id")
projectID, _ := strconv.ParseInt(projectIDStr, 10, 64)
limitStr := r.URL.Query().Get("limit")
limit, _ := strconv.Atoi(limitStr)
if limit <= 0 {
limit = 20
}
offsetStr := r.URL.Query().Get("offset")
offset, _ := strconv.Atoi(offsetStr)
if offset < 0 {
offset = 0
}
sessions, err := s.svc.ContextManager().ListChatSessions(r.Context(), ctxmgr.SessionFilter{
UserID: userID,
ProjectID: projectID,
Limit: limit,
Offset: offset,
})
if err != nil {
http.Error(w, "list chat sessions failed: "+err.Error(), http.StatusInternalServerError)
return
}
writeJSONResponse(w, http.StatusOK, sessions)
}
// handleGetChatMessages 分页获取指定会话的历史多轮消息记录
func (s *Server) handleGetChatMessages(w http.ResponseWriter, r *http.Request) {
if s.svc == nil || s.svc.ContextManager() == nil {
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
return
}
sessionID := r.URL.Query().Get("session_id")
if sessionID == "" {
http.Error(w, "missing session_id query param", http.StatusBadRequest)
return
}
limitStr := r.URL.Query().Get("limit")
limit, _ := strconv.Atoi(limitStr)
if limit <= 0 {
limit = 50
}
offsetStr := r.URL.Query().Get("offset")
offset, _ := strconv.Atoi(offsetStr)
if offset < 0 {
offset = 0
}
msgs, err := s.svc.ContextManager().GetChatHistory(r.Context(), sessionID, limit, offset)
if err != nil {
http.Error(w, "get chat messages failed: "+err.Error(), http.StatusInternalServerError)
return
}
writeJSONResponse(w, http.StatusOK, msgs)
}
// handleRecommendCases 基于语义向量检索往期相似标杆案例
func (s *Server) handleRecommendCases(w http.ResponseWriter, r *http.Request) {
if s.svc == nil || s.svc.ContextManager() == nil {
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
return
}
query := r.URL.Query().Get("query")
if query == "" {
http.Error(w, "missing query param", http.StatusBadRequest)
return
}
taskKey := r.URL.Query().Get("task_key")
excludeDoc := r.URL.Query().Get("exclude_doc")
limitStr := r.URL.Query().Get("limit")
limit, _ := strconv.Atoi(limitStr)
if limit <= 0 {
limit = 3
}
minScoreStr := r.URL.Query().Get("min_score")
minScore, _ := strconv.ParseFloat(minScoreStr, 64)
if minScore <= 0 {
minScore = 0.50
}
cases, err := s.svc.ContextManager().SearchSimilarCases(r.Context(), ctxmgr.CaseSearchOptions{
Query: query,
TaskKey: taskKey,
ExcludeDoc: excludeDoc,
Limit: limit,
MinScore: minScore,
})
if err != nil {
http.Error(w, "search similar cases failed: "+err.Error(), http.StatusInternalServerError)
return
}
writeJSONResponse(w, http.StatusOK, cases)
}
+6 -1
View File
@@ -75,7 +75,12 @@ func (s *Server) Routes() http.Handler {
mux.HandleFunc("PUT /api/v1/context/user/preference", s.handleUpdateUserPreference)
mux.HandleFunc("GET /api/v1/context/project", s.handleGetProjectContext)
// 7. 受控文档编制、审核、批准生命周期与认领治理
// 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)