From dffa92a15a4ce74d183e3ffc6e47deb6d14f329a Mon Sep 17 00:00:00 2001 From: Siri Varma Vegiraju Date: Thu, 11 Sep 2025 15:03:11 -0700 Subject: [PATCH 1/2] Add Tool Calling to Java SDK (#1481) * Use Java Bean for connection details and add more tests (#1317) * Use Java Bean for connection details and add more tests Signed-off-by: Artur Ciocanu * Simplify mock setup Signed-off-by: Artur Ciocanu * Adding even more tests for test coverage Signed-off-by: Artur Ciocanu --------- Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Co-authored-by: Cassie Coyle Signed-off-by: sirivarma * Update CONTRIBUTING.md Signed-off-by: Siri Varma Vegiraju Signed-off-by: sirivarma * Bump codecov/codecov-action from 5.4.0 to 5.4.2 (#1318) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.4.0 to 5.4.2. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.4.0...v5.4.2) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.4.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Cassie Coyle Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com> Signed-off-by: sirivarma * Fix URL building logic (#1320) * Fix URL building logic Signed-off-by: Artur Ciocanu * Add test for query params Signed-off-by: Artur Ciocanu * Fix the assertion in the test Signed-off-by: Artur Ciocanu * Adjust the tests Signed-off-by: Artur Ciocanu * Remove uneeded changes from IT test Signed-off-by: Artur Ciocanu * Revert some unintended changes Signed-off-by: Artur Ciocanu * Simplify the testing a little bit Signed-off-by: Artur Ciocanu * Adjust the test to use ServerRequest Signed-off-by: Artur Ciocanu * Test removing things from method invoke controller Signed-off-by: Artur Ciocanu * Add query param encoding test Signed-off-by: Artur Ciocanu * Revert some unintended changes Signed-off-by: Artur Ciocanu * Some tiny styles Signed-off-by: Artur Ciocanu --------- Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Signed-off-by: sirivarma * Generate updated javadocs for 1.14.1 Signed-off-by: Dapr Bot Signed-off-by: sirivarma * Add Conversation AI to Java SDK (#1235) * Conversation first commit Signed-off-by: Siri Varma Vegiraju Signed-off-by: sirivarma Signed-off-by: siri-varma * Add unit tests Signed-off-by: sirivarma Signed-off-by: siri-varma * change ai to conv Signed-off-by: sirivarma Signed-off-by: siri-varma * Move to single module Signed-off-by: sirivarma Signed-off-by: siri-varma * Remove module Signed-off-by: sirivarma Signed-off-by: siri-varma * Add Integration tests Signed-off-by: siri-varma * Update sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java Co-authored-by: Cassie Coyle Signed-off-by: Siri Varma Vegiraju Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Address comments Signed-off-by: siri-varma * Import tag Signed-off-by: siri-varma * Address comments Signed-off-by: siri-varma * Make common config Signed-off-by: siri-varma * Address comments Signed-off-by: siri-varma * fix constant Signed-off-by: siri-varma * fix constant Signed-off-by: siri-varma * fix constant Signed-off-by: siri-varma * fix s Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Make common config Signed-off-by: siri-varma * Update README.md Signed-off-by: Siri Varma Vegiraju * Update README.md Signed-off-by: Siri Varma Vegiraju --------- Signed-off-by: Siri Varma Vegiraju Signed-off-by: sirivarma Signed-off-by: siri-varma Signed-off-by: Siri Varma Vegiraju Co-authored-by: Cassie Coyle Co-authored-by: Cassie Coyle Signed-off-by: sirivarma * Add docs for usage of Jobs SDK (#1323) * Add doc for jobs Signed-off-by: siri-varma * Add docs for Jobs Signed-off-by: siri-varma * Apply suggestions from code review Co-authored-by: Cassie Coyle Signed-off-by: Siri Varma Vegiraju --------- Signed-off-by: siri-varma Signed-off-by: Siri Varma Vegiraju Co-authored-by: artur-ciocanu Co-authored-by: Cassie Coyle Signed-off-by: sirivarma * Use dapr/durabletask-java (#1336) * microsoft durabletask-java -> dapr durabletask-java Signed-off-by: Cassandra Coyle * update another ref Signed-off-by: Cassandra Coyle * 1.5.2 release Signed-off-by: Cassandra Coyle * fix import order Signed-off-by: Cassandra Coyle * Sdk new changes Signed-off-by: siri-varma * Refine workflows Signed-off-by: siri-varma * add ; Signed-off-by: Cassandra Coyle * rm try Signed-off-by: Cassandra Coyle --------- Signed-off-by: Cassandra Coyle Signed-off-by: siri-varma Co-authored-by: siri-varma Signed-off-by: sirivarma * Update master version to 1.16.0-SNAPSHOT Signed-off-by: Dapr Bot Signed-off-by: sirivarma * Renaming and exposing connection details (#1341) Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Signed-off-by: sirivarma * [Master] Fix Vulnerabilities (#1354) * update okio Signed-off-by: Cassandra Coyle * rm unused dep Signed-off-by: Cassandra Coyle --------- Signed-off-by: Cassandra Coyle Signed-off-by: sirivarma * Feat Add TLS & mTLS support for gRPC with root CA and insecure mode (#1361) * feat: Support for GRPC ssl Signed-off-by: Javier Aliaga * add tests Signed-off-by: Cassandra Coyle * fix CI Signed-off-by: Cassandra Coyle * add back else if Signed-off-by: Cassandra Coyle * channel cleanup Signed-off-by: Cassandra Coyle * add root ca support Signed-off-by: Cassandra Coyle * checkstyles Signed-off-by: Cassandra Coyle * add insecure Signed-off-by: Cassandra Coyle * fix checkstyles Signed-off-by: Cassandra Coyle * use InsecureTrustManagerFactory Signed-off-by: Cassandra Coyle * fix test Signed-off-by: Cassandra Coyle --------- Signed-off-by: Javier Aliaga Signed-off-by: Cassandra Coyle Co-authored-by: Javier Aliaga Signed-off-by: sirivarma * Fix the issue with retries not happening correctly for Activities and Workflows (#1343) * Add coverage for some properties (#1297) Signed-off-by: sirivarma * Make the DAPR version being used consistent across all tests (#1299) Signed-off-by: sirivarma * Separate Dapr constants from IT container constants (#1315) Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Signed-off-by: sirivarma * Use Java Bean for connection details and add more tests (#1317) * Use Java Bean for connection details and add more tests Signed-off-by: Artur Ciocanu * Simplify mock setup Signed-off-by: Artur Ciocanu * Adding even more tests for test coverage Signed-off-by: Artur Ciocanu --------- Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Co-authored-by: Cassie Coyle Signed-off-by: sirivarma * Update CONTRIBUTING.md Signed-off-by: Siri Varma Vegiraju Signed-off-by: sirivarma * Bump codecov/codecov-action from 5.4.0 to 5.4.2 (#1318) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.4.0 to 5.4.2. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.4.0...v5.4.2) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.4.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Cassie Coyle Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com> Signed-off-by: sirivarma * Fix URL building logic (#1320) * Fix URL building logic Signed-off-by: Artur Ciocanu * Add test for query params Signed-off-by: Artur Ciocanu * Fix the assertion in the test Signed-off-by: Artur Ciocanu * Adjust the tests Signed-off-by: Artur Ciocanu * Remove uneeded changes from IT test Signed-off-by: Artur Ciocanu * Revert some unintended changes Signed-off-by: Artur Ciocanu * Simplify the testing a little bit Signed-off-by: Artur Ciocanu * Adjust the test to use ServerRequest Signed-off-by: Artur Ciocanu * Test removing things from method invoke controller Signed-off-by: Artur Ciocanu * Add query param encoding test Signed-off-by: Artur Ciocanu * Revert some unintended changes Signed-off-by: Artur Ciocanu * Some tiny styles Signed-off-by: Artur Ciocanu --------- Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Signed-off-by: sirivarma * Generate updated javadocs for 1.14.1 Signed-off-by: Dapr Bot Signed-off-by: sirivarma * Add Conversation AI to Java SDK (#1235) * Conversation first commit Signed-off-by: Siri Varma Vegiraju Signed-off-by: sirivarma Signed-off-by: siri-varma * Add unit tests Signed-off-by: sirivarma Signed-off-by: siri-varma * change ai to conv Signed-off-by: sirivarma Signed-off-by: siri-varma * Move to single module Signed-off-by: sirivarma Signed-off-by: siri-varma * Remove module Signed-off-by: sirivarma Signed-off-by: siri-varma * Add Integration tests Signed-off-by: siri-varma * Update sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java Co-authored-by: Cassie Coyle Signed-off-by: Siri Varma Vegiraju Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Address comments Signed-off-by: siri-varma * Import tag Signed-off-by: siri-varma * Address comments Signed-off-by: siri-varma * Make common config Signed-off-by: siri-varma * Address comments Signed-off-by: siri-varma * fix constant Signed-off-by: siri-varma * fix constant Signed-off-by: siri-varma * fix constant Signed-off-by: siri-varma * fix s Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Make common config Signed-off-by: siri-varma * Update README.md Signed-off-by: Siri Varma Vegiraju * Update README.md Signed-off-by: Siri Varma Vegiraju --------- Signed-off-by: Siri Varma Vegiraju Signed-off-by: sirivarma Signed-off-by: siri-varma Signed-off-by: Siri Varma Vegiraju Co-authored-by: Cassie Coyle Co-authored-by: Cassie Coyle Signed-off-by: sirivarma * Add docs for usage of Jobs SDK (#1323) * Add doc for jobs Signed-off-by: siri-varma * Add docs for Jobs Signed-off-by: siri-varma * Apply suggestions from code review Co-authored-by: Cassie Coyle Signed-off-by: Siri Varma Vegiraju --------- Signed-off-by: siri-varma Signed-off-by: Siri Varma Vegiraju Co-authored-by: artur-ciocanu Co-authored-by: Cassie Coyle Signed-off-by: sirivarma * Use dapr/durabletask-java (#1336) * microsoft durabletask-java -> dapr durabletask-java Signed-off-by: Cassandra Coyle * update another ref Signed-off-by: Cassandra Coyle * 1.5.2 release Signed-off-by: Cassandra Coyle * fix import order Signed-off-by: Cassandra Coyle * Sdk new changes Signed-off-by: siri-varma * Refine workflows Signed-off-by: siri-varma * add ; Signed-off-by: Cassandra Coyle * rm try Signed-off-by: Cassandra Coyle --------- Signed-off-by: Cassandra Coyle Signed-off-by: siri-varma Co-authored-by: siri-varma Signed-off-by: sirivarma * Update master version to 1.16.0-SNAPSHOT Signed-off-by: Dapr Bot Signed-off-by: sirivarma * Fix NPE Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix NPE Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix NPE Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix NPE Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix NPE Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix NPE Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix things Signed-off-by: siri-varma Signed-off-by: sirivarma * Renaming and exposing connection details (#1341) Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Signed-off-by: sirivarma * [Master] Fix Vulnerabilities (#1354) * update okio Signed-off-by: Cassandra Coyle * rm unused dep Signed-off-by: Cassandra Coyle --------- Signed-off-by: Cassandra Coyle Signed-off-by: sirivarma * Feat Add TLS & mTLS support for gRPC with root CA and insecure mode (#1361) * feat: Support for GRPC ssl Signed-off-by: Javier Aliaga * add tests Signed-off-by: Cassandra Coyle * fix CI Signed-off-by: Cassandra Coyle * add back else if Signed-off-by: Cassandra Coyle * channel cleanup Signed-off-by: Cassandra Coyle * add root ca support Signed-off-by: Cassandra Coyle * checkstyles Signed-off-by: Cassandra Coyle * add insecure Signed-off-by: Cassandra Coyle * fix checkstyles Signed-off-by: Cassandra Coyle * use InsecureTrustManagerFactory Signed-off-by: Cassandra Coyle * fix test Signed-off-by: Cassandra Coyle --------- Signed-off-by: Javier Aliaga Signed-off-by: Cassandra Coyle Co-authored-by: Javier Aliaga Signed-off-by: sirivarma * Address comments Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix things Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix things Signed-off-by: siri-varma Signed-off-by: sirivarma --------- Signed-off-by: sirivarma Signed-off-by: Artur Ciocanu Signed-off-by: Siri Varma Vegiraju Signed-off-by: dependabot[bot] Signed-off-by: Dapr Bot Signed-off-by: Siri Varma Vegiraju Signed-off-by: siri-varma Signed-off-by: Cassandra Coyle Signed-off-by: Javier Aliaga Co-authored-by: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com> Co-authored-by: artur-ciocanu Co-authored-by: Artur Ciocanu Co-authored-by: Cassie Coyle Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com> Co-authored-by: Dapr Bot Co-authored-by: Cassie Coyle Co-authored-by: Javier Aliaga Signed-off-by: sirivarma * 1.5.4 (#1375) Signed-off-by: Cassandra Coyle Signed-off-by: sirivarma * Bump codecov/codecov-action from 5.4.2 to 5.4.3 (#1379) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.4.2 to 5.4.3. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.4.2...v5.4.3) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.4.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: sirivarma * Bump fossas/fossa-action from 1.6.0 to 1.7.0 (#1380) Bumps [fossas/fossa-action](https://github.com/fossas/fossa-action) from 1.6.0 to 1.7.0. - [Release notes](https://github.com/fossas/fossa-action/releases) - [Commits](https://github.com/fossas/fossa-action/compare/v1.6.0...v1.7.0) --- updated-dependencies: - dependency-name: fossas/fossa-action dependency-version: 1.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Cassie Coyle Signed-off-by: sirivarma * Fix component spec parsing (#1370) * Fix component spec parsing Signed-off-by: Deepak * Fix component spec parsing Signed-off-by: Deepak * Fix component spec metadata parsing Signed-off-by: Deepak * fix checkstyle-error Signed-off-by: Deepak --------- Signed-off-by: Deepak Co-authored-by: Cassie Coyle Co-authored-by: artur-ciocanu Signed-off-by: sirivarma * chore: Add grpc keepalives (#1382) * chore: Add grpc keepalives Signed-off-by: Javier Aliaga * chore: Make grpc keepalive configurable Signed-off-by: Javier Aliaga * chore: Fix review comments Signed-off-by: Javier Aliaga * chore: Missing keepalive config for GRPC TLS INSECURE Signed-off-by: Javier Aliaga * chore: Add test Signed-off-by: Javier Aliaga * fix: Comment typo Signed-off-by: Javier Aliaga --------- Signed-off-by: Javier Aliaga Signed-off-by: sirivarma * Fix : Typo in code comments (#1381) * Fix Typo in Comments Signed-off-by: Deepak * Update review comments Signed-off-by: Deepak --------- Signed-off-by: Deepak Co-authored-by: artur-ciocanu Signed-off-by: sirivarma * Spring boot workflow patterns examples with mechanical markdown and tests (#1377) * Feat Add TLS & mTLS support for gRPC with root CA and insecure mode (#1361) * feat: Support for GRPC ssl Signed-off-by: Javier Aliaga * add tests Signed-off-by: Cassandra Coyle * fix CI Signed-off-by: Cassandra Coyle * add back else if Signed-off-by: Cassandra Coyle * channel cleanup Signed-off-by: Cassandra Coyle * add root ca support Signed-off-by: Cassandra Coyle * checkstyles Signed-off-by: Cassandra Coyle * add insecure Signed-off-by: Cassandra Coyle * fix checkstyles Signed-off-by: Cassandra Coyle * use InsecureTrustManagerFactory Signed-off-by: Cassandra Coyle * fix test Signed-off-by: Cassandra Coyle --------- Signed-off-by: Javier Aliaga Signed-off-by: Cassandra Coyle Co-authored-by: Javier Aliaga Signed-off-by: salaboy * spring boot workflow patterns initial version Signed-off-by: salaboy * adding README for workflows Signed-off-by: salaboy * adding child example Signed-off-by: salaboy * updating examples to work with markdown tests Signed-off-by: salaboy * running mechanical markdown for workflow examples Signed-off-by: salaboy * Fix the issue with retries not happening correctly for Activities and Workflows (#1343) * Add coverage for some properties (#1297) Signed-off-by: sirivarma * Make the DAPR version being used consistent across all tests (#1299) Signed-off-by: sirivarma * Separate Dapr constants from IT container constants (#1315) Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Signed-off-by: sirivarma * Use Java Bean for connection details and add more tests (#1317) * Use Java Bean for connection details and add more tests Signed-off-by: Artur Ciocanu * Simplify mock setup Signed-off-by: Artur Ciocanu * Adding even more tests for test coverage Signed-off-by: Artur Ciocanu --------- Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Co-authored-by: Cassie Coyle Signed-off-by: sirivarma * Update CONTRIBUTING.md Signed-off-by: Siri Varma Vegiraju Signed-off-by: sirivarma * Bump codecov/codecov-action from 5.4.0 to 5.4.2 (#1318) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.4.0 to 5.4.2. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.4.0...v5.4.2) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.4.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Cassie Coyle Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com> Signed-off-by: sirivarma * Fix URL building logic (#1320) * Fix URL building logic Signed-off-by: Artur Ciocanu * Add test for query params Signed-off-by: Artur Ciocanu * Fix the assertion in the test Signed-off-by: Artur Ciocanu * Adjust the tests Signed-off-by: Artur Ciocanu * Remove uneeded changes from IT test Signed-off-by: Artur Ciocanu * Revert some unintended changes Signed-off-by: Artur Ciocanu * Simplify the testing a little bit Signed-off-by: Artur Ciocanu * Adjust the test to use ServerRequest Signed-off-by: Artur Ciocanu * Test removing things from method invoke controller Signed-off-by: Artur Ciocanu * Add query param encoding test Signed-off-by: Artur Ciocanu * Revert some unintended changes Signed-off-by: Artur Ciocanu * Some tiny styles Signed-off-by: Artur Ciocanu --------- Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Signed-off-by: sirivarma * Generate updated javadocs for 1.14.1 Signed-off-by: Dapr Bot Signed-off-by: sirivarma * Add Conversation AI to Java SDK (#1235) * Conversation first commit Signed-off-by: Siri Varma Vegiraju Signed-off-by: sirivarma Signed-off-by: siri-varma * Add unit tests Signed-off-by: sirivarma Signed-off-by: siri-varma * change ai to conv Signed-off-by: sirivarma Signed-off-by: siri-varma * Move to single module Signed-off-by: sirivarma Signed-off-by: siri-varma * Remove module Signed-off-by: sirivarma Signed-off-by: siri-varma * Add Integration tests Signed-off-by: siri-varma * Update sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java Co-authored-by: Cassie Coyle Signed-off-by: Siri Varma Vegiraju Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Address comments Signed-off-by: siri-varma * Import tag Signed-off-by: siri-varma * Address comments Signed-off-by: siri-varma * Make common config Signed-off-by: siri-varma * Address comments Signed-off-by: siri-varma * fix constant Signed-off-by: siri-varma * fix constant Signed-off-by: siri-varma * fix constant Signed-off-by: siri-varma * fix s Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Make common config Signed-off-by: siri-varma * Update README.md Signed-off-by: Siri Varma Vegiraju * Update README.md Signed-off-by: Siri Varma Vegiraju --------- Signed-off-by: Siri Varma Vegiraju Signed-off-by: sirivarma Signed-off-by: siri-varma Signed-off-by: Siri Varma Vegiraju Co-authored-by: Cassie Coyle Co-authored-by: Cassie Coyle Signed-off-by: sirivarma * Add docs for usage of Jobs SDK (#1323) * Add doc for jobs Signed-off-by: siri-varma * Add docs for Jobs Signed-off-by: siri-varma * Apply suggestions from code review Co-authored-by: Cassie Coyle Signed-off-by: Siri Varma Vegiraju --------- Signed-off-by: siri-varma Signed-off-by: Siri Varma Vegiraju Co-authored-by: artur-ciocanu Co-authored-by: Cassie Coyle Signed-off-by: sirivarma * Use dapr/durabletask-java (#1336) * microsoft durabletask-java -> dapr durabletask-java Signed-off-by: Cassandra Coyle * update another ref Signed-off-by: Cassandra Coyle * 1.5.2 release Signed-off-by: Cassandra Coyle * fix import order Signed-off-by: Cassandra Coyle * Sdk new changes Signed-off-by: siri-varma * Refine workflows Signed-off-by: siri-varma * add ; Signed-off-by: Cassandra Coyle * rm try Signed-off-by: Cassandra Coyle --------- Signed-off-by: Cassandra Coyle Signed-off-by: siri-varma Co-authored-by: siri-varma Signed-off-by: sirivarma * Update master version to 1.16.0-SNAPSHOT Signed-off-by: Dapr Bot Signed-off-by: sirivarma * Fix NPE Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix NPE Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix NPE Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix NPE Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix NPE Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix NPE Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix things Signed-off-by: siri-varma Signed-off-by: sirivarma * Renaming and exposing connection details (#1341) Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Signed-off-by: sirivarma * [Master] Fix Vulnerabilities (#1354) * update okio Signed-off-by: Cassandra Coyle * rm unused dep Signed-off-by: Cassandra Coyle --------- Signed-off-by: Cassandra Coyle Signed-off-by: sirivarma * Feat Add TLS & mTLS support for gRPC with root CA and insecure mode (#1361) * feat: Support for GRPC ssl Signed-off-by: Javier Aliaga * add tests Signed-off-by: Cassandra Coyle * fix CI Signed-off-by: Cassandra Coyle * add back else if Signed-off-by: Cassandra Coyle * channel cleanup Signed-off-by: Cassandra Coyle * add root ca support Signed-off-by: Cassandra Coyle * checkstyles Signed-off-by: Cassandra Coyle * add insecure Signed-off-by: Cassandra Coyle * fix checkstyles Signed-off-by: Cassandra Coyle * use InsecureTrustManagerFactory Signed-off-by: Cassandra Coyle * fix test Signed-off-by: Cassandra Coyle --------- Signed-off-by: Javier Aliaga Signed-off-by: Cassandra Coyle Co-authored-by: Javier Aliaga Signed-off-by: sirivarma * Address comments Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix things Signed-off-by: siri-varma Signed-off-by: sirivarma * Fix things Signed-off-by: siri-varma Signed-off-by: sirivarma --------- Signed-off-by: sirivarma Signed-off-by: Artur Ciocanu Signed-off-by: Siri Varma Vegiraju Signed-off-by: dependabot[bot] Signed-off-by: Dapr Bot Signed-off-by: Siri Varma Vegiraju Signed-off-by: siri-varma Signed-off-by: Cassandra Coyle Signed-off-by: Javier Aliaga Co-authored-by: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com> Co-authored-by: artur-ciocanu Co-authored-by: Artur Ciocanu Co-authored-by: Cassie Coyle Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com> Co-authored-by: Dapr Bot Co-authored-by: Cassie Coyle Co-authored-by: Javier Aliaga Signed-off-by: salaboy * 1.5.4 (#1375) Signed-off-by: Cassandra Coyle Signed-off-by: salaboy * fixing order Id correlation Signed-off-by: salaboy * fixing waiting time for tests to run Signed-off-by: salaboy * fixing app name Signed-off-by: salaboy * adding app name and removing log lines Signed-off-by: salaboy * Bump codecov/codecov-action from 5.4.2 to 5.4.3 (#1379) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.4.2 to 5.4.3. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.4.2...v5.4.3) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.4.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: salaboy * Bump fossas/fossa-action from 1.6.0 to 1.7.0 (#1380) Bumps [fossas/fossa-action](https://github.com/fossas/fossa-action) from 1.6.0 to 1.7.0. - [Release notes](https://github.com/fossas/fossa-action/releases) - [Commits](https://github.com/fossas/fossa-action/compare/v1.6.0...v1.7.0) --- updated-dependencies: - dependency-name: fossas/fossa-action dependency-version: 1.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Cassie Coyle Signed-off-by: salaboy * Fix component spec parsing (#1370) * Fix component spec parsing Signed-off-by: Deepak * Fix component spec parsing Signed-off-by: Deepak * Fix component spec metadata parsing Signed-off-by: Deepak * fix checkstyle-error Signed-off-by: Deepak --------- Signed-off-by: Deepak Co-authored-by: Cassie Coyle Co-authored-by: artur-ciocanu Signed-off-by: salaboy * Update spring-boot-examples/workflows/README.md Co-authored-by: Cassie Coyle Signed-off-by: salaboy * fixing comments Signed-off-by: salaboy * Update body.json Signed-off-by: artur-ciocanu Signed-off-by: salaboy * Update FanOutInWorkflow.java Signed-off-by: artur-ciocanu Signed-off-by: salaboy * clean up logs for multiple executions, for standalone mode Signed-off-by: salaboy * Update application.properties Signed-off-by: artur-ciocanu --------- Signed-off-by: Javier Aliaga Signed-off-by: Cassandra Coyle Signed-off-by: salaboy Signed-off-by: sirivarma Signed-off-by: Artur Ciocanu Signed-off-by: Siri Varma Vegiraju Signed-off-by: dependabot[bot] Signed-off-by: Dapr Bot Signed-off-by: Siri Varma Vegiraju Signed-off-by: siri-varma Signed-off-by: Deepak Signed-off-by: artur-ciocanu Co-authored-by: Cassie Coyle Co-authored-by: Javier Aliaga Co-authored-by: Siri Varma Vegiraju Co-authored-by: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com> Co-authored-by: artur-ciocanu Co-authored-by: Artur Ciocanu Co-authored-by: Cassie Coyle Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com> Co-authored-by: Dapr Bot Co-authored-by: iddeepak <87892182+iddeepak@users.noreply.github.com> Signed-off-by: sirivarma * System Properties + Env Var Docs (#1384) * add properties file to docs and align system properties to env var name convention with . as _ Signed-off-by: Cassandra Coyle * reset env var to what it was Signed-off-by: Cassandra Coyle --------- Signed-off-by: Cassandra Coyle Co-authored-by: artur-ciocanu Signed-off-by: sirivarma * Updating docs to have spring boot 3.x banner and not hardcoded deps (#1366) * updating docs to have spring boot 3.x banner and not hardcoded deps Signed-off-by: salaboy * Update _index.md Signed-off-by: salaboy * Update _index.md Adding links to discord and github issues Signed-off-by: salaboy --------- Signed-off-by: salaboy Co-authored-by: artur-ciocanu Signed-off-by: sirivarma * Cleaup Test Dependencies (#1332) * Update CONTRIBUTING.md Signed-off-by: Siri Varma Vegiraju * Fix things Signed-off-by: sirivarma * Fix things? Signed-off-by: sirivarma * Fix logback version Signed-off-by: siri-varma * Fix logback version Signed-off-by: siri-varma * Add pom Signed-off-by: siri-varma * Add pom Signed-off-by: siri-varma * Add properties Signed-off-by: siri-varma * Added test scope Signed-off-by: siri-varma * MArk as test Signed-off-by: siri-varma * Update pom.xml Signed-off-by: Siri Varma Vegiraju * Update pom.xml Signed-off-by: Siri Varma Vegiraju --------- Signed-off-by: Siri Varma Vegiraju Signed-off-by: sirivarma Signed-off-by: siri-varma Co-authored-by: artur-ciocanu Signed-off-by: sirivarma * chore: New task execution task id test (#1352) * chore: New task execution task id test test how taskExecutionTaskId can be used for idempotency Signed-off-by: Javier Aliaga * chore: Clean up not used files Signed-off-by: Javier Aliaga * docs: Task execution keys Signed-off-by: Javier Aliaga * test: Modify unit tests Signed-off-by: Javier Aliaga * Remove new lines Signed-off-by: artur-ciocanu --------- Signed-off-by: Javier Aliaga Signed-off-by: artur-ciocanu Co-authored-by: Cassie Coyle Co-authored-by: artur-ciocanu Signed-off-by: sirivarma * Revert "chore: New task execution task id test (#1352)" (#1389) This reverts commit 949584f69f4bc78ca59abe6ad000f1f2ace21768. Signed-off-by: Javier Aliaga Signed-off-by: sirivarma * 1.5.5 (#1390) Signed-off-by: Cassandra Coyle Signed-off-by: sirivarma * Add Documentation for Conversation AI SDK (#1387) Signed-off-by: sirivarma * Cleanup Spring Dependencies (#1334) * Update CONTRIBUTING.md Signed-off-by: Siri Varma Vegiraju * Fix spring Signed-off-by: siri-varma * Add context Signed-off-by: siri-varma * Phase 1 Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Fix spring Signed-off-by: siri-varma * Add context Signed-off-by: siri-varma * Phase 1 Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * move version Signed-off-by: sirivarma * Fix pom Signed-off-by: sirivarma * change version Signed-off-by: sirivarma * remove unused Signed-off-by: siri-varma * Address comments Signed-off-by: siri-varma * Fix test Signed-off-by: siri-varma * Fix test Signed-off-by: siri-varma * Fix things Signed-off-by: sirivarma --------- Signed-off-by: Siri Varma Vegiraju Signed-off-by: siri-varma Signed-off-by: sirivarma Co-authored-by: artur-ciocanu Signed-off-by: sirivarma * Bump org.springframework:spring-context in /dapr-spring (#1394) Bumps [org.springframework:spring-context](https://github.com/spring-projects/spring-framework) from 6.1.8 to 6.1.14. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.8...v6.1.14) --- updated-dependencies: - dependency-name: org.springframework:spring-context dependency-version: 6.1.14 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: sirivarma * Compensation example for Workflows (#1333) * add basic compensation example for wf Signed-off-by: Cassandra Coyle * update commands to run + wf id Signed-off-by: Cassandra Coyle * update readme + add mechanical markdown Signed-off-by: Cassandra Coyle * fix import Signed-off-by: Cassandra Coyle * fix mechanical markdown + add how to test it locally Signed-off-by: Cassandra Coyle * move compensation example readme to workflows readme Signed-off-by: Cassandra Coyle * Update BookCarActivity.java Signed-off-by: artur-ciocanu * Update BookFlightActivity.java Signed-off-by: artur-ciocanu * Update BookHotelActivity.java Signed-off-by: artur-ciocanu * Update BookTripClient.java Signed-off-by: artur-ciocanu * Update BookTripWorker.java Signed-off-by: artur-ciocanu * Update BookTripWorkflow.java Signed-off-by: artur-ciocanu * Update CancelCarActivity.java Signed-off-by: artur-ciocanu * Update CancelFlightActivity.java Signed-off-by: artur-ciocanu * Update CancelHotelActivity.java Signed-off-by: artur-ciocanu * add retry IT tests and catch TaskFailedException Signed-off-by: Cassandra Coyle * add test for no compensation if successful and assert attempts Signed-off-by: Cassandra Coyle * update mechanical markdown Signed-off-by: Cassandra Coyle * add back pubsub... but this should be removed long term Signed-off-by: Cassandra Coyle * try adding waitforsidecar Signed-off-by: Cassandra Coyle * rm tests from examples pr Signed-off-by: Cassandra Coyle * reset unintended changes Signed-off-by: Cassandra Coyle --------- Signed-off-by: Cassandra Coyle Signed-off-by: artur-ciocanu Co-authored-by: artur-ciocanu Signed-off-by: sirivarma * Adding remote endpoint request from inside activity with retry (#1388) * adding remote endpoint request from inside activity with retry Signed-off-by: salaboy * adding retry with Microcks payloads Signed-off-by: salaboy * fixing review comments Signed-off-by: salaboy * chore: New task execution task id test (#1352) * chore: New task execution task id test test how taskExecutionTaskId can be used for idempotency Signed-off-by: Javier Aliaga * chore: Clean up not used files Signed-off-by: Javier Aliaga * docs: Task execution keys Signed-off-by: Javier Aliaga * test: Modify unit tests Signed-off-by: Javier Aliaga * Remove new lines Signed-off-by: artur-ciocanu --------- Signed-off-by: Javier Aliaga Signed-off-by: artur-ciocanu Co-authored-by: Cassie Coyle Co-authored-by: artur-ciocanu Signed-off-by: salaboy * Revert "chore: New task execution task id test (#1352)" (#1389) This reverts commit 949584f69f4bc78ca59abe6ad000f1f2ace21768. Signed-off-by: Javier Aliaga Signed-off-by: salaboy * 1.5.5 (#1390) Signed-off-by: Cassandra Coyle Signed-off-by: salaboy * Add Documentation for Conversation AI SDK (#1387) Signed-off-by: salaboy * Cleanup Spring Dependencies (#1334) * Update CONTRIBUTING.md Signed-off-by: Siri Varma Vegiraju * Fix spring Signed-off-by: siri-varma * Add context Signed-off-by: siri-varma * Phase 1 Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Fix spring Signed-off-by: siri-varma * Add context Signed-off-by: siri-varma * Phase 1 Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * Fix things Signed-off-by: siri-varma * move version Signed-off-by: sirivarma * Fix pom Signed-off-by: sirivarma * change version Signed-off-by: sirivarma * remove unused Signed-off-by: siri-varma * Address comments Signed-off-by: siri-varma * Fix test Signed-off-by: siri-varma * Fix test Signed-off-by: siri-varma * Fix things Signed-off-by: sirivarma --------- Signed-off-by: Siri Varma Vegiraju Signed-off-by: siri-varma Signed-off-by: sirivarma Co-authored-by: artur-ciocanu Signed-off-by: salaboy * network is needed Signed-off-by: salaboy --------- Signed-off-by: salaboy Signed-off-by: Javier Aliaga Signed-off-by: artur-ciocanu Signed-off-by: Cassandra Coyle Signed-off-by: Siri Varma Vegiraju Signed-off-by: siri-varma Signed-off-by: sirivarma Co-authored-by: Javier Aliaga Co-authored-by: Cassie Coyle Co-authored-by: artur-ciocanu Co-authored-by: Siri Varma Vegiraju Signed-off-by: sirivarma * Bump org.springframework:spring-context in /dapr-spring (#1398) Bumps [org.springframework:spring-context](https://github.com/spring-projects/spring-framework) from 6.1.14 to 6.1.20. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.14...v6.1.20) --- updated-dependencies: - dependency-name: org.springframework:spring-context dependency-version: 6.1.20 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: sirivarma * Grouping IT tests per API surface (#1401) * Grouping IT tests per API surface Signed-off-by: Artur Ciocanu * Fix build Signed-off-by: Artur Ciocanu --------- Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Signed-off-by: sirivarma * Ensure OTEL version from examples aligns with SDK and ITs (#1403) Signed-off-by: sirivarma * chore: Support configuration for max grpc inbound message (#1411) Signed-off-by: Javier Aliaga Signed-off-by: sirivarma * Adding Support for Suspend / Resume Workflows (#1405) * adding IT test Signed-off-by: salaboy * adding initial version of suspend/resume example Signed-off-by: salaboy * updating README Signed-off-by: salaboy * Update README.md Signed-off-by: salaboy * following Javi's suggestion Signed-off-by: salaboy * fixing wrong year in headers Signed-off-by: salaboy * fixing paths in one more README.md file Signed-off-by: salaboy * adding output validation Signed-off-by: salaboy * adding missing port Signed-off-by: salaboy * fixing check conditions Signed-off-by: salaboy --------- Signed-off-by: salaboy Co-authored-by: Cassie Coyle Signed-off-by: sirivarma * Add retry handler support (#1412) * Add retry handler support Signed-off-by: Mason * Wrap DurableTask objects Signed-off-by: Mason * Rename method Signed-off-by: Mason * Add isNonRetriable field to WorkflowTaskFailureDetails Signed-off-by: Mason * Add unit test Signed-off-by: Mason * Removed duplicate WorkflowFailureDetails class Signed-off-by: Mason * Removed unneeded when statements in retry policy unit test Signed-off-by: Mason * Add unit test to test both RetryPolicy and RetryHandler Signed-off-by: Mason * Create toRetryPolicy method Signed-off-by: Mason --------- Signed-off-by: Mason Co-authored-by: Siri Varma Vegiraju Co-authored-by: Cassie Coyle Signed-off-by: sirivarma * Pull out createTimer logic (#1419) * pull out logical changes from @salaboy's PR to release it Signed-off-by: Cassandra Coyle * add missing import Signed-off-by: Cassandra Coyle --------- Signed-off-by: Cassandra Coyle Signed-off-by: sirivarma * Make sure there no multiple empty lines and every file ends with a new line (#1417) Signed-off-by: sirivarma * Automated Header Checks (#1416) Signed-off-by: sirivarma * Bump org.springframework:spring-web in /dapr-spring (#1424) Bumps [org.springframework:spring-web](https://github.com/spring-projects/spring-framework) from 6.1.20 to 6.1.21. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.20...v6.1.21) --- updated-dependencies: - dependency-name: org.springframework:spring-web dependency-version: 6.1.21 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: sirivarma * Aligning Spring Boot and SpringFramework versions (#1427) * aligning versions Signed-off-by: salaboy * letting SB to manage the logback version, so it is aligned Signed-off-by: salaboy * removing dep from sdk-tests Signed-off-by: salaboy * removing logback Signed-off-by: salaboy --------- Signed-off-by: salaboy Signed-off-by: sirivarma * Update pom.xml to have testcontainers 1.21.3 (#1436) Signed-off-by: salaboy Signed-off-by: sirivarma * Adding examples to Spring Boot (duration, zoneddatetime and suspend/resume) (#1413) * implementing createtime with zoneddatetime Signed-off-by: salaboy * adding duration and zoneddatetime examples Signed-off-by: salaboy * using external event wf to test suspend resume Signed-off-by: salaboy --------- Signed-off-by: salaboy Signed-off-by: sirivarma * Bump org.apache.commons:commons-lang3 from 3.9 to 3.18.0 (#1446) Bumps org.apache.commons:commons-lang3 from 3.9 to 3.18.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-lang3 dependency-version: 3.18.0 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: artur-ciocanu Signed-off-by: sirivarma * Update dapr docs for Hugo upgrade (#1443) Signed-off-by: Marc Duiker Co-authored-by: Cassie Coyle Signed-off-by: sirivarma * Adds note about workflow start time (#1444) Signed-off-by: joshvanl Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com> Co-authored-by: artur-ciocanu Signed-off-by: sirivarma * Migrate PubSub removing flaky test (#1407) * Migrate PubSub removing flaky test Signed-off-by: Matheus Cruz * Adjust assertion Signed-off-by: Matheus Cruz * Change assert Signed-off-by: Matheus Cruz * Apply pull request suggestions Signed-off-by: Matheus Cruz * Use custom ObjectSerializer Signed-off-by: Matheus Cruz --------- Signed-off-by: Matheus Cruz Co-authored-by: artur-ciocanu Signed-off-by: sirivarma * supporting spring boot property for API TOKEN on workflow interceptor (#1452) Signed-off-by: salaboy Signed-off-by: sirivarma * Supporting placement and scheduler custom images (#1450) * supporting placement and scheduler custom images Signed-off-by: salaboy * Bump org.apache.commons:commons-lang3 from 3.9 to 3.18.0 (#1446) Bumps org.apache.commons:commons-lang3 from 3.9 to 3.18.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-lang3 dependency-version: 3.18.0 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: artur-ciocanu Signed-off-by: salaboy * Update dapr docs for Hugo upgrade (#1443) Signed-off-by: Marc Duiker Co-authored-by: Cassie Coyle Signed-off-by: salaboy * Adds note about workflow start time (#1444) Signed-off-by: joshvanl Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com> Co-authored-by: artur-ciocanu Signed-off-by: salaboy * adding test to validate canonical names with substitutes Signed-off-by: salaboy * Migrate PubSub removing flaky test (#1407) * Migrate PubSub removing flaky test Signed-off-by: Matheus Cruz * Adjust assertion Signed-off-by: Matheus Cruz * Change assert Signed-off-by: Matheus Cruz * Apply pull request suggestions Signed-off-by: Matheus Cruz * Use custom ObjectSerializer Signed-off-by: Matheus Cruz --------- Signed-off-by: Matheus Cruz Co-authored-by: artur-ciocanu Signed-off-by: salaboy * adding tests for coverage Signed-off-by: salaboy --------- Signed-off-by: salaboy Signed-off-by: dependabot[bot] Signed-off-by: Marc Duiker Signed-off-by: joshvanl Signed-off-by: Matheus Cruz Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: artur-ciocanu Co-authored-by: Marc Duiker Co-authored-by: Cassie Coyle Co-authored-by: Josh van Leeuwen Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com> Co-authored-by: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com> Signed-off-by: sirivarma * chore: Replace ossrh url (#1454) Signed-off-by: Javier Aliaga Signed-off-by: sirivarma * Upgrading to 1.15.7 (#1458) * upgrading to 1.15.7 Signed-off-by: salaboy * using DAPR VERSION Signed-off-by: salaboy --------- Signed-off-by: salaboy Signed-off-by: sirivarma * updating sched api on DaprContainer (#1462) Signed-off-by: salaboy Signed-off-by: sirivarma * Adding app health check parameters (#1465) * adding app health check parameters Signed-off-by: salaboy * fixing style Signed-off-by: salaboy * testing with configure for coverage Signed-off-by: salaboy * updating sched api on DaprContainer (#1462) Signed-off-by: salaboy * fix Artur comments Signed-off-by: salaboy --------- Signed-off-by: salaboy Signed-off-by: sirivarma * Add note about the dapr.client.* properties (#1467) Signed-off-by: Matheus Cruz Signed-off-by: sirivarma * conversation ai Signed-off-by: sirivarma * Fix pom Signed-off-by: sirivarma * Fix pom Signed-off-by: sirivarma * fix things Signed-off-by: sirivarma * Fix things Signed-off-by: sirivarma * docs: Add gRPC response settings to properties documentation (#1482) * docs: Add gRPC response settings to properties documentation Signed-off-by: MyMirelHub <15373565+MyMirelHub@users.noreply.github.com> * Apply suggestion from @javier-aliaga Co-authored-by: Javier Aliaga Signed-off-by: Mirel Isaj <15373565+MyMirelHub@users.noreply.github.com> --------- Signed-off-by: MyMirelHub <15373565+MyMirelHub@users.noreply.github.com> Signed-off-by: Mirel Isaj <15373565+MyMirelHub@users.noreply.github.com> Co-authored-by: Javier Aliaga Signed-off-by: sirivarma * Generate updated javadocs for 1.14.2 Signed-off-by: Dapr Bot Signed-off-by: sirivarma * chore: Use 1.16.0 rc 2 (#1463) * chore: Use 1.16.0 rc Signed-off-by: Javier Aliaga * chore: Use dapr:1.16-rc.2 Signed-off-by: Javier Aliaga * chore: Add overwrite flag to jobs Signed-off-by: Javier Aliaga --------- Signed-off-by: Javier Aliaga Signed-off-by: sirivarma * Add Failure Policy for Jobs SDK (#1448) * Update CONTRIBUTING.md Signed-off-by: Siri Varma Vegiraju * Add failrue policy Signed-off-by: sirivarma * Add tests Signed-off-by: sirivarma * Add Tests Signed-off-by: sirivarma * Upgrading to 1.15.7 (#1458) * upgrading to 1.15.7 Signed-off-by: salaboy * using DAPR VERSION Signed-off-by: salaboy --------- Signed-off-by: salaboy Signed-off-by: siri-varma * Rename classes Signed-off-by: siri-varma * add rc Signed-off-by: sirivarma * fix checkstyle Signed-off-by: sirivarma * Fix things Signed-off-by: sirivarma * Test latest Signed-off-by: sirivarma * fix checkstyle Signed-off-by: sirivarma * Address comments Signed-off-by: sirivarma * Address comments Signed-off-by: sirivarma --------- Signed-off-by: Siri Varma Vegiraju Signed-off-by: sirivarma Signed-off-by: salaboy Signed-off-by: siri-varma Co-authored-by: artur-ciocanu Co-authored-by: salaboy Signed-off-by: sirivarma * Fix things Signed-off-by: sirivarma * Fix things Signed-off-by: sirivarma * Fix things Signed-off-by: sirivarma * Fix things Signed-off-by: sirivarma * Fix things Signed-off-by: sirivarma * fxi things Signed-off-by: siri-varma * Add better tests Signed-off-by: siri-varma * Add examples Signed-off-by: siri-varma * Add examples Signed-off-by: siri-varma * Update README.md Signed-off-by: Siri Varma Vegiraju * Update UserMessageDemo.java Signed-off-by: Siri Varma Vegiraju * Update UserMessageDemo.java Signed-off-by: Siri Varma Vegiraju * Fix tool calling Signed-off-by: sirivarma * Address comments Signed-off-by: sirivarma --------- Signed-off-by: Artur Ciocanu Signed-off-by: sirivarma Signed-off-by: Siri Varma Vegiraju Signed-off-by: dependabot[bot] Signed-off-by: Dapr Bot Signed-off-by: Siri Varma Vegiraju Signed-off-by: siri-varma Signed-off-by: Cassandra Coyle Signed-off-by: Javier Aliaga Signed-off-by: Deepak Signed-off-by: salaboy Signed-off-by: artur-ciocanu Signed-off-by: Mason Signed-off-by: Marc Duiker Signed-off-by: joshvanl Signed-off-by: Matheus Cruz Signed-off-by: MyMirelHub <15373565+MyMirelHub@users.noreply.github.com> Signed-off-by: Mirel Isaj <15373565+MyMirelHub@users.noreply.github.com> Co-authored-by: artur-ciocanu Co-authored-by: Artur Ciocanu Co-authored-by: Cassie Coyle Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com> Co-authored-by: Dapr Bot Co-authored-by: Cassie Coyle Co-authored-by: Javier Aliaga Co-authored-by: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com> Co-authored-by: iddeepak <87892182+iddeepak@users.noreply.github.com> Co-authored-by: salaboy Co-authored-by: Mason Co-authored-by: Marc Duiker Co-authored-by: Josh van Leeuwen Co-authored-by: Mirel Isaj <15373565+MyMirelHub@users.noreply.github.com> --- .../conversation/AssistantMessageDemo.java | 136 +++++ .../conversation/DemoConversationAI.java | 49 -- .../io/dapr/examples/conversation/README.md | 41 +- .../examples/conversation/ToolsCallDemo.java | 109 ++++ .../conversation/UserMessageDemo.java | 72 +++ .../DaprConversationAlpha2IT.java | 386 ++++++++++++++ .../conversations/DaprConversationIT.java | 1 + .../java/io/dapr/client/DaprClientImpl.java | 289 +++++++++++ .../io/dapr/client/DaprPreviewClient.java | 11 + .../io/dapr/client/ProtobufValueHelper.java | 76 +++ .../dapr/client/domain/AssistantMessage.java | 67 +++ .../domain/ConversationInputAlpha2.java | 63 +++ .../client/domain/ConversationMessage.java | 44 ++ .../domain/ConversationMessageContent.java | 40 ++ .../domain/ConversationMessageRole.java | 44 ++ .../domain/ConversationRequestAlpha2.java | 209 ++++++++ .../domain/ConversationResponseAlpha2.java | 54 ++ .../domain/ConversationResultAlpha2.java | 42 ++ .../domain/ConversationResultChoices.java | 68 +++ .../domain/ConversationResultMessage.java | 72 +++ .../client/domain/ConversationToolCalls.java | 61 +++ .../ConversationToolCallsOfFunction.java | 54 ++ .../dapr/client/domain/ConversationTools.java | 40 ++ .../domain/ConversationToolsFunction.java | 75 +++ .../dapr/client/domain/DeveloperMessage.java | 61 +++ .../io/dapr/client/domain/SystemMessage.java | 59 +++ .../io/dapr/client/domain/ToolMessage.java | 77 +++ .../io/dapr/client/domain/UserMessage.java | 61 +++ .../client/DaprPreviewClientGrpcTest.java | 487 ++++++++++++++++++ .../dapr/client/ProtobufValueHelperTest.java | 423 +++++++++++++++ 30 files changed, 3212 insertions(+), 59 deletions(-) create mode 100644 examples/src/main/java/io/dapr/examples/conversation/AssistantMessageDemo.java delete mode 100644 examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java create mode 100644 examples/src/main/java/io/dapr/examples/conversation/ToolsCallDemo.java create mode 100644 examples/src/main/java/io/dapr/examples/conversation/UserMessageDemo.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationAlpha2IT.java create mode 100644 sdk/src/main/java/io/dapr/client/ProtobufValueHelper.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/AssistantMessage.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationInputAlpha2.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationMessage.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationMessageContent.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationMessageRole.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationRequestAlpha2.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationResponseAlpha2.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationResultAlpha2.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationResultChoices.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationResultMessage.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationToolCalls.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationToolCallsOfFunction.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationTools.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationToolsFunction.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/DeveloperMessage.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/SystemMessage.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/ToolMessage.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/UserMessage.java create mode 100644 sdk/src/test/java/io/dapr/client/ProtobufValueHelperTest.java diff --git a/examples/src/main/java/io/dapr/examples/conversation/AssistantMessageDemo.java b/examples/src/main/java/io/dapr/examples/conversation/AssistantMessageDemo.java new file mode 100644 index 000000000..7ff2d43be --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/conversation/AssistantMessageDemo.java @@ -0,0 +1,136 @@ +/* + * Copyright 2021 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.conversation; + +import io.dapr.client.DaprClientBuilder; +import io.dapr.client.DaprPreviewClient; +import io.dapr.client.domain.AssistantMessage; +import io.dapr.client.domain.ConversationInputAlpha2; +import io.dapr.client.domain.ConversationMessage; +import io.dapr.client.domain.ConversationMessageContent; +import io.dapr.client.domain.ConversationRequestAlpha2; +import io.dapr.client.domain.ConversationResponseAlpha2; +import io.dapr.client.domain.ConversationResultAlpha2; +import io.dapr.client.domain.ConversationResultChoices; +import io.dapr.client.domain.ConversationToolCalls; +import io.dapr.client.domain.ConversationToolCallsOfFunction; +import io.dapr.client.domain.SystemMessage; +import io.dapr.client.domain.ToolMessage; +import io.dapr.client.domain.UserMessage; +import reactor.core.publisher.Mono; + +import java.util.ArrayList; +import java.util.List; + +public class AssistantMessageDemo { + /** + * The main method to demonstrate conversation AI with assistant messages and conversation history. + * + * @param args Input arguments (unused). + */ + public static void main(String[] args) { + try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) { + System.out.println("Demonstrating Conversation AI with Assistant Messages and Conversation History"); + + // Create a conversation history with multiple message types + List conversationHistory = new ArrayList<>(); + + // 1. System message to set context + SystemMessage systemMessage = new SystemMessage(List.of( + new ConversationMessageContent("You are a helpful assistant that can help with weather queries.") + )); + systemMessage.setName("WeatherBot"); + conversationHistory.add(systemMessage); + + // 2. Initial user greeting + UserMessage greeting = new UserMessage(List.of( + new ConversationMessageContent("Hello! I need help with weather information.") + )); + greeting.setName("User123"); + conversationHistory.add(greeting); + + // 3. Assistant response with tool call + AssistantMessage assistantResponse = new AssistantMessage( + List.of(new ConversationMessageContent("I'll help you with weather information. Let me check the weather for you.")), + List.of(new ConversationToolCalls( + new ConversationToolCallsOfFunction("get_weather", "{\"location\": \"San Francisco\", \"unit\": \"fahrenheit\"}") + )) + ); + assistantResponse.setName("WeatherBot"); + conversationHistory.add(assistantResponse); + + // 4. Tool response (simulating weather API response) + ToolMessage toolResponse = new ToolMessage(List.of( + new ConversationMessageContent("{\"temperature\": \"72F\", \"condition\": \"sunny\", \"humidity\": \"65%\"}") + )); + toolResponse.setName("weather_api"); + conversationHistory.add(toolResponse); + + // 5. Current user question + UserMessage currentQuestion = new UserMessage(List.of( + new ConversationMessageContent("Based on that weather data, should I wear a jacket today?") + )); + currentQuestion.setName("User123"); + conversationHistory.add(currentQuestion); + + // Create conversation input with the full history + ConversationInputAlpha2 conversationInput = new ConversationInputAlpha2(conversationHistory); + conversationInput.setScrubPii(false); + + // Create the conversation request + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("echo", List.of(conversationInput)) + .setContextId("assistant-demo-context") + .setTemperature(0.8d); + + // Send the request + System.out.println("Sending conversation with assistant messages and history..."); + System.out.println("Conversation includes:"); + System.out.println("- System message (context setting)"); + System.out.println("- User greeting"); + System.out.println("- Assistant response with tool call"); + System.out.println("- Tool response with weather data"); + System.out.println("- User follow-up question"); + + Mono responseMono = client.converseAlpha2(request); + ConversationResponseAlpha2 response = responseMono.block(); + + // Process and display the response + if (response != null && response.getOutputs() != null && !response.getOutputs().isEmpty()) { + ConversationResultAlpha2 result = response.getOutputs().get(0); + if (result.getChoices() != null && !result.getChoices().isEmpty()) { + ConversationResultChoices choice = result.getChoices().get(0); + + if (choice.getMessage() != null && choice.getMessage().getContent() != null) { + System.out.printf("Assistant Response: %s%n", choice.getMessage().getContent()); + } + + // Check for additional tool calls in the response + if (choice.getMessage() != null && choice.getMessage().getToolCalls() != null) { + System.out.println("Assistant requested additional tool calls:"); + choice.getMessage().getToolCalls().forEach(toolCall -> { + System.out.printf("Tool: %s, Arguments: %s%n", + toolCall.getFunction().getName(), + toolCall.getFunction().getArguments()); + }); + } + } + } + + System.out.println("Assistant message demonstration completed."); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java deleted file mode 100644 index 09c957026..000000000 --- a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2021 The Dapr Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and -limitations under the License. -*/ - -package io.dapr.examples.conversation; - -import io.dapr.client.DaprClientBuilder; -import io.dapr.client.DaprPreviewClient; -import io.dapr.client.domain.ConversationInput; -import io.dapr.client.domain.ConversationRequest; -import io.dapr.client.domain.ConversationResponse; -import reactor.core.publisher.Mono; - -import java.util.List; - -public class DemoConversationAI { - /** - * The main method to start the client. - * - * @param args Input arguments (unused). - */ - public static void main(String[] args) { - try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) { - System.out.println("Sending the following input to LLM: Hello How are you? This is the my number 672-123-4567"); - - ConversationInput daprConversationInput = new ConversationInput("Hello How are you? " - + "This is the my number 672-123-4567"); - - // Component name is the name provided in the metadata block of the conversation.yaml file. - Mono responseMono = client.converse(new ConversationRequest("echo", - List.of(daprConversationInput)) - .setContextId("contextId") - .setScrubPii(true).setTemperature(1.1d)); - ConversationResponse response = responseMono.block(); - System.out.printf("Conversation output: %s", response.getConversationOutputs().get(0).getResult()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/examples/src/main/java/io/dapr/examples/conversation/README.md b/examples/src/main/java/io/dapr/examples/conversation/README.md index 29468cfb3..cb12b6460 100644 --- a/examples/src/main/java/io/dapr/examples/conversation/README.md +++ b/examples/src/main/java/io/dapr/examples/conversation/README.md @@ -45,30 +45,51 @@ Run `dapr init` to initialize Dapr in Self-Hosted Mode if it's not already initi ### Running the example This example uses the Java SDK Dapr client in order to **Converse** with an LLM. -`DemoConversationAI.java` is the example class demonstrating these features. +`UserMessageDemo.java` is the example class demonstrating these features. Kindly check [DaprPreviewClient.java](https://github.com/dapr/java-sdk/blob/master/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java) for a detailed description of the supported APIs. ```java -public class DemoConversationAI { +public class UserMessageDemo { /** * The main method to start the client. * * @param args Input arguments (unused). */ public static void main(String[] args) { - try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) { + Map, String> overrides = Map.of( + Properties.HTTP_PORT, "3500", + Properties.GRPC_PORT, "50001" + ); + + try (DaprPreviewClient client = new DaprClientBuilder().withPropertyOverrides(overrides).buildPreviewClient()) { System.out.println("Sending the following input to LLM: Hello How are you? This is the my number 672-123-4567"); - ConversationInput daprConversationInput = new ConversationInput("Hello How are you? " - + "This is the my number 672-123-4567"); + // Create user message with content + UserMessage userMessage = new UserMessage(List.of(new ConversationMessageContent("Hello How are you? " + + "This is the my number 672-123-4567"))); + + // Create conversation input with the user message + ConversationInputAlpha2 daprConversationInput = new ConversationInputAlpha2(List.of(userMessage)); // Component name is the name provided in the metadata block of the conversation.yaml file. - Mono responseMono = client.converse(new ConversationRequest("echo", + Mono responseMono = client.converseAlpha2(new ConversationRequestAlpha2("echo", List.of(daprConversationInput)) .setContextId("contextId") - .setScrubPii(true).setTemperature(1.1d)); - ConversationResponse response = responseMono.block(); - System.out.printf("Conversation output: %s", response.getConversationOutpus().get(0).getResult()); + .setScrubPii(true) + .setTemperature(1.1d)); + + ConversationResponseAlpha2 response = responseMono.block(); + + // Extract and print the conversation result + if (response != null && response.getOutputs() != null && !response.getOutputs().isEmpty()) { + ConversationResultAlpha2 result = response.getOutputs().get(0); + if (result.getChoices() != null && !result.getChoices().isEmpty()) { + ConversationResultChoices choice = result.getChoices().get(0); + if (choice.getMessage() != null && choice.getMessage().getContent() != null) { + System.out.printf("Conversation output: %s", choice.getMessage().getContent()); + } + } + } } catch (Exception e) { throw new RuntimeException(e); } @@ -88,7 +109,7 @@ sleep: 10 --> ```bash -dapr run --resources-path ./components/conversation --app-id myapp --app-port 8080 --dapr-http-port 3500 --dapr-grpc-port 51439 --log-level debug -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.conversation.DemoConversationAI +dapr run --resources-path ./components/conversation --app-id myapp --app-port 8080 --dapr-http-port 3500 --dapr-grpc-port 51439 --log-level debug -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.conversation.UserMessageDemo ``` diff --git a/examples/src/main/java/io/dapr/examples/conversation/ToolsCallDemo.java b/examples/src/main/java/io/dapr/examples/conversation/ToolsCallDemo.java new file mode 100644 index 000000000..30335802c --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/conversation/ToolsCallDemo.java @@ -0,0 +1,109 @@ +/* + * Copyright 2021 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.conversation; + +import io.dapr.client.DaprClientBuilder; +import io.dapr.client.DaprPreviewClient; +import io.dapr.client.domain.ConversationInputAlpha2; +import io.dapr.client.domain.ConversationMessageContent; +import io.dapr.client.domain.ConversationRequestAlpha2; +import io.dapr.client.domain.ConversationResponseAlpha2; +import io.dapr.client.domain.ConversationResultAlpha2; +import io.dapr.client.domain.ConversationResultChoices; +import io.dapr.client.domain.ConversationTools; +import io.dapr.client.domain.ConversationToolsFunction; +import io.dapr.client.domain.SystemMessage; +import io.dapr.client.domain.UserMessage; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ToolsCallDemo { + /** + * The main method to demonstrate conversation AI with tools/function calling. + * + * @param args Input arguments (unused). + */ + public static void main(String[] args) { + try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) { + System.out.println("Demonstrating Conversation AI with Tools/Function Calling"); + + // Create system message to set context + SystemMessage systemMessage = new SystemMessage(List.of( + new ConversationMessageContent("You are a helpful weather assistant. Use the provided tools to get weather information.") + )); + + // Create user message asking for weather + UserMessage userMessage = new UserMessage(List.of( + new ConversationMessageContent("What's the weather like in San Francisco?") + )); + + // Create conversation input with messages + ConversationInputAlpha2 conversationInput = new ConversationInputAlpha2(List.of(systemMessage, userMessage)); + + // Define function parameters for the weather tool + Map functionParams = new HashMap<>(); + functionParams.put("location", "string"); + functionParams.put("unit", "string"); + + // Create the weather function definition + ConversationToolsFunction weatherFunction = new ConversationToolsFunction("get_current_weather", functionParams); + weatherFunction.setDescription("Get the current weather for a specified location"); + + // Create the tool wrapper + ConversationTools weatherTool = new ConversationTools(weatherFunction); + + // Create the conversation request with tools + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("echo", List.of(conversationInput)) + .setContextId("weather-demo-context") + .setTemperature(0.7d) + .setTools(List.of(weatherTool)); + + // Send the request + System.out.println("Sending request to AI with weather tool available..."); + Mono responseMono = client.converseAlpha2(request); + ConversationResponseAlpha2 response = responseMono.block(); + + // Process and display the response + if (response != null && response.getOutputs() != null && !response.getOutputs().isEmpty()) { + ConversationResultAlpha2 result = response.getOutputs().get(0); + if (result.getChoices() != null && !result.getChoices().isEmpty()) { + ConversationResultChoices choice = result.getChoices().get(0); + + // Check if the AI wants to call a tool + if (choice.getMessage() != null && choice.getMessage().getToolCalls() != null) { + System.out.println("AI requested to call tools:"); + choice.getMessage().getToolCalls().forEach(toolCall -> { + System.out.printf("Tool: %s, Arguments: %s%n", + toolCall.getFunction().getName(), + toolCall.getFunction().getArguments()); + }); + } + + // Display the message content if available + if (choice.getMessage() != null && choice.getMessage().getContent() != null) { + System.out.printf("AI Response: %s%n", choice.getMessage().getContent()); + } + } + } + + System.out.println("Tools call demonstration completed."); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/examples/src/main/java/io/dapr/examples/conversation/UserMessageDemo.java b/examples/src/main/java/io/dapr/examples/conversation/UserMessageDemo.java new file mode 100644 index 000000000..e5afbc475 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/conversation/UserMessageDemo.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.conversation; + +import io.dapr.client.DaprClientBuilder; +import io.dapr.client.DaprPreviewClient; +import io.dapr.client.domain.ConversationInputAlpha2; +import io.dapr.client.domain.ConversationMessageContent; +import io.dapr.client.domain.ConversationRequestAlpha2; +import io.dapr.client.domain.ConversationResponseAlpha2; +import io.dapr.client.domain.ConversationResultAlpha2; +import io.dapr.client.domain.ConversationResultChoices; +import io.dapr.client.domain.UserMessage; +import io.dapr.config.Properties; +import io.dapr.config.Property; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Map; + +public class UserMessageDemo { + /** + * The main method to start the client. + * + * @param args Input arguments (unused). + */ + public static void main(String[] args) { + try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) { + System.out.println("Sending the following input to LLM: Hello How are you? This is the my number 672-123-4567"); + + // Create user message with content + UserMessage userMessage = new UserMessage(List.of(new ConversationMessageContent("Hello How are you? " + + "This is the my number 672-123-4567"))); + + // Create conversation input with the user message + ConversationInputAlpha2 daprConversationInput = new ConversationInputAlpha2(List.of(userMessage)); + + // Component name is the name provided in the metadata block of the conversation.yaml file. + Mono responseMono = client.converseAlpha2(new ConversationRequestAlpha2("echo", + List.of(daprConversationInput)) + .setContextId("contextId") + .setScrubPii(true) + .setTemperature(1.1d)); + + ConversationResponseAlpha2 response = responseMono.block(); + + // Extract and print the conversation result + if (response != null && response.getOutputs() != null && !response.getOutputs().isEmpty()) { + ConversationResultAlpha2 result = response.getOutputs().get(0); + if (result.getChoices() != null && !result.getChoices().isEmpty()) { + ConversationResultChoices choice = result.getChoices().get(0); + if (choice.getMessage() != null && choice.getMessage().getContent() != null) { + System.out.printf("Conversation output: %s", choice.getMessage().getContent()); + } + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationAlpha2IT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationAlpha2IT.java new file mode 100644 index 000000000..00522eae6 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationAlpha2IT.java @@ -0,0 +1,386 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.it.testcontainers.conversations; + +import io.dapr.client.DaprPreviewClient; +import io.dapr.client.domain.AssistantMessage; +import io.dapr.client.domain.ConversationInputAlpha2; +import io.dapr.client.domain.ConversationMessage; +import io.dapr.client.domain.ConversationMessageContent; +import io.dapr.client.domain.ConversationRequestAlpha2; +import io.dapr.client.domain.ConversationResponseAlpha2; +import io.dapr.client.domain.ConversationResultAlpha2; +import io.dapr.client.domain.ConversationResultChoices; +import io.dapr.client.domain.ConversationToolCalls; +import io.dapr.client.domain.ConversationToolCallsOfFunction; +import io.dapr.client.domain.ConversationTools; +import io.dapr.client.domain.ConversationToolsFunction; +import io.dapr.client.domain.DeveloperMessage; +import io.dapr.client.domain.SystemMessage; +import io.dapr.client.domain.ToolMessage; +import io.dapr.client.domain.UserMessage; +import io.dapr.it.testcontainers.DaprPreviewClientConfiguration; +import io.dapr.testcontainers.Component; +import io.dapr.testcontainers.DaprContainer; +import io.dapr.testcontainers.DaprLogLevel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.Network; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest( + webEnvironment = WebEnvironment.RANDOM_PORT, + classes = { + DaprPreviewClientConfiguration.class, + TestConversationApplication.class + } +) +@Testcontainers +@Tag("testcontainers") +public class DaprConversationAlpha2IT { + + private static final Network DAPR_NETWORK = Network.newNetwork(); + private static final Random RANDOM = new Random(); + private static final int PORT = RANDOM.nextInt(1000) + 8000; + + @Container + private static final DaprContainer DAPR_CONTAINER = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) + .withAppName("conversation-alpha2-dapr-app") + .withComponent(new Component("echo", "conversation.echo", "v1", new HashMap<>())) + .withNetwork(DAPR_NETWORK) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) + .withAppChannelAddress("host.testcontainers.internal") + .withAppPort(PORT); + + /** + * Expose the Dapr port to the host. + * + * @param registry the dynamic property registry + */ + @DynamicPropertySource + static void daprProperties(DynamicPropertyRegistry registry) { + registry.add("dapr.http.endpoint", DAPR_CONTAINER::getHttpEndpoint); + registry.add("dapr.grpc.endpoint", DAPR_CONTAINER::getGrpcEndpoint); + registry.add("server.port", () -> PORT); + } + + @Autowired + private DaprPreviewClient daprPreviewClient; + + @BeforeEach + public void setUp() { + org.testcontainers.Testcontainers.exposeHostPorts(PORT); + } + + @Test + public void testConverseAlpha2WithUserMessage() { + // Create a user message + UserMessage userMessage = new UserMessage(List.of(new ConversationMessageContent("Hello, how are you?"))); + userMessage.setName("TestUser"); + + // Create input with the message + ConversationInputAlpha2 input = new ConversationInputAlpha2(List.of(userMessage)); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("echo", List.of(input)); + + ConversationResponseAlpha2 response = daprPreviewClient.converseAlpha2(request).block(); + + assertNotNull(response); + assertNotNull(response.getOutputs()); + assertEquals(1, response.getOutputs().size()); + + ConversationResultAlpha2 result = response.getOutputs().get(0); + assertNotNull(result.getChoices()); + assertTrue(result.getChoices().size() > 0); + + ConversationResultChoices choice = result.getChoices().get(0); + assertNotNull(choice.getMessage()); + assertNotNull(choice.getMessage().getContent()); + } + + @Test + public void testConverseAlpha2WithAllMessageTypes() { + List messages = new ArrayList<>(); + + // System message + SystemMessage systemMsg = new SystemMessage(List.of(new ConversationMessageContent("You are a helpful assistant."))); + systemMsg.setName("system"); + messages.add(systemMsg); + + // User message + UserMessage userMsg = new UserMessage(List.of(new ConversationMessageContent("Hello!"))); + userMsg.setName("user"); + messages.add(userMsg); + + // Assistant message + AssistantMessage assistantMsg = new AssistantMessage(List.of(new ConversationMessageContent("Hi there!")), + List.of(new ConversationToolCalls( + new ConversationToolCallsOfFunction("get_weather", "{\"location\": \"New York\"}")))); + assistantMsg.setName("assistant"); + messages.add(assistantMsg); + + // Tool message + ToolMessage toolMsg = new ToolMessage(List.of(new ConversationMessageContent("Weather data: 72F"))); + toolMsg.setName("tool"); + messages.add(toolMsg); + + // Developer message + DeveloperMessage devMsg = new DeveloperMessage(List.of(new ConversationMessageContent("Debug info"))); + devMsg.setName("developer"); + messages.add(devMsg); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(messages); + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("echo", List.of(input)); + + ConversationResponseAlpha2 response = daprPreviewClient.converseAlpha2(request).block(); + + assertNotNull(response); + assertNotNull(response.getOutputs()); + assertTrue(response.getOutputs().size() > 0); + } + + @Test + public void testConverseAlpha2WithScrubPII() { + // Create a user message with PII + UserMessage userMessage = new UserMessage(List.of(new ConversationMessageContent("My email is test@example.com and phone is +1234567890"))); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(List.of(userMessage)); + input.setScrubPii(true); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("echo", List.of(input)); + request.setScrubPii(true); + + ConversationResponseAlpha2 response = daprPreviewClient.converseAlpha2(request).block(); + + assertNotNull(response); + assertNotNull(response.getOutputs()); + assertTrue(response.getOutputs().size() > 0); + + // Verify response structure (actual PII scrubbing depends on echo component implementation) + ConversationResultChoices choice = response.getOutputs().get(0).getChoices().get(0); + assertNotNull(choice.getMessage()); + assertNotNull(choice.getMessage().getContent()); + } + + @Test + public void testConverseAlpha2WithTools() { + // Create a tool function + Map parameters = new HashMap<>(); + parameters.put("location", "string"); + parameters.put("unit", "celsius"); + ConversationToolsFunction function = new ConversationToolsFunction("get_weather", parameters); + function.setDescription("Get current weather information"); + + ConversationTools tool = new ConversationTools(function); + + // Create user message + UserMessage userMessage = new UserMessage(List.of(new ConversationMessageContent("What's the weather like?"))); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(List.of(userMessage)); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("echo", List.of(input)); + request.setTools(List.of(tool)); + request.setToolChoice("auto"); + + ConversationResponseAlpha2 response = daprPreviewClient.converseAlpha2(request).block(); + + assertNotNull(response); + assertNotNull(response.getOutputs()); + assertTrue(response.getOutputs().size() > 0); + } + + @Test + public void testConverseAlpha2WithMetadataAndParameters() { + UserMessage userMessage = new UserMessage(List.of(new ConversationMessageContent("Hello world"))); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(List.of(userMessage)); + + // Set metadata and parameters + Map metadata = new HashMap<>(); + metadata.put("request-id", "test-123"); + metadata.put("source", "integration-test"); + + Map parameters = new HashMap<>(); + parameters.put("max_tokens", "1000"); + parameters.put("temperature", "0.7"); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("echo", List.of(input)); + request.setContextId("test-context-123"); + request.setTemperature(0.8); + request.setMetadata(metadata); + request.setParameters(parameters); + + ConversationResponseAlpha2 response = daprPreviewClient.converseAlpha2(request).block(); + + assertNotNull(response); + assertNotNull(response.getOutputs()); + assertTrue(response.getOutputs().size() > 0); + + // Verify context ID is handled properly + // Note: actual context ID behavior depends on echo component implementation + assertNotNull(response.getContextId()); + } + + @Test + public void testConverseAlpha2WithAssistantToolCalls() { + // Create a tool call + ConversationToolCallsOfFunction toolFunction = + new ConversationToolCallsOfFunction("get_weather", "{\"location\": \"New York\"}"); + ConversationToolCalls toolCall = new ConversationToolCalls(toolFunction); + toolCall.setId("call_123"); + + // Create assistant message with tool calls + AssistantMessage assistantMsg = new AssistantMessage(List.of(new ConversationMessageContent("Hi there!")), + List.of(new ConversationToolCalls( + new ConversationToolCallsOfFunction("get_weather", "{\"location\": \"New York\"}")))); // Note: Current implementation doesn't support setting tool calls in constructor + // This tests the structure and ensures no errors occur + + ConversationInputAlpha2 input = new ConversationInputAlpha2(List.of(assistantMsg)); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("echo", List.of(input)); + + ConversationResponseAlpha2 response = daprPreviewClient.converseAlpha2(request).block(); + + assertNotNull(response); + assertNotNull(response.getOutputs()); + assertTrue(response.getOutputs().size() > 0); + } + + @Test + public void testConverseAlpha2WithComplexScenario() { + List messages = new ArrayList<>(); + + // System message setting context + SystemMessage systemMsg = new SystemMessage(List.of(new ConversationMessageContent("You are a helpful weather assistant."))); + systemMsg.setName("WeatherBot"); + messages.add(systemMsg); + + // User asking for weather + UserMessage userMsg = new UserMessage(List.of(new ConversationMessageContent("What's the weather in San Francisco?"))); + userMsg.setName("User123"); + messages.add(userMsg); + + // Assistant response + AssistantMessage assistantMsg = new AssistantMessage(List.of(new ConversationMessageContent("Hi there!")), + List.of(new ConversationToolCalls( + new ConversationToolCallsOfFunction("get_weather", "{\"location\": \"New York\"}")))); + assistantMsg.setName("WeatherBot"); + messages.add(assistantMsg); + + // Tool response + ToolMessage toolMsg = new ToolMessage(List.of(new ConversationMessageContent("{\"temperature\": \"68F\", \"condition\": \"sunny\"}"))); + toolMsg.setName("weather_api"); + messages.add(toolMsg); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(messages); + input.setScrubPii(false); + + // Create tools + Map functionParams = new HashMap<>(); + functionParams.put("location", "string"); + functionParams.put("unit", "fahrenheit"); + ConversationToolsFunction weatherFunction = new ConversationToolsFunction("get_current_weather", + functionParams); + weatherFunction.setDescription("Get current weather for a location"); + + + ConversationTools weatherTool = new ConversationTools(weatherFunction); + + // Set up complete request + Map metadata = new HashMap<>(); + metadata.put("conversation-type", "weather-query"); + metadata.put("user-session", "session-456"); + + Map parameters = new HashMap<>(); + parameters.put("max_tokens", "2000"); + parameters.put("response_format", "json"); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("echo", List.of(input)); + request.setContextId("weather-conversation-789"); + request.setTemperature(0.7); + request.setScrubPii(false); + request.setTools(List.of(weatherTool)); + request.setToolChoice("auto"); + request.setMetadata(metadata); + request.setParameters(parameters); + + ConversationResponseAlpha2 response = daprPreviewClient.converseAlpha2(request).block(); + + assertNotNull(response); + assertNotNull(response.getOutputs()); + assertTrue(response.getOutputs().size() > 0); + + ConversationResultAlpha2 result = response.getOutputs().get(0); + assertNotNull(result.getChoices()); + assertTrue(result.getChoices().size() > 0); + + ConversationResultChoices choice = result.getChoices().get(0); + assertNotNull(choice.getFinishReason()); + assertTrue(choice.getIndex() >= 0); + + if (choice.getMessage() != null) { + assertNotNull(choice.getMessage().getContent()); + } + } + + @Test + public void testConverseAlpha2MultipleInputs() { + // Create multiple conversation inputs + List inputs = new ArrayList<>(); + + // First input - greeting + UserMessage greeting = new UserMessage(List.of(new ConversationMessageContent("Hello!"))); + ConversationInputAlpha2 input1 = new ConversationInputAlpha2(List.of(greeting)); + inputs.add(input1); + + // Second input - question + UserMessage question = new UserMessage(List.of(new ConversationMessageContent("How are you?"))); + ConversationInputAlpha2 input2 = new ConversationInputAlpha2(List.of(question)); + input2.setScrubPii(true); + inputs.add(input2); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("echo", inputs); + + ConversationResponseAlpha2 response = daprPreviewClient.converseAlpha2(request).block(); + + assertNotNull(response); + assertNotNull(response.getOutputs()); + assertTrue(response.getOutputs().size() > 0); + + // Should handle multiple inputs appropriately + for (ConversationResultAlpha2 result : response.getOutputs()) { + assertNotNull(result.getChoices()); + assertTrue(result.getChoices().size() > 0); + } + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationIT.java index 7177d2aab..dd35ce690 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationIT.java @@ -128,3 +128,4 @@ public class DaprConversationIT { response.getConversationOutputs().get(0).getResult()); } } + diff --git a/sdk/src/main/java/io/dapr/client/DaprClientImpl.java b/sdk/src/main/java/io/dapr/client/DaprClientImpl.java index 8bae8f51d..012921a89 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClientImpl.java +++ b/sdk/src/main/java/io/dapr/client/DaprClientImpl.java @@ -17,9 +17,12 @@ import com.google.common.base.Strings; import com.google.protobuf.Any; import com.google.protobuf.ByteString; import com.google.protobuf.Empty; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; import io.dapr.client.domain.ActorMetadata; import io.dapr.client.domain.AppConnectionPropertiesHealthMetadata; import io.dapr.client.domain.AppConnectionPropertiesMetadata; +import io.dapr.client.domain.AssistantMessage; import io.dapr.client.domain.BulkPublishEntry; import io.dapr.client.domain.BulkPublishRequest; import io.dapr.client.domain.BulkPublishResponse; @@ -29,9 +32,21 @@ import io.dapr.client.domain.ComponentMetadata; import io.dapr.client.domain.ConfigurationItem; import io.dapr.client.domain.ConstantFailurePolicy; import io.dapr.client.domain.ConversationInput; +import io.dapr.client.domain.ConversationInputAlpha2; +import io.dapr.client.domain.ConversationMessage; +import io.dapr.client.domain.ConversationMessageContent; import io.dapr.client.domain.ConversationOutput; import io.dapr.client.domain.ConversationRequest; +import io.dapr.client.domain.ConversationRequestAlpha2; import io.dapr.client.domain.ConversationResponse; +import io.dapr.client.domain.ConversationResponseAlpha2; +import io.dapr.client.domain.ConversationResultAlpha2; +import io.dapr.client.domain.ConversationResultChoices; +import io.dapr.client.domain.ConversationResultMessage; +import io.dapr.client.domain.ConversationToolCalls; +import io.dapr.client.domain.ConversationToolCallsOfFunction; +import io.dapr.client.domain.ConversationTools; +import io.dapr.client.domain.ConversationToolsFunction; import io.dapr.client.domain.DaprMetadata; import io.dapr.client.domain.DeleteJobRequest; import io.dapr.client.domain.DeleteStateRequest; @@ -64,6 +79,7 @@ import io.dapr.client.domain.StateOptions; import io.dapr.client.domain.SubscribeConfigurationRequest; import io.dapr.client.domain.SubscribeConfigurationResponse; import io.dapr.client.domain.SubscriptionMetadata; +import io.dapr.client.domain.ToolMessage; import io.dapr.client.domain.TransactionalStateOperation; import io.dapr.client.domain.UnlockRequest; import io.dapr.client.domain.UnlockResponseStatus; @@ -1620,6 +1636,7 @@ public class DaprClientImpl extends AbstractDaprClient { /** * {@inheritDoc} */ + @Deprecated(forRemoval = true) @Override public Mono converse(ConversationRequest conversationRequest) { @@ -1690,6 +1707,278 @@ public class DaprClientImpl extends AbstractDaprClient { } } + /** + * {@inheritDoc} + */ + @Override + public Mono converseAlpha2(ConversationRequestAlpha2 conversationRequestAlpha2) { + try { + if ((conversationRequestAlpha2.getName() == null) || (conversationRequestAlpha2.getName().trim().isEmpty())) { + throw new IllegalArgumentException("LLM name cannot be null or empty."); + } + + if (conversationRequestAlpha2.getInputs() == null || conversationRequestAlpha2.getInputs().isEmpty()) { + throw new IllegalArgumentException("Conversation Inputs cannot be null or empty."); + } + + DaprProtos.ConversationRequestAlpha2 protoRequest = buildConversationRequestProto(conversationRequestAlpha2); + + Mono conversationResponseMono = Mono.deferContextual( + context -> this.createMono( + it -> intercept(context, asyncStub).converseAlpha2(protoRequest, it) + ) + ); + + DaprProtos.ConversationResponseAlpha2 conversationResponse = conversationResponseMono.block(); + + assert conversationResponse != null; + List results = buildConversationResults(conversationResponse.getOutputsList()); + return Mono.just(new ConversationResponseAlpha2(conversationResponse.getContextId(), results)); + } catch (Exception ex) { + return DaprException.wrapMono(ex); + } + } + + private DaprProtos.ConversationRequestAlpha2 buildConversationRequestProto(ConversationRequestAlpha2 request) { + DaprProtos.ConversationRequestAlpha2.Builder builder = DaprProtos.ConversationRequestAlpha2 + .newBuilder() + .setTemperature(request.getTemperature()) + .setScrubPii(request.isScrubPii()) + .setName(request.getName()); + + if (request.getContextId() != null) { + builder.setContextId(request.getContextId()); + } + + if (request.getToolChoice() != null) { + builder.setToolChoice(request.getToolChoice()); + } + + + if (request.getTools() != null) { + for (ConversationTools tool : request.getTools()) { + builder.addTools(buildConversationTools(tool)); + } + } + + if (request.getMetadata() != null) { + builder.putAllMetadata(request.getMetadata()); + } + + if (request.getParameters() != null) { + Map parameters = request.getParameters() + .entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> { + try { + return Any.newBuilder().setValue(ByteString.copyFrom(objectSerializer.serialize(e.getValue()))) + .build(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + }) + ); + builder.putAllParameters(parameters); + } + + for (ConversationInputAlpha2 input : request.getInputs()) { + DaprProtos.ConversationInputAlpha2.Builder inputBuilder = DaprProtos.ConversationInputAlpha2 + .newBuilder() + .setScrubPii(input.isScrubPii()); + + if (input.getMessages() != null) { + for (ConversationMessage message : input.getMessages()) { + DaprProtos.ConversationMessage protoMessage = buildConversationMessage(message); + inputBuilder.addMessages(protoMessage); + } + } + + builder.addInputs(inputBuilder.build()); + } + + return builder.build(); + } + + private DaprProtos.ConversationTools buildConversationTools(ConversationTools tool) { + ConversationToolsFunction function = tool.getFunction(); + + DaprProtos.ConversationToolsFunction.Builder protoFunction = DaprProtos.ConversationToolsFunction.newBuilder() + .setName(function.getName()); + + if (function.getDescription() != null) { + protoFunction.setDescription(function.getDescription()); + } + + if (function.getParameters() != null) { + Map functionParams = function.getParameters() + .entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> { + try { + return ProtobufValueHelper.toProtobufValue(e.getValue()); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + )); + + protoFunction.setParameters(Struct.newBuilder().putAllFields(functionParams).build()); + } + + return DaprProtos.ConversationTools.newBuilder().setFunction(protoFunction).build(); + } + + private DaprProtos.ConversationMessage buildConversationMessage(ConversationMessage message) { + DaprProtos.ConversationMessage.Builder messageBuilder = DaprProtos.ConversationMessage.newBuilder(); + + switch (message.getRole()) { + case TOOL: + DaprProtos.ConversationMessageOfTool.Builder toolMessage = + DaprProtos.ConversationMessageOfTool.newBuilder(); + if (message.getName() != null) { + toolMessage.setName(message.getName()); + } + if (message.getContent() != null) { + toolMessage.addAllContent(getConversationMessageContent(message)); + } + if (((ToolMessage)message).getToolId() != null) { + toolMessage.setToolId(((ToolMessage)message).getToolId()); + } + messageBuilder.setOfTool(toolMessage); + break; + case USER: + DaprProtos.ConversationMessageOfUser.Builder userMessage = + DaprProtos.ConversationMessageOfUser.newBuilder(); + if (message.getName() != null) { + userMessage.setName(message.getName()); + } + if (message.getContent() != null) { + userMessage.addAllContent(getConversationMessageContent(message)); + } + messageBuilder.setOfUser(userMessage); + break; + case ASSISTANT: + DaprProtos.ConversationMessageOfAssistant.Builder assistantMessage = + DaprProtos.ConversationMessageOfAssistant.newBuilder(); + + if (message.getName() != null) { + assistantMessage.setName(message.getName()); + } + if (message.getContent() != null) { + assistantMessage.addAllContent(getConversationMessageContent(message)); + } + if (((AssistantMessage)message).getToolCalls() != null) { + assistantMessage.addAllToolCalls(getConversationToolCalls((AssistantMessage)message)); + } + messageBuilder.setOfAssistant(assistantMessage); + break; + case DEVELOPER: + DaprProtos.ConversationMessageOfDeveloper.Builder developerMessage = + DaprProtos.ConversationMessageOfDeveloper.newBuilder(); + if (message.getName() != null) { + developerMessage.setName(message.getName()); + } + if (message.getContent() != null) { + developerMessage.addAllContent(getConversationMessageContent(message)); + } + messageBuilder.setOfDeveloper(developerMessage); + break; + case SYSTEM: + DaprProtos.ConversationMessageOfSystem.Builder systemMessage = + DaprProtos.ConversationMessageOfSystem.newBuilder(); + if (message.getName() != null) { + systemMessage.setName(message.getName()); + } + if (message.getContent() != null) { + systemMessage.addAllContent(getConversationMessageContent(message)); + } + messageBuilder.setOfSystem(systemMessage); + break; + default: + throw new IllegalArgumentException("No role of type " + message.getRole() + " found"); + } + + return messageBuilder.build(); + } + + private List buildConversationResults( + List protoResults) { + List results = new ArrayList<>(); + + for (DaprProtos.ConversationResultAlpha2 protoResult : protoResults) { + List choices = new ArrayList<>(); + + for (DaprProtos.ConversationResultChoices protoChoice : protoResult.getChoicesList()) { + ConversationResultMessage message = buildConversationResultMessage(protoChoice); + choices.add(new ConversationResultChoices(protoChoice.getFinishReason(), protoChoice.getIndex(), message)); + } + + results.add(new ConversationResultAlpha2(choices)); + } + + return results; + } + + private ConversationResultMessage buildConversationResultMessage(DaprProtos.ConversationResultChoices protoChoice) { + if (!protoChoice.hasMessage()) { + return null; + } + + List toolCalls = new ArrayList<>(); + + for (DaprProtos.ConversationToolCalls protoToolCall : protoChoice.getMessage().getToolCallsList()) { + ConversationToolCallsOfFunction function = null; + if (protoToolCall.hasFunction()) { + function = new ConversationToolCallsOfFunction( + protoToolCall.getFunction().getName(), + protoToolCall.getFunction().getArguments() + ); + } + + ConversationToolCalls conversationToolCalls = new ConversationToolCalls(function); + conversationToolCalls.setId(protoToolCall.getId()); + + toolCalls.add(conversationToolCalls); + } + + return new ConversationResultMessage(protoChoice.getMessage().getContent(), toolCalls + ); + } + + private List getConversationMessageContent( + ConversationMessage conversationMessage) { + + List conversationMessageContents = new ArrayList<>(); + for (ConversationMessageContent conversationMessageContent: conversationMessage.getContent()) { + conversationMessageContents.add(DaprProtos.ConversationMessageContent.newBuilder() + .setText(conversationMessageContent.getText()) + .build()); + } + + return conversationMessageContents; + } + + private List getConversationToolCalls( + AssistantMessage assistantMessage) { + List conversationToolCalls = new ArrayList<>(); + for (ConversationToolCalls conversationToolCall: assistantMessage.getToolCalls()) { + DaprProtos.ConversationToolCalls.Builder toolCallsBuilder = DaprProtos.ConversationToolCalls.newBuilder() + .setFunction(DaprProtos.ConversationToolCallsOfFunction.newBuilder() + .setName(conversationToolCall.getFunction().getName()) + .setArguments(conversationToolCall.getFunction().getArguments()) + .build()); + if (conversationToolCall.getId() != null) { + toolCallsBuilder.setId(conversationToolCall.getId()); + } + + conversationToolCalls.add(toolCallsBuilder.build()); + } + + return conversationToolCalls; + } + private DaprMetadata buildDaprMetadata(DaprProtos.GetMetadataResponse response) throws IOException { String id = response.getId(); String runtimeVersion = response.getRuntimeVersion(); diff --git a/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java b/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java index 89c6eded8..92c6a61c3 100644 --- a/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java +++ b/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java @@ -18,7 +18,9 @@ import io.dapr.client.domain.BulkPublishRequest; import io.dapr.client.domain.BulkPublishResponse; import io.dapr.client.domain.BulkPublishResponseFailedEntry; import io.dapr.client.domain.ConversationRequest; +import io.dapr.client.domain.ConversationRequestAlpha2; import io.dapr.client.domain.ConversationResponse; +import io.dapr.client.domain.ConversationResponseAlpha2; import io.dapr.client.domain.DeleteJobRequest; import io.dapr.client.domain.GetJobRequest; import io.dapr.client.domain.GetJobResponse; @@ -313,5 +315,14 @@ public interface DaprPreviewClient extends AutoCloseable { * @param conversationRequest request to be passed to the LLM. * @return {@link ConversationResponse}. */ + @Deprecated public Mono converse(ConversationRequest conversationRequest); + + /* + * Converse with an LLM using Alpha2 API. + * + * @param conversationRequestAlpha2 request to be passed to the LLM with Alpha2 features. + * @return {@link ConversationResponseAlpha2}. + */ + public Mono converseAlpha2(ConversationRequestAlpha2 conversationRequestAlpha2); } diff --git a/sdk/src/main/java/io/dapr/client/ProtobufValueHelper.java b/sdk/src/main/java/io/dapr/client/ProtobufValueHelper.java new file mode 100644 index 000000000..409967f8e --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/ProtobufValueHelper.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client; + +import com.google.protobuf.ListValue; +import com.google.protobuf.NullValue; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import io.dapr.serializer.DaprObjectSerializer; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * Helper class to convert Java objects to Google Protobuf Value types. + */ +public class ProtobufValueHelper { + + /** + * Converts a Java object to a Google Protobuf Value. + * + * @param obj the Java object to convert + * @return the corresponding Protobuf Value + * @throws IOException if serialization fails + */ + public static Value toProtobufValue(Object obj) throws IOException { + if (obj == null) { + return Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build(); + } + + if (obj instanceof Boolean) { + return Value.newBuilder().setBoolValue((Boolean) obj).build(); + } + + if (obj instanceof String) { + return Value.newBuilder().setStringValue((String) obj).build(); + } + + if (obj instanceof Number) { + return Value.newBuilder().setNumberValue(((Number) obj).doubleValue()).build(); + } + + if (obj instanceof List) { + ListValue.Builder listBuilder = ListValue.newBuilder(); + for (Object item : (List) obj) { + listBuilder.addValues(toProtobufValue(item)); + } + return Value.newBuilder().setListValue(listBuilder.build()).build(); + } + + if (obj instanceof Map) { + Struct.Builder structBuilder = Struct.newBuilder(); + for (Map.Entry entry : ((Map) obj).entrySet()) { + String key = entry.getKey().toString(); + Value value = toProtobufValue(entry.getValue()); + structBuilder.putFields(key, value); + } + return Value.newBuilder().setStructValue(structBuilder.build()).build(); + } + + // Fallback: convert to string + return Value.newBuilder().setStringValue(obj.toString()).build(); + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/AssistantMessage.java b/sdk/src/main/java/io/dapr/client/domain/AssistantMessage.java new file mode 100644 index 000000000..1007066aa --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/AssistantMessage.java @@ -0,0 +1,67 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +import java.util.List; + +/** + * Assistant message containing responses from the AI model. + * Can include regular content and/or tool calls that the model wants to make. + */ +public class AssistantMessage implements ConversationMessage { + + private String name; + private final List content; + private final List toolCalls; + + /** + * Creates an assistant message with content and optional tool calls. + * @param content the content of the assistant message. + * @param toolCalls the tool calls requested by the assistant. + */ + public AssistantMessage(List content, List toolCalls) { + this.content = List.copyOf(content); + this.toolCalls = List.copyOf(toolCalls); + } + + @Override + public ConversationMessageRole getRole() { + return ConversationMessageRole.ASSISTANT; + } + + @Override + public String getName() { + return name; + } + + /** + * Sets the name of the assistant participant. + * + * @param name the name to set + * @return this instance for method chaining + */ + public AssistantMessage setName(String name) { + this.name = name; + return this; + } + + @Override + public List getContent() { + return content; + } + + public List getToolCalls() { + return toolCalls; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationInputAlpha2.java b/sdk/src/main/java/io/dapr/client/domain/ConversationInputAlpha2.java new file mode 100644 index 000000000..52ee2f40a --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationInputAlpha2.java @@ -0,0 +1,63 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +import java.util.List; + +/** + * Represents an Alpha2 input for conversation with enhanced message support. + */ +public class ConversationInputAlpha2 { + + private final List messages; + private boolean scrubPii; + + /** + * Constructor. + * + * @param messages the list of conversation messages + */ + public ConversationInputAlpha2(List messages) { + this.messages = List.copyOf(messages); + } + + /** + * Gets the list of conversation messages. + * + * @return the list of messages + */ + public List getMessages() { + return messages; + } + + /** + * Checks if Personally Identifiable Information (PII) should be scrubbed before sending to the LLM. + * + * @return {@code true} if PII should be scrubbed, {@code false} otherwise. + */ + public boolean isScrubPii() { + return scrubPii; + } + + /** + * Enable obfuscation of sensitive information present in the content field. Optional + * + * @param scrubPii A boolean indicating whether to remove PII. + * @return this. + */ + public ConversationInputAlpha2 setScrubPii(boolean scrubPii) { + this.scrubPii = scrubPii; + return this; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationMessage.java b/sdk/src/main/java/io/dapr/client/domain/ConversationMessage.java new file mode 100644 index 000000000..c26c0d041 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationMessage.java @@ -0,0 +1,44 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +import java.util.List; + +/** + * Interface representing a conversation message with role-specific content. + * Supports different message types: system, user, assistant, developer, and tool. + */ +public interface ConversationMessage { + + /** + * Gets the role of the message sender. + * + * @return the message role + */ + ConversationMessageRole getRole(); + + /** + * Gets the name of the participant in the message. + * + * @return the participant name, or null if not specified + */ + String getName(); + + /** + * Gets the content of the message. + * + * @return the message content + */ + List getContent(); +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationMessageContent.java b/sdk/src/main/java/io/dapr/client/domain/ConversationMessageContent.java new file mode 100644 index 000000000..78a4ad604 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationMessageContent.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +/** + * Represents the content of a conversation message. + */ +public class ConversationMessageContent { + + private final String text; + + /** + * Constructor. + * + * @param text the text content of the message + */ + public ConversationMessageContent(String text) { + this.text = text; + } + + /** + * Gets the text content of the message. + * + * @return the text content + */ + public String getText() { + return text; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationMessageRole.java b/sdk/src/main/java/io/dapr/client/domain/ConversationMessageRole.java new file mode 100644 index 000000000..0bfd1b076 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationMessageRole.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +/** + * Enum representing the different roles a conversation message can have. + */ +public enum ConversationMessageRole { + /** + * System message that sets the behavior or context for the conversation. + */ + SYSTEM, + + /** + * User message containing input from the human user. + */ + USER, + + /** + * Assistant message containing responses from the AI model. + */ + ASSISTANT, + + /** + * Tool message containing results from function/tool calls. + */ + TOOL, + + /** + * Developer message for development and debugging purposes. + */ + DEVELOPER +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationRequestAlpha2.java b/sdk/src/main/java/io/dapr/client/domain/ConversationRequestAlpha2.java new file mode 100644 index 000000000..2f85fbd7d --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationRequestAlpha2.java @@ -0,0 +1,209 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +import java.util.List; +import java.util.Map; + +/** + * Represents the Alpha2 conversation configuration with enhanced features including + * tools, improved message handling, and better compatibility with OpenAI ChatCompletion API. + */ +public class ConversationRequestAlpha2 { + + private final String name; + private final List inputs; + private String contextId; + private boolean scrubPii; + private double temperature; + private List tools; + private String toolChoice; + private Map parameters; + private Map metadata; + + /** + * Constructs a ConversationRequestAlpha2 with a component name and conversation inputs. + * + * @param name The name of the Dapr conversation component. See a list of all available conversation components + * @see + * @param inputs the list of Dapr conversation inputs (Alpha2 format) + */ + public ConversationRequestAlpha2(String name, List inputs) { + this.name = name; + this.inputs = inputs; + } + + /** + * Gets the conversation component name. + * + * @return the conversation component name + */ + public String getName() { + return name; + } + + /** + * Gets the list of Dapr conversation input (Alpha2 format). + * + * @return the list of conversation input + */ + public List getInputs() { + return inputs; + } + + /** + * Gets the context identifier. + * + * @return the context identifier + */ + public String getContextId() { + return contextId; + } + + /** + * Sets the context identifier. + * + * @param contextId the context identifier to set + * @return the current instance of {@link ConversationRequestAlpha2} + */ + public ConversationRequestAlpha2 setContextId(String contextId) { + this.contextId = contextId; + return this; + } + + /** + * Checks if PII scrubbing is enabled. + * + * @return true if PII scrubbing is enabled, false otherwise + */ + public boolean isScrubPii() { + return scrubPii; + } + + /** + * Enable obfuscation of sensitive information returning from the LLM. Optional. + * + * @param scrubPii whether to enable PII scrubbing + * @return the current instance of {@link ConversationRequestAlpha2} + */ + public ConversationRequestAlpha2 setScrubPii(boolean scrubPii) { + this.scrubPii = scrubPii; + return this; + } + + /** + * Gets the temperature of the model. Used to optimize for consistency and creativity. Optional + * + * @return the temperature value + */ + public double getTemperature() { + return temperature; + } + + /** + * Sets the temperature of the model. Used to optimize for consistency and creativity. Optional + * + * @param temperature the temperature value to set + * @return the current instance of {@link ConversationRequestAlpha2} + */ + public ConversationRequestAlpha2 setTemperature(double temperature) { + this.temperature = temperature; + return this; + } + + /** + * Gets the tools available to be used by the LLM during the conversation. + * + * @return the list of tools + */ + public List getTools() { + return tools; + } + + /** + * Sets the tools available to be used by the LLM during the conversation. + * These are sent on a per request basis. + * + * @param tools the tools to set + * @return the current instance of {@link ConversationRequestAlpha2} + */ + public ConversationRequestAlpha2 setTools(List tools) { + this.tools = tools; + return this; + } + + /** + * Gets the tool choice setting which controls which (if any) tool is called by the model. + * + * @return the tool choice setting + */ + public String getToolChoice() { + return toolChoice; + } + + /** + * Sets the tool choice setting which controls which (if any) tool is called by the model. + * - "none" means the model will not call any tool and instead generates a message + * - "auto" means the model can pick between generating a message or calling one or more tools + * - "required" requires one or more functions to be called + * - Alternatively, a specific tool name may be used here + * + * @param toolChoice the tool choice setting to set + * @return the current instance of {@link ConversationRequestAlpha2} + */ + public ConversationRequestAlpha2 setToolChoice(String toolChoice) { + this.toolChoice = toolChoice; + return this; + } + + /** + * Gets the parameters for all custom fields. + * + * @return the parameters map + */ + public Map getParameters() { + return parameters; + } + + /** + * Sets the parameters for all custom fields. + * + * @param parameters the parameters to set + * @return the current instance of {@link ConversationRequestAlpha2} + */ + public ConversationRequestAlpha2 setParameters(Map parameters) { + this.parameters = parameters; + return this; + } + + /** + * Gets the metadata passing to conversation components. + * + * @return the metadata map + */ + public Map getMetadata() { + return metadata; + } + + /** + * Sets the metadata passing to conversation components. + * + * @param metadata the metadata to set + * @return the current instance of {@link ConversationRequestAlpha2} + */ + public ConversationRequestAlpha2 setMetadata(Map metadata) { + this.metadata = metadata; + return this; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationResponseAlpha2.java b/sdk/src/main/java/io/dapr/client/domain/ConversationResponseAlpha2.java new file mode 100644 index 000000000..9ef6dac7a --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationResponseAlpha2.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +import java.util.List; + +/** + * Alpha2 response from the Dapr Conversation API with enhanced features. + */ +public class ConversationResponseAlpha2 { + + private final String contextId; + private final List outputs; + + /** + * Constructor. + * + * @param contextId context id supplied to LLM. + * @param outputs outputs from the LLM (Alpha2 format). + */ + public ConversationResponseAlpha2(String contextId, List outputs) { + this.contextId = contextId; + this.outputs = List.copyOf(outputs); + } + + /** + * The ID of an existing chat (like in ChatGPT). + * + * @return String identifier. + */ + public String getContextId() { + return this.contextId; + } + + /** + * Get list of conversation outputs (Alpha2 format). + * + * @return List{@link ConversationResultAlpha2}. + */ + public List getOutputs() { + return this.outputs; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationResultAlpha2.java b/sdk/src/main/java/io/dapr/client/domain/ConversationResultAlpha2.java new file mode 100644 index 000000000..369caeb65 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationResultAlpha2.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +import java.util.List; + +/** + * Alpha2 result for conversation output with enhanced choice-based structure. + */ +public class ConversationResultAlpha2 { + + private final List choices; + + /** + * Constructor. + * + * @param choices the list of conversation result choices. + */ + public ConversationResultAlpha2(List choices) { + this.choices = List.copyOf(choices); + } + + /** + * Gets the list of conversation result choices. + * + * @return the list of conversation result choices + */ + public List getChoices() { + return choices; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationResultChoices.java b/sdk/src/main/java/io/dapr/client/domain/ConversationResultChoices.java new file mode 100644 index 000000000..468286987 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationResultChoices.java @@ -0,0 +1,68 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +/** + * Represents a conversation result choice with finish reason, index, and message. + */ +public class ConversationResultChoices { + + private final String finishReason; + private final long index; + private final ConversationResultMessage message; + + /** + * Constructor. + * + * @param finishReason the reason the model stopped generating tokens + * @param index the index of the choice in the list of choices + * @param message the result message + */ + public ConversationResultChoices(String finishReason, long index, ConversationResultMessage message) { + this.finishReason = finishReason; + this.index = index; + this.message = message; + } + + /** + * Gets the reason the model stopped generating tokens. + * This will be "stop" if the model hit a natural stop point or a provided stop sequence, + * "length" if the maximum number of tokens specified in the request was reached, + * "content_filter" if content was omitted due to a flag from content filters, + * "tool_calls" if the model called a tool. + * + * @return the finish reason + */ + public String getFinishReason() { + return finishReason; + } + + /** + * Gets the index of the choice in the list of choices. + * + * @return the index + */ + public long getIndex() { + return index; + } + + /** + * Gets the result message. + * + * @return the message + */ + public ConversationResultMessage getMessage() { + return message; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationResultMessage.java b/sdk/src/main/java/io/dapr/client/domain/ConversationResultMessage.java new file mode 100644 index 000000000..bbeeebae6 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationResultMessage.java @@ -0,0 +1,72 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +import java.util.List; + +/** + * Represents a conversation result message with content and optional tool calls. + */ +public class ConversationResultMessage { + + private final String content; + private final List toolCalls; + + /** + * Constructor. + * + * @param content the contents of the message + * @param toolCalls the tool calls generated by the model (optional) + */ + public ConversationResultMessage(String content, List toolCalls) { + this.content = content; + this.toolCalls = toolCalls != null ? List.copyOf(toolCalls) : null; + } + + /** + * Constructor for message without tool calls. + * + * @param content the contents of the message + */ + public ConversationResultMessage(String content) { + this(content, null); + } + + /** + * Gets the contents of the message. + * + * @return the message content + */ + public String getContent() { + return content; + } + + /** + * Gets the tool calls generated by the model. + * + * @return the tool calls, or null if none + */ + public List getToolCalls() { + return toolCalls; + } + + /** + * Checks if the message has tool calls. + * + * @return true if there are tool calls, false otherwise + */ + public boolean hasToolCalls() { + return toolCalls != null && !toolCalls.isEmpty(); + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationToolCalls.java b/sdk/src/main/java/io/dapr/client/domain/ConversationToolCalls.java new file mode 100644 index 000000000..73646e664 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationToolCalls.java @@ -0,0 +1,61 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +/** + * Represents a tool call request sent from the LLM to the client to execute. + */ +public class ConversationToolCalls { + + private String id; + private final ConversationToolCallsOfFunction function; + + /** + * Constructor without ID. + * + * @param function the function to call + */ + public ConversationToolCalls(ConversationToolCallsOfFunction function) { + this.function = function; + } + + /** + * Gets the unique identifier for the tool call. + * + * @return the tool call ID, or null if not provided + */ + public String getId() { + return id; + } + + /** + * Set with ID. + * + * @param id the unique identifier for the tool call + * @return this instance for method chaining + */ + public ConversationToolCalls setId(String id) { + this.id = id; + return this; + } + + /** + * Gets the function to call. + * + * @return the function details + */ + public ConversationToolCallsOfFunction getFunction() { + return function; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationToolCallsOfFunction.java b/sdk/src/main/java/io/dapr/client/domain/ConversationToolCallsOfFunction.java new file mode 100644 index 000000000..0edacd7d9 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationToolCallsOfFunction.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +/** + * Represents a function call within a tool call. + */ +public class ConversationToolCallsOfFunction { + + private final String name; + private final String arguments; + + /** + * Constructor. + * + * @param name the name of the function to call + * @param arguments the arguments to call the function with, as generated by the model in JSON format + */ + public ConversationToolCallsOfFunction(String name, String arguments) { + this.name = name; + this.arguments = arguments; + } + + /** + * Gets the name of the function to call. + * + * @return the function name + */ + public String getName() { + return name; + } + + /** + * Gets the arguments to call the function with, as generated by the model in JSON format. + * Note that the model does not always generate valid JSON, and may hallucinate parameters + * not defined by your function schema. Validate the arguments in your code before calling your function. + * + * @return the function arguments in JSON format + */ + public String getArguments() { + return arguments; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationTools.java b/sdk/src/main/java/io/dapr/client/domain/ConversationTools.java new file mode 100644 index 000000000..f36fa0545 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationTools.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +/** + * Represents tool definitions that can be used during conversation. + */ +public class ConversationTools { + + private final ConversationToolsFunction function; + + /** + * Constructor. + * + * @param function the function definition + */ + public ConversationTools(ConversationToolsFunction function) { + this.function = function; + } + + /** + * Gets the function definition. + * + * @return the function definition + */ + public ConversationToolsFunction getFunction() { + return function; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationToolsFunction.java b/sdk/src/main/java/io/dapr/client/domain/ConversationToolsFunction.java new file mode 100644 index 000000000..11e3afdf3 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationToolsFunction.java @@ -0,0 +1,75 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +import java.util.Map; + +/** + * Represents a function definition for conversation tools. + */ +public class ConversationToolsFunction { + + private String description; + private final String name; + private final Map parameters; + + /** + * Constructor. + * + * @param name the function name + * @param parameters the function parameters schema + */ + public ConversationToolsFunction(String name, Map parameters) { + this.name = name; + this.parameters = parameters; + } + + /** + * Gets the function name. + * + * @return the function name + */ + public String getName() { + return name; + } + + /** + * Gets the function description. + * + * @return the function description + */ + public String getDescription() { + return description; + } + + /** + * Sets the function description. + * + * @param description the function description + * @return this instance for method chaining + */ + public ConversationToolsFunction setDescription(String description) { + this.description = description; + return this; + } + + /** + * Gets the function parameters schema. + * + * @return the function parameters + */ + public Map getParameters() { + return parameters; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/DeveloperMessage.java b/sdk/src/main/java/io/dapr/client/domain/DeveloperMessage.java new file mode 100644 index 000000000..5dbaa58a5 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/DeveloperMessage.java @@ -0,0 +1,61 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +import java.util.List; + +/** + * Developer message for development and debugging purposes. + * Used for providing additional context or instructions during development. + */ +public class DeveloperMessage implements ConversationMessage { + + private String name; + private final List content; + + /** + * Creates a developer message with content. + * + * @param content the content of the developer message + */ + public DeveloperMessage(List content) { + this.content = List.copyOf(content); + } + + @Override + public ConversationMessageRole getRole() { + return ConversationMessageRole.DEVELOPER; + } + + @Override + public String getName() { + return name; + } + + /** + * Sets the name of the developer participant. + * + * @param name the name to set + * @return this instance for method chaining + */ + public DeveloperMessage setName(String name) { + this.name = name; + return this; + } + + @Override + public List getContent() { + return content; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/SystemMessage.java b/sdk/src/main/java/io/dapr/client/domain/SystemMessage.java new file mode 100644 index 000000000..aacdb80d5 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/SystemMessage.java @@ -0,0 +1,59 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +import java.util.List; + +/** + * System message that sets the behavior or context for the conversation. + * Used to provide instructions or context to the AI model. + */ +public class SystemMessage implements ConversationMessage { + + private String name; + private final List content; + + /** + * Creates a system message with content. + * + * @param content the content of the system message + */ + public SystemMessage(List content) { + this.content = List.copyOf(content); + } + + @Override + public ConversationMessageRole getRole() { + return ConversationMessageRole.SYSTEM; + } + + @Override + public String getName() { + return name; + } + + /** + * Sets the name of the system participant. + * + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + @Override + public List getContent() { + return content; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ToolMessage.java b/sdk/src/main/java/io/dapr/client/domain/ToolMessage.java new file mode 100644 index 000000000..e88e37af4 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ToolMessage.java @@ -0,0 +1,77 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +import java.util.List; + +/** + * Tool message containing results from function/tool calls. + * Used to provide the response from a tool execution back to the AI model. + */ +public class ToolMessage implements ConversationMessage { + + private String toolId; + private String name; + private final List content; + + /** + * Creates a tool message with content. + * + * @param content the content containing the tool execution result + */ + public ToolMessage(List content) { + this.content = List.copyOf(content); + } + + @Override + public ConversationMessageRole getRole() { + return ConversationMessageRole.TOOL; + } + + @Override + public String getName() { + return name; + } + + /** + * Sets the tool identifier. + * + * @param toolId the tool identifier to set + * @return this instance for method chaining + */ + public ToolMessage setToolId(String toolId) { + this.toolId = toolId; + return this; + } + + /** + * Sets the name of the tool participant. + * + * @param name the name to set + * @return this instance for method chaining + */ + public ToolMessage setName(String name) { + this.name = name; + return this; + } + + @Override + public List getContent() { + return content; + } + + public String getToolId() { + return toolId; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/UserMessage.java b/sdk/src/main/java/io/dapr/client/domain/UserMessage.java new file mode 100644 index 000000000..30ae023a8 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/UserMessage.java @@ -0,0 +1,61 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client.domain; + +import java.util.List; + +/** + * User message containing input from the human user. + * Represents questions, requests, or other input from the end user. + */ +public class UserMessage implements ConversationMessage { + + private String name; + private final List content; + + /** + * Creates a user message with content. + * + * @param content the content of the user message + */ + public UserMessage(List content) { + this.content = List.copyOf(content); + } + + @Override + public ConversationMessageRole getRole() { + return ConversationMessageRole.USER; + } + + @Override + public String getName() { + return name; + } + + /** + * Sets the name of the user participant. + * + * @param name the name to set + * @return this instance for method chaining + */ + public UserMessage setName(String name) { + this.name = name; + return this; + } + + @Override + public List getContent() { + return content; + } +} diff --git a/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java b/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java index 8f0667b8d..f7b5584cc 100644 --- a/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java @@ -16,12 +16,29 @@ package io.dapr.client; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; import com.google.protobuf.Any; import com.google.protobuf.ByteString; +import io.dapr.client.domain.AssistantMessage; import io.dapr.client.domain.BulkPublishEntry; import io.dapr.client.domain.BulkPublishRequest; import io.dapr.client.domain.BulkPublishResponse; import io.dapr.client.domain.CloudEvent; +import io.dapr.client.domain.ConversationToolCallsOfFunction; +import io.dapr.client.domain.ConversationToolsFunction; +import io.dapr.client.domain.ConversationInputAlpha2; +import io.dapr.client.domain.ConversationMessage; +import io.dapr.client.domain.ConversationMessageContent; +import io.dapr.client.domain.ConversationRequestAlpha2; +import io.dapr.client.domain.ConversationResponseAlpha2; +import io.dapr.client.domain.ConversationResultAlpha2; +import io.dapr.client.domain.ConversationResultChoices; +import io.dapr.client.domain.ConversationToolCalls; +import io.dapr.client.domain.ConversationTools; +import io.dapr.client.domain.DeleteJobRequest; +import io.dapr.client.domain.DeveloperMessage; +import io.dapr.client.domain.GetJobRequest; +import io.dapr.client.domain.GetJobResponse; import io.dapr.client.domain.ConstantFailurePolicy; import io.dapr.client.domain.ConversationInput; import io.dapr.client.domain.ConversationRequest; @@ -35,7 +52,10 @@ import io.dapr.client.domain.QueryStateItem; import io.dapr.client.domain.QueryStateRequest; import io.dapr.client.domain.QueryStateResponse; import io.dapr.client.domain.ScheduleJobRequest; +import io.dapr.client.domain.SystemMessage; +import io.dapr.client.domain.ToolMessage; import io.dapr.client.domain.UnlockResponseStatus; +import io.dapr.client.domain.UserMessage; import io.dapr.client.domain.query.Query; import io.dapr.serializer.DaprObjectSerializer; import io.dapr.serializer.DefaultObjectSerializer; @@ -1329,6 +1349,473 @@ public class DaprPreviewClientGrpcTest { assertEquals("Name in the request cannot be null or empty", exception.getMessage()); } + @Test + public void converseAlpha2ShouldThrowIllegalArgumentExceptionWhenNameIsNull() { + List messages = new ArrayList<>(); + SystemMessage systemMsg = new SystemMessage(List.of(new ConversationMessageContent("System info"))); + systemMsg.setName("system"); + messages.add(systemMsg); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(messages); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2(null, List.of(input)); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + previewClient.converseAlpha2(request).block()); + assertEquals("LLM name cannot be null or empty.", exception.getMessage()); + } + + @Test + public void converseAlpha2ShouldThrowIllegalArgumentExceptionWhenNameIsEmpty() { + List messages = new ArrayList<>(); + SystemMessage systemMsg = new SystemMessage(List.of(new ConversationMessageContent("System info"))); + systemMsg.setName("system"); + messages.add(systemMsg); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(messages); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("", List.of(input)); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + previewClient.converseAlpha2(request).block()); + assertEquals("LLM name cannot be null or empty.", exception.getMessage()); + } + + @Test + public void converseAlpha2ShouldThrowIllegalArgumentExceptionWhenNameIsWhitespace() { + List messages = new ArrayList<>(); + SystemMessage systemMsg = new SystemMessage(List.of(new ConversationMessageContent("System info"))); + systemMsg.setName("system"); + messages.add(systemMsg); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(messages); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2(" ", null); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + previewClient.converseAlpha2(request).block()); + assertEquals("LLM name cannot be null or empty.", exception.getMessage()); + } + + @Test + public void converseAlpha2ShouldThrowIllegalArgumentExceptionWhenInputIsNull() { + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("abc", null); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + previewClient.converseAlpha2(request).block()); + assertEquals("Conversation Inputs cannot be null or empty.", exception.getMessage()); + } + + @Test + public void converseAlpha2ShouldThrowIllegalArgumentExceptionWhenInputIsEmpty() { + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("abc", new ArrayList<>()); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + previewClient.converseAlpha2(request).block()); + assertEquals("Conversation Inputs cannot be null or empty.", exception.getMessage()); + } + + @Test + public void converseAlpha2ExceptionThrownTest() { + doAnswer((Answer) invocation -> { + throw newStatusRuntimeException("INVALID_ARGUMENT", "bad argument"); + }).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any()); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", null); + + assertThrows(IllegalArgumentException.class, () -> previewClient.converseAlpha2(request).block()); + } + + @Test + public void converseAlpha2CallbackExceptionThrownTest() { + doAnswer((Answer) invocation -> { + StreamObserver observer = + (StreamObserver) invocation.getArguments()[1]; + observer.onError(newStatusRuntimeException("INVALID_ARGUMENT", "bad argument")); + return null; + }).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any()); + + List messages = new ArrayList<>(); + SystemMessage systemMsg = new SystemMessage(List.of(new ConversationMessageContent("System info"))); + systemMsg.setName("system"); + messages.add(systemMsg); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(messages); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", List.of(input)); + Mono result = previewClient.converseAlpha2(request); + + assertThrowsDaprException( + ExecutionException.class, + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: bad argument", + () -> result.block()); + } + + @Test + public void converseAlpha2MinimalRequestTest() { + DaprProtos.ConversationResponseAlpha2 grpcResponse = DaprProtos.ConversationResponseAlpha2.newBuilder() + .setContextId("test-context") + .addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder() + .addChoices(DaprProtos.ConversationResultChoices.newBuilder() + .setFinishReason("stop") + .setIndex(0) + .setMessage(DaprProtos.ConversationResultMessage.newBuilder() + .setContent("Hello! How can I help you today?") + .build()) + .build()) + .build()) + .build(); + + doAnswer((Answer) invocation -> { + StreamObserver observer = + (StreamObserver) invocation.getArguments()[1]; + observer.onNext(grpcResponse); + observer.onCompleted(); + return null; + }).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any()); + + List messages = new ArrayList<>(); + DeveloperMessage devMsg = new DeveloperMessage(List.of(new ConversationMessageContent("Debug info"))); + devMsg.setName("developer"); + messages.add(devMsg); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(messages); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", List.of(input)); + ConversationResponseAlpha2 response = previewClient.converseAlpha2(request).block(); + + assertNotNull(response); + assertEquals("test-context", response.getContextId()); + assertEquals(1, response.getOutputs().size()); + + ConversationResultAlpha2 result = response.getOutputs().get(0); + assertEquals(1, result.getChoices().size()); + + ConversationResultChoices choice = result.getChoices().get(0); + assertEquals("stop", choice.getFinishReason()); + assertEquals(0, choice.getIndex()); + assertEquals("Hello! How can I help you today?", choice.getMessage().getContent()); + } + + @Test + public void converseAlpha2ComplexRequestTest() { + // Create messages + List messages = new ArrayList<>(); + UserMessage userMessage = new UserMessage(List.of(new ConversationMessageContent("Hello, how are you?"))); + userMessage.setName("John"); + messages.add(userMessage); + + // Create input + ConversationInputAlpha2 input = new ConversationInputAlpha2(messages); + input.setScrubPii(true); + + // Create tools + Map functionParams = new HashMap<>(); + functionParams.put("location", "Required location parameter"); + List tools = new ArrayList<>(); + ConversationToolsFunction function = new ConversationToolsFunction("get_weather", functionParams); + function.setDescription("Get current weather"); + + ConversationTools tool = new ConversationTools(function); + tools.add(tool); + + Map metadata = new HashMap<>(); + metadata.put("key1", "value1"); + + Map parameters = new HashMap<>(); + parameters.put("max_tokens", "1000"); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", List.of(input)); + request.setContextId("test-context"); + request.setTemperature(0.7); + request.setScrubPii(true); + request.setTools(tools); + request.setToolChoice("auto"); + request.setMetadata(metadata); + request.setParameters(parameters); + + // Mock response with tool calls + DaprProtos.ConversationResponseAlpha2 grpcResponse = DaprProtos.ConversationResponseAlpha2.newBuilder() + .setContextId("test-context") + .addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder() + .addChoices(DaprProtos.ConversationResultChoices.newBuilder() + .setFinishReason("tool_calls") + .setIndex(0) + .setMessage(DaprProtos.ConversationResultMessage.newBuilder() + .setContent("I'll help you get the weather information.") + .addToolCalls(DaprProtos.ConversationToolCalls.newBuilder() + .setId("call_123") + .setFunction(DaprProtos.ConversationToolCallsOfFunction.newBuilder() + .setName("get_weather") + .setArguments("{\"location\": \"New York\"}") + .build()) + .build()) + .build()) + .build()) + .build()) + .build(); + + doAnswer((Answer) invocation -> { + StreamObserver observer = + (StreamObserver) invocation.getArguments()[1]; + observer.onNext(grpcResponse); + observer.onCompleted(); + return null; + }).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any()); + + ConversationResponseAlpha2 response = previewClient.converseAlpha2(request).block(); + + assertNotNull(response); + assertEquals("test-context", response.getContextId()); + + ConversationResultChoices choice = response.getOutputs().get(0).getChoices().get(0); + assertEquals("tool_calls", choice.getFinishReason()); + assertEquals("I'll help you get the weather information.", choice.getMessage().getContent()); + assertEquals(1, choice.getMessage().getToolCalls().size()); + + ConversationToolCalls toolCall = choice.getMessage().getToolCalls().get(0); + assertEquals("call_123", toolCall.getId()); + assertEquals("get_weather", toolCall.getFunction().getName()); + assertEquals("{\"location\": \"New York\"}", toolCall.getFunction().getArguments()); + + // Verify the request was built correctly + ArgumentCaptor captor = + ArgumentCaptor.forClass(DaprProtos.ConversationRequestAlpha2.class); + verify(daprStub).converseAlpha2(captor.capture(), any()); + + DaprProtos.ConversationRequestAlpha2 capturedRequest = captor.getValue(); + assertEquals("openai", capturedRequest.getName()); + assertEquals("test-context", capturedRequest.getContextId()); + assertEquals(0.7, capturedRequest.getTemperature(), 0.001); + assertTrue(capturedRequest.getScrubPii()); + assertEquals("auto", capturedRequest.getToolChoice()); + assertEquals("value1", capturedRequest.getMetadataMap().get("key1")); + assertEquals(1, capturedRequest.getToolsCount()); + assertEquals("get_weather", capturedRequest.getTools(0).getFunction().getName()); + } + + @Test + public void converseAlpha2AllMessageTypesTest() { + List messages = new ArrayList<>(); + + // System message + SystemMessage systemMsg = new SystemMessage(List.of(new ConversationMessageContent("You are a helpful assistant."))); + systemMsg.setName("system"); + messages.add(systemMsg); + + // User message + UserMessage userMsg = new UserMessage(List.of(new ConversationMessageContent("Hello!"))); + userMsg.setName("user"); + messages.add(userMsg); + + // Assistant message + AssistantMessage assistantMsg = new AssistantMessage(List.of(new ConversationMessageContent("Hi there!")), + List.of(new ConversationToolCalls(new ConversationToolCallsOfFunction("abc", "parameters")))); + assistantMsg.setName("assistant"); + messages.add(assistantMsg); + + // Tool message + ToolMessage toolMsg = new ToolMessage(List.of(new ConversationMessageContent("Weather data: 72F"))); + toolMsg.setName("tool"); + messages.add(toolMsg); + + // Developer message + DeveloperMessage devMsg = new DeveloperMessage(List.of(new ConversationMessageContent("Debug info"))); + devMsg.setName("developer"); + messages.add(devMsg); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(messages); + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", List.of(input)); + + DaprProtos.ConversationResponseAlpha2 grpcResponse = DaprProtos.ConversationResponseAlpha2.newBuilder() + .addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder() + .addChoices(DaprProtos.ConversationResultChoices.newBuilder() + .setFinishReason("stop") + .setIndex(0) + .setMessage(DaprProtos.ConversationResultMessage.newBuilder() + .setContent("Processed all message types") + .build()) + .build()) + .build()) + .build(); + + doAnswer((Answer) invocation -> { + StreamObserver observer = + (StreamObserver) invocation.getArguments()[1]; + observer.onNext(grpcResponse); + observer.onCompleted(); + return null; + }).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any()); + + ConversationResponseAlpha2 response = previewClient.converseAlpha2(request).block(); + + assertNotNull(response); + assertEquals("Processed all message types", response.getOutputs().get(0).getChoices().get(0).getMessage().getContent()); + + // Verify all message types were processed + ArgumentCaptor captor = + ArgumentCaptor.forClass(DaprProtos.ConversationRequestAlpha2.class); + verify(daprStub).converseAlpha2(captor.capture(), any()); + + DaprProtos.ConversationRequestAlpha2 capturedRequest = captor.getValue(); + assertEquals(1, capturedRequest.getInputsCount()); + assertEquals(5, capturedRequest.getInputs(0).getMessagesCount()); + + // Verify each message type was converted correctly + List capturedMessages = capturedRequest.getInputs(0).getMessagesList(); + assertTrue(capturedMessages.get(0).hasOfSystem()); + assertTrue(capturedMessages.get(1).hasOfUser()); + assertTrue(capturedMessages.get(2).hasOfAssistant()); + assertTrue(capturedMessages.get(3).hasOfTool()); + assertTrue(capturedMessages.get(4).hasOfDeveloper()); + } + + @Test + public void converseAlpha2ResponseWithoutMessageTest() { + List messages = new ArrayList<>(); + DeveloperMessage devMsg = new DeveloperMessage(List.of(new ConversationMessageContent("Debug info"))); + devMsg.setName("developer"); + messages.add(devMsg); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(messages); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", List.of(input)); + + DaprProtos.ConversationResponseAlpha2 grpcResponse = DaprProtos.ConversationResponseAlpha2.newBuilder() + .addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder() + .addChoices(DaprProtos.ConversationResultChoices.newBuilder() + .setFinishReason("stop") + .setIndex(0) + // No message set + .build()) + .build()) + .build(); + + doAnswer((Answer) invocation -> { + StreamObserver observer = + (StreamObserver) invocation.getArguments()[1]; + observer.onNext(grpcResponse); + observer.onCompleted(); + return null; + }).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any()); + + ConversationResponseAlpha2 response = previewClient.converseAlpha2(request).block(); + + assertNotNull(response); + ConversationResultChoices choice = response.getOutputs().get(0).getChoices().get(0); + assertEquals("stop", choice.getFinishReason()); + assertEquals(0, choice.getIndex()); + assertNull(choice.getMessage()); + } + + @Test + public void converseAlpha2MultipleResultsTest() { + List messages = new ArrayList<>(); + DeveloperMessage devMsg = new DeveloperMessage(List.of(new ConversationMessageContent("Debug info"))); + devMsg.setName("developer"); + messages.add(devMsg); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(messages); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", List.of(input)); + + DaprProtos.ConversationResponseAlpha2 grpcResponse = DaprProtos.ConversationResponseAlpha2.newBuilder() + .addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder() + .addChoices(DaprProtos.ConversationResultChoices.newBuilder() + .setFinishReason("stop") + .setIndex(0) + .setMessage(DaprProtos.ConversationResultMessage.newBuilder() + .setContent("First choice") + .build()) + .build()) + .addChoices(DaprProtos.ConversationResultChoices.newBuilder() + .setFinishReason("stop") + .setIndex(1) + .setMessage(DaprProtos.ConversationResultMessage.newBuilder() + .setContent("Second choice") + .build()) + .build()) + .build()) + .addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder() + .addChoices(DaprProtos.ConversationResultChoices.newBuilder() + .setFinishReason("length") + .setIndex(0) + .setMessage(DaprProtos.ConversationResultMessage.newBuilder() + .setContent("Third result") + .build()) + .build()) + .build()) + .build(); + + doAnswer((Answer) invocation -> { + StreamObserver observer = + (StreamObserver) invocation.getArguments()[1]; + observer.onNext(grpcResponse); + observer.onCompleted(); + return null; + }).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any()); + + ConversationResponseAlpha2 response = previewClient.converseAlpha2(request).block(); + + assertNotNull(response); + assertEquals(2, response.getOutputs().size()); + + // First result with 2 choices + ConversationResultAlpha2 firstResult = response.getOutputs().get(0); + assertEquals(2, firstResult.getChoices().size()); + assertEquals("First choice", firstResult.getChoices().get(0).getMessage().getContent()); + assertEquals("Second choice", firstResult.getChoices().get(1).getMessage().getContent()); + + // Second result with 1 choice + ConversationResultAlpha2 secondResult = response.getOutputs().get(1); + assertEquals(1, secondResult.getChoices().size()); + assertEquals("Third result", secondResult.getChoices().get(0).getMessage().getContent()); + } + + @Test + public void converseAlpha2ToolCallWithoutFunctionTest() { + List messages = new ArrayList<>(); + UserMessage userMsg = new UserMessage(List.of(new ConversationMessageContent("Debug info"))); + userMsg.setName("developer"); + messages.add(userMsg); + + ConversationInputAlpha2 input = new ConversationInputAlpha2(messages); + + ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", List.of(input)); + DaprProtos.ConversationResponseAlpha2 grpcResponse = DaprProtos.ConversationResponseAlpha2.newBuilder() + .addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder() + .addChoices(DaprProtos.ConversationResultChoices.newBuilder() + .setFinishReason("tool_calls") + .setIndex(0) + .setMessage(DaprProtos.ConversationResultMessage.newBuilder() + .setContent("Test content") + .addToolCalls(DaprProtos.ConversationToolCalls.newBuilder() + .setId("call_123") + // No function set + .build()) + .build()) + .build()) + .build()) + .build(); + + doAnswer((Answer) invocation -> { + StreamObserver observer = + (StreamObserver) invocation.getArguments()[1]; + observer.onNext(grpcResponse); + observer.onCompleted(); + return null; + }).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any()); + + ConversationResponseAlpha2 response = previewClient.converseAlpha2(request).block(); + + assertNotNull(response); + ConversationToolCalls toolCall = response.getOutputs().get(0).getChoices().get(0) + .getMessage().getToolCalls().get(0); + assertEquals("call_123", toolCall.getId()); + assertNull(toolCall.getFunction()); + } + private DaprProtos.QueryStateResponse buildQueryStateResponse(List> resp,String token) throws JsonProcessingException { List items = new ArrayList<>(); diff --git a/sdk/src/test/java/io/dapr/client/ProtobufValueHelperTest.java b/sdk/src/test/java/io/dapr/client/ProtobufValueHelperTest.java new file mode 100644 index 000000000..c345f34ff --- /dev/null +++ b/sdk/src/test/java/io/dapr/client/ProtobufValueHelperTest.java @@ -0,0 +1,423 @@ +/* + * Copyright 2021 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client; + +import com.google.protobuf.ListValue; +import com.google.protobuf.NullValue; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ProtobufValueHelperTest { + + @Test + public void testToProtobufValue_Null() throws IOException { + Value result = ProtobufValueHelper.toProtobufValue(null); + + assertNotNull(result); + assertTrue(result.hasNullValue()); + assertEquals(NullValue.NULL_VALUE, result.getNullValue()); + } + + @Test + public void testToProtobufValue_Boolean_True() throws IOException { + Value result = ProtobufValueHelper.toProtobufValue(true); + + assertNotNull(result); + assertTrue(result.hasBoolValue()); + assertEquals(true, result.getBoolValue()); + } + + @Test + public void testToProtobufValue_Boolean_False() throws IOException { + Value result = ProtobufValueHelper.toProtobufValue(false); + + assertNotNull(result); + assertTrue(result.hasBoolValue()); + assertEquals(false, result.getBoolValue()); + } + + @Test + public void testToProtobufValue_String() throws IOException { + String testString = "Hello, World!"; + Value result = ProtobufValueHelper.toProtobufValue(testString); + + assertNotNull(result); + assertTrue(result.hasStringValue()); + assertEquals(testString, result.getStringValue()); + } + + @Test + public void testToProtobufValue_String_Empty() throws IOException { + String emptyString = ""; + Value result = ProtobufValueHelper.toProtobufValue(emptyString); + + assertNotNull(result); + assertTrue(result.hasStringValue()); + assertEquals(emptyString, result.getStringValue()); + } + + @Test + public void testToProtobufValue_Integer() throws IOException { + Integer testInt = 42; + Value result = ProtobufValueHelper.toProtobufValue(testInt); + + assertNotNull(result); + assertTrue(result.hasNumberValue()); + assertEquals(42.0, result.getNumberValue(), 0.001); + } + + @Test + public void testToProtobufValue_Long() throws IOException { + Long testLong = 9876543210L; + Value result = ProtobufValueHelper.toProtobufValue(testLong); + + assertNotNull(result); + assertTrue(result.hasNumberValue()); + assertEquals(9876543210.0, result.getNumberValue(), 0.001); + } + + @Test + public void testToProtobufValue_Double() throws IOException { + Double testDouble = 3.14159; + Value result = ProtobufValueHelper.toProtobufValue(testDouble); + + assertNotNull(result); + assertTrue(result.hasNumberValue()); + assertEquals(testDouble, result.getNumberValue(), 0.00001); + } + + @Test + public void testToProtobufValue_Float() throws IOException { + Float testFloat = 2.718f; + Value result = ProtobufValueHelper.toProtobufValue(testFloat); + + assertNotNull(result); + assertTrue(result.hasNumberValue()); + assertEquals(2.718, result.getNumberValue(), 0.001); + } + + @Test + public void testToProtobufValue_BigInteger() throws IOException { + BigInteger testBigInt = new BigInteger("123456789012345678901234567890"); + Value result = ProtobufValueHelper.toProtobufValue(testBigInt); + + assertNotNull(result); + assertTrue(result.hasNumberValue()); + assertEquals(1.2345678901234568E29, result.getNumberValue(), 1E20); + } + + @Test + public void testToProtobufValue_BigDecimal() throws IOException { + BigDecimal testBigDecimal = new BigDecimal("123.456789"); + Value result = ProtobufValueHelper.toProtobufValue(testBigDecimal); + + assertNotNull(result); + assertTrue(result.hasNumberValue()); + assertEquals(123.456789, result.getNumberValue(), 0.000001); + } + + @Test + public void testToProtobufValue_EmptyList() throws IOException { + List emptyList = new ArrayList<>(); + Value result = ProtobufValueHelper.toProtobufValue(emptyList); + + assertNotNull(result); + assertTrue(result.hasListValue()); + ListValue listValue = result.getListValue(); + assertEquals(0, listValue.getValuesCount()); + } + + @Test + public void testToProtobufValue_SimpleList() throws IOException { + List testList = Arrays.asList("hello", 42, true, null); + Value result = ProtobufValueHelper.toProtobufValue(testList); + + assertNotNull(result); + assertTrue(result.hasListValue()); + ListValue listValue = result.getListValue(); + assertEquals(4, listValue.getValuesCount()); + + // Verify each element + assertEquals("hello", listValue.getValues(0).getStringValue()); + assertEquals(42.0, listValue.getValues(1).getNumberValue(), 0.001); + assertEquals(true, listValue.getValues(2).getBoolValue()); + assertEquals(NullValue.NULL_VALUE, listValue.getValues(3).getNullValue()); + } + + @Test + public void testToProtobufValue_NestedList() throws IOException { + List innerList = Arrays.asList(1, 2, 3); + List outerList = Arrays.asList("outer", innerList, "end"); + Value result = ProtobufValueHelper.toProtobufValue(outerList); + + assertNotNull(result); + assertTrue(result.hasListValue()); + ListValue listValue = result.getListValue(); + assertEquals(3, listValue.getValuesCount()); + + // Verify nested list + assertEquals("outer", listValue.getValues(0).getStringValue()); + assertTrue(listValue.getValues(1).hasListValue()); + ListValue nestedList = listValue.getValues(1).getListValue(); + assertEquals(3, nestedList.getValuesCount()); + assertEquals(1.0, nestedList.getValues(0).getNumberValue(), 0.001); + assertEquals(2.0, nestedList.getValues(1).getNumberValue(), 0.001); + assertEquals(3.0, nestedList.getValues(2).getNumberValue(), 0.001); + assertEquals("end", listValue.getValues(2).getStringValue()); + } + + @Test + public void testToProtobufValue_EmptyMap() throws IOException { + Map emptyMap = new HashMap<>(); + Value result = ProtobufValueHelper.toProtobufValue(emptyMap); + + assertNotNull(result); + assertTrue(result.hasStructValue()); + Struct struct = result.getStructValue(); + assertEquals(0, struct.getFieldsCount()); + } + + @Test + public void testToProtobufValue_SimpleMap() throws IOException { + Map testMap = new LinkedHashMap<>(); + testMap.put("name", "John Doe"); + testMap.put("age", 30); + testMap.put("active", true); + testMap.put("description", null); + + Value result = ProtobufValueHelper.toProtobufValue(testMap); + + assertNotNull(result); + assertTrue(result.hasStructValue()); + Struct struct = result.getStructValue(); + assertEquals(4, struct.getFieldsCount()); + + // Verify each field + assertEquals("John Doe", struct.getFieldsMap().get("name").getStringValue()); + assertEquals(30.0, struct.getFieldsMap().get("age").getNumberValue(), 0.001); + assertEquals(true, struct.getFieldsMap().get("active").getBoolValue()); + assertEquals(NullValue.NULL_VALUE, struct.getFieldsMap().get("description").getNullValue()); + } + + @Test + public void testToProtobufValue_NestedMap() throws IOException { + Map innerMap = new HashMap<>(); + innerMap.put("city", "New York"); + innerMap.put("zipcode", 10001); + + Map outerMap = new HashMap<>(); + outerMap.put("name", "John"); + outerMap.put("address", innerMap); + outerMap.put("hobbies", Arrays.asList("reading", "coding")); + + Value result = ProtobufValueHelper.toProtobufValue(outerMap); + + assertNotNull(result); + assertTrue(result.hasStructValue()); + Struct struct = result.getStructValue(); + assertEquals(3, struct.getFieldsCount()); + + // Verify nested structure + assertEquals("John", struct.getFieldsMap().get("name").getStringValue()); + + // Verify nested map + assertTrue(struct.getFieldsMap().get("address").hasStructValue()); + Struct nestedStruct = struct.getFieldsMap().get("address").getStructValue(); + assertEquals("New York", nestedStruct.getFieldsMap().get("city").getStringValue()); + assertEquals(10001.0, nestedStruct.getFieldsMap().get("zipcode").getNumberValue(), 0.001); + + // Verify nested list + assertTrue(struct.getFieldsMap().get("hobbies").hasListValue()); + ListValue hobbiesList = struct.getFieldsMap().get("hobbies").getListValue(); + assertEquals(2, hobbiesList.getValuesCount()); + assertEquals("reading", hobbiesList.getValues(0).getStringValue()); + assertEquals("coding", hobbiesList.getValues(1).getStringValue()); + } + + @Test + public void testToProtobufValue_MapWithNonStringKeys() throws IOException { + Map intKeyMap = new HashMap<>(); + intKeyMap.put(1, "one"); + intKeyMap.put(2, "two"); + + Value result = ProtobufValueHelper.toProtobufValue(intKeyMap); + + assertNotNull(result); + assertTrue(result.hasStructValue()); + Struct struct = result.getStructValue(); + assertEquals(2, struct.getFieldsCount()); + + // Keys should be converted to strings + assertTrue(struct.getFieldsMap().containsKey("1")); + assertTrue(struct.getFieldsMap().containsKey("2")); + assertEquals("one", struct.getFieldsMap().get("1").getStringValue()); + assertEquals("two", struct.getFieldsMap().get("2").getStringValue()); + } + + @Test + public void testToProtobufValue_CustomObject() throws IOException { + // Test with a custom object that will fall back to toString() + TestCustomObject customObj = new TestCustomObject("test", 123); + Value result = ProtobufValueHelper.toProtobufValue(customObj); + + assertNotNull(result); + assertTrue(result.hasStringValue()); + assertEquals("TestCustomObject{name='test', value=123}", result.getStringValue()); + } + + @Test + public void testToProtobufValue_ComplexNestedStructure() throws IOException { + // Create a complex nested structure + Map config = new HashMap<>(); + config.put("timeout", 30); + config.put("retries", 3); + + Map server = new HashMap<>(); + server.put("host", "localhost"); + server.put("port", 8080); + server.put("ssl", true); + server.put("config", config); + + List tags = Arrays.asList("prod", "critical", "monitoring"); + + Map application = new HashMap<>(); + application.put("name", "my-app"); + application.put("version", "1.0.0"); + application.put("server", server); + application.put("tags", tags); + application.put("metadata", null); + + Value result = ProtobufValueHelper.toProtobufValue(application); + + assertNotNull(result); + assertTrue(result.hasStructValue()); + Struct appStruct = result.getStructValue(); + + // Verify top-level fields + assertEquals("my-app", appStruct.getFieldsMap().get("name").getStringValue()); + assertEquals("1.0.0", appStruct.getFieldsMap().get("version").getStringValue()); + assertEquals(NullValue.NULL_VALUE, appStruct.getFieldsMap().get("metadata").getNullValue()); + + // Verify server object + assertTrue(appStruct.getFieldsMap().get("server").hasStructValue()); + Struct serverStruct = appStruct.getFieldsMap().get("server").getStructValue(); + assertEquals("localhost", serverStruct.getFieldsMap().get("host").getStringValue()); + assertEquals(8080.0, serverStruct.getFieldsMap().get("port").getNumberValue(), 0.001); + assertEquals(true, serverStruct.getFieldsMap().get("ssl").getBoolValue()); + + // Verify nested config + assertTrue(serverStruct.getFieldsMap().get("config").hasStructValue()); + Struct configStruct = serverStruct.getFieldsMap().get("config").getStructValue(); + assertEquals(30.0, configStruct.getFieldsMap().get("timeout").getNumberValue(), 0.001); + assertEquals(3.0, configStruct.getFieldsMap().get("retries").getNumberValue(), 0.001); + + // Verify tags list + assertTrue(appStruct.getFieldsMap().get("tags").hasListValue()); + ListValue tagsList = appStruct.getFieldsMap().get("tags").getListValue(); + assertEquals(3, tagsList.getValuesCount()); + assertEquals("prod", tagsList.getValues(0).getStringValue()); + assertEquals("critical", tagsList.getValues(1).getStringValue()); + assertEquals("monitoring", tagsList.getValues(2).getStringValue()); + } + + @Test + public void testToProtobufValue_OpenAPIFunctionSchema() throws IOException { + // Test with the exact schema structure provided by the user + Map functionSchema = new LinkedHashMap<>(); + functionSchema.put("type", "function"); + functionSchema.put("name", "get_horoscope"); + functionSchema.put("description", "Get today's horoscope for an astrological sign."); + + Map parameters = new LinkedHashMap<>(); + parameters.put("type", "object"); + + Map properties = new LinkedHashMap<>(); + Map signProperty = new LinkedHashMap<>(); + signProperty.put("type", "string"); + signProperty.put("description", "An astrological sign like Taurus or Aquarius"); + properties.put("sign", signProperty); + + parameters.put("properties", properties); + parameters.put("required", Arrays.asList("sign")); + + functionSchema.put("parameters", parameters); + + Value result = ProtobufValueHelper.toProtobufValue(functionSchema); + + assertNotNull(result); + assertTrue(result.hasStructValue()); + Struct rootStruct = result.getStructValue(); + + // Verify root level fields + assertEquals("function", rootStruct.getFieldsMap().get("type").getStringValue()); + assertEquals("get_horoscope", rootStruct.getFieldsMap().get("name").getStringValue()); + assertEquals("Get today's horoscope for an astrological sign.", + rootStruct.getFieldsMap().get("description").getStringValue()); + + // Verify parameters object + assertTrue(rootStruct.getFieldsMap().get("parameters").hasStructValue()); + Struct parametersStruct = rootStruct.getFieldsMap().get("parameters").getStructValue(); + assertEquals("object", parametersStruct.getFieldsMap().get("type").getStringValue()); + + // Verify properties object + assertTrue(parametersStruct.getFieldsMap().get("properties").hasStructValue()); + Struct propertiesStruct = parametersStruct.getFieldsMap().get("properties").getStructValue(); + + // Verify sign property + assertTrue(propertiesStruct.getFieldsMap().get("sign").hasStructValue()); + Struct signStruct = propertiesStruct.getFieldsMap().get("sign").getStructValue(); + assertEquals("string", signStruct.getFieldsMap().get("type").getStringValue()); + assertEquals("An astrological sign like Taurus or Aquarius", + signStruct.getFieldsMap().get("description").getStringValue()); + + // Verify required array + assertTrue(parametersStruct.getFieldsMap().get("required").hasListValue()); + ListValue requiredList = parametersStruct.getFieldsMap().get("required").getListValue(); + assertEquals(1, requiredList.getValuesCount()); + assertEquals("sign", requiredList.getValues(0).getStringValue()); + } + + /** + * Helper class for testing custom object conversion + */ + private static class TestCustomObject { + private final String name; + private final int value; + + public TestCustomObject(String name, int value) { + this.name = name; + this.value = value; + } + + @Override + public String toString() { + return "TestCustomObject{name='" + name + "', value=" + value + "}"; + } + } +} From ac073e209de3e954bb66b32c241b5dd421710d3b Mon Sep 17 00:00:00 2001 From: Cassie Coyle Date: Thu, 11 Sep 2025 17:32:30 -0500 Subject: [PATCH 2/2] rename cross app -> multi app (#1555) Signed-off-by: Cassandra Coyle --- .../java/io/dapr/examples/workflows/README.md | 52 +++++++++---------- .../App2TransformActivity.java | 2 +- .../{crossapp => multiapp}/App2Worker.java | 2 +- .../App3FinalizeActivity.java | 2 +- .../{crossapp => multiapp}/App3Worker.java | 2 +- .../MultiAppWorker.java} | 8 +-- .../MultiAppWorkflow.java} | 24 ++++----- .../MultiAppWorkflowClient.java} | 12 ++--- .../App2TransformActivity.java | 2 +- .../{crossapp => multiapp}/App2Worker.java | 4 +- .../App3FinalizeActivity.java | 2 +- .../{crossapp => multiapp}/App3Worker.java | 4 +- .../MultiAppWorker.java} | 12 ++--- .../MultiAppWorkflow.java} | 12 ++--- .../WorkflowsMultiAppCallActivityIT.java} | 32 ++++++------ 15 files changed, 86 insertions(+), 86 deletions(-) rename examples/src/main/java/io/dapr/examples/workflows/{crossapp => multiapp}/App2TransformActivity.java (96%) rename examples/src/main/java/io/dapr/examples/workflows/{crossapp => multiapp}/App2Worker.java (96%) rename examples/src/main/java/io/dapr/examples/workflows/{crossapp => multiapp}/App3FinalizeActivity.java (96%) rename examples/src/main/java/io/dapr/examples/workflows/{crossapp => multiapp}/App3Worker.java (96%) rename examples/src/main/java/io/dapr/examples/workflows/{crossapp/CrossAppWorker.java => multiapp/MultiAppWorker.java} (82%) rename examples/src/main/java/io/dapr/examples/workflows/{crossapp/CrossAppWorkflow.java => multiapp/MultiAppWorkflow.java} (73%) rename examples/src/main/java/io/dapr/examples/workflows/{crossapp/CrossAppWorkflowClient.java => multiapp/MultiAppWorkflowClient.java} (87%) rename sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/{crossapp => multiapp}/App2TransformActivity.java (95%) rename sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/{crossapp => multiapp}/App2Worker.java (92%) rename sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/{crossapp => multiapp}/App3FinalizeActivity.java (95%) rename sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/{crossapp => multiapp}/App3Worker.java (92%) rename sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/{crossapp/CrossAppWorker.java => multiapp/MultiAppWorker.java} (77%) rename sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/{crossapp/CrossAppWorkflow.java => multiapp/MultiAppWorkflow.java} (81%) rename sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/{crossapp/WorkflowsCrossAppCallActivityIT.java => multiapp/WorkflowsMultiAppCallActivityIT.java} (90%) diff --git a/examples/src/main/java/io/dapr/examples/workflows/README.md b/examples/src/main/java/io/dapr/examples/workflows/README.md index 6cede9c01..8a672570d 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/README.md +++ b/examples/src/main/java/io/dapr/examples/workflows/README.md @@ -53,7 +53,7 @@ Those examples contain the following workflow patterns: 4. [External Event Pattern](#external-event-pattern) 5. [Child-workflow Pattern](#child-workflow-pattern) 6. [Compensation Pattern](#compensation-pattern) -7. [Cross-App Pattern](#cross-app-pattern) +7. [Multi-App Pattern](#multi-app-pattern) ### Chaining Pattern In the chaining pattern, a sequence of activities executes in a specific order. @@ -682,30 +682,30 @@ Key Points: 4. Each activity simulates work with a short delay for demonstration purposes -### Cross-App Pattern +### Multi-App Pattern -The cross-app pattern allows workflows to call activities that are hosted in different Dapr applications. This is useful for microservices architectures allowing multiple applications to host activities that can be orchestrated by Dapr Workflows. +The multi-app pattern allows workflows to call activities that are hosted in different Dapr applications. This is useful for microservices architectures allowing multiple applications to host activities that can be orchestrated by Dapr Workflows. -The `CrossAppWorkflow` class defines the workflow. It demonstrates calling activities in different apps using the `appId` parameter in `WorkflowTaskOptions`. See the code snippet below: +The `MultiAppWorkflow` class defines the workflow. It demonstrates calling activities in different apps using the `appId` parameter in `WorkflowTaskOptions`. See the code snippet below: ```java -public class CrossAppWorkflow implements Workflow { +public class MultiAppWorkflow implements Workflow { @Override public WorkflowStub create() { return ctx -> { var logger = ctx.getLogger(); logger.info("=== WORKFLOW STARTING ==="); - logger.info("Starting CrossAppWorkflow: {}", ctx.getName()); + logger.info("Starting MultiAppWorkflow: {}", ctx.getName()); logger.info("Workflow name: {}", ctx.getName()); logger.info("Workflow instance ID: {}", ctx.getInstanceId()); String input = ctx.getInput(String.class); - logger.info("CrossAppWorkflow received input: {}", input); + logger.info("MultiAppWorkflow received input: {}", input); logger.info("Workflow input: {}", input); // Call an activity in another app by passing in an active appID to the WorkflowTaskOptions - logger.info("Calling cross-app activity in 'app2'..."); - logger.info("About to call cross-app activity in app2..."); - String crossAppResult = ctx.callActivity( + logger.info("Calling multi-app activity in 'app2'..."); + logger.info("About to call multi-app activity in app2..."); + String multiAppResult = ctx.callActivity( App2TransformActivity.class.getName(), input, new WorkflowTaskOptions("app2"), @@ -713,18 +713,18 @@ public class CrossAppWorkflow implements Workflow { ).await(); // Call another activity in a different app - logger.info("Calling cross-app activity in 'app3'..."); - logger.info("About to call cross-app activity in app3..."); + logger.info("Calling multi-app activity in 'app3'..."); + logger.info("About to call multi-app activity in app3..."); String finalResult = ctx.callActivity( App3FinalizeActivity.class.getName(), - crossAppResult, + multiAppResult, new WorkflowTaskOptions("app3"), String.class ).await(); - logger.info("Final cross-app activity result: {}", finalResult); - logger.info("Final cross-app activity result: {}", finalResult); + logger.info("Final multi-app activity result: {}", finalResult); + logger.info("Final multi-app activity result: {}", finalResult); - logger.info("CrossAppWorkflow finished with: {}", finalResult); + logger.info("MultiAppWorkflow finished with: {}", finalResult); logger.info("=== WORKFLOW COMPLETING WITH: {} ===" , finalResult); ctx.complete(finalResult); }; @@ -784,31 +784,31 @@ public class App3FinalizeActivity implements WorkflowActivity { **Important Limitations:** - **Cross-app calls are currently supported for activities only** -- **Child workflow cross-app calls (suborchestration) are NOT supported** +- **Child workflow multi-app calls (suborchestration) are NOT supported** - The app ID must match the Dapr application ID of the target service **Running the Cross-App Example:** This example requires running multiple Dapr applications simultaneously. You'll need to run the following commands in separate terminals: -1. **Start the main workflow worker (crossapp-worker):** +1. **Start the main workflow worker (multiapp-worker):** ```sh -dapr run --app-id crossapp-worker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.CrossAppWorker +dapr run --app-id multiapp-worker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.multiapp.MultiAppWorker ``` 2. **Start app2 worker (handles App2TransformActivity):** ```sh -dapr run --app-id app2 --resources-path ./components/workflows --dapr-grpc-port 50002 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.App2Worker +dapr run --app-id app2 --resources-path ./components/workflows --dapr-grpc-port 50002 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.multiapp.App2Worker ``` 3. **Start app3 worker (handles App3FinalizeActivity):** ```sh -dapr run --app-id app3 --resources-path ./components/workflows --dapr-grpc-port 50003 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.App3Worker +dapr run --app-id app3 --resources-path ./components/workflows --dapr-grpc-port 50003 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.multiapp.App3Worker ``` 4. **Run the workflow client:** ```sh -java -Djava.util.logging.ConsoleHandler.level=FINE -Dio.dapr.durabletask.level=FINE -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.CrossAppWorkflowClient "Hello World" +java -Djava.util.logging.ConsoleHandler.level=FINE -Dio.dapr.durabletask.level=FINE -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.multiapp.MultiAppWorkflowClient "Hello World" ``` **Expected Output:** @@ -819,15 +819,15 @@ The client will show: Input: Hello World Created DaprWorkflowClient successfully Attempting to start new workflow... -Started a new cross-app workflow with instance ID: 001113f3-b9d9-438c-932a-a9a9b70b9460 +Started a new multi-app workflow with instance ID: 001113f3-b9d9-438c-932a-a9a9b70b9460 Waiting for workflow completion... Workflow instance with ID: 001113f3-b9d9-438c-932a-a9a9b70b9460 completed with result: HELLO WORLD [TRANSFORMED BY APP2] [FINALIZED BY APP3] ``` The workflow demonstrates: -1. The workflow starts in the main worker (crossapp-worker) -2. Calls an activity in 'app2' using cross-app functionality -3. Calls an activity in 'app3' using cross-app functionality +1. The workflow starts in the main worker (multiapp-worker) +2. Calls an activity in 'app2' using multi-app functionality +3. Calls an activity in 'app3' using multi-app functionality 4. The workflow completes with the final result from all activities This pattern is particularly useful for: diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2TransformActivity.java b/examples/src/main/java/io/dapr/examples/workflows/multiapp/App2TransformActivity.java similarity index 96% rename from examples/src/main/java/io/dapr/examples/workflows/crossapp/App2TransformActivity.java rename to examples/src/main/java/io/dapr/examples/workflows/multiapp/App2TransformActivity.java index 62db807d1..3cae1fb36 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2TransformActivity.java +++ b/examples/src/main/java/io/dapr/examples/workflows/multiapp/App2TransformActivity.java @@ -11,7 +11,7 @@ limitations under the License. */ -package io.dapr.examples.workflows.crossapp; +package io.dapr.examples.workflows.multiapp; import io.dapr.workflows.WorkflowActivity; import io.dapr.workflows.WorkflowActivityContext; diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2Worker.java b/examples/src/main/java/io/dapr/examples/workflows/multiapp/App2Worker.java similarity index 96% rename from examples/src/main/java/io/dapr/examples/workflows/crossapp/App2Worker.java rename to examples/src/main/java/io/dapr/examples/workflows/multiapp/App2Worker.java index 8bad2b8f5..49ba35b99 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2Worker.java +++ b/examples/src/main/java/io/dapr/examples/workflows/multiapp/App2Worker.java @@ -11,7 +11,7 @@ limitations under the License. */ -package io.dapr.examples.workflows.crossapp; +package io.dapr.examples.workflows.multiapp; import io.dapr.workflows.runtime.WorkflowRuntime; import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3FinalizeActivity.java b/examples/src/main/java/io/dapr/examples/workflows/multiapp/App3FinalizeActivity.java similarity index 96% rename from examples/src/main/java/io/dapr/examples/workflows/crossapp/App3FinalizeActivity.java rename to examples/src/main/java/io/dapr/examples/workflows/multiapp/App3FinalizeActivity.java index 0f7c0ddad..81f71754f 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3FinalizeActivity.java +++ b/examples/src/main/java/io/dapr/examples/workflows/multiapp/App3FinalizeActivity.java @@ -11,7 +11,7 @@ limitations under the License. */ -package io.dapr.examples.workflows.crossapp; +package io.dapr.examples.workflows.multiapp; import io.dapr.workflows.WorkflowActivity; import io.dapr.workflows.WorkflowActivityContext; diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3Worker.java b/examples/src/main/java/io/dapr/examples/workflows/multiapp/App3Worker.java similarity index 96% rename from examples/src/main/java/io/dapr/examples/workflows/crossapp/App3Worker.java rename to examples/src/main/java/io/dapr/examples/workflows/multiapp/App3Worker.java index dc49baa76..574c9351f 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3Worker.java +++ b/examples/src/main/java/io/dapr/examples/workflows/multiapp/App3Worker.java @@ -11,7 +11,7 @@ limitations under the License. */ -package io.dapr.examples.workflows.crossapp; +package io.dapr.examples.workflows.multiapp; import io.dapr.workflows.runtime.WorkflowRuntime; import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorker.java b/examples/src/main/java/io/dapr/examples/workflows/multiapp/MultiAppWorker.java similarity index 82% rename from examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorker.java rename to examples/src/main/java/io/dapr/examples/workflows/multiapp/MultiAppWorker.java index ecf7dfcb6..3993c5b6d 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorker.java +++ b/examples/src/main/java/io/dapr/examples/workflows/multiapp/MultiAppWorker.java @@ -11,21 +11,21 @@ limitations under the License. */ -package io.dapr.examples.workflows.crossapp; +package io.dapr.examples.workflows.multiapp; import io.dapr.workflows.runtime.WorkflowRuntime; import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; -public class CrossAppWorker { +public class MultiAppWorker { public static void main(String[] args) throws Exception { // Register the Workflow with the builder WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder() - .registerWorkflow(CrossAppWorkflow.class); + .registerWorkflow(MultiAppWorkflow.class); // Build and start the workflow runtime try (WorkflowRuntime runtime = builder.build()) { - System.out.println("CrossAppWorker started - registered CrossAppWorkflow only"); + System.out.println("MultiAppWorker started - registered MultiAppWorkflow only"); runtime.start(); } } diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/multiapp/MultiAppWorkflow.java similarity index 73% rename from examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflow.java rename to examples/src/main/java/io/dapr/examples/workflows/multiapp/MultiAppWorkflow.java index d7eeb9d88..b45dfe9f0 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflow.java +++ b/examples/src/main/java/io/dapr/examples/workflows/multiapp/MultiAppWorkflow.java @@ -11,7 +11,7 @@ limitations under the License. */ -package io.dapr.examples.workflows.crossapp; +package io.dapr.examples.workflows.multiapp; import io.dapr.workflows.Workflow; import io.dapr.workflows.WorkflowStub; @@ -21,24 +21,24 @@ import io.dapr.workflows.WorkflowTaskOptions; * Example workflow that demonstrates cross-app activity calls. * This workflow calls activities in different apps using the appId parameter. */ -public class CrossAppWorkflow implements Workflow { +public class MultiAppWorkflow implements Workflow { @Override public WorkflowStub create() { return ctx -> { var logger = ctx.getLogger(); logger.info("=== WORKFLOW STARTING ==="); - logger.info("Starting CrossAppWorkflow: {}", ctx.getName()); + logger.info("Starting MultiAppWorkflow: {}", ctx.getName()); logger.info("Workflow name: {}", ctx.getName()); logger.info("Workflow instance ID: {}", ctx.getInstanceId()); String input = ctx.getInput(String.class); - logger.info("CrossAppWorkflow received input: {}", input); + logger.info("MultiAppWorkflow received input: {}", input); logger.info("Workflow input: {}", input); // Call an activity in another app by passing in an active appID to the WorkflowTaskOptions - logger.info("Calling cross-app activity in 'app2'..."); - logger.info("About to call cross-app activity in app2..."); - String crossAppResult = ctx.callActivity( + logger.info("Calling multi-app activity in 'app2'..."); + logger.info("About to call multi-app activity in app2..."); + String multiAppResult = ctx.callActivity( App2TransformActivity.class.getName(), input, new WorkflowTaskOptions("app2"), @@ -46,17 +46,17 @@ public class CrossAppWorkflow implements Workflow { ).await(); // Call another activity in a different app - logger.info("Calling cross-app activity in 'app3'..."); - logger.info("About to call cross-app activity in app3..."); + logger.info("Calling multi-app activity in 'app3'..."); + logger.info("About to call multi-app activity in app3..."); String finalResult = ctx.callActivity( App3FinalizeActivity.class.getName(), - crossAppResult, + multiAppResult, new WorkflowTaskOptions("app3"), String.class ).await(); - logger.info("Final cross-app activity result: {}", finalResult); + logger.info("Final multi-app activity result: {}", finalResult); - logger.info("CrossAppWorkflow finished with: {}", finalResult); + logger.info("MultiAppWorkflow finished with: {}", finalResult); logger.info("=== WORKFLOW COMPLETING WITH: {} ===", finalResult); ctx.complete(finalResult); }; diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflowClient.java b/examples/src/main/java/io/dapr/examples/workflows/multiapp/MultiAppWorkflowClient.java similarity index 87% rename from examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflowClient.java rename to examples/src/main/java/io/dapr/examples/workflows/multiapp/MultiAppWorkflowClient.java index 1e7910cd2..63ec49ca2 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflowClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/multiapp/MultiAppWorkflowClient.java @@ -11,7 +11,7 @@ limitations under the License. */ -package io.dapr.examples.workflows.crossapp; +package io.dapr.examples.workflows.multiapp; import io.dapr.workflows.client.DaprWorkflowClient; import io.dapr.workflows.client.WorkflowInstanceStatus; @@ -25,17 +25,17 @@ import java.util.concurrent.TimeoutException; * 2. Start a new workflow instance * 3. Wait for completion and get results */ -public class CrossAppWorkflowClient { +public class MultiAppWorkflowClient { public static void main(String[] args) { if (args.length < 1) { - System.out.println("Usage: CrossAppWorkflowClientExample "); - System.out.println("Example: CrossAppWorkflowClientExample \"Hello World\""); + System.out.println("Usage: MultiAppWorkflowClientExample "); + System.out.println("Example: MultiAppWorkflowClientExample \"Hello World\""); return; } String input = args[0]; - System.out.println("=== Starting Cross-App Workflow Client ==="); + System.out.println("=== Starting Multi-App Workflow Client ==="); System.out.println("Input: " + input); try (DaprWorkflowClient client = new DaprWorkflowClient()) { @@ -43,7 +43,7 @@ public class CrossAppWorkflowClient { // Start a new workflow instance System.out.println("Attempting to start new workflow..."); - String instanceId = client.scheduleNewWorkflow(CrossAppWorkflow.class, input); + String instanceId = client.scheduleNewWorkflow(MultiAppWorkflow.class, input); System.out.printf("Started a new cross-app workflow with instance ID: %s%n", instanceId); // Wait for the workflow to complete diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2TransformActivity.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/App2TransformActivity.java similarity index 95% rename from sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2TransformActivity.java rename to sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/App2TransformActivity.java index 653e14b59..20f3a27e7 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2TransformActivity.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/App2TransformActivity.java @@ -11,7 +11,7 @@ * limitations under the License. */ -package io.dapr.it.testcontainers.workflows.crossapp; +package io.dapr.it.testcontainers.workflows.multiapp; import io.dapr.workflows.WorkflowActivity; import io.dapr.workflows.WorkflowActivityContext; diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2Worker.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/App2Worker.java similarity index 92% rename from sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2Worker.java rename to sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/App2Worker.java index f9748f302..2f66ba513 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2Worker.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/App2Worker.java @@ -11,14 +11,14 @@ * limitations under the License. */ -package io.dapr.it.testcontainers.workflows.crossapp; +package io.dapr.it.testcontainers.workflows.multiapp; import io.dapr.workflows.runtime.WorkflowRuntime; import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; /** * App2Worker - registers the App2TransformActivity. - * This app will handle cross-app activity calls from the main workflow. + * This app will handle multi-app activity calls from the main workflow. */ public class App2Worker { diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3FinalizeActivity.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/App3FinalizeActivity.java similarity index 95% rename from sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3FinalizeActivity.java rename to sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/App3FinalizeActivity.java index 106db9a1f..86a64feb0 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3FinalizeActivity.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/App3FinalizeActivity.java @@ -11,7 +11,7 @@ * limitations under the License. */ -package io.dapr.it.testcontainers.workflows.crossapp; +package io.dapr.it.testcontainers.workflows.multiapp; import io.dapr.workflows.WorkflowActivity; import io.dapr.workflows.WorkflowActivityContext; diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3Worker.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/App3Worker.java similarity index 92% rename from sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3Worker.java rename to sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/App3Worker.java index be1d8de46..c983c013b 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3Worker.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/App3Worker.java @@ -11,14 +11,14 @@ * limitations under the License. */ -package io.dapr.it.testcontainers.workflows.crossapp; +package io.dapr.it.testcontainers.workflows.multiapp; import io.dapr.workflows.runtime.WorkflowRuntime; import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; /** * App3Worker - registers the App3FinalizeActivity. - * This app will handle cross-app activity calls from the main workflow. + * This app will handle multi-app activity calls from the main workflow. */ public class App3Worker { diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorker.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/MultiAppWorker.java similarity index 77% rename from sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorker.java rename to sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/MultiAppWorker.java index 36a2fdf79..806b92396 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorker.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/MultiAppWorker.java @@ -11,27 +11,27 @@ * limitations under the License. */ -package io.dapr.it.testcontainers.workflows.crossapp; +package io.dapr.it.testcontainers.workflows.multiapp; import io.dapr.workflows.runtime.WorkflowRuntime; import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; /** - * CrossAppWorker - registers only the CrossAppWorkflow. + * MultiAppWorker - registers only the MultiAppWorkflow. * This is the main workflow orchestrator that will call activities in other apps. */ -public class CrossAppWorker { +public class MultiAppWorker { public static void main(String[] args) throws Exception { - System.out.println("=== Starting CrossAppWorker (Workflow Orchestrator) ==="); + System.out.println("=== Starting MultiAppWorker (Workflow Orchestrator) ==="); // Register the Workflow with the builder WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder() - .registerWorkflow(CrossAppWorkflow.class); + .registerWorkflow(MultiAppWorkflow.class); // Build and start the workflow runtime try (WorkflowRuntime runtime = builder.build()) { - System.out.println("CrossAppWorker started - registered CrossAppWorkflow only"); + System.out.println("MultiAppWorker started - registered MultiAppWorkflow only"); System.out.println("Waiting for workflow orchestration requests..."); runtime.start(); } diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorkflow.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/MultiAppWorkflow.java similarity index 81% rename from sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorkflow.java rename to sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/MultiAppWorkflow.java index 806408621..b6cc301b9 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorkflow.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/MultiAppWorkflow.java @@ -11,27 +11,27 @@ * limitations under the License. */ -package io.dapr.it.testcontainers.workflows.crossapp; +package io.dapr.it.testcontainers.workflows.multiapp; import io.dapr.workflows.Workflow; import io.dapr.workflows.WorkflowStub; import io.dapr.workflows.WorkflowTaskOptions; import org.slf4j.Logger; -public class CrossAppWorkflow implements Workflow { +public class MultiAppWorkflow implements Workflow { @Override public WorkflowStub create() { return ctx -> { Logger logger = ctx.getLogger(); String instanceId = ctx.getInstanceId(); - logger.info("Starting CrossAppWorkflow: {}", ctx.getName()); + logger.info("Starting MultiAppWorkflow: {}", ctx.getName()); logger.info("Instance ID: {}", instanceId); String input = ctx.getInput(String.class); logger.info("Workflow input: {}", input); // Call App2TransformActivity in app2 - logger.info("Calling cross-app activity in 'app2'..."); + logger.info("Calling multi-app activity in 'app2'..."); String transformedByApp2 = ctx.callActivity( App2TransformActivity.class.getName(), input, @@ -40,7 +40,7 @@ public class CrossAppWorkflow implements Workflow { ).await(); // Call App3FinalizeActivity in app3 - logger.info("Calling cross-app activity in 'app3'..."); + logger.info("Calling multi-app activity in 'app3'..."); String finalizedByApp3 = ctx.callActivity( App3FinalizeActivity.class.getName(), transformedByApp2, @@ -48,7 +48,7 @@ public class CrossAppWorkflow implements Workflow { String.class ).await(); - logger.info("Final cross-app activity result: {}", finalizedByApp3); + logger.info("Final multi-app activity result: {}", finalizedByApp3); ctx.complete(finalizedByApp3); }; } diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/WorkflowsCrossAppCallActivityIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/WorkflowsMultiAppCallActivityIT.java similarity index 90% rename from sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/WorkflowsCrossAppCallActivityIT.java rename to sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/WorkflowsMultiAppCallActivityIT.java index 868d0adec..dfa591abf 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/WorkflowsCrossAppCallActivityIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/multiapp/WorkflowsMultiAppCallActivityIT.java @@ -11,7 +11,7 @@ * limitations under the License. */ -package io.dapr.it.testcontainers.workflows.crossapp; +package io.dapr.it.testcontainers.workflows.multiapp; import io.dapr.testcontainers.Component; import io.dapr.testcontainers.DaprContainer; @@ -42,17 +42,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; /** - * Cross-App Pattern integration test. + * Multi-App Pattern integration test. * - * This test demonstrates the cross-app pattern by: - * 1. Starting 3 Dapr containers (crossapp-worker, app2, app3) + * This test demonstrates the multi-app pattern by: + * 1. Starting 3 Dapr containers (multiapp-worker, app2, app3) * 2. Launching Java processes that register workflows/activities in separate apps - * 3. Executing a cross-app workflow + * 3. Executing a multi-app workflow * 4. Asserting successful completion */ @Testcontainers @Tag("testcontainers") -public class WorkflowsCrossAppCallActivityIT { +public class WorkflowsMultiAppCallActivityIT { private static final Network DAPR_NETWORK = Network.newNetwork(); @@ -71,7 +71,7 @@ public class WorkflowsCrossAppCallActivityIT { // Main workflow orchestrator container @Container private final static DaprContainer MAIN_WORKFLOW_SIDECAR = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) - .withAppName("crossapp-worker") + .withAppName("multiapp-worker") .withNetwork(DAPR_NETWORK) .withNetworkAliases("main-workflow-sidecar") .withPlacementContainer(sharedPlacementContainer) @@ -113,18 +113,18 @@ public class WorkflowsCrossAppCallActivityIT { // TestContainers for each app @Container - private static GenericContainer crossappWorker = new GenericContainer<>("openjdk:17-jdk-slim") + private static GenericContainer multiappWorker = new GenericContainer<>("openjdk:17-jdk-slim") .withCopyFileToContainer(MountableFile.forHostPath("target"), "/app") .withWorkingDirectory("/app") .withCommand("java", "-cp", "test-classes:classes:dependency/*:*", - "-Ddapr.app.id=crossapp-worker", + "-Ddapr.app.id=multiapp-worker", "-Ddapr.grpc.endpoint=main-workflow-sidecar:50001", "-Ddapr.http.endpoint=main-workflow-sidecar:3500", - "io.dapr.it.testcontainers.workflows.crossapp.CrossAppWorker") + "io.dapr.it.testcontainers.workflows.multiapp.MultiAppWorker") .withNetwork(DAPR_NETWORK) .dependsOn(MAIN_WORKFLOW_SIDECAR) - .waitingFor(Wait.forLogMessage(".*CrossAppWorker started.*", 1)) - .withLogConsumer(outputFrame -> System.out.println("CrossAppWorker: " + outputFrame.getUtf8String())); + .waitingFor(Wait.forLogMessage(".*MultiAppWorker started.*", 1)) + .withLogConsumer(outputFrame -> System.out.println("MultiAppWorker: " + outputFrame.getUtf8String())); @Container private final static GenericContainer app2Worker = new GenericContainer<>("openjdk:17-jdk-slim") @@ -134,7 +134,7 @@ public class WorkflowsCrossAppCallActivityIT { "-Ddapr.app.id=app2", "-Ddapr.grpc.endpoint=app2-sidecar:50001", "-Ddapr.http.endpoint=app2-sidecar:3500", - "io.dapr.it.testcontainers.workflows.crossapp.App2Worker") + "io.dapr.it.testcontainers.workflows.multiapp.App2Worker") .withNetwork(DAPR_NETWORK) .dependsOn(APP2_SIDECAR) .waitingFor(Wait.forLogMessage(".*App2Worker started.*", 1)) @@ -148,14 +148,14 @@ public class WorkflowsCrossAppCallActivityIT { "-Ddapr.app.id=app3", "-Ddapr.grpc.endpoint=app3-sidecar:50001", "-Ddapr.http.endpoint=app3-sidecar:3500", - "io.dapr.it.testcontainers.workflows.crossapp.App3Worker") + "io.dapr.it.testcontainers.workflows.multiapp.App3Worker") .withNetwork(DAPR_NETWORK) .dependsOn(APP3_SIDECAR) .waitingFor(Wait.forLogMessage(".*App3Worker started.*", 1)) .withLogConsumer(outputFrame -> System.out.println("App3Worker: " + outputFrame.getUtf8String())); @Test - public void testCrossAppWorkflow() throws Exception { + public void testMultiAppWorkflow() throws Exception { // TestContainers wait strategies ensure all containers are ready before this test runs String input = "Hello World"; @@ -173,7 +173,7 @@ public class WorkflowsCrossAppCallActivityIT { DaprWorkflowClient workflowClient = new DaprWorkflowClient(clientProperties); try { - String instanceId = workflowClient.scheduleNewWorkflow(CrossAppWorkflow.class, input); + String instanceId = workflowClient.scheduleNewWorkflow(MultiAppWorkflow.class, input); assertNotNull(instanceId, "Workflow instance ID should not be null"); workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(30), false);