diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java index 72e327ed623..d1ddacd4d80 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java @@ -88,6 +88,7 @@ * @author Thomas Vitale * @author Claudio Silva Junior * @author Alexandros Pappas + * @author Jonghoon Park * @since 1.0.0 */ public class AnthropicChatModel implements ChatModel { @@ -357,6 +358,18 @@ private ChatResponseMetadata from(AnthropicApi.ChatCompletionResponse result, Us .build(); } + private Source getSourceByMedia(Media media) { + String data = this.fromMediaData(media.getData()); + + // http is not allowed and redirect not allowed + if (data.startsWith("https://")) { + return new Source(data); + } + else { + return new Source(media.getMimeType().toString(), data); + } + } + private String fromMediaData(Object mediaData) { if (mediaData instanceof byte[] bytes) { return Base64.getEncoder().encodeToString(bytes); @@ -457,8 +470,7 @@ ChatCompletionRequest createRequest(Prompt prompt, boolean stream) { if (!CollectionUtils.isEmpty(userMessage.getMedia())) { List mediaContent = userMessage.getMedia().stream().map(media -> { Type contentBlockType = getContentBlockTypeByMedia(media); - var source = new Source(media.getMimeType().toString(), - this.fromMediaData(media.getData())); + var source = getSourceByMedia(media); return new ContentBlock(contentBlockType, source); }).toList(); contents.addAll(mediaContent); diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java index 87098c6d938..09626d213d5 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,6 +57,7 @@ * @author Thomas Vitale * @author Jihoon Kim * @author Alexandros Pappas + * @author Jonghoon Park * @since 1.0.0 */ public class AnthropicApi { @@ -1038,13 +1039,15 @@ public String getValue() { * @param mediaType The media type of the content. For example, "image/png" or * "image/jpeg". * @param data The base64-encoded data of the content. + * @param url The url of the content. (image only) */ @JsonInclude(Include.NON_NULL) public record Source( // @formatter:off @JsonProperty("type") String type, @JsonProperty("media_type") String mediaType, - @JsonProperty("data") String data) { + @JsonProperty("data") String data, + @JsonProperty("url") String url) { // @formatter:on /** @@ -1053,7 +1056,11 @@ public record Source( * @param data The content data. */ public Source(String mediaType, String data) { - this("base64", mediaType, data); + this("base64", mediaType, data, null); + } + + public Source(String url) { + this("url", null, null, url); } } diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java index 556cd548c70..5353d48c47f 100644 --- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java +++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -301,14 +301,12 @@ void multiModalityEmbeddedImage(String modelName) throws IOException { assertThat(response).containsAnyOf("bananas", "apple", "bowl", "basket", "fruit stand"); } - @Disabled("Currently Anthropic API does not support external image URLs") @ParameterizedTest(name = "{0} : {displayName} ") - @ValueSource(strings = { "claude-3-opus-latest", "claude-3-5-sonnet-latest", "claude-3-haiku-latest", - "claude-3-7-sonnet-latest" }) + @ValueSource(strings = { "claude-3-opus-latest", "claude-3-5-sonnet-latest", "claude-3-7-sonnet-latest" }) void multiModalityImageUrl(String modelName) throws IOException { // TODO: add url method that wrapps the checked exception. - URL url = new URL("https://docs.spring.io/spring-ai/reference/1.0.0-SNAPSHOT/_images/multimodal.test.png"); + URL url = new URL("https://docs.spring.io/spring-ai/reference/_images/multimodal.test.png"); // @formatter:off String response = ChatClient.create(this.chatModel).prompt()