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.

To learn more about refusals triggered by API safety filters for Claude Sonnet 4.5, see [Understanding Sonnet 4.5's API Safety Filters](https://support.claude.com/en/articles/12449294-understanding-sonnet-4-5-s-api-safety-filters).

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"
}
No additional refusal message is included. You must handle the response and provide appropriate user-facing messaging.

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.

Usage metrics are still provided in the response for billing purposes, even when the response is refused.

You will be billed for output tokens up until the refusal.

If you encounter `refusal` stop reasons frequently while using Claude Sonnet 4.5 or Opus 4.1, you can try updating your API calls to use Haiku 4.5 (`claude-haiku-4-5-20251001`), which has different usage restrictions. Learn more about [understanding Sonnet 4.5's API safety filters](https://support.claude.com/en/articles/12449294-understanding-sonnet-4-5-s-api-safety-filters).

Implementation guide

Here’s how to detect and handle streaming refusals in your application:

```bash cURL # Stream request and check for refusal response=$(curl -N https://api.anthropic.com/v1/messages \ --header "anthropic-version: 2023-06-01" \ --header "content-type: application/json" \ --header "x-api-key: $ANTHROPIC_API_KEY" \ --data '{ "model": "claude-opus-4-7", "messages": [{"role": "user", "content": "Hello"}], "max_tokens": 256, "stream": true }')

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}"
end

Current refusal types

The API currently handles refusals in three different ways:

Refusal TypeResponse FormatWhen It Occurs
Streaming classifier refusalsstop_reason: refusalDuring streaming when content violates policies
API input and copyright validation400 error codesWhen input fails validation checks
Model-generated refusalsStandard text responsesWhen the model itself decides to refuse
Future API versions will expand the **`stop_reason`: `refusal`** pattern to unify refusal handling across all types.

Best practices

  • Monitor for refusals: Include stop_reason: refusal checks 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

Xem thêm: