Repository navigation
fix: validate Bedrock conversation messages #857
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,6 @@ | |
| import io.kestra.plugin.aws.shared.ConnectionUtils; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import jakarta.validation.constraints.NotEmpty; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import lombok.*; | ||
| import lombok.experimental.SuperBuilder; | ||
|
|
@@ -84,7 +83,7 @@ public class Converse extends AbstractConnection implements RunnableTask<Convers | |
| description = "Ordered list of conversation turns. Each entry must have a `role` (`user` or `assistant`) and `content` (text string)." | ||
| ) | ||
| @PluginProperty(group = "main") | ||
| @NotEmpty | ||
| @NotNull | ||
| private Property<List<Map<String, String>>> messages; | ||
|
|
||
| @Schema( | ||
|
|
@@ -115,6 +114,9 @@ public Output run(RunContext runContext) throws Exception { | |
| } catch (ClassCastException e) { | ||
| throw new IllegalArgumentException("messages must be a list of {role, content} maps", e); | ||
| } | ||
| if (rawMessages.isEmpty()) { | ||
| throw new IllegalArgumentException("messages must not be empty"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 [Guidelines] Error message does not say what to do |
||
| } | ||
|
|
||
| var sdkMessages = BedrockUtils.buildMessages(rawMessages); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,11 +11,14 @@ | |
| import io.kestra.core.runners.RunContextFactory; | ||
|
|
||
| import jakarta.inject.Inject; | ||
| import jakarta.validation.MessageInterpolator; | ||
| import jakarta.validation.Validation; | ||
| import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; | ||
| import software.amazon.awssdk.services.bedrockruntime.model.*; | ||
|
|
||
| import static org.hamcrest.MatcherAssert.assertThat; | ||
| import static org.hamcrest.Matchers.*; | ||
| import static org.junit.jupiter.api.Assertions.assertThrows; | ||
| import static org.mockito.ArgumentMatchers.any; | ||
| import static org.mockito.Mockito.*; | ||
|
|
||
|
|
@@ -77,4 +80,51 @@ void givenValidMessages_whenConverse_thenOutputContainsContent() throws Exceptio | |
| assertThat(output.getInputTokens(), is(10)); | ||
| assertThat(output.getOutputTokens(), is(15)); | ||
| } | ||
|
|
||
| @Test | ||
| void givenEmptyMessages_whenConverse_thenThrowsExpectedException() { | ||
| RunContext runContext = runContextFactory.of(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟢 [Guidelines] Explicit local types and fixed task ids |
||
|
|
||
| Converse task = Converse.builder() | ||
| .id("test-converse-empty-messages") | ||
| .type(Converse.class.getName()) | ||
| .region(Property.ofValue("us-east-1")) | ||
| .modelId(Property.ofValue("anthropic.claude-3-5-sonnet-20241022-v2:0")) | ||
| .messages(Property.ofValue(List.of())) | ||
| .build(); | ||
|
|
||
| var exception = assertThrows(IllegalArgumentException.class, () -> task.run(runContext)); | ||
|
|
||
| assertThat(exception.getMessage(), is("messages must not be empty")); | ||
| } | ||
|
|
||
| @Test | ||
| void givenValidMessages_whenValidated_thenDoesNotProduceHv000030() { | ||
| Converse task = Converse.builder() | ||
| .id("test-converse-validation") | ||
| .type(Converse.class.getName()) | ||
| .region(Property.ofValue("us-east-1")) | ||
| .modelId(Property.ofValue("anthropic.claude-3-5-sonnet-20241022-v2:0")) | ||
| .messages(Property.ofValue(List.of(Map.of("role", "user", "content", "Hello!")))) | ||
| .build(); | ||
|
|
||
| var violations = Validation.byDefaultProvider() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 [Tests] Hand-rolled validator instead of ModelValidator |
||
| .configure() | ||
| .messageInterpolator(new MessageInterpolator() { | ||
| @Override | ||
| public String interpolate(String messageTemplate, Context context) { | ||
| return messageTemplate; | ||
| } | ||
|
|
||
| @Override | ||
| public String interpolate(String messageTemplate, Context context, java.util.Locale locale) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟢 [Guidelines] Fully qualified java.util.Locale |
||
| return messageTemplate; | ||
| } | ||
| }) | ||
| .buildValidatorFactory() | ||
| .getValidator() | ||
| .validate(task); | ||
|
|
||
| assertThat(violations, empty()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 [Guidelines] Empty check duplicated in both tasks
Problem: The same
rawMessages.isEmpty()guard is copied intoConverseandConverseStream(line 120), so the two can drift apart; the guidelines ask for shared behaviour to be extracted (DRY).Fix: Move the check to the top of
BedrockUtils.buildMessages(...), which both tasks already call with the rendered list, and drop it from the tworun()methods.