github.com/anthropics/anthropic-sdk-go@v1.70.1/betamessage_test.go (about) 1 // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 3 package anthropic_test 4 5 import ( 6 "context" 7 "encoding/json" 8 "errors" 9 "os" 10 "strings" 11 "testing" 12 13 "github.com/anthropics/anthropic-sdk-go" 14 "github.com/anthropics/anthropic-sdk-go/internal/testutil" 15 "github.com/anthropics/anthropic-sdk-go/option" 16 "github.com/anthropics/anthropic-sdk-go/shared/constant" 17 ) 18 19 func TestBetaMessageNewWithOptionalParams(t *testing.T) { 20 baseURL := "http://localhost:4010" 21 if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { 22 baseURL = envURL 23 } 24 if !testutil.CheckTestServer(t, baseURL) { 25 return 26 } 27 client := anthropic.NewClient( 28 option.WithBaseURL(baseURL), 29 option.WithAPIKey("my-anthropic-api-key"), 30 ) 31 _, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{ 32 MaxTokens: 1024, 33 Messages: []anthropic.BetaMessageParam{{ 34 Content: []anthropic.BetaContentBlockParamUnion{{ 35 OfText: &anthropic.BetaTextBlockParam{ 36 Text: "x", 37 CacheControl: anthropic.BetaCacheControlEphemeralParam{ 38 TTL: anthropic.BetaCacheControlEphemeralTTLTTL5m, 39 }, 40 Citations: []anthropic.BetaTextCitationParamUnion{{ 41 OfCharLocation: &anthropic.BetaCitationCharLocationParam{ 42 CitedText: "The grass is green. The sky is blue.", 43 DocumentIndex: 0, 44 DocumentTitle: anthropic.String("x"), 45 EndCharIndex: 0, 46 StartCharIndex: 0, 47 }, 48 }}, 49 }, 50 }}, 51 Role: anthropic.BetaMessageParamRoleUser, 52 ClearAt: anthropic.BetaMessageParamClearAtNextUserMessage, 53 OutputConfig: anthropic.BetaSystemMessageOutputConfigParam{ 54 Effort: anthropic.BetaSystemMessageOutputConfigEffortLow, 55 }, 56 }}, 57 Model: anthropic.ModelClaudeOpus5, 58 CacheControl: anthropic.BetaCacheControlEphemeralParam{ 59 TTL: anthropic.BetaCacheControlEphemeralTTLTTL5m, 60 }, 61 Container: anthropic.BetaMessageNewParamsContainerUnion{ 62 OfContainers: &anthropic.BetaContainerParams{ 63 ID: anthropic.String("id"), 64 Skills: []anthropic.BetaSkillParams{{ 65 SkillID: "pdf", 66 Type: anthropic.BetaSkillParamsTypeAnthropic, 67 Version: anthropic.String("latest"), 68 }}, 69 }, 70 }, 71 ContextManagement: anthropic.BetaContextManagementConfigParam{ 72 Edits: []anthropic.BetaContextManagementConfigEditUnionParam{{ 73 OfClearToolUses20250919: &anthropic.BetaClearToolUses20250919EditParam{ 74 ClearAtLeast: anthropic.BetaInputTokensClearAtLeastParam{ 75 Value: 0, 76 }, 77 ClearToolInputs: anthropic.BetaClearToolUses20250919EditClearToolInputsUnionParam{ 78 OfBool: anthropic.Bool(true), 79 }, 80 ExcludeTools: []string{"string"}, 81 Keep: anthropic.BetaToolUsesKeepParam{ 82 Value: 0, 83 }, 84 Trigger: anthropic.BetaClearToolUses20250919EditTriggerUnionParam{ 85 OfInputTokens: &anthropic.BetaInputTokensTriggerParam{ 86 Value: 1, 87 }, 88 }, 89 }, 90 }}, 91 }, 92 Diagnostics: anthropic.BetaDiagnosticsParam{ 93 PreviousMessageID: anthropic.String("previous_message_id"), 94 }, 95 FallbackCreditToken: anthropic.BetaMessageNewParamsFallbackCreditTokenUnion{ 96 OfString: anthropic.String("x"), 97 }, 98 Fallbacks: anthropic.BetaFallbacksParamUnion{ 99 OfDefault: constant.ValueOf[constant.Default](), 100 }, 101 InferenceGeo: anthropic.String("inference_geo"), 102 MCPServers: []anthropic.BetaRequestMCPServerURLDefinitionParam{{ 103 Name: "name", 104 URL: "url", 105 AuthorizationToken: anthropic.String("authorization_token"), 106 ToolConfiguration: anthropic.BetaRequestMCPServerToolConfigurationParam{ 107 AllowedTools: []string{"string"}, 108 Enabled: anthropic.Bool(true), 109 }, 110 }}, 111 Metadata: anthropic.BetaMetadataParam{ 112 UserID: anthropic.String("13803d75-b4b5-4c3e-b2a2-6f21399b021b"), 113 }, 114 OutputConfig: anthropic.BetaOutputConfigParam{ 115 Effort: anthropic.BetaOutputConfigEffortLow, 116 Format: anthropic.BetaJSONOutputFormatParam{ 117 Schema: map[string]any{ 118 "foo": "bar", 119 }, 120 }, 121 TaskBudget: anthropic.BetaTokenTaskBudgetParam{ 122 Total: 1024, 123 Remaining: anthropic.Int(0), 124 }, 125 }, 126 OutputFormat: anthropic.BetaJSONOutputFormatParam{ 127 Schema: map[string]any{ 128 "foo": "bar", 129 }, 130 }, 131 ServiceTier: anthropic.BetaMessageNewParamsServiceTierAuto, 132 Speed: anthropic.BetaMessageNewParamsSpeedStandard, 133 StopSequences: []string{"string"}, 134 System: []anthropic.BetaTextBlockParam{{ 135 Text: "Today's date is 2024-06-01.", 136 CacheControl: anthropic.BetaCacheControlEphemeralParam{ 137 TTL: anthropic.BetaCacheControlEphemeralTTLTTL5m, 138 }, 139 Citations: []anthropic.BetaTextCitationParamUnion{{ 140 OfCharLocation: &anthropic.BetaCitationCharLocationParam{ 141 CitedText: "The grass is green. The sky is blue.", 142 DocumentIndex: 0, 143 DocumentTitle: anthropic.String("x"), 144 EndCharIndex: 0, 145 StartCharIndex: 0, 146 }, 147 }}, 148 }}, 149 Temperature: anthropic.Float(1), 150 Thinking: anthropic.BetaThinkingConfigParamUnion{ 151 OfAdaptive: &anthropic.BetaThinkingConfigAdaptiveParam{ 152 BlockBinding: anthropic.BetaThinkingBlockBindingParam{ 153 PrefixMismatchBehavior: anthropic.BetaThinkingPrefixMismatchBehaviorError, 154 }, 155 Display: anthropic.BetaThinkingConfigAdaptiveDisplaySummarized, 156 }, 157 }, 158 ToolChoice: anthropic.BetaToolChoiceUnionParam{ 159 OfAuto: &anthropic.BetaToolChoiceAutoParam{ 160 DisableParallelToolUse: anthropic.Bool(true), 161 }, 162 }, 163 Tools: []anthropic.BetaToolUnionParam{{ 164 OfTool: &anthropic.BetaToolParam{ 165 InputSchema: anthropic.BetaToolInputSchemaParam{ 166 Properties: map[string]any{ 167 "location": "bar", 168 "unit": "bar", 169 }, 170 Required: []string{"location"}, 171 }, 172 Name: "name", 173 AllowedCallers: []string{"direct"}, 174 CacheControl: anthropic.BetaCacheControlEphemeralParam{ 175 TTL: anthropic.BetaCacheControlEphemeralTTLTTL5m, 176 }, 177 DeferLoading: anthropic.Bool(true), 178 Description: anthropic.String("Get the current weather in a given location"), 179 EagerInputStreaming: anthropic.Bool(true), 180 InputExamples: []map[string]any{{ 181 "foo": "bar", 182 }}, 183 Strict: anthropic.Bool(true), 184 Type: anthropic.BetaToolTypeCustom, 185 }, 186 }}, 187 TopK: anthropic.Int(5), 188 TopP: anthropic.Float(0.7), 189 Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaMessageBatches2024_09_24}, 190 UserProfileID: anthropic.String("anthropic-user-profile-id"), 191 WorkspaceID: anthropic.String("wrkspc_011CZkZaBF1tNoB5wlCeusgy"), 192 }) 193 if err != nil { 194 var apierr *anthropic.Error 195 if errors.As(err, &apierr) { 196 t.Log(string(apierr.DumpRequest(true))) 197 } 198 t.Fatalf("err should be nil: %s", err.Error()) 199 } 200 } 201 202 func TestBetaMessageCountTokensWithOptionalParams(t *testing.T) { 203 baseURL := "http://localhost:4010" 204 if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { 205 baseURL = envURL 206 } 207 if !testutil.CheckTestServer(t, baseURL) { 208 return 209 } 210 client := anthropic.NewClient( 211 option.WithBaseURL(baseURL), 212 option.WithAPIKey("my-anthropic-api-key"), 213 ) 214 _, err := client.Beta.Messages.CountTokens(context.TODO(), anthropic.BetaMessageCountTokensParams{ 215 Messages: []anthropic.BetaMessageParam{{ 216 Content: []anthropic.BetaContentBlockParamUnion{{ 217 OfText: &anthropic.BetaTextBlockParam{ 218 Text: "x", 219 CacheControl: anthropic.BetaCacheControlEphemeralParam{ 220 TTL: anthropic.BetaCacheControlEphemeralTTLTTL5m, 221 }, 222 Citations: []anthropic.BetaTextCitationParamUnion{{ 223 OfCharLocation: &anthropic.BetaCitationCharLocationParam{ 224 CitedText: "The grass is green. The sky is blue.", 225 DocumentIndex: 0, 226 DocumentTitle: anthropic.String("x"), 227 EndCharIndex: 0, 228 StartCharIndex: 0, 229 }, 230 }}, 231 }, 232 }}, 233 Role: anthropic.BetaMessageParamRoleUser, 234 ClearAt: anthropic.BetaMessageParamClearAtNextUserMessage, 235 OutputConfig: anthropic.BetaSystemMessageOutputConfigParam{ 236 Effort: anthropic.BetaSystemMessageOutputConfigEffortLow, 237 }, 238 }}, 239 Model: anthropic.ModelClaudeOpus5, 240 CacheControl: anthropic.BetaCacheControlEphemeralParam{ 241 TTL: anthropic.BetaCacheControlEphemeralTTLTTL5m, 242 }, 243 ContextManagement: anthropic.BetaContextManagementConfigParam{ 244 Edits: []anthropic.BetaContextManagementConfigEditUnionParam{{ 245 OfClearToolUses20250919: &anthropic.BetaClearToolUses20250919EditParam{ 246 ClearAtLeast: anthropic.BetaInputTokensClearAtLeastParam{ 247 Value: 0, 248 }, 249 ClearToolInputs: anthropic.BetaClearToolUses20250919EditClearToolInputsUnionParam{ 250 OfBool: anthropic.Bool(true), 251 }, 252 ExcludeTools: []string{"string"}, 253 Keep: anthropic.BetaToolUsesKeepParam{ 254 Value: 0, 255 }, 256 Trigger: anthropic.BetaClearToolUses20250919EditTriggerUnionParam{ 257 OfInputTokens: &anthropic.BetaInputTokensTriggerParam{ 258 Value: 1, 259 }, 260 }, 261 }, 262 }}, 263 }, 264 MCPServers: []anthropic.BetaRequestMCPServerURLDefinitionParam{{ 265 Name: "name", 266 URL: "url", 267 AuthorizationToken: anthropic.String("authorization_token"), 268 ToolConfiguration: anthropic.BetaRequestMCPServerToolConfigurationParam{ 269 AllowedTools: []string{"string"}, 270 Enabled: anthropic.Bool(true), 271 }, 272 }}, 273 OutputConfig: anthropic.BetaOutputConfigParam{ 274 Effort: anthropic.BetaOutputConfigEffortLow, 275 Format: anthropic.BetaJSONOutputFormatParam{ 276 Schema: map[string]any{ 277 "foo": "bar", 278 }, 279 }, 280 TaskBudget: anthropic.BetaTokenTaskBudgetParam{ 281 Total: 1024, 282 Remaining: anthropic.Int(0), 283 }, 284 }, 285 OutputFormat: anthropic.BetaJSONOutputFormatParam{ 286 Schema: map[string]any{ 287 "foo": "bar", 288 }, 289 }, 290 Speed: anthropic.BetaMessageCountTokensParamsSpeedStandard, 291 System: anthropic.BetaMessageCountTokensParamsSystemUnion{ 292 OfBetaTextBlockArray: []anthropic.BetaTextBlockParam{{ 293 Text: "Today's date is 2024-06-01.", 294 CacheControl: anthropic.BetaCacheControlEphemeralParam{ 295 TTL: anthropic.BetaCacheControlEphemeralTTLTTL5m, 296 }, 297 Citations: []anthropic.BetaTextCitationParamUnion{{ 298 OfCharLocation: &anthropic.BetaCitationCharLocationParam{ 299 CitedText: "The grass is green. The sky is blue.", 300 DocumentIndex: 0, 301 DocumentTitle: anthropic.String("x"), 302 EndCharIndex: 0, 303 StartCharIndex: 0, 304 }, 305 }}, 306 }}, 307 }, 308 Thinking: anthropic.BetaThinkingConfigParamUnion{ 309 OfAdaptive: &anthropic.BetaThinkingConfigAdaptiveParam{ 310 BlockBinding: anthropic.BetaThinkingBlockBindingParam{ 311 PrefixMismatchBehavior: anthropic.BetaThinkingPrefixMismatchBehaviorError, 312 }, 313 Display: anthropic.BetaThinkingConfigAdaptiveDisplaySummarized, 314 }, 315 }, 316 ToolChoice: anthropic.BetaToolChoiceUnionParam{ 317 OfAuto: &anthropic.BetaToolChoiceAutoParam{ 318 DisableParallelToolUse: anthropic.Bool(true), 319 }, 320 }, 321 Tools: []anthropic.BetaMessageCountTokensParamsToolUnion{{ 322 OfTool: &anthropic.BetaToolParam{ 323 InputSchema: anthropic.BetaToolInputSchemaParam{ 324 Properties: map[string]any{ 325 "location": "bar", 326 "unit": "bar", 327 }, 328 Required: []string{"location"}, 329 }, 330 Name: "name", 331 AllowedCallers: []string{"direct"}, 332 CacheControl: anthropic.BetaCacheControlEphemeralParam{ 333 TTL: anthropic.BetaCacheControlEphemeralTTLTTL5m, 334 }, 335 DeferLoading: anthropic.Bool(true), 336 Description: anthropic.String("Get the current weather in a given location"), 337 EagerInputStreaming: anthropic.Bool(true), 338 InputExamples: []map[string]any{{ 339 "foo": "bar", 340 }}, 341 Strict: anthropic.Bool(true), 342 Type: anthropic.BetaToolTypeCustom, 343 }, 344 }}, 345 Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaMessageBatches2024_09_24}, 346 UserProfileID: anthropic.String("anthropic-user-profile-id"), 347 WorkspaceID: anthropic.String("wrkspc_011CZkZaBF1tNoB5wlCeusgy"), 348 }) 349 if err != nil { 350 var apierr *anthropic.Error 351 if errors.As(err, &apierr) { 352 t.Log(string(apierr.DumpRequest(true))) 353 } 354 t.Fatalf("err should be nil: %s", err.Error()) 355 } 356 } 357 358 func TestBetaAccumulate(t *testing.T) { 359 for name, testCase := range map[string]struct { 360 expected anthropic.BetaMessage 361 events []string 362 }{ 363 "empty message": { 364 expected: anthropic.BetaMessage{Usage: anthropic.BetaUsage{}}, 365 events: []string{ 366 `{"type": "message_start", "message": {}}`, 367 `{"type: "message_stop"}`, 368 }, 369 }, 370 "text content block": { 371 events: []string{ 372 `{"type": "message_start", "message": {}}`, 373 `{"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": "This "}}`, 374 `{"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "is a "}}`, 375 `{"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta": "text": "text block!"}}`, 376 `{"type": "content_block_stop", "index": 0}`, 377 `{"type": "message_stop"}`, 378 }, 379 expected: anthropic.BetaMessage{Content: []anthropic.BetaContentBlockUnion{ 380 {Type: "text", Text: "This is a text block!"}, 381 }}, 382 }, 383 "text content block with citations": { 384 events: []string{ 385 `{"type": "message_start", "message": {}}`, 386 `{"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": "1 + 1"}}`, 387 `{"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": " = 2"}}`, 388 `{"type": "content_block_delta", "index": 0, "delta": {"type": "citations_delta", "citation": {"type": "char_location", "cited_text": "1 + 1 = 2", "document_index": 0, "document_title": "Math Facts", "start_char_index": 300, "end_char_index": 310 }}}`, 389 `{"type": "content_block_stop", "index": 0}`, 390 `{"type": "message_stop"}`, 391 }, 392 expected: anthropic.BetaMessage{Content: []anthropic.BetaContentBlockUnion{ 393 {Type: "text", Text: "1 + 1 = 2", Citations: []anthropic.BetaTextCitationUnion{{ 394 Type: "char_location", 395 CitedText: "1 + 1 = 2", 396 DocumentIndex: 0, 397 DocumentTitle: "Math Facts", 398 StartCharIndex: 300, 399 EndCharIndex: 310, 400 }}}, 401 }}, 402 }, 403 "tool use block": { 404 events: []string{ 405 `{"type": "message_start", "message": {}}`, 406 `{"type": "content_block_start", "index": 0, "content_block": {"type": "tool_use", "id": "toolu_id", "name": "tool_name", "input": {}}}`, 407 `{"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "{\"argument\":"}}`, 408 `{"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": " \"value\"}"}}`, 409 `{"type": "content_block_stop", "index": 0}`, 410 `{"type": "message_stop"}`, 411 }, 412 expected: anthropic.BetaMessage{Content: []anthropic.BetaContentBlockUnion{ 413 {Type: "tool_use", ID: "toolu_id", Name: "tool_name", Input: []byte(`{"argument": "value"}`)}, 414 }}, 415 }, 416 "tool use block with no params": { 417 events: []string{ 418 `{"type": "message_start", "message": {}}`, 419 `{"type": "content_block_start": "index": 0, "content_block": {"type": "tool_use", "id": "toolu_id", "name": "tool_name", input: {}}}`, 420 `{"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": ""}}`, 421 `{"type": "content_block_stop", "index": 0}`, 422 `{"type": "message_stop"}`, 423 }, 424 expected: anthropic.BetaMessage{Content: []anthropic.BetaContentBlockUnion{ 425 {Type: "tool_use", ID: "toolu_id", Name: "tool_name"}, 426 }}, 427 }, 428 "server tool use block": { 429 events: []string{ 430 `{"type": "message_start", "message": {}}`, 431 `{"type": "content_block_start": "index": 0, "content_block": {"type": "server_tool_use", "id": "srvtoolu_id", "name": "web_search", input: {}}}`, 432 `{"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": ""}}`, 433 `{"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "{\"query\": \"weat"}}`, 434 `{"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "her\"}"}}`, 435 `{"type": "content_block_stop", "index": 0}`, 436 `{"type": "message_stop"}`, 437 }, 438 expected: anthropic.BetaMessage{Content: []anthropic.BetaContentBlockUnion{ 439 {Type: "server_tool_use", ID: "srvtoolu_id", Name: "web_search", Input: []byte(`{"query": "weather"}`)}, 440 }}, 441 }, 442 "thinking block": { 443 events: []string{ 444 `{"type": "message_start", "message": {}}`, 445 `{"type": "content_block_start", "index": 0, "content_block": {"type": "thinking", "thinking": "Let me think..."}}`, 446 `{"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": " 447 First, let's try this..."}}`, 448 `{"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": " 449 Therefore, the answer is..."}}`, 450 `{"type": "content_block_delta", "index": 0, "delta": {"type": "signature_delta", "signature": "ThinkingSignature"}}`, 451 `{"type": "content_block_stop", "index": 0}`, 452 `{"type": "message_stop"}`, 453 }, 454 expected: anthropic.BetaMessage{Content: []anthropic.BetaContentBlockUnion{ 455 {Type: "thinking", Thinking: "Let me think...\nFirst, let's try this...\nTherefore, the answer is...", Signature: "ThinkingSignature"}, 456 }}, 457 }, 458 "redacted thinking block": { 459 events: []string{ 460 `{"type": "message_start", "message": {}}`, 461 `{"type": "content_block_start", "index": 0, "content_block": {"type": "redacted_thinking", "data": "Redacted"}}`, 462 `{"type": "content_block_stop", "index": 0}`, 463 `{"type": "message_stop"}`, 464 }, 465 expected: anthropic.BetaMessage{Content: []anthropic.BetaContentBlockUnion{ 466 {Type: "redacted_thinking", Data: "Redacted"}, 467 }}, 468 }, 469 "compaction block": { 470 events: []string{ 471 `{"type": "message_start", "message": {}}`, 472 `{"type": "content_block_start", "index": 0, "content_block": {"type": "compaction", "content": ""}}`, 473 `{"type": "content_block_delta", "index": 0, "delta": {"type": "compaction_delta", "content": "Summary of the conversation so far."}}`, 474 `{"type": "content_block_stop", "index": 0}`, 475 `{"type": "message_stop"}`, 476 }, 477 expected: anthropic.BetaMessage{Content: []anthropic.BetaContentBlockUnion{ 478 {Type: "compaction", Content: anthropic.BetaContentBlockUnionContent{OfString: "Summary of the conversation so far."}}, 479 }}, 480 }, 481 "refusal with stop_details and usage": { 482 events: []string{ 483 `{"type": "message_start", "message": {}}`, 484 `{"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": "I cannot help"}}`, 485 `{"type": "content_block_stop", "index": 0}`, 486 `{"type": "message_delta", "delta": {"stop_reason": "refusal", "stop_details": {"type": "refusal", "category": "cyber", "explanation": "Declined by a streaming policy classifier."}}, "usage": {"input_tokens": 15, "output_tokens": 8, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "server_tool_use": {"web_search_requests": 2}}}`, 487 `{"type": "message_stop"}`, 488 }, 489 expected: anthropic.BetaMessage{ 490 Content: []anthropic.BetaContentBlockUnion{ 491 {Type: "text", Text: "I cannot help"}, 492 }, 493 StopReason: "refusal", 494 StopDetails: anthropic.BetaRefusalStopDetails{ 495 Type: "refusal", 496 Category: "cyber", 497 Explanation: "Declined by a streaming policy classifier.", 498 }, 499 Usage: anthropic.BetaUsage{ 500 InputTokens: 15, 501 OutputTokens: 8, 502 ServerToolUse: anthropic.BetaServerToolUsage{WebSearchRequests: 2}, 503 }, 504 }, 505 }, 506 "message_delta usage with fallback_credit": { 507 events: []string{ 508 `{"type": "message_start", "message": {}}`, 509 `{"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": "Hi"}}`, 510 `{"type": "content_block_stop", "index": 0}`, 511 `{"type": "message_delta", "delta": {"stop_reason": "end_turn"}, "usage": {"input_tokens": 15, "output_tokens": 8, "fallback_credit": {"status": {"type": "not_applied", "reason": "expired", "remove_to_redeem": ["fallback_credit_token"]}}}}`, 512 `{"type": "message_stop"}`, 513 }, 514 expected: anthropic.BetaMessage{ 515 Content: []anthropic.BetaContentBlockUnion{ 516 {Type: "text", Text: "Hi"}, 517 }, 518 StopReason: "end_turn", 519 Usage: anthropic.BetaUsage{ 520 InputTokens: 15, 521 OutputTokens: 8, 522 FallbackCredit: anthropic.BetaFallbackCreditUsage{ 523 Status: anthropic.BetaFallbackCreditUsageStatusUnion{ 524 Type: "not_applied", 525 Reason: anthropic.BetaFallbackCreditNotAppliedReasonExpired, 526 RemoveToRedeem: []string{"fallback_credit_token"}, 527 }, 528 }, 529 }, 530 }, 531 }, 532 "multiple content blocks": { 533 events: []string{ 534 `{"type": "message_start", "message": {}}`, 535 `{"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": "Let me look up "}}`, 536 `{"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "the weather for "}}`, 537 `{"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta": "text": "you."}}`, 538 `{"type": "content_block_stop", "index": 0}`, 539 `{"type": "content_block_start", "index": 1, "content_block": {"type": "thinking", "thinking": ""}}`, 540 `{"type": "content_block_delta", "index": 1, "delta": {"type": "thinking_delta", "thinking": "I can look this "}}`, 541 `{"type": "content_block_delta", "index": 1, "delta": {"type": "thinking_delta", "thinking": "up using a tool."}}`, 542 `{"type": "content_block_stop", "index": 1}`, 543 `{"type": "content_block_start", "index": 2, "content_block": {"type": "tool_use", "id": "toolu_id", "name": "get_weather", "input": {}}}`, 544 `{"type": "content_block_delta", "index": 2, "delta": {"type": "input_json_delta", "partial_json": "{\"city\": "}}`, 545 `{"type": "content_block_delta", "index": 2, "delta": {"type": "input_json_delta", "partial_json": "\"Los Angeles\"}"}}`, 546 `{"type": "content_block_stop", "index": 2}`, 547 `{"type": "content_block_start", "index": 3, "content_block": {"type": "text", "text": ""}}`, 548 `{"type": "content_block_delta", "index": 3, "delta": {"type": "text_delta", "text": "The weather in Los Angeles"}}`, 549 `{"type": "content_block_delta", "index": 3, "delta": {"type": "text_delta", "text": " is 85 degrees Fahrenheit!"}}`, 550 `{"type": "content_block_stop", "index": 3"}`, 551 `{"type": "message_stop"}`, 552 }, 553 expected: anthropic.BetaMessage{Content: []anthropic.BetaContentBlockUnion{ 554 {Type: "text", Text: "Let me look up the weather for you."}, 555 {Type: "thinking", Thinking: "I can look this up using a tool."}, 556 {Type: "tool_use", ID: "toolu_id", Name: "get_weather", Input: []byte(`{"city": "Los Angeles"}`)}, 557 {Type: "text", Text: "The weather in Los Angeles is 85 degrees Fahrenheit!"}, 558 }}, 559 }, 560 "fallback block relabels accumulated model": { 561 events: []string{ 562 `{"type": "message_start", "message": {"model": "model-a"}}`, 563 `{"type": "content_block_start", "index": 0, "content_block": {"type": "fallback", "from": {"model": "model-a"}, "to": {"model": "model-b"}}}`, 564 `{"type": "content_block_stop", "index": 0}`, 565 `{"type": "message_delta", "delta": {"stop_reason": "end_turn"}, "usage": {"output_tokens": 5}}`, 566 `{"type": "message_stop"}`, 567 }, 568 expected: anthropic.BetaMessage{ 569 Model: "model-b", 570 Content: []anthropic.BetaContentBlockUnion{ 571 { 572 Type: "fallback", 573 From: anthropic.BetaFallbackInfo{Model: "model-a"}, 574 To: anthropic.BetaFallbackInfo{Model: "model-b"}, 575 }, 576 }, 577 StopReason: "end_turn", 578 Usage: anthropic.BetaUsage{OutputTokens: 5}, 579 }, 580 }, 581 "interleaved content blocks": { 582 events: []string{ 583 `{"type": "message_start", "message": {}}`, 584 `{"type": "content_block_start", "index": 0, "content_block": {"type": "thinking", "thinking": ""}}`, 585 `{"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "Let me think."}}`, 586 `{"type": "content_block_delta", "index": 0, "delta": {"type": "signature_delta", "signature": "sig123"}}`, 587 `{"type": "content_block_stop", "index": 0}`, 588 `{"type": "content_block_start", "index": 1, "content_block": {"type": "text", "text": ""}}`, 589 `{"type": "content_block_delta", "index": 1, "delta": {"type": "text_delta", "text": "Hello"}}`, 590 `{"type": "content_block_start", "index": 2, "content_block": {"type": "tool_use", "id": "toolu_id", "name": "get_weather", "input": {}}}`, 591 `{"type": "content_block_delta", "index": 1, "delta": {"type": "text_delta", "text": " world"}}`, 592 `{"type": "content_block_delta", "index": 2, "delta": {"type": "input_json_delta", "partial_json": "{\"city\": "}}`, 593 `{"type": "content_block_delta", "index": 1, "delta": {"type": "text_delta", "text": "!"}}`, 594 `{"type": "content_block_delta", "index": 2, "delta": {"type": "input_json_delta", "partial_json": "\"Los Angeles\"}"}}`, 595 `{"type": "content_block_stop", "index": 1}`, 596 `{"type": "content_block_stop", "index": 2}`, 597 `{"type": "message_stop"}`, 598 }, 599 expected: anthropic.BetaMessage{Content: []anthropic.BetaContentBlockUnion{ 600 {Type: "thinking", Thinking: "Let me think.", Signature: "sig123"}, 601 {Type: "text", Text: "Hello world!"}, 602 {Type: "tool_use", ID: "toolu_id", Name: "get_weather", Input: []byte(`{"city": "Los Angeles"}`)}, 603 }}, 604 }, 605 } { 606 t.Run(name, func(t *testing.T) { 607 message := anthropic.BetaMessage{} 608 for _, eventStr := range testCase.events { 609 event := anthropic.BetaRawMessageStreamEventUnion{} 610 err := (&event).UnmarshalJSON([]byte(eventStr)) 611 if err != nil { 612 t.Fatal(err) 613 } 614 (&message).Accumulate(event) 615 } 616 marshaledMessage, err := json.Marshal(message) 617 if err != nil { 618 t.Fatal(err) 619 } 620 marshaledExpectedMessage, err := json.Marshal(testCase.expected) 621 if err != nil { 622 t.Fatal(err) 623 } 624 if string(marshaledMessage) != string(marshaledExpectedMessage) { 625 t.Fatalf("Mismatched message: expected %s but got %s", marshaledExpectedMessage, marshaledMessage) 626 } 627 }) 628 } 629 } 630 631 func TestBetaAccumulateContentBlockIndexErrors(t *testing.T) { 632 for name, testCase := range map[string]struct { 633 events []string 634 wantErr string 635 }{ 636 "start with an index gap": { 637 events: []string{ 638 `{"type": "message_start", "message": {}}`, 639 `{"type": "content_block_start", "index": 1, "content_block": {"type": "text", "text": ""}}`, 640 }, 641 wantErr: "expected index 0", 642 }, 643 "delta for a block that never started": { 644 events: []string{ 645 `{"type": "message_start", "message": {}}`, 646 `{"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}`, 647 `{"type": "content_block_delta", "index": 1, "delta": {"type": "text_delta", "text": "hi"}}`, 648 }, 649 wantErr: "only 1 content blocks", 650 }, 651 "delta with a negative index": { 652 events: []string{ 653 `{"type": "message_start", "message": {}}`, 654 `{"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}`, 655 `{"type": "content_block_delta", "index": -1, "delta": {"type": "text_delta", "text": "hi"}}`, 656 }, 657 wantErr: "index -1", 658 }, 659 "stop for a block that never started": { 660 events: []string{ 661 `{"type": "message_start", "message": {}}`, 662 `{"type": "content_block_stop", "index": 0}`, 663 }, 664 wantErr: "only 0 content blocks", 665 }, 666 } { 667 t.Run(name, func(t *testing.T) { 668 message := anthropic.BetaMessage{} 669 for i, eventStr := range testCase.events { 670 event := anthropic.BetaRawMessageStreamEventUnion{} 671 if err := (&event).UnmarshalJSON([]byte(eventStr)); err != nil { 672 t.Fatal(err) 673 } 674 err := (&message).Accumulate(event) 675 if i < len(testCase.events)-1 { 676 if err != nil { 677 t.Fatal(err) 678 } 679 continue 680 } 681 if err == nil { 682 t.Fatalf("expected error containing %q, got nil", testCase.wantErr) 683 } 684 if !strings.Contains(err.Error(), testCase.wantErr) { 685 t.Fatalf("expected error containing %q, got %q", testCase.wantErr, err.Error()) 686 } 687 } 688 }) 689 } 690 }