Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.achobeta.domain.rag.service.impl;

import com.achobeta.domain.rag.service.IVectorService;
import com.achobeta.types.event.UserMistakeEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

/**
* 用户错误行为事件监听器
* 负责将用户的错误行为异步写入向量库,用于后续的学习动态追踪
*/
@Slf4j
@Service
public class UserMistakeEventListener {

@Autowired
@Qualifier("weaviateVectorRepository")
private IVectorService vectorService;

/**
* 监听用户错误行为事件,异步处理
*/
@Async
@EventListener
public void handleUserMistakeEvent(UserMistakeEvent event) {
try {
log.info("处理用户错误行为事件,userId: {}, mistakeType: {}, sessionId: {}",
event.getUserId(), event.getMistakeType().getCode(), event.getSessionId());

// 构建向量数据
Map<String, Object> vectorData = buildVectorData(event);

// 生成唯一的向量ID
String vectorId = generateVectorId(event);

// 异步写入向量库,使用统一的行为类型
boolean success = vectorService.storeLearningVector(
event.getUserId(),
vectorId, // 使用vectorId作为questionId
event.getQuestionContent() != null ? event.getQuestionContent() : "AI求助",
"qa", // 统一使用qa作为行为类型
event.getSubject(),
null // knowledgePointId暂时为null
);

if (success) {
log.info("用户错误行为已成功写入向量库,userId: {}, vectorId: {}", event.getUserId(), vectorId);
} else {
log.warn("向量库写入失败,userId: {}, vectorId: {}", event.getUserId(), vectorId);
}

log.info("用户错误行为已写入向量库,userId: {}, vectorId: {}", event.getUserId(), vectorId);

} catch (Exception e) {
log.error("处理用户错误行为事件失败,userId: {}, sessionId: {}",
event.getUserId(), event.getSessionId(), e);
}
}

/**
* 构建向量数据
*/
private Map<String, Object> buildVectorData(UserMistakeEvent event) {
Map<String, Object> data = new HashMap<>();

// 基本信息
data.put("userId", event.getUserId());
data.put("mistakeType", event.getMistakeType().getCode());
data.put("mistakeDescription", event.getMistakeType().getDescription());
data.put("sessionId", event.getSessionId());

// 时间信息
data.put("eventTime", event.getEventTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
data.put("timestamp", System.currentTimeMillis());

// 学习内容信息
if (event.getQuestionContent() != null) {
data.put("questionContent", event.getQuestionContent());
data.put("questionLength", event.getQuestionContent().length());
}

if (event.getSubject() != null) {
data.put("subject", event.getSubject());
}

if (event.getKnowledgePoint() != null) {
data.put("knowledgePoint", event.getKnowledgePoint());
}

// 错误详情
if (event.getMistakeDescription() != null) {
data.put("detailedDescription", event.getMistakeDescription());
}

// 上下文信息
if (event.getContextInfo() != null) {
data.put("contextInfo", event.getContextInfo());
}

// 行为分类
data.put("behaviorCategory", "mistake");
data.put("learningPhase", determineLearningPhase(event));

return data;
}

/**
* 生成向量ID
*/
private String generateVectorId(UserMistakeEvent event) {
return String.format("mistake_%s_%s_%d",
event.getUserId(),
event.getSessionId(),
System.currentTimeMillis());
}

/**
* 判断学习阶段
*/
private String determineLearningPhase(UserMistakeEvent event) {
switch (event.getMistakeType()) {
case AI_HELP_REQUEST:
return "seeking_help";
case CONCEPT_MISUNDERSTANDING:
return "concept_learning";
case CALCULATION_ERROR:
return "practice";
case METHOD_ERROR:
return "problem_solving";
default:
return "unknown";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@

import com.achobeta.api.dto.AiSolveRequestDTO;
import com.achobeta.domain.ai.service.IAiService;
import com.achobeta.types.annotation.GlobalInterception;
import com.achobeta.types.common.UserContext;
import com.achobeta.types.event.UserMistakeEvent;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

/**
Expand All @@ -27,10 +33,18 @@
public class AiSolveController {

private final IAiService aiService;
private final ApplicationEventPublisher eventPublisher;

@GlobalInterception
@PostMapping("stream")
public SseEmitter stream(@Valid @RequestBody AiSolveRequestDTO requestDTO) {
String question = requestDTO.getQuestion();
String userId = UserContext.getUserId();
String sessionId = UUID.randomUUID().toString();

// 记录用户求助AI的行为
publishMistakeEvent(userId, question, sessionId);

// 设置超时时间为5分钟
SseEmitter emitter = new SseEmitter(300000L);

Expand Down Expand Up @@ -121,5 +135,28 @@ public SseEmitter stream(@Valid @RequestBody AiSolveRequestDTO requestDTO) {
return emitter;
}

/**
* 发布用户错误行为事件
*/
private void publishMistakeEvent(String userId, String question, String sessionId) {
try {
UserMistakeEvent mistakeEvent = UserMistakeEvent.builder()
.userId(userId)
.mistakeType(UserMistakeEvent.MistakeType.AI_HELP_REQUEST)
.questionContent(question)
.mistakeDescription("用户向AI求助解题,表明遇到了不会的问题")
.subject("AI答疑") // 设置科目
.eventTime(LocalDateTime.now())
.sessionId(sessionId)
.contextInfo("AI答疑接口调用")
.build();

log.info("发布用户错误行为事件,userId: {}, sessionId: {}", userId, sessionId);
eventPublisher.publishEvent(mistakeEvent);

} catch (Exception e) {
log.error("发布用户错误行为事件失败,userId: {}", userId, e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.achobeta.types.event;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
* 用户错误行为事件
* 用于追踪用户在学习过程中遇到的问题和错误
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserMistakeEvent {

/**
* 用户ID
*/
private String userId;

/**
* 错误类型
*/
private MistakeType mistakeType;

/**
* 问题内容
*/
private String questionContent;

/**
* 错误描述
*/
private String mistakeDescription;

/**
* 相关科目
*/
private String subject;

/**
* 知识点
*/
private String knowledgePoint;

/**
* 事件发生时间
*/
private LocalDateTime eventTime;

/**
* 会话ID(用于关联同一次学习会话)
*/
private String sessionId;

/**
* 额外的上下文信息
*/
private String contextInfo;

/**
* 错误类型枚举
*/
public enum MistakeType {
/**
* 求助AI - 表示用户遇到不会的题目需要AI帮助
*/
AI_HELP_REQUEST("ai_help", "AI求助"),

/**
* 概念理解错误
*/
CONCEPT_MISUNDERSTANDING("concept_error", "概念理解错误"),

/**
* 计算错误
*/
CALCULATION_ERROR("calc_error", "计算错误"),

/**
* 方法选择错误
*/
METHOD_ERROR("method_error", "方法选择错误"),

/**
* 其他错误
*/
OTHER("other", "其他错误");

private final String code;
private final String description;

MistakeType(String code, String description) {
this.code = code;
this.description = description;
}

public String getCode() {
return code;
}

public String getDescription() {
return description;
}
}
}
Loading