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
Expand Up @@ -22,6 +22,14 @@
'provider': ('生成服务未能完成当前操作。', '请核对原任务;错误码和请求标识可用于向供应商排查。'),
}

# MiniMax content moderation: 1026 rejects the input, 1027 the generated output.
MINIMAX_SENSITIVE = {
'1026': ('供应商判定输入内容涉及敏感信息,已拒绝生成。',
'请修改提示词或更换参考素材后再生成;相同输入重试仍会被拒绝。'),
'1027': ('供应商判定生成内容涉及敏感信息,已拒绝交付。',
'请调整提示词或参考素材,避免可能触发审核的角色或内容后再生成;相同输入重试仍会被拒绝。'),
}


def _identifier(value, limit, secrets):
if isinstance(value, int) and not isinstance(value, bool):
Expand Down Expand Up @@ -80,6 +88,10 @@ def provider_failure(value, *, stage, headers=None, http_status=None, secrets=()
suggestion = '请核对参考类型和生成模式,或选择其他已适配模型;当前输入和已有结果保留。'
if name == 'inputimagesensitivecontentdetected.privacyinformation':
message = '供应商判定参考图片可能包含真人,已拒绝生成。'
elif code in MINIMAX_SENSITIVE:
# A moderation verdict is deterministic: the same input fails again, so
# the suggestion must lead to changing it rather than retrying.
category, (message, suggestion) = 'input', MINIMAX_SENSITIVE[code]
elif category == 'provider' and value.get('status') in {'cancelled', 'expired'}:
message = '供应商任务已取消。' if value['status'] == 'cancelled' else '供应商任务已过期。'
elif http_status == 404:
Expand Down
4 changes: 4 additions & 0 deletions src/canvas/generationFailureText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const BY_MESSAGE: Record<string, MessageKey> = {
"errors.generationFailure.referenceUnsupported",
"供应商判定参考图片可能包含真人,已拒绝生成。":
"errors.generationFailure.privacy",
"供应商判定输入内容涉及敏感信息,已拒绝生成。":
"errors.generationFailure.sensitiveInput",
"供应商判定生成内容涉及敏感信息,已拒绝交付。":
"errors.generationFailure.sensitiveOutput",
"供应商任务已取消。": "errors.generationFailure.cancelled",
"供应商任务已过期。": "errors.generationFailure.expired",
"供应商未找到该任务。": "errors.generationFailure.notFoundTask",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/messages/en-US/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,10 @@ export const errors = {
"The provider rejected the reference type or input combination.",
"errors.generationFailure.privacy":
"The provider rejected the reference image because it may contain a real person.",
"errors.generationFailure.sensitiveInput":
"The provider flagged the input as sensitive content and declined to generate. Change the prompt or reference media before generating again.",
"errors.generationFailure.sensitiveOutput":
"The provider flagged the generated content as sensitive and withheld it. Adjust the prompt or reference media before generating again.",
"errors.history.failed":
"Generation failed. Check the service log.",
"errors.connection.endpointMissing": "The node no longer exists",
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/messages/zh-CN/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,8 @@ export const errors = {
"errors.generationFailure.referenceAudio": "音频参考不能单独使用,请搭配参考图片。",
"errors.generationFailure.referenceUnsupported": "供应商拒绝了参考类型或输入组合。",
"errors.generationFailure.privacy": "供应商判定参考图片可能包含真人,已拒绝生成。",
"errors.generationFailure.sensitiveInput": "供应商判定输入内容涉及敏感信息,已拒绝生成。请修改提示词或参考素材后再生成。",
"errors.generationFailure.sensitiveOutput": "供应商判定生成内容涉及敏感信息,已拒绝交付。请调整提示词或参考素材后再生成。",
"errors.history.failed": "生成失败,请检查服务日志。",
"errors.connection.endpointMissing": "节点已不存在",
"errors.connection.sourceEmpty": "当前节点内容为空,请先上传或生成节点内容",
Expand Down
19 changes: 19 additions & 0 deletions tests/generationFailureText.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,22 @@ test("submission unknown failures remain explicit", () => {
);
setActiveLocale("zh-CN");
});

test("provider moderation verdicts are explained in both languages", () => {
const moderated = (message: string) =>
failure({ stage: "poll", httpStatus: 200, providerStatus: "failed", providerCode: "1027", message });
setActiveLocale("en-US");
assert.match(
generationFailureMessage(moderated("供应商判定输入内容涉及敏感信息,已拒绝生成。")),
/flagged the input as sensitive/,
);
assert.match(
generationFailureMessage(moderated("供应商判定生成内容涉及敏感信息,已拒绝交付。")),
/flagged the generated content as sensitive/,
);
setActiveLocale("zh-CN");
assert.match(
generationFailureMessage(moderated("供应商判定生成内容涉及敏感信息,已拒绝交付。")),
/生成内容涉及敏感信息/,
);
});
24 changes: 24 additions & 0 deletions tests/test_minimax_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,27 @@ def test_official_variants_enforce_native_outputs(model, resolutions, minimum):
adapter.payload(configured, {'parameters': {'resolution': invalid}}, prepared)
with pytest.raises(ProjectError, match='输出规格无效'):
adapter.payload(configured, {'parameters': {'duration': minimum - 1}}, prepared)


@pytest.mark.anyio
@pytest.mark.parametrize(('code', 'message'), [(1026, '输入内容涉及敏感信息'), (1027, '生成内容涉及敏感信息')])
async def test_moderation_failures_say_the_content_was_rejected_instead_of_a_generic_provider_error(code, message):
def handle(request):
if request.method == 'POST':
return httpx.Response(200, json={'task_id': 'task-1'})
return httpx.Response(200, json={'task': {'id': 'task-1', 'status': 'failed',
'error': {'code': code, 'message': 'private moderation detail'}}})
accepted = []
async def submitted(identity):
accepted.append(identity)
adapter = MinimaxVideoGeneration(None, httpx.MockTransport(handle), poll_interval=0)
adapter.prepare_inputs = lambda *args: {'content': [{'type': 'text', 'text': 'test'}]}
with pytest.raises(GenerationError) as failure:
await adapter.generate(config(), task('project'), submitted)
details = failure.value.details
assert accepted == ['task-1']
assert details.category == 'input' and details.stage == 'poll' and details.providerStatus == 'failed'
assert details.providerCode == str(code) and message in details.message
# Moderation is deterministic: the suggestion must not invite an unchanged retry.
assert '相同输入重试仍会被拒绝' in details.suggestion
assert 'private moderation detail' not in str(failure.value)
Loading