Xử lý Streaming Refusals trong Claude API
Trust: ★★★☆☆ (0.90) · 0 validations · factual
Published: 2026-05-09 · Source: crawler_authoritative
Tình huống
Nhà phát triển sử dụng Claude API với streaming responses cần xử lý các trường hợp Vi phạm chính sách nội dung trong thời gian thực. Áp dụng cho các model Claude 4 trở lên (Claude Opus 4.7, Claude Sonnet 4.6, Claude Haiku 4.5).
Insight
Từ Claude 4 models trở đi, streaming responses từ Claude API trả về stop_reason: “refusal” khi streaming classifiers phát hiện Vi phạm chính sách. Khi nhận được refusal, response sẽ không chứa thông báo từ chối bổ sung - nhà phát triển phải tự xử lý và hiển thị thông báo phù hợp cho người dùng. API hiện có 3 loại từ chối: (1) Streaming classifier refusals - stop_reason: refusal trong quá trình streaming khi nội dung vi phạm chính sách, (2) API input và copyright validation - trả về mã lỗi 400 khi input không qua validation, (3) Model-generated refusals - model tự quyết định từ chối bằng text response tiêu chuẩn. Lưu ý quan trọng: người dùng vẫn bị tính phí cho output tokens cho đến thời điểm refusal (usage metrics vẫn được cung cấp cho mục đích billing). Nếu gặp refusal thường xuyên với Claude Sonnet 4.5 hoặc Opus 4.1, có thể thử chuyển sang Haiku 4.5 (claude-haiku-4-5-20251001) có các giới hạn sử dụng khác biệt. Các phiên bản API tương lai sẽ mở rộng pattern stop_reason: refusal để thống nhất cách xử lý tất cả các loại refusals.
Hành động
Khi nhận được stop_reason: ‘refusal’, PHẢI reset conversation context trước khi tiếp tục. Có thể remove hoặc rephrase turn đã trigger refusal, hoặc clear toàn bộ conversation history. Tiếp tục mà không reset sẽ dẫn đến refusals liên tục. Cách detect refusal trong code: theo dõi event.type = 'message_delta' và kiểm tra event.delta.stop_reason = ‘refusal’ (Python/TypeScript/C#/Go/Java/PHP/Ruby). Bash: grep ‘“stop_reason”:“refusal”‘. Các best practices bao gồm: (1) Monitor refusals - thêm stop_reason: refusal checks vào error handling, (2) Reset automatically - tự động reset context khi phát hiện refusal, (3) Provide custom messaging - tạo thông báo thân thiện cho user khi xảy ra refusal, (4) Track refusal patterns - theo dõi tần suất refusals để identify vấn đề với prompts.
Điều kiện áp dụng
Chỉ áp dụng cho Claude 4 models trở lên sử dụng streaming API. Claude Sonnet 4.5 và Opus 4.1 có thể gặp refusals thường xuyên hơn - có thể cân nhắc dùng Haiku 4.5 thay thế. Không áp dụng cho các nền tảng thương mại điện tử Việt Nam như Shopee, TikTok Shop, Lazada.
Nội dung gốc (Original)
Streaming refusals
Starting with Claude 4 models, streaming responses from Claude’s API return stop_reason: "refusal" when streaming classifiers intervene to handle potential policy violations. This new safety feature helps maintain content compliance during real-time streaming.
API response format
When streaming classifiers detect content that violates Anthropic’s policies, the API returns this response:
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "Hello.."
}
],
"stop_reason": "refusal"
}Reset context after refusal
When you receive stop_reason: refusal, you must reset the conversation context before continuing. You can remove or rephrase the turn that triggered the refusal, or clear the conversation history entirely. Attempting to continue without resetting will result in continued refusals.
You will be billed for output tokens up until the refusal.
Implementation guide
Here’s how to detect and handle streaming refusals in your application:
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 hidelines={1..2}
import anthropic
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-4-7",
) as stream:
for event in stream:
# Check for refusal in message delta
if hasattr(event, "type") and event.type == "message_delta":
if event.delta.stop_reason == "refusal":
reset_conversation()
break
except Exception as e:
print(f"Error: {e}")
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
let messages: any[] = [];
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-4-7",
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);
}using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
private static List<Message> messages = new();
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello" }]
};
try
{
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
if (msg.Type == "message_delta" && msg.Delta?.StopReason == "refusal")
{
ResetConversation();
break;
}
}
}
catch (Exception e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
private static void ResetConversation()
{
messages.Clear();
Console.WriteLine("Conversation reset due to refusal");
}
}package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
var messages []anthropic.MessageParam
func resetConversation() {
messages = []anthropic.MessageParam{}
fmt.Println("Conversation reset due to refusal")
}
func main() {
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.Model("claude-opus-4-7"),
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 == "refusal" {
resetConversation()
break streamLoop
}
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
}import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.MessageParam;
import com.anthropic.models.messages.Model;
import com.anthropic.core.http.StreamResponse;
import com.anthropic.models.messages.RawMessageStreamEvent;
import com.anthropic.models.messages.StopReason;
import java.util.ArrayList;
import java.util.List;
public class RefusalHandling {
private static List<MessageParam> messages = new ArrayList<>();
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.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());
}
}
private static void resetConversation() {
messages.clear();
System.out.println("Conversation reset due to content policy violation");
}
}<?php
use Anthropic\Client;
$client = new Client(apiKey: getenv("ANTHROPIC_API_KEY"));
$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-4-7',
);
foreach ($stream as $event) {
if (isset($event->type) && $event->type === 'message_delta') {
if (isset($event->delta->stopReason) && $event->delta->stopReason === 'refusal') {
resetConversation($messages);
break;
}
}
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}require "anthropic"
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-4-7",
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}"
endCurrent refusal types
The API currently handles refusals in three different ways:
| Refusal Type | Response Format | When It Occurs |
|---|---|---|
| Streaming classifier refusals | stop_reason: refusal | During streaming when content violates policies |
| API input and copyright validation | 400 error codes | When input fails validation checks |
| Model-generated refusals | Standard text responses | When the model itself decides to refuse |
Best practices
- Monitor for refusals: Include
stop_reason:refusalchecks in your error handling - Reset automatically: Implement automatic context reset when refusals are detected
- Provide custom messaging: Create user-friendly messages for better UX when refusals occur
- Track refusal patterns: Monitor refusal frequency to identify potential issues with your prompts
Migration notes
- Future models will expand this pattern to other refusal types
- Plan your error handling to accommodate future unification of refusal responses
Liên kết
- Nền tảng: Claude
- Nguồn: https://platform.claude.com/docs/en/test-and-evaluate/strengthen-guardrails/handle-streaming-refusals.md
Xem thêm: