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.config.PluginConfig
import top.jie65535.mirai.data.ChatHistoryStore import top.jie65535.mirai.data.ChatHistoryStore
import top.jie65535.mirai.llm.LargeLanguageModels 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 top.jie65535.mirai.util.RetryBackoff
import java.io.File import java.io.File
import java.security.MessageDigest import java.security.MessageDigest
@@ -334,7 +336,8 @@ object UserProfileAnalysisService {
break break
} }
val report = analyzeConversationBatch( val report = try {
analyzeConversationBatch(
batch = batch, batch = batch,
minAuthoredTextChars = PluginConfig.profileAutoMinAuthoredTextChars, minAuthoredTextChars = PluginConfig.profileAutoMinAuthoredTextChars,
model = model, model = model,
@@ -343,6 +346,13 @@ object UserProfileAnalysisService {
onRetryFailure = { message, cause -> JChatGPT.logger.warning(message, cause) }, onRetryFailure = { message, cause -> JChatGPT.logger.warning(message, cause) },
onCommittedOperations = ProfileOperationLogger::log, onCommittedOperations = ProfileOperationLogger::log,
) )
} catch (cause: ModelSafetyRejectionException) {
JChatGPT.logger.warning(
"$groupId 会话画像 [${batch.startTime}, ${batch.endTime}) 被模型安全策略拒绝," +
"已跳过该批并继续后续历史 code=${cause.errorCode ?: "unknown"}"
)
null
}
cursor = cursor.copy( cursor = cursor.copy(
cursorTime = batch.endTime, cursorTime = batch.endTime,
updatedAt = System.currentTimeMillis(), updatedAt = System.currentTimeMillis(),
@@ -545,6 +555,14 @@ object UserProfileAnalysisService {
return result to reductions return result to reductions
} catch (cause: Exception) { } catch (cause: Exception) {
if (cause is CancellationException) throw cause if (cause is CancellationException) throw cause
if (cause is ModelRequestRejectedException) {
onRetryFailure(
"${batch.groupId} 会话画像 [${batch.startTime}, ${batch.endTime}) " +
"被模型拒绝,已停止重试",
cause,
)
throw cause
}
lastFailure = cause lastFailure = cause
handleRetryFailure( handleRetryFailure(
attempt = attempt, attempt = attempt,
@@ -591,6 +609,14 @@ object UserProfileAnalysisService {
return result to reduction return result to reduction
} catch (cause: Exception) { } catch (cause: Exception) {
if (cause is CancellationException) throw cause if (cause is CancellationException) throw cause
if (cause is ModelRequestRejectedException) {
JChatGPT.logger.warning(
"用户 ${batch.userId} 画像批次 [${batch.startTime}, ${batch.endTime}) " +
"被模型拒绝,已停止重试",
cause,
)
throw cause
}
lastFailure = cause lastFailure = cause
handleRetryFailure( handleRetryFailure(
attempt = attempt, attempt = attempt,
@@ -631,6 +657,13 @@ object UserProfileAnalysisService {
return result to plan return result to plan
} catch (cause: Exception) { } catch (cause: Exception) {
if (cause is CancellationException) throw cause if (cause is CancellationException) throw cause
if (cause is ModelRequestRejectedException) {
JChatGPT.logger.warning(
"用户 ${profile.userId} 画像压缩被模型拒绝,已停止重试",
cause,
)
throw cause
}
lastFailure = cause lastFailure = cause
handleRetryFailure( handleRetryFailure(
attempt = attempt, attempt = attempt,
@@ -7,6 +7,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.message.data.MessageSourceKind
import top.jie65535.mirai.data.ChatMessageRecord import top.jie65535.mirai.data.ChatMessageRecord
import top.jie65535.mirai.llm.ModelSafetyRejectionException
import java.io.IOException import java.io.IOException
import java.nio.file.Files import java.nio.file.Files
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@@ -122,6 +123,25 @@ class UserProfileAnalysisServiceTest {
assertFalse(UserProfileStore.isConversationProcessed(INPUT_HASH)) 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 @Test
fun skipsConversationAlreadyProcessedByAnEarlierCall() = withProfileStore { fun skipsConversationAlreadyProcessedByAnEarlierCall() = withProfileStore {
val model = FakeConversationProfileModel { successfulResult() } val model = FakeConversationProfileModel { successfulResult() }