Version: 0.9.75.dev.260505

后端:
1.收口阶段 6 agent 结构迁移,将 newAgent 内核与 agentsvc 编排层迁入 services/agent
- 切换 Agent 启动装配与 HTTP handler 直连 agent sv,移除旧 service agent bridge
- 补齐 Agent 对 memory、task、task-class、schedule 的 RPC 适配与契约字段
- 扩展 schedule、task、task-class RPC/contract 支撑 Agent 查询、写入与 provider 切流
- 更新迁移文档、README 与相关注释,明确 agent 当前切流点和剩余 memory 迁移面
This commit is contained in:
Losita
2026-05-05 16:00:57 +08:00
parent e1819c5653
commit d7184b776b
174 changed files with 2189 additions and 1236 deletions

View File

@@ -42,8 +42,8 @@ func (h *Handler) AddTaskClass(ctx context.Context, req *pb.JSONRequest) (*pb.JS
if err := json.Unmarshal(req.PayloadJson, &contractReq); err != nil {
return nil, grpcErrorFromServiceError(respond.WrongParamType)
}
err := h.svc.AddOrUpdateTaskClass(ctx, toModelTaskClassRequest(contractReq), contractReq.UserID, taskClassCreate, 0)
return jsonResponse(nil, err)
taskClassID, err := h.svc.AddOrUpdateTaskClass(ctx, toModelTaskClassRequest(contractReq), contractReq.UserID, taskClassCreate, 0)
return jsonResponse(taskclasscontracts.UpsertTaskClassResponse{TaskClassID: taskClassID, Created: true}, err)
}
func (h *Handler) ListTaskClasses(ctx context.Context, req *pb.JSONRequest) (*pb.JSONResponse, error) {
@@ -78,8 +78,20 @@ func (h *Handler) UpdateTaskClass(ctx context.Context, req *pb.JSONRequest) (*pb
if err := json.Unmarshal(req.PayloadJson, &contractReq); err != nil {
return nil, grpcErrorFromServiceError(respond.WrongParamType)
}
err := h.svc.AddOrUpdateTaskClass(ctx, toModelTaskClassRequest(contractReq), contractReq.UserID, taskClassUpdate, contractReq.TaskClassID)
return jsonResponse(nil, err)
taskClassID, err := h.svc.AddOrUpdateTaskClass(ctx, toModelTaskClassRequest(contractReq), contractReq.UserID, taskClassUpdate, contractReq.TaskClassID)
return jsonResponse(taskclasscontracts.UpsertTaskClassResponse{TaskClassID: taskClassID, Created: false}, err)
}
func (h *Handler) GetAgentTaskClasses(ctx context.Context, req *pb.JSONRequest) (*pb.JSONResponse, error) {
if err := h.ensureReady(req); err != nil {
return nil, err
}
var contractReq taskclasscontracts.AgentTaskClassesRequest
if err := json.Unmarshal(req.PayloadJson, &contractReq); err != nil {
return nil, grpcErrorFromServiceError(respond.WrongParamType)
}
data, err := h.svc.GetAgentTaskClasses(ctx, contractReq)
return jsonResponse(data, err)
}
func (h *Handler) InsertTaskClassItemIntoSchedule(ctx context.Context, req *pb.JSONRequest) (*pb.JSONResponse, error) {

View File

@@ -14,6 +14,7 @@ const (
TaskClass_ListTaskClasses_FullMethodName = "/smartflow.taskclass.TaskClass/ListTaskClasses"
TaskClass_GetTaskClass_FullMethodName = "/smartflow.taskclass.TaskClass/GetTaskClass"
TaskClass_UpdateTaskClass_FullMethodName = "/smartflow.taskclass.TaskClass/UpdateTaskClass"
TaskClass_GetAgentTaskClasses_FullMethodName = "/smartflow.taskclass.TaskClass/GetAgentTaskClasses"
TaskClass_InsertTaskClassItemIntoSchedule_FullMethodName = "/smartflow.taskclass.TaskClass/InsertTaskClassItemIntoSchedule"
TaskClass_DeleteTaskClassItem_FullMethodName = "/smartflow.taskclass.TaskClass/DeleteTaskClassItem"
TaskClass_DeleteTaskClass_FullMethodName = "/smartflow.taskclass.TaskClass/DeleteTaskClass"
@@ -26,6 +27,7 @@ type TaskClassClient interface {
ListTaskClasses(ctx context.Context, in *JSONRequest, opts ...grpc.CallOption) (*JSONResponse, error)
GetTaskClass(ctx context.Context, in *JSONRequest, opts ...grpc.CallOption) (*JSONResponse, error)
UpdateTaskClass(ctx context.Context, in *JSONRequest, opts ...grpc.CallOption) (*JSONResponse, error)
GetAgentTaskClasses(ctx context.Context, in *JSONRequest, opts ...grpc.CallOption) (*JSONResponse, error)
InsertTaskClassItemIntoSchedule(ctx context.Context, in *JSONRequest, opts ...grpc.CallOption) (*JSONResponse, error)
DeleteTaskClassItem(ctx context.Context, in *JSONRequest, opts ...grpc.CallOption) (*JSONResponse, error)
DeleteTaskClass(ctx context.Context, in *JSONRequest, opts ...grpc.CallOption) (*JSONResponse, error)
@@ -70,6 +72,12 @@ func (c *taskClassClient) UpdateTaskClass(ctx context.Context, in *JSONRequest,
return out, err
}
func (c *taskClassClient) GetAgentTaskClasses(ctx context.Context, in *JSONRequest, opts ...grpc.CallOption) (*JSONResponse, error) {
out := new(JSONResponse)
err := c.cc.Invoke(ctx, TaskClass_GetAgentTaskClasses_FullMethodName, in, out, opts...)
return out, err
}
func (c *taskClassClient) InsertTaskClassItemIntoSchedule(ctx context.Context, in *JSONRequest, opts ...grpc.CallOption) (*JSONResponse, error) {
out := new(JSONResponse)
err := c.cc.Invoke(ctx, TaskClass_InsertTaskClassItemIntoSchedule_FullMethodName, in, out, opts...)
@@ -100,6 +108,7 @@ type TaskClassServer interface {
ListTaskClasses(context.Context, *JSONRequest) (*JSONResponse, error)
GetTaskClass(context.Context, *JSONRequest) (*JSONResponse, error)
UpdateTaskClass(context.Context, *JSONRequest) (*JSONResponse, error)
GetAgentTaskClasses(context.Context, *JSONRequest) (*JSONResponse, error)
InsertTaskClassItemIntoSchedule(context.Context, *JSONRequest) (*JSONResponse, error)
DeleteTaskClassItem(context.Context, *JSONRequest) (*JSONResponse, error)
DeleteTaskClass(context.Context, *JSONRequest) (*JSONResponse, error)
@@ -123,6 +132,9 @@ func (UnimplementedTaskClassServer) GetTaskClass(context.Context, *JSONRequest)
func (UnimplementedTaskClassServer) UpdateTaskClass(context.Context, *JSONRequest) (*JSONResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateTaskClass not implemented")
}
func (UnimplementedTaskClassServer) GetAgentTaskClasses(context.Context, *JSONRequest) (*JSONResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAgentTaskClasses not implemented")
}
func (UnimplementedTaskClassServer) InsertTaskClassItemIntoSchedule(context.Context, *JSONRequest) (*JSONResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method InsertTaskClassItemIntoSchedule not implemented")
}
@@ -181,6 +193,7 @@ var TaskClass_ServiceDesc = grpc.ServiceDesc{
{MethodName: "ListTaskClasses", Handler: _TaskClass_JSON_Handler(TaskClass_ListTaskClasses_FullMethodName, TaskClassServer.ListTaskClasses)},
{MethodName: "GetTaskClass", Handler: _TaskClass_JSON_Handler(TaskClass_GetTaskClass_FullMethodName, TaskClassServer.GetTaskClass)},
{MethodName: "UpdateTaskClass", Handler: _TaskClass_JSON_Handler(TaskClass_UpdateTaskClass_FullMethodName, TaskClassServer.UpdateTaskClass)},
{MethodName: "GetAgentTaskClasses", Handler: _TaskClass_JSON_Handler(TaskClass_GetAgentTaskClasses_FullMethodName, TaskClassServer.GetAgentTaskClasses)},
{MethodName: "InsertTaskClassItemIntoSchedule", Handler: _TaskClass_JSON_Handler(TaskClass_InsertTaskClassItemIntoSchedule_FullMethodName, TaskClassServer.InsertTaskClassItemIntoSchedule)},
{MethodName: "DeleteTaskClassItem", Handler: _TaskClass_JSON_Handler(TaskClass_DeleteTaskClassItem_FullMethodName, TaskClassServer.DeleteTaskClassItem)},
{MethodName: "DeleteTaskClass", Handler: _TaskClass_JSON_Handler(TaskClass_DeleteTaskClass_FullMethodName, TaskClassServer.DeleteTaskClass)},

View File

@@ -10,6 +10,7 @@ service TaskClass {
rpc ListTaskClasses(JSONRequest) returns (JSONResponse);
rpc GetTaskClass(JSONRequest) returns (JSONResponse);
rpc UpdateTaskClass(JSONRequest) returns (JSONResponse);
rpc GetAgentTaskClasses(JSONRequest) returns (JSONResponse);
rpc InsertTaskClassItemIntoSchedule(JSONRequest) returns (JSONResponse);
rpc DeleteTaskClassItem(JSONRequest) returns (JSONResponse);
rpc DeleteTaskClass(JSONRequest) returns (JSONResponse);

View File

@@ -13,6 +13,7 @@ import (
"github.com/LoveLosita/smartflow/backend/model"
"github.com/LoveLosita/smartflow/backend/respond"
taskclassdao "github.com/LoveLosita/smartflow/backend/services/task_class/dao"
taskclasscontracts "github.com/LoveLosita/smartflow/backend/shared/contracts/taskclass"
"github.com/go-redis/redis/v8"
"gorm.io/gorm"
)
@@ -34,34 +35,39 @@ func NewTaskClassService(taskClassRepo *taskclassdao.TaskClassDAO, cacheRepo *ro
}
}
// AddOrUpdateTaskClass 为指定用户添加任务类
func (sv *TaskClassService) AddOrUpdateTaskClass(ctx context.Context, req *model.UserAddTaskClassRequest, userID int, method int, targetTaskClassID int) error {
// AddOrUpdateTaskClass 为指定用户新增或更新任务类,并返回最终 task_class_id。
//
// 职责边界:
// 1. 继续复用旧 service/dao 的校验和事务写入语义,不在 RPC 迁移期重写业务规则;
// 2. 只额外把事务内拿到的稳定主键返回给调用方,供 agent 工具结果回填;
// 3. 失败时返回 0 + error调用方不得猜测 ID。
func (sv *TaskClassService) AddOrUpdateTaskClass(ctx context.Context, req *model.UserAddTaskClassRequest, userID int, method int, targetTaskClassID int) (int, error) {
//1.先校验参数
if req.Mode == "auto" {
if req.StartDate == "" || req.EndDate == "" {
return respond.MissingParamForAutoScheduling
return 0, respond.MissingParamForAutoScheduling
}
st, err := time.Parse("2006-01-02", req.StartDate)
if err != nil {
return respond.WrongParamType
return 0, respond.WrongParamType
}
ed, err := time.Parse("2006-01-02", req.EndDate)
if err != nil {
return respond.WrongParamType
return 0, respond.WrongParamType
}
if st.After(ed) {
return respond.InvalidDateRange
return 0, respond.InvalidDateRange
}
}
if req.Mode == "" || req.Name == "" || len(req.Items) == 0 {
return respond.MissingParam
return 0, respond.MissingParam
}
// 1. excluded_slots 属于“半天块索引”,每个索引映射 2 节1->1-2...6->11-12
// 2. 若允许 7~12会在粗排网格展开时产生越界节次触发运行时 panic
// 3. 这里统一在写入入口拦截,避免脏数据落库后污染后续排程链路。
for _, slot := range req.Config.ExcludedSlots {
if slot < 1 || slot > 6 {
return respond.WrongParamType
return 0, respond.WrongParamType
}
}
// 1. excluded_days_of_week 表示“整天不可排”的硬约束,粗排时会直接整天屏蔽;
@@ -69,10 +75,11 @@ func (sv *TaskClassService) AddOrUpdateTaskClass(ctx context.Context, req *model
// 3. 若写入非法值,会导致粗排过滤口径和前端展示口径不一致,因此入口直接拦截。
for _, dayOfWeek := range req.Config.ExcludedDaysOfWeek {
if dayOfWeek < 1 || dayOfWeek > 7 {
return respond.WrongParamType
return 0, respond.WrongParamType
}
}
//2.写数据库(事务内)
taskClassID := 0
if err := sv.taskClassRepo.Transaction(func(txDAO *taskclassdao.TaskClassDAO) error {
taskClass, items, err := conv.ProcessUserAddTaskClassRequest(req, userID)
if err != nil {
@@ -82,10 +89,11 @@ func (sv *TaskClassService) AddOrUpdateTaskClass(ctx context.Context, req *model
taskClass.ID = targetTaskClassID
}
taskClassID, err := txDAO.AddOrUpdateTaskClass(userID, taskClass)
updatedID, err := txDAO.AddOrUpdateTaskClass(userID, taskClass)
if err != nil {
return err
}
taskClassID = updatedID
for i := range items {
items[i].CategoryID = &taskClassID
@@ -95,10 +103,130 @@ func (sv *TaskClassService) AddOrUpdateTaskClass(ctx context.Context, req *model
}
return nil
}); err != nil {
return err
return 0, err
}
return nil
return taskClassID, nil
}
// GetAgentTaskClasses 为 agent provider 读取完整任务类事实。
//
// 职责边界:
// 1. 只读取 task_classes/task_items 并转换为跨进程契约;
// 2. TaskClassIDs 为空时读取用户全部任务类,非空时按用户与 ID 双重过滤;
// 3. 不复用前端详情 DTO避免丢失 item.status 等编排状态。
func (sv *TaskClassService) GetAgentTaskClasses(ctx context.Context, req taskclasscontracts.AgentTaskClassesRequest) (taskclasscontracts.AgentTaskClassesResponse, error) {
if sv == nil || sv.taskClassRepo == nil {
return taskclasscontracts.AgentTaskClassesResponse{}, errors.New("task-class service 未初始化")
}
ids := append([]int(nil), req.TaskClassIDs...)
if len(ids) == 0 {
basicClasses, err := sv.taskClassRepo.GetUserTaskClasses(req.UserID)
if err != nil {
return taskclasscontracts.AgentTaskClassesResponse{}, err
}
ids = make([]int, 0, len(basicClasses))
for _, taskClass := range basicClasses {
if taskClass.ID > 0 {
ids = append(ids, taskClass.ID)
}
}
}
if len(ids) == 0 {
return taskclasscontracts.AgentTaskClassesResponse{TaskClasses: []taskclasscontracts.AgentTaskClass{}}, nil
}
taskClasses, err := sv.taskClassRepo.GetCompleteTaskClassesByIDs(ctx, req.UserID, ids)
if err != nil {
return taskclasscontracts.AgentTaskClassesResponse{}, err
}
return taskClassesToAgentContract(taskClasses), nil
}
func taskClassesToAgentContract(taskClasses []model.TaskClass) taskclasscontracts.AgentTaskClassesResponse {
out := make([]taskclasscontracts.AgentTaskClass, 0, len(taskClasses))
for _, taskClass := range taskClasses {
userID := 0
if taskClass.UserID != nil {
userID = *taskClass.UserID
}
items := make([]taskclasscontracts.AgentTaskClassItem, 0, len(taskClass.Items))
for _, item := range taskClass.Items {
items = append(items, taskclasscontracts.AgentTaskClassItem{
ID: item.ID,
CategoryID: cloneIntPtr(item.CategoryID),
Order: cloneIntPtr(item.Order),
Content: derefString(item.Content),
EmbeddedTime: toTaskClassContractTargetTime(item.EmbeddedTime),
Status: cloneIntPtr(item.Status),
})
}
out = append(out, taskclasscontracts.AgentTaskClass{
ID: taskClass.ID,
UserID: userID,
Name: derefString(taskClass.Name),
Mode: derefString(taskClass.Mode),
StartDate: formatDatePtr(taskClass.StartDate),
EndDate: formatDatePtr(taskClass.EndDate),
SubjectType: derefString(taskClass.SubjectType),
DifficultyLevel: derefString(taskClass.DifficultyLevel),
CognitiveIntensity: derefString(taskClass.CognitiveIntensity),
TotalSlots: derefInt(taskClass.TotalSlots),
AllowFillerCourse: derefBoolDefault(taskClass.AllowFillerCourse, false),
Strategy: derefString(taskClass.Strategy),
ExcludedSlots: append([]int(nil), []int(taskClass.ExcludedSlots)...),
ExcludedDaysOfWeek: append([]int(nil), []int(taskClass.ExcludedDaysOfWeek)...),
Items: items,
})
}
return taskclasscontracts.AgentTaskClassesResponse{TaskClasses: out}
}
func toTaskClassContractTargetTime(value *model.TargetTime) *taskclasscontracts.TargetTime {
if value == nil {
return nil
}
return &taskclasscontracts.TargetTime{
Week: value.Week,
DayOfWeek: value.DayOfWeek,
SectionFrom: value.SectionFrom,
SectionTo: value.SectionTo,
}
}
func cloneIntPtr(value *int) *int {
if value == nil {
return nil
}
copied := *value
return &copied
}
func derefString(value *string) string {
if value == nil {
return ""
}
return *value
}
func derefInt(value *int) int {
if value == nil {
return 0
}
return *value
}
func derefBoolDefault(value *bool, fallback bool) bool {
if value == nil {
return fallback
}
return *value
}
func formatDatePtr(value *time.Time) string {
if value == nil {
return ""
}
return value.Format("2006-01-02")
}
func (sv *TaskClassService) GetUserTaskClassInfos(ctx context.Context, userID int) (*model.UserGetTaskClassesResponse, error) {