实现对话系统的完整交互日志记录
·
一、背景
在我们的智能问答系统初期版本中,日志记录仅能捕获AI的回答内容。随着需求发展,我们需要: 1. 完整记录用户提问和AI回答的全流程对话 2. 明确区分消息来源(用户/AI) 3. 支持结构化存储和分析
二、技术实现路径
第一阶段:基础日志记录
初始版本只能记录AI的流式响应:

日志文件数据结构定义:
package com.dpsk.dpsk_quiz_sys_java.pojo.dto;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class EventRecord {
private final long timestamp;
private final String eventId;
private final String eventType;
private final String rawData;
private final String content;
public EventRecord(long timestamp, String eventId, String eventType, String rawData, String content) {
this.timestamp = timestamp;
this.eventId = eventId;
this.eventType = eventType;
this.rawData = rawData;
this.content = content;
}
// Getters
public long getTimestamp() {
return timestamp;
}
public String getEventId() {
return eventId;
}
public String getEventType() {
return eventType;
}
public String getRawData() {
return rawData;
}
public String getContent() {
return content;
}
// 格式化为可读字符串
public String toLogString() {
LocalDateTime time = LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
);
return String.format("[%s] ID: %s, Type: %s, Content: %s",
time, eventId, eventType, content);
}
// 可选:添加 Jackson 注解如果需要序列化
@Override
public String toString() {
return "EventRecord{" +
"timestamp=" + timestamp +
", eventId='" + eventId + '\'' +
", eventType='" + eventType + '\'' +
", content='" + content + '\'' +
'}';
}
}
主要方法:
// 记录完整交互日志
private void logCompleteInteraction(List<EventRecord> records) {
StringBuilder logBuilder = new StringBuilder("\n===== Deepseek 完整交互记录 =====\n");
logBuilder.append(String.format("共收到 %d 个事件:\n", records.size()));
// for (EventRecord record : records) {
// logBuilder.append(record.toString()).append("\n");
// }
// 提取并拼接所有内容
String fullResponse = records.stream()
.map(EventRecord::getContent)
.collect(Collectors.joining());
logBuilder.append("\n完整响应内容:\n").append(fullResponse);
logBuilder.append("\n===== 交互结束 =====\n");
logger.info(logBuilder.toString());
}
}
缺陷:
-
丢失用户提问上下文
-
无法关联问答对
-
流式响应被分散记录
第二阶段:结构化设计
1.数据模型定义:
package com.dpsk.dpsk_quiz_sys_java.pojo.dto;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class ResponseRecord {
private final String id;
private final int type; // 0-AI回答 / 1-用户提问
private final long timestamp;
private final String content;
private final String rawData; // 原始数据
// 类型常量
public static final int TYPE_AI_RESPONSE = 0;
public static final int TYPE_USER_QUERY = 1;
public ResponseRecord(String id, int type, long timestamp, String content, String rawData) {
this.id = id;
this.type = type;
this.timestamp = timestamp;
this.content = content;
this.rawData = rawData;
}
// Getters
public String getId() { return id; }
public int getType() { return type; }
public long getTimestamp() { return timestamp; }
public String getContent() { return content; }
public String getRawData() { return rawData; }
public String getFormattedTime() {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault())
.toString();
}
@Override
public String toString() {
return String.format("[%s] %s | ID: %s\nContent: %s\nRaw: %s",
getFormattedTime(),
type == TYPE_AI_RESPONSE ? "AI Response" : "User Query",
id,
content,
rawData);
}
}
2.后端改造:
List<Map<String, String>> inputMessages = JsonUtils.parseJsonList(messages);
List<ResponseRecord> userQueries = inputMessages.stream()
.filter(msg -> "user".equals(msg.get("role")))
.map(msg -> new ResponseRecord(
UUID.randomUUID().toString(),
ResponseRecord.TYPE_USER_QUERY, // 1
System.currentTimeMillis(),
(String) msg.get("content"),
JsonUtils.convertObj2Json(msg) // 原始数据
))
.collect(Collectors.toList());
logUserQueries(userQueries);
接受前端返回的message用于记录用户提问。
@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
if (DONE.equals(data)) {
return;
}
String content = getContent(data);
// 记录AI响应(type=0)
responseRecords.add(new ResponseRecord(
id,
ResponseRecord.TYPE_AI_RESPONSE, // 0
System.currentTimeMillis(),
content,
data
));
pw.write("data:" + JsonUtils.convertObj2Json(new ContentDto(content)) + "\n\n");
pw.flush();
}
在后端响应的同时,记录ai响应的内容,到日志文件中。
3.日志记录方法:
//日志记录方法
private void logUserQueries(List<ResponseRecord> userQueries) {
if (userQueries.isEmpty()) return;
StringBuilder log = new StringBuilder("\n===== 用户提问记录 =====\n");
userQueries.forEach(query ->
log.append(query.toString()).append("\n\n"));
log.append("共收到 ").append(userQueries.size()).append(" 条用户提问");
logger.info(log.toString());
}
private void logCompleteInteraction(List<ResponseRecord> aiResponses) {
StringBuilder log = new StringBuilder("\n===== AI响应记录 =====\n");
// aiResponses.forEach(response ->
// log.append(response.toString()).append("\n\n"));
// 统计信息
String fullContent = aiResponses.stream()
.map(ResponseRecord::getContent)
.collect(Collectors.joining());
log.append("完整响应内容:\n").append(fullContent)
.append("\n共生成 ").append(aiResponses.size()).append(" 条响应片段");
logger.info(log.toString());
}
}
实现分层日志记录。
输出效果:

三、主要收获:
-
类型显式声明优于隐式判断
-
前端标记+后端验证的双重保障
-
结构化日志显著提升可观测性
-
上下文保存使调试更高效
四、延伸思考
未来可扩展方向:
-
将会话ID贯穿全链路
-
增加情感分析标记
-
实现基于类型的响应策略
-
持久化存储到数据库(关键目标)
更多推荐


所有评论(0)