Skip to content

Commit a2a2520

Browse files
committed
Merge remote-tracking branch 'refs/remotes/origin/main' into orchestration-spec-update-12a-tests
2 parents 1739b5d + 4aecbbf commit a2a2520

File tree

9 files changed

+188
-85
lines changed

9 files changed

+188
-85
lines changed

.github/workflows/continuous-integration.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
branches: [ "*" ]
55
push:
66
branches: [ "main" ]
7+
workflow_dispatch: # triggered by the prepare-release workflow
78

89
env:
910
MVN_MULTI_THREADED_ARGS: --batch-mode --no-transfer-progress --fail-at-end --show-version --threads 1C
Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,37 +20,26 @@ jobs:
2020
prerequisites:
2121
name: "Prerequisites"
2222
outputs:
23-
release-version: ${{ steps.determine-branch-names.outputs.RELEASE_VERSION }}
24-
release-tag: ${{ steps.determine-branch-names.outputs.RELEASE_TAG }}
2523
code-branch: ${{ steps.determine-branch-names.outputs.CODE_BRANCH_NAME }}
24+
release-tag: ${{ steps.determine-branch-names.outputs.RELEASE_TAG }}
2625
release-commit: ${{ steps.determine-branch-names.outputs.RELEASE_COMMIT }}
27-
permissions:
28-
pull-requests: read
29-
contents: read
26+
permissions: write-all # contents and push are needed to see the draft release
3027
runs-on: ubuntu-latest
3128
steps:
3229
- name: "Determine Branch Names"
3330
id: determine-branch-names
3431
run: |
35-
if [[ "${{ github.event_name }}" == "pull_request_review" ]]; then
36-
echo "[DEBUG] Taking branch name from pull request event"
37-
BRANCH_NAME=${{ github.event.pull_request.head.ref }}
38-
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
39-
echo "[DEBUG] Taking branch name from workflow dispatch event"
40-
BRANCH_NAME=$(gh pr view ${{ github.event.inputs.release_pr_number }} --repo "${{ github.repository }}" --json headRefName | jq -r '.headRefName')
41-
else
42-
echo "Cannot determine branch name from event '${{ github.event_name }}'"
43-
exit 1
44-
fi
32+
CODE_BRANCH_NAME=$(gh pr view ${{github.event.inputs.release_pr_number}} --repo ${{github.repository}} --json headRefName --jq '.headRefName')
33+
RELEASE_VERSION=$(echo $CODE_BRANCH_NAME | cut -d '-' -f2)
34+
RELEASE_TAG=rel/$RELEASE_VERSION
35+
RELEASE_COMMIT=$(gh release view $RELEASE_TAG --repo ${{github.repository}} --json targetCommitish --jq '.targetCommitish')
4536
46-
RELEASE_VERSION=$(echo "$BRANCH_NAME" | cut -d '-' -f2)
47-
37+
echo "CODE_BRANCH_NAME=$CODE_BRANCH_NAME" >> $GITHUB_OUTPUT
4838
echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_OUTPUT
49-
echo "RELEASE_TAG=rel/$RELEASE_VERSION" >> $GITHUB_OUTPUT
50-
echo "CODE_BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
51-
echo "RELEASE_COMMIT=$(gh release view "$RELEASE_TAG" --repo "${{ github.repository }}" --json targetCommitish | jq -r '.targetCommitish')" >> $GITHUB_OUTPUT
39+
echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_OUTPUT
40+
echo "RELEASE_COMMIT=$RELEASE_COMMIT" >> $GITHUB_OUTPUT
5241
53-
echo "[DEBUG] Current GITHUB_OUTPUT: '$(cat $GITHUB_OUTPUT)'"
42+
echo -e "[DEBUG] Current GITHUB_OUTPUT:\n$(cat $GITHUB_OUTPUT)"
5443
env:
5544
GH_TOKEN: ${{ github.token }}
5645

.github/workflows/prepare-release.yaml

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -140,18 +140,6 @@ jobs:
140140
git config --global user.email "[email protected]"
141141
git config --global user.name "SAP Cloud SDK Bot"
142142
143-
- name: "Set New Version and Reset Release Notes"
144-
run: |
145-
python .pipeline/scripts/set-release-versions.py --version ${{ needs.bump-version.outputs.new-version }}
146-
git add .
147-
git commit -m "Update to version ${{ needs.bump-version.outputs.new-version }}"
148-
149-
# Reset release notes for next version
150-
cp .pipeline/scripts/release_notes_template.md docs/release-notes/release_notes.md
151-
git add docs/release-notes/release_notes.md
152-
git commit -m "Reset release notes"
153-
git push
154-
155143
- name: "Create Code PR"
156144
run: |
157145
COMMIT_URL=${{ github.event.repository.html_url }}/commit/${{ needs.bump-version.outputs.release-commit }}
@@ -166,6 +154,24 @@ jobs:
166154
env:
167155
GH_TOKEN: ${{ secrets.BOT_SDK_JS_FOR_DOCS_REPO_PR }}
168156

157+
- name: "Run Continuous Integration" # It should trigger on push but sometimes doesn't
158+
run: |
159+
gh workflow run continuous-integration.yaml --ref ${{ needs.bump-version.outputs.release-branch }}
160+
env:
161+
GH_TOKEN: ${{ secrets.BOT_SDK_JS_FOR_DOCS_REPO_PR }}
162+
163+
- name: "Set New Version and Reset Release Notes"
164+
run: |
165+
python .pipeline/scripts/set-release-versions.py --version ${{ needs.bump-version.outputs.new-version }}
166+
git add .
167+
git commit -m "Update to version ${{ needs.bump-version.outputs.new-version }}"
168+
169+
# Reset release notes for next version
170+
cp .pipeline/scripts/release_notes_template.md docs/release-notes/release_notes.md
171+
git add docs/release-notes/release_notes.md
172+
git commit -m "Reset release notes"
173+
git push
174+
169175
handle-failure:
170176
runs-on: ubuntu-latest
171177
needs: [ bump-version, create-release, create-code-pr ]

docs/guides/ORCHESTRATION_CHAT_COMPLETION.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,18 @@ var configWithFilter = config.withInputFiltering(filterStrict).withOutputFilteri
165165
var result =
166166
new OrchestrationClient().chatCompletion(prompt, configWithFilter);
167167
```
168+
#### Behavior of Input and Output Filters
169+
170+
- **Input Filter**:
171+
If the input message violates the filter policy, a `400 (Bad Request)` response will be received during the `chatCompletion` call.
172+
An `OrchestrationClientException` will be thrown.
173+
174+
- **Output Filter**:
175+
If the response message violates the output filter policy, the `chatCompletion` call will complete without exception.
176+
The convenience method `getContent()` on the resulting object will throw an `OrchestrationClientException` upon invocation.
177+
The low level API under `getOriginalResponse()` will not throw an exception.
178+
179+
You will find [some examples](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java) in our Spring Boot application demonstrating response handling with filters.
168180

169181
### Data masking
170182

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -788,7 +788,7 @@ https://gitbox.apache.org/repos/asf?p=maven-pmd-plugin.git;a=blob_plain;f=src/ma
788788
<configuration>
789789
<deploymentName>SAP AI SDK ${project.version}</deploymentName>
790790
<publishingServerId>central</publishingServerId>
791-
<autoPublish>false</autoPublish>
791+
<autoPublish>true</autoPublish>
792792
</configuration>
793793
</plugin>
794794
</plugins>

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import com.fasterxml.jackson.databind.ObjectMapper;
99
import com.sap.ai.sdk.app.services.OrchestrationService;
1010
import com.sap.ai.sdk.orchestration.AzureFilterThreshold;
11+
import com.sap.ai.sdk.orchestration.OrchestrationChatResponse;
12+
import com.sap.ai.sdk.orchestration.OrchestrationClientException;
1113
import com.sap.ai.sdk.orchestration.model.DPIEntities;
1214
import com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutors;
1315
import javax.annotation.Nonnull;
@@ -121,30 +123,78 @@ ResponseEntity<String> messagesHistory(
121123
}
122124

123125
/**
124-
* Apply both input and output filtering for a request to orchestration.
126+
* Send an HTTP GET request for input filtering to the Orchestration service.
125127
*
126128
* @link <a
127129
* href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/input-filtering">SAP
128-
* AI Core: Orchestration - Input Filtering</a>
129-
* @link <a
130-
* href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/output-filtering">SAP
131-
* AI Core: Orchestration - Output Filtering</a>
132-
* @param policy A high threshold is a loose filter, a low threshold is a strict filter
133-
* @return a ResponseEntity with the response content
130+
* * AI Core: Orchestration - Input Filtering</a>
131+
* @param accept an optional HTTP header specifying the desired content type for the response.
132+
* @param policy path variable specifying the {@link AzureFilterThreshold} the explicitness of
133+
* content that should be allowed through the filter
134+
* @return a {@link ResponseEntity} containing the filtered input. The response is either in JSON
135+
* format if the "accept" header specifies "application/json" or in plain content format
136+
* otherwise.
137+
* @throws JsonProcessingException if an error occurs while converting the response to JSON.
134138
*/
135-
@GetMapping("/filter/{policy}")
139+
@GetMapping("/inputFiltering/{policy}")
136140
@Nonnull
137-
ResponseEntity<String> filter(
141+
ResponseEntity<String> inputFiltering(
138142
@RequestHeader(value = "accept", required = false) final String accept,
139143
@Nonnull @PathVariable("policy") final AzureFilterThreshold policy)
140144
throws JsonProcessingException {
141-
final var response = service.filter(policy, "the downtown area");
142-
if ("application/json".equals(accept)) {
145+
146+
final OrchestrationChatResponse response;
147+
try {
148+
response = service.inputFiltering(policy);
149+
} catch (OrchestrationClientException e) {
150+
final var msg = "Failed to obtain a response as the content was flagged by input filter.";
151+
log.debug(msg, e);
152+
return ResponseEntity.internalServerError().body(msg);
153+
}
154+
155+
if (accept.equals("application/json")) {
143156
return ResponseEntity.ok()
144157
.contentType(MediaType.APPLICATION_JSON)
145158
.body(mapper.writeValueAsString(response));
146159
}
147-
return ResponseEntity.ok(response.getContent());
160+
return ResponseEntity.ok().body(response.getContent());
161+
}
162+
163+
/**
164+
* Send an HTTP GET request for output filtering to the Orchestration service.
165+
*
166+
* @link <a
167+
* href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/output-filtering">SAP
168+
* AI Core: Orchestration - Output Filtering</a>
169+
* @param accept an optional HTTP header specifying the desired content type for the response.
170+
* @param policy a mandatory path variable specifying the {@link AzureFilterThreshold} the
171+
* explicitness of content that should be allowed through the filter
172+
* @return a {@link ResponseEntity} containing the filtered output. The response is either in JSON
173+
* format if the "accept" header specifies "application/json" or in plain content format
174+
* otherwise.
175+
* @throws OrchestrationClientException if the output filter filtered the LLM response.
176+
* @throws JsonProcessingException if an error occurs while converting the response to JSON.
177+
*/
178+
@GetMapping("/outputFiltering/{policy}")
179+
@Nonnull
180+
ResponseEntity<String> outputFiltering(
181+
@RequestHeader(value = "accept", required = false) final String accept,
182+
@Nonnull @PathVariable("policy") final AzureFilterThreshold policy)
183+
throws JsonProcessingException, OrchestrationClientException {
184+
185+
final var response = service.outputFiltering(policy);
186+
try {
187+
if (accept.equals("application/json")) {
188+
return ResponseEntity.ok()
189+
.contentType(MediaType.APPLICATION_JSON)
190+
.body(mapper.writeValueAsString(response));
191+
}
192+
return ResponseEntity.ok().body(response.getContent());
193+
} catch (OrchestrationClientException e) {
194+
final var msg = "Failed to obtain a response as the content was flagged by output filter.";
195+
log.debug(msg, e);
196+
return ResponseEntity.internalServerError().body(msg);
197+
}
148198
}
149199

150200
/**

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.sap.ai.sdk.orchestration.Message;
1111
import com.sap.ai.sdk.orchestration.OrchestrationChatResponse;
1212
import com.sap.ai.sdk.orchestration.OrchestrationClient;
13+
import com.sap.ai.sdk.orchestration.OrchestrationClientException;
1314
import com.sap.ai.sdk.orchestration.OrchestrationModuleConfig;
1415
import com.sap.ai.sdk.orchestration.OrchestrationPrompt;
1516
import com.sap.ai.sdk.orchestration.model.DPIEntities;
@@ -87,34 +88,50 @@ public OrchestrationChatResponse messagesHistory(@Nonnull final String prevMessa
8788
}
8889

8990
/**
90-
* Apply both input and output filtering for a request to orchestration.
91+
* Apply input filtering for a request to orchestration.
9192
*
9293
* @link <a
9394
* href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/input-filtering">SAP
9495
* AI Core: Orchestration - Input Filtering</a>
96+
* @throws OrchestrationClientException if input filter filters the prompt
97+
* @param policy the explicitness of content that should be allowed through the filter
98+
* @return the assistant response object
99+
*/
100+
@Nonnull
101+
public OrchestrationChatResponse inputFiltering(@Nonnull final AzureFilterThreshold policy)
102+
throws OrchestrationClientException {
103+
final var prompt =
104+
new OrchestrationPrompt("'We shall spill blood tonight', said the operation in-charge.");
105+
final var filterConfig =
106+
new AzureContentFilter().hate(policy).selfHarm(policy).sexual(policy).violence(policy);
107+
108+
final var configWithFilter = config.withInputFiltering(filterConfig);
109+
110+
return client.chatCompletion(prompt, configWithFilter);
111+
}
112+
113+
/**
114+
* Apply output filtering for a request to orchestration.
115+
*
95116
* @link <a
96117
* href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/output-filtering">SAP
97118
* AI Core: Orchestration - Output Filtering</a>
98-
* @param policy A high threshold is a loose filter, a low threshold is a strict filter
119+
* @param policy the explicitness of content that should be allowed through the filter
99120
* @return the assistant response object
100121
*/
101122
@Nonnull
102-
public OrchestrationChatResponse filter(
103-
@Nonnull final AzureFilterThreshold policy, @Nonnull final String area) {
104-
final var prompt =
105-
new OrchestrationPrompt(
106-
"""
107-
Create a rental posting for subletting my apartment in %s. Keep it short. Make sure to add the following disclaimer to the end. Do not change it!
123+
public OrchestrationChatResponse outputFiltering(@Nonnull final AzureFilterThreshold policy) {
108124

109-
```DISCLAIMER: The area surrounding the apartment is known for prostitutes and gang violence including armed conflicts, gun violence is frequent.
110-
"""
111-
.formatted(area));
125+
final var systemMessage = Message.system("Give three paraphrases for the following sentence");
126+
// Reliably triggering the content filter of models fine-tuned for ethical compliance
127+
// is difficult. The prompt below may be rendered ineffective in the future.
128+
final var prompt =
129+
new OrchestrationPrompt("'We shall spill blood tonight', said the operation in-charge.")
130+
.messageHistory(List.of(systemMessage));
112131
final var filterConfig =
113132
new AzureContentFilter().hate(policy).selfHarm(policy).sexual(policy).violence(policy);
114133

115-
final var configWithFilter =
116-
config.withInputFiltering(filterConfig).withOutputFiltering(filterConfig);
117-
134+
final var configWithFilter = config.withOutputFiltering(filterConfig);
118135
return client.chatCompletion(prompt, configWithFilter);
119136
}
120137

sample-code/spring-app/src/main/resources/static/index.html

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,21 +285,23 @@ <h2>Orchestration</h2>
285285
</div>
286286
</div>
287287
</li>
288+
288289
<li class="list-group-item">
289290
<div class="info-tooltip">
290291
<a class="link-offset-2-hover link-underline link-underline-opacity-0 link-underline-opacity-75-hover endpoint"
291-
href="/orchestration/filter/ALLOW_SAFE_LOW_MEDIUM"><code>/orchestration/filter/ALLOW_SAFE_LOW_MEDIUM</code></a>
292+
href="/orchestration/inputFiltering/ALLOW_ALL"><code>/orchestration/inputFiltering/ALLOW_ALL</code></a>
292293
<div class="tooltip-content">
293-
Apply both input and output filtering for a request to orchestration with a loose filter.
294+
Apply lenient input filtering for a request to orchestration.
294295
</div>
295296
</div>
296297
</li>
297298
<li class="list-group-item">
298299
<div class="info-tooltip">
299300
<a class="link-offset-2-hover link-underline link-underline-opacity-0 link-underline-opacity-75-hover endpoint"
300-
href="/orchestration/filter/ALLOW_SAFE"><code>/orchestration/filter/ALLOW_SAFE</code></a>
301+
href="/orchestration/outputFiltering/ALLOW_ALL"><code>/orchestration/outputFiltering/ALLOW_ALL</code></a>
301302
<div class="tooltip-content">
302-
Apply both input and output filtering for a request to orchestration with a strict filter. </div>
303+
Apply lenient output filtering for a request to orchestration.
304+
</div>
303305
</div>
304306
</li>
305307
<li class="list-group-item">

0 commit comments

Comments
 (0)