diff --git a/docs/release_notes.md b/docs/release_notes.md index e5d59842b..6effc3ae7 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -26,6 +26,7 @@ - [Orchestration] Added `GPT_52` model for `OrchestrationAiModel`. - [OpenAi] Added `GPT_52` model from `OpenAiModel`. - [Orchestration] Added `GEMINI_EMBEDDING` model for `OrchestrationEmbeddingModel`. +- [Orchestration] Added citations for Perplexity `SONAR` model in `client.chatCompletion().getOriginalResponse().getFinalResult().getCitations()` ### 📈 Improvements diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java index d9e95d1b7..a164a1c24 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java @@ -8,6 +8,7 @@ import com.sap.ai.sdk.orchestration.OrchestrationFilterException; import com.sap.ai.sdk.orchestration.model.AzureContentSafetyInput; import com.sap.ai.sdk.orchestration.model.AzureContentSafetyOutput; +import com.sap.ai.sdk.orchestration.model.Citation; import com.sap.ai.sdk.orchestration.model.DPIEntities; import com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutors; import java.io.IOException; @@ -408,4 +409,27 @@ Object completionWithFallback( } return response.getContent(); } + + @GetMapping("/citations") + @Nonnull + Object citations( + @Nullable @RequestParam(value = "format", required = false) final String format) { + final var response = service.citations(); + if ("json".equals(format)) { + return response; + } + + final StringBuilder content = new StringBuilder(response.getContent().replaceAll("\n", "
")); + final var citations = response.getOriginalResponse().getFinalResult().getCitations(); + + if (!citations.isEmpty()) { + content.append("

Citations:"); + for (final Citation citation : citations) { + content.append( + "
%d. %s" + .formatted(citation.getRefId(), citation.getUrl(), citation.getTitle())); + } + } + return content.toString(); + } } diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java index 91f9b1ab2..296a0fed9 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java @@ -4,6 +4,7 @@ import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GPT_41_NANO; import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GPT_5_MINI; import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.Parameter.TEMPERATURE; +import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.SONAR; import static com.sap.ai.sdk.orchestration.OrchestrationEmbeddingModel.TEXT_EMBEDDING_3_SMALL; import static com.sap.ai.sdk.orchestration.OrchestrationTemplateReference.ScopeEnum.RESOURCE_GROUP; @@ -857,4 +858,16 @@ public OrchestrationChatResponse completionWithFallbackAllFail( .withLlmConfig(new OrchestrationAiModel("broken_name_2", Map.of(), "latest")); return client.chatCompletion(prompt, brokenConfig, secondBrokenConfig); } + + /** + * Chat request using the SONAR model which provides citations. + * + * @return the assistant response object with citations + */ + @Nonnull + public OrchestrationChatResponse citations() { + val prompt = new OrchestrationPrompt("Where does \"Hello World\" come from?"); + val sonarConfig = new OrchestrationModuleConfig().withLlmConfig(SONAR); + return client.chatCompletion(prompt, sonarConfig); + } } diff --git a/sample-code/spring-app/src/main/resources/static/index.html b/sample-code/spring-app/src/main/resources/static/index.html index 3567e7a0d..d6d37ad8f 100644 --- a/sample-code/spring-app/src/main/resources/static/index.html +++ b/sample-code/spring-app/src/main/resources/static/index.html @@ -328,6 +328,19 @@

Orchestration

+ +
  • +
    + +
    + Chat request to an LLM through the Orchestration service + with citations from Perplexity Sonar. +
    +

    diff --git a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java index 0b2ada3c4..383bae321 100644 --- a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java +++ b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java @@ -595,4 +595,10 @@ void testCompletionWithFallbackAllFail() { .isInstanceOf(OrchestrationClientException.class) .hasMessageContaining("Model broken_name_2 not supported."); } + + @Test + void testCitations() { + val result = service.citations(); + assertThat(result.getOriginalResponse().getFinalResult().getCitations()).isNotEmpty(); + } }