feat: 🎯 新增 Token 估算与裁剪工具 * 在 `backend/pkg/token_budget.go` 中新增 Token 估算与裁剪工具 * 最大上下文 Token 数量设置为 224000,预留冗余 28000 * 从最旧消息开始裁剪,直到历史 Token 数量低于预算 * 根据裁剪后的历史消息数量动态计算 Redis 动态窗口大小 refactor: ♻️ 接入 Token 裁剪至 Service 主流程 * 在 `backend/service/agent.go` 中接入 Token 裁剪逻辑 * 先从历史数据获取(缓存未命中则查询数据库) * 按 Token 预算裁剪历史消息,裁剪后再喂模型 * 根据裁剪结果动态调整 Redis 会话窗口 refactor: ♻️ 改造 Redis 历史队列为会话级动态窗口 * 在 `backend/dao/agent-cache.go` 中新增 `SetSessionWindowSize` 与 `EnforceHistoryWindow` * `PushMessage` 和 `BackfillHistory` 方法使用会话动态窗口,而非固定 20 条历史消息 * 默认窗口大小提升至 128,但会被会话动态窗口值覆盖
85 lines
2.0 KiB
Go
85 lines
2.0 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/LoveLosita/smartflow/backend/model"
|
|
"github.com/LoveLosita/smartflow/backend/respond"
|
|
"github.com/LoveLosita/smartflow/backend/service"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type AgentHandler struct {
|
|
svc *service.AgentService
|
|
}
|
|
|
|
// NewAgentHandler 组装 AgentHandler。
|
|
func NewAgentHandler(svc *service.AgentService) *AgentHandler {
|
|
return &AgentHandler{
|
|
svc: svc,
|
|
}
|
|
}
|
|
|
|
func writeSSEData(w io.Writer, payload string) error {
|
|
_, err := io.WriteString(w, "data: "+payload+"\n\n")
|
|
return err
|
|
}
|
|
|
|
func (api *AgentHandler) ChatAgent(c *gin.Context) {
|
|
// 1) 设置 SSE 响应头
|
|
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
|
c.Writer.Header().Set("Cache-Control", "no-cache")
|
|
c.Writer.Header().Set("Connection", "keep-alive")
|
|
c.Writer.Header().Set("Transfer-Encoding", "chunked")
|
|
c.Writer.Header().Set("X-Accel-Buffering", "no")
|
|
|
|
// 2) 解析请求体
|
|
var req model.UserSendMessageRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, respond.WrongParamType)
|
|
return
|
|
}
|
|
|
|
// 3) 规范化会话 ID
|
|
conversationID := strings.TrimSpace(req.ConversationID)
|
|
if conversationID == "" {
|
|
conversationID = uuid.NewString()
|
|
}
|
|
c.Writer.Header().Set("X-Conversation-ID", conversationID)
|
|
|
|
userID := c.GetInt("user_id")
|
|
outChan, errChan := api.svc.AgentChat(c.Request.Context(), req.Message, req.Thinking, req.Model, userID, conversationID)
|
|
|
|
// 4) 转发 SSE 流
|
|
c.Stream(func(w io.Writer) bool {
|
|
select {
|
|
case err, ok := <-errChan:
|
|
if ok && err != nil {
|
|
errPayload, _ := json.Marshal(map[string]any{
|
|
"error": map[string]any{
|
|
"message": err.Error(),
|
|
"type": "server_error",
|
|
},
|
|
})
|
|
_ = writeSSEData(w, string(errPayload))
|
|
_ = writeSSEData(w, "[DONE]")
|
|
}
|
|
return false
|
|
case msg, ok := <-outChan:
|
|
if !ok {
|
|
return false
|
|
}
|
|
if err := writeSSEData(w, msg); err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
case <-c.Request.Context().Done():
|
|
return false
|
|
}
|
|
})
|
|
}
|