处理流式拒绝响应
AI translation, not an official translation. Refer to the original for technical details.
On this page
从 Claude 4 模型开始,当流式分类器介入处理潜在的政策违规时,Claude API 的流式响应会返回 stop_reason: "refusal"。此安全功能有助于在实时流式传输过程中保持内容合规性。
API 响应格式
当流式分类器检测到违反 Anthropic 政策的内容时,API 将返回以下响应:
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "Hello.."
}
],
"stop_reason": "refusal",
"stop_details": {
"type": "refusal",
"category": "cyber",
"explanation": "This request was declined because it could enable cyber harm."
}
}
在事件流中,stop_details 会与 stop_reason 一同出现在 message_delta 事件中。
发生拒绝时,stop_details 对象始终存在,但其 category 和 explanation 字段可能为 null,例如当拒绝响应未映射到任何命名类别时。请基于 stop_reason 或 stop_details.type 进行分支判断,而不要假设 category 和 explanation 一定有值,并在它们为 null 时提供自定义的面向用户的提示信息。
拒绝后重置上下文
当您收到 stop_reason: refusal 时,必须在继续之前重置对话上下文。您可以删除或重新表述触发拒绝的轮次,也可以完全清除对话历史。若不重置即尝试继续,将持续收到拒绝响应。
当拒绝发生在 Claude 生成任何输出之前时,您无需为该请求付费(Claude API 层面),响应中的使用量计数仅供参考。当 Claude 在拒绝发生之前已生成输出时,该请求将正常计费。
实现指南
以下是在您的应用程序中检测和处理流式拒绝的方法:
Check for refusal in the stream
if echo "$response" | grep -q '"stop_reason":"refusal"'; then echo "Response refused - resetting conversation context" # Reset your conversation state here fi
```python Python
client = anthropic.Anthropic()
messages = []
def reset_conversation():
"""Reset conversation context after refusal"""
global messages
messages = []
print("Conversation reset due to refusal")
try:
with client.messages.stream(
max_tokens=1024,
messages=messages + [{"role": "user", "content": "Hello"}],
model="claude-opus-5",
) as stream:
for event in stream:
# Check for refusal in message delta
if event.type == "message_delta":
if event.delta.stop_reason == "refusal":
reset_conversation()
break
except Exception as e:
print(f"Error: {e}")
const client = new Anthropic();
let messages: Anthropic.MessageParam[] = [];
function resetConversation() {
// Reset conversation context after refusal
messages = [];
console.log("Conversation reset due to refusal");
}
try {
const stream = await client.messages.stream({
messages: [...messages, { role: "user", content: "Hello" }],
model: "claude-opus-5",
max_tokens: 1024
});
for await (const event of stream) {
// Check for refusal in message delta
if (event.type === "message_delta" && event.delta.stop_reason === "refusal") {
resetConversation();
break;
}
}
} catch (error) {
console.error("Error:", error);
}
List<Message> messages = new();
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus5,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello" }]
};
try
{
await foreach (var streamEvent in client.Messages.CreateStreaming(parameters))
{
if (
streamEvent.TryPickDelta(out var deltaEvent)
&& deltaEvent.Delta.StopReason == StopReason.Refusal
)
{
ResetConversation();
break;
}
}
}
catch (Exception e)
{
Console.WriteLine($"Error: {e.Message}");
}
void ResetConversation()
{
messages.Clear();
Console.WriteLine("Conversation reset due to refusal");
}
var messages []anthropic.MessageParam
func resetConversation() {
messages = []anthropic.MessageParam{}
fmt.Println("Conversation reset due to refusal")
}
// ...
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello")),
},
})
streamLoop:
for stream.Next() {
event := stream.Current()
switch eventVariant := event.AsAny().(type) {
case anthropic.MessageDeltaEvent:
if eventVariant.Delta.StopReason == anthropic.StopReasonRefusal {
resetConversation()
break streamLoop
}
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
import com.anthropic.core.http.StreamResponse;
import com.anthropic.models.messages.RawMessageStreamEvent;
import com.anthropic.models.messages.StopReason;
// ...
List<MessageParam> messages = new ArrayList<>();
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5)
.maxTokens(1024L)
.addUserMessage("Hello")
.build();
try (StreamResponse<RawMessageStreamEvent> stream = client.messages().createStreaming(params)) {
stream.stream().forEach(event -> {
event.messageDelta().ifPresent(deltaEvent -> {
deltaEvent.delta().stopReason().ifPresent(stopReason -> {
if (stopReason.equals(StopReason.REFUSAL)) {
resetConversation();
}
});
});
});
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
void resetConversation() {
messages.clear();
IO.println("Conversation reset due to refusal");
}
$client = new Client();
$messages = [];
function resetConversation(&$messages) {
$messages = [];
echo "Conversation reset due to refusal\n";
}
try {
$stream = $client->messages->createStream(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Hello']
],
model: 'claude-opus-5',
);
foreach ($stream as $event) {
if ($event->type === 'message_delta' && $event->delta->stopReason === 'refusal') {
resetConversation($messages);
break;
}
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
client = Anthropic::Client.new
messages = []
def reset_conversation(messages)
messages.clear
puts "Conversation reset due to refusal"
end
begin
stream = client.messages.stream(
model: :"claude-opus-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }]
)
stream.each do |event|
if event.type == :message_delta && event.delta.stop_reason == :refusal
reset_conversation(messages)
break
end
end
rescue => e
puts "Error: #{e.message}"
end
当前拒绝类型
API 目前以三种不同方式处理拒绝:
| 拒绝类型 | 响应格式 | 触发时机 |
|---|---|---|
| 流式分类器拒绝 | stop_reason: refusal | 流式传输过程中内容违反政策时 |
| API 输入及版权校验 | 400 错误码 | 输入未通过校验检查时 |
| 模型生成的拒绝 | 标准文本响应 | 模型本身拒绝请求时 |
最佳实践
- 监控拒绝响应: 在错误处理逻辑中加入对
stop_reason:refusal的检查 - 自动重置: 检测到拒绝时自动实现上下文重置
- 回退至其他模型: 配置服务端回退或 SDK 中间件,使被拒绝的请求在另一个 Claude 模型上重试,而非将拒绝响应直接呈现给用户
- 手动重试时兑换回退积分: 如果您自行构建重试逻辑,请传入拒绝响应中的回退积分令牌,以避免提示缓存费用被计收两次
- 提供自定义消息: 在发生拒绝时创建友好的用户提示,以提升用户体验
- 追踪拒绝模式: 监控拒绝频率,以发现提示词中存在的潜在问题
迁移说明
如果您在此功能首次推出时就已构建了拒绝处理逻辑,或正在将其添加到现有集成中,请检查以下事项:
- 拒绝响应是正常响应,而非错误。 拒绝响应以 HTTP 200 成功响应的形式返回,携带
stop_reason:"refusal",因此仅基于错误率构建的监控无法捕捉到它。请将拒绝响应作为独立信号进行追踪。 - 拒绝响应包含结构化详情。 在所有模型上,拒绝响应还包含一个
stop_details对象,用于标识导致拒绝的政策类别。完整的响应结构请参阅拒绝响应与回退。 - 在不同模型上重试。 将被拒绝的请求重新发送至同一模型通常会再次触发拒绝。建议不要仅重置上下文,而是通过服务端回退、SDK 中间件或手动重试在回退模型上重试,并在自行构建重试时兑换回退积分。
- 检查批量结果中的拒绝响应。 消息批处理中被拒绝的请求会作为成功结果返回,携带
stop_reason:"refusal",而非作为错误结果返回。 - 将处理逻辑集中于
stop_reason。 API 持续将拒绝处理整合到stop_reason:"refusal"周围,因此请基于停止原因进行分支判断,而非依赖特定模型的行为。