profile: handle rejected model responses

This commit is contained in:
2026-08-05 00:10:17 +08:00
parent a53a16ea37
commit cef405616f
2 changed files with 62 additions and 9 deletions
@@ -8,6 +8,8 @@ import top.jie65535.mirai.JChatGPT
import top.jie65535.mirai.config.PluginConfig
import top.jie65535.mirai.data.ChatHistoryStore
import top.jie65535.mirai.llm.LargeLanguageModels
import top.jie65535.mirai.llm.ModelRequestRejectedException
import top.jie65535.mirai.llm.ModelSafetyRejectionException
import top.jie65535.mirai.util.RetryBackoff
import java.io.File
import java.security.MessageDigest
@@ -334,15 +336,23 @@ object UserProfileAnalysisService {
break
}
val report = analyzeConversationBatch(
batch = batch,
minAuthoredTextChars = PluginConfig.profileAutoMinAuthoredTextChars,
model = model,
retryMax = PluginConfig.profileRetryMax,
summaryMaxLength = PluginConfig.profileSummaryMaxLength,
onRetryFailure = { message, cause -> JChatGPT.logger.warning(message, cause) },
onCommittedOperations = ProfileOperationLogger::log,
)
val report = try {
analyzeConversationBatch(
batch = batch,
minAuthoredTextChars = PluginConfig.profileAutoMinAuthoredTextChars,
model = model,
retryMax = PluginConfig.profileRetryMax,
summaryMaxLength = PluginConfig.profileSummaryMaxLength,
onRetryFailure = { message, cause -> JChatGPT.logger.warning(message, cause) },
onCommittedOperations = ProfileOperationLogger::log,
)
} catch (cause: ModelSafetyRejectionException) {
JChatGPT.logger.warning(
"$groupId 会话画像 [${batch.startTime}, ${batch.endTime}) 被模型安全策略拒绝," +
"已跳过该批并继续后续历史 code=${cause.errorCode ?: "unknown"}"
)
null
}
cursor = cursor.copy(
cursorTime = batch.endTime,
updatedAt = System.currentTimeMillis(),
@@ -545,6 +555,14 @@ object UserProfileAnalysisService {
return result to reductions
} catch (cause: Exception) {
if (cause is CancellationException) throw cause
if (cause is ModelRequestRejectedException) {
onRetryFailure(
"${batch.groupId} 会话画像 [${batch.startTime}, ${batch.endTime}) " +
"被模型拒绝,已停止重试",
cause,
)
throw cause
}
lastFailure = cause
handleRetryFailure(
attempt = attempt,
@@ -591,6 +609,14 @@ object UserProfileAnalysisService {
return result to reduction
} catch (cause: Exception) {
if (cause is CancellationException) throw cause
if (cause is ModelRequestRejectedException) {
JChatGPT.logger.warning(
"用户 ${batch.userId} 画像批次 [${batch.startTime}, ${batch.endTime}) " +
"被模型拒绝,已停止重试",
cause,
)
throw cause
}
lastFailure = cause
handleRetryFailure(
attempt = attempt,
@@ -631,6 +657,13 @@ object UserProfileAnalysisService {
return result to plan
} catch (cause: Exception) {
if (cause is CancellationException) throw cause
if (cause is ModelRequestRejectedException) {
JChatGPT.logger.warning(
"用户 ${profile.userId} 画像压缩被模型拒绝,已停止重试",
cause,
)
throw cause
}
lastFailure = cause
handleRetryFailure(
attempt = attempt,
@@ -7,6 +7,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import net.mamoe.mirai.message.data.MessageSourceKind
import top.jie65535.mirai.data.ChatMessageRecord
import top.jie65535.mirai.llm.ModelSafetyRejectionException
import java.io.IOException
import java.nio.file.Files
import java.util.concurrent.atomic.AtomicInteger
@@ -122,6 +123,25 @@ class UserProfileAnalysisServiceTest {
assertFalse(UserProfileStore.isConversationProcessed(INPUT_HASH))
}
@Test
fun safetyRejectionDoesNotRetryOrCommit() = withProfileStore {
val model = FakeConversationProfileModel {
throw ModelSafetyRejectionException(
errorType = "invalid_request_error",
errorCode = "cyber_policy",
message = "blocked by policy",
)
}
assertFailsWith<ModelSafetyRejectionException> {
analyze(batch(), model, retryMax = 3)
}
assertEquals(1, model.calls)
assertNull(UserProfileStore.load(USER_A))
assertFalse(UserProfileStore.isConversationProcessed(INPUT_HASH))
}
@Test
fun skipsConversationAlreadyProcessedByAnEarlierCall() = withProfileStore {
val model = FakeConversationProfileModel { successfulResult() }