Skip to content

AnthropicHandler: Streaming-Parsing der Anthropic Messages API

源码版本v4.0.10

Verantwortung

AnthropicHandler ist die konkrete Implementierung der ApiHandler-Schnittstelle für die Anthropic Messages API und zugleich der Fallback-Handler im default-Zweig von buildApiHandler (default Anthropic:504). Er setzt die vom Aufrufer übergebenen systemPrompt / messages / tools zu einer Anfrage an die Anthropic-SDK zusammen und übersetzt die nativen BetaRawMessageStreamEvent-Ereignisse des Streams während des Empfangs in Cline einheitliche ApiStreamChunk (ApiStreamChunk:3), sodass die obere Task-Schicht keine Anthropic-Protokolldetails kennen muss.

Zusätzlich übernimmt er drei Anthropic-spezifische Aufgaben: erstens prompt cache (Prompt-Zwischenspeicherung), indem am Ende des System-Prompts ein cache_control: ephemeral-Breakpoint gesetzt wird (cache_control:128); zweitens extended thinking, wobei je nach Modell-Unterstützung für Reasoning entschieden wird, ob das thinking-Feld gesetzt wird und wie hoch budget_tokens ausfällt (thinkingConfig:107); drittens die beiden Beta-Suffixe fast mode und 1m context, die über die Marker :fast bzw. :1m am Ende der Modell-ID erkannt und dann über client.beta.messages.create mit Beta-Header abgewickelt werden (fast mode detection:70).

Entwurfsmotivation

  • Lazy-Initialisierung des Clients: Der Handler erzeugt beim Konstruieren noch keinen Anthropic-SDK-Client, sondern erst bei der ersten createMessage-Anfrage in ensureClient (ensureClient:44). So führt das vorzeitige new eines Handlers durch buildApiHandler – etwa um das thinkingBudget zu begrenzen – zu keiner echten Anfrage.
  • Chunk-Übersetzung im Stream: Native Anthropic-Stream-Ereignisse gibt es in sechs Varianten (message_start / message_delta / content_block_start / content_block_delta / content_block_stop / message_stop), von denen Cline nur diejenigen interessieren, die sich auf text / reasoning / tool_calls / usage abbilden lassen (for await chunk:184); andere wie message_stop werden direkt übersprungen.
  • Inkrementelles Zusammenbauen der Werkzeugargumente: Bei content_block_start werden tool_use.id und name entnommen und in lastStartedToolCall abgelegt; spätere input_json_delta-Fragmente liefern das partial_json stückweise per yield aus (input_json_delta:276). Die obere Schicht ist für das Zusammenfügen zum vollständigen JSON verantwortlich.
  • thinking und temperature schließen sich aus: Wenn thinking aktiviert ist, muss temperature auf undefined gesetzt werden (temperature thinking:123) – eine offizielle Einschränkung von Anthropic.
  • Position des Prompt-Cache-Breakpoints: cache_control wird am Ende des system-Arrays gesetzt statt am Ende von tools, da tools unverändert bleiben, der system-Inhalt aber umfangreich ist – am Ende von system ist die Trefferquote am höchsten (system cache_control:124).
  • Retry-Dekorator: An createMessage ist @withRetry() angebracht; nur bei 429 oder RetriableError wird erneut versucht, standardmäßig dreimal mit exponentiellem Backoff (withRetry:63).
  • Adaptive Thinking: Claude Opus 4.5+ verwendet kein budgeted extended thinking mehr, sondern type: "adaptive" mit optionalem effort-Feld. Der Handler erkennt die Modell-ID und wählt den passenden Zweig (adaptive thinking:100).

Schlüsseldateien

Datenfluss

Der Handler entscheidet nach dem Aufruf zunächst, ob der Prompt-Cache-Zweig oder der normale Zweig genommen wird. Beide Pfade bauen ein unterschiedliches requestBody auf und ziehen den Stream über client.messages.create bzw. client.beta.messages.create:

typescript
// apps/vscode/src/core/api/providers/anthropic.ts
if (model.info.supportsPromptCache) {
	const anthropicMessages = sanitizeAnthropicMessages(messages, true)
	const requestBody: AnthropicMessageCreateParamsStreaming & Record<string, unknown> = {
		model: modelId,
		thinking: thinkingConfig,
		max_tokens: model.info.maxTokens || 8192,
		temperature: isAdaptiveThinkingModel ? undefined : reasoningOn ? undefined : 0,
		system: [
			{
				text: systemPrompt,
				type: "text",
				cache_control: { type: "ephemeral" },
			},
		], // setting cache breakpoint for system prompt so new tasks can reuse it
		messages: anthropicMessages,
		stream: true,
		tools: nativeToolsOn ? tools : undefined,
		tool_choice: nativeToolsOn && !thinkingEnabled ? { type: "any" } : undefined,
	}
	// ...
	stream = useFastMode
		? await createFastModeMessage(requestBody)
		: await client.messages.create(requestBody, /* options */)
}

Der Schlüssel dieses Zweigs liegt darin, dass cache_control auf dem letzten Element des system-Arrays gesetzt wird (cache_control:128). Anthropic speichert den Präfix bis zu diesem Breakpoint zwischen; eine folgende Anfrage mit demselben Präfix trifft direkt den Cache und liest aus ihm, was die Input-Token-Kosten für den System-Prompt spürbar senkt. tool_choice wird bei deaktiviertem thinking auf {type:"any"} gesetzt (tool_choice any:140) – Bedeutung: „Werkzeuge vorhanden, also verwenden". Bei aktiviertem thinking löst diese Erzwingung einen Inkompatibilitätsfehler von Anthropic aus, weshalb es dann undefined bleiben muss und das Modell selbst entscheidet.

Nachdem der Stream vorliegt, erfolgt die Chunk-Übersetzung in einer for await-Schleife. Der folgende Abschnitt zeigt die Kernlogik für text und reasoning:

typescript
// apps/vscode/src/core/api/providers/anthropic.ts
for await (const chunk of stream) {
	switch (chunk?.type) {
		case "message_start":
			{
				const usage = chunk.message.usage
				yield {
					type: "usage",
					inputTokens: usage.input_tokens || 0,
					outputTokens: usage.output_tokens || 0,
					cacheWriteTokens: usage.cache_creation_input_tokens || undefined,
					cacheReadTokens: usage.cache_read_input_tokens || undefined,
				}
			}
			break
		// ...
		case "content_block_delta":
			switch (chunk.delta.type) {
				case "text_delta":
					yield { type: "text", text: chunk.delta.text }
					break
				case "input_json_delta":
					if (lastStartedToolCall.id && lastStartedToolCall.name && chunk.delta.partial_json) {
						yield {
							type: "tool_calls",
							tool_call: {
								...lastStartedToolCall,
								function: { ...lastStartedToolCall, id: lastStartedToolCall.id, name: lastStartedToolCall.name, arguments: chunk.delta.partial_json },
							},
						}
					}
					break
			}
			break
	}
}

message_start liefert in einem Rutsch die vier Usage-Felder input_tokens / output_tokens / cache_creation / cache_read (message_start:186); spätere message_delta liefern inkrementell weitere output_tokens. Task nutzt diese Felder zur Kostenberechnung und zur Abschätzung der Kontextbelegung. Die JSON-Parameter eines Werkzeugaufrufs werden als partial_json-String-Fragmente stückweise herausgegeben; die obere Schicht parseAssistantMessageV2 setzt die Fragmente selbst zusammen und führt dann JSON.parse aus.

Grenzen und Fehler

  • Fehlender API-Key: ensureClient wirft direkt "Anthropic API key is required", wenn options.apiKey leer ist (apiKey required:46). Dieser Fehler bläst bis zur Task-Schicht durch und wird dort angezeigt.
  • Fehler beim SDK-Konstruktor: Ein Fehler aus new Anthropic(...) wird als "Error creating Anthropic client: ..." neu verpackt und erneut geworfen (client wrap:56).
  • Unbekannte Modell-ID: Ist modelId nicht in der anthropicModels-Tabelle enthalten, fällt getModel auf anthropicDefaultModelId zurück (fallback:311), sodass die obere Schicht nicht abstürzt.
  • 429-Ratenlimit: Der @withRetry()-Dekorator liest die Header retry-after / x-ratelimit-reset zur Verzögerung; drei Versuche, danach wird der ursprüngliche Fehler geworfen (withRetry:29).
  • Redacted-Thinking-Blöcke: Erhält content_block_start ein redacted_thinking, wird ein Placehalter-Reasoning per yield geliefert und die verschlüsselte data unverändert zurückgegeben (redacted_thinking:219), da Anthropic bei Folgerequests den Wert unverändert erwartet.
  • Zeilenumbruch zwischen mehreren Text-Blöcken: Bei content_block_start für den zweiten oder einen späteren Text-Block wird zuerst ein \n per yield geliefert (text block newline:237), damit die Blöcke nicht aneinanderkleben.
  • fast mode + 1m gleichzeitig: Enthält die Modell-ID sowohl :fast als auch :1m, werden im Beta-Header-Array gleichzeitig fast-mode-2026-02-01 und context-1m-2025-08-07 gesetzt (fastModeBetas:76).

Zusammenfassung

AnthropicHandler ist die am besten ausgebaute Provider-Implementierung in Cline: Sie verwaltet gleichzeitig Prompt-Cache-Breakpoints, extended thinking / adaptive thinking, die beiden Beta-Pfade fast mode und 1m context sowie die vollständige Übersetzung nativer Stream-Ereignisse in einheitliche ApiStreamChunk. Wer diese verstanden hat, findet in OpenAI / Gemini / Bedrock und den anderen Handlern stets dasselbe Muster – Anfrage aufbauen, Stream ziehen, Chunks übersetzen – lediglich mit abweichenden Protokollfeldern. Wie der System-Prompt Felder wie cache_creation_input_tokens konsumiert, ist in der PromptBuilder-Schicht beschrieben.

  • Einstieg zur Provider-Auswahl: /providers/api-handler
  • Aufbau des System-Prompts: /prompts/prompt-builder
  • Übersetzung der Werkzeuge in Anthropic-Tool-Definitionen: /prompts/toolset

Siehe offizielle Dokumentation: Cline-Dokumentation · README